diff --git a/docker-compose.yml b/docker-compose.yml index fa0abdd5d..df1b25454 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ sapldb: ports: - "5432:5432" sapl: - image: interlegis/sapl:3.1.102 + image: interlegis/sapl:3.1.107 restart: always environment: ADMIN_PASSWORD: interlegis diff --git a/sapl/audiencia/forms.py b/sapl/audiencia/forms.py index 3ad74cca9..9312a237b 100644 --- a/sapl/audiencia/forms.py +++ b/sapl/audiencia/forms.py @@ -16,18 +16,21 @@ class AudienciaForm(forms.ModelForm): tipo_materia = forms.ModelChoiceField( label=_('Tipo Matéria'), - required=True, + required=False, queryset=TipoMateriaLegislativa.objects.all(), empty_label='Selecione', ) numero_materia = forms.CharField( - label='Número Matéria', required=True) + label='Número Matéria', required=False) ano_materia = forms.CharField( label='Ano Matéria', - initial=int(data_atual.year), - required=True) + required=False) + + materia = forms.ModelChoiceField(required=False, + widget=forms.HiddenInput(), + queryset=MateriaLegislativa.objects.all()) class Meta: model = AudienciaPublica @@ -36,7 +39,7 @@ class AudienciaForm(forms.ModelForm): 'observacao', 'audiencia_cancelada', 'url_audio', 'url_video', 'upload_pauta', 'upload_ata', 'upload_anexo', 'tipo_materia', 'numero_materia', - 'ano_materia'] + 'ano_materia', 'materia'] def __init__(self, **kwargs): @@ -59,17 +62,38 @@ class AudienciaForm(forms.ModelForm): if not self.is_valid(): return cleaned_data - try: - materia = MateriaLegislativa.objects.get( - numero=self.cleaned_data['numero_materia'], - ano=self.cleaned_data['ano_materia'], - tipo=self.cleaned_data['tipo_materia']) - except ObjectDoesNotExist: - msg = _('A matéria a ser inclusa não existe no cadastro' - ' de matérias legislativas.') - raise ValidationError(msg) + materia = cleaned_data['numero_materia'] + ano_materia = cleaned_data['ano_materia'] + tipo_materia = cleaned_data['tipo_materia'] + + if materia and ano_materia and tipo_materia: + try: + materia = MateriaLegislativa.objects.get( + numero=materia, + ano=ano_materia, + tipo=tipo_materia) + except ObjectDoesNotExist: + msg = _('A matéria %s nº %s/%s não existe no cadastro' + ' de matérias legislativas.' % (tipo_materia, materia, ano_materia)) + raise ValidationError(msg) + else: + cleaned_data['materia'] = materia + else: - cleaned_data['materia'] = materia + campos = [materia, tipo_materia, ano_materia] + if campos.count(None) + campos.count('') < len(campos): + msg = _('Preencha todos os campos relacionados à Matéria Legislativa') + raise ValidationError(msg) + + if not cleaned_data['numero']: + + ultima_audiencia = AudienciaPublica.objects.all().order_by('numero').last() + if ultima_audiencia: + cleaned_data['numero'] = ultima_audiencia.numero + 1 + else: + cleaned_data['numero'] = 1 + + if self.cleaned_data['hora_inicio'] and self.cleaned_data['hora_fim']: if (self.cleaned_data['hora_fim'] < @@ -77,9 +101,4 @@ class AudienciaForm(forms.ModelForm): msg = _('A hora de fim não pode ser anterior a hora de ínicio') raise ValidationError(msg) - return self.cleaned_data - - @transaction.atomic() - def save(self, commit=True): - audiencia = super(AudienciaForm, self).save(commit) - return audiencia \ No newline at end of file + return cleaned_data diff --git a/sapl/audiencia/migrations/0005_auto_20180806_1236.py b/sapl/audiencia/migrations/0005_auto_20180806_1236.py new file mode 100644 index 000000000..9601a233d --- /dev/null +++ b/sapl/audiencia/migrations/0005_auto_20180806_1236.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-08-06 15:36 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('audiencia', '0004_auto_20180305_1006'), + ] + + operations = [ + migrations.AlterField( + model_name='audienciapublica', + name='hora_fim', + field=models.CharField(blank=True, max_length=5, null=True, verbose_name='Horário Fim(hh:mm)'), + ), + ] diff --git a/sapl/audiencia/migrations/0006_auto_20180808_0856.py b/sapl/audiencia/migrations/0006_auto_20180808_0856.py new file mode 100644 index 000000000..3809f66cf --- /dev/null +++ b/sapl/audiencia/migrations/0006_auto_20180808_0856.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-08-08 11:56 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('audiencia', '0005_auto_20180806_1236'), + ] + + operations = [ + migrations.AlterField( + model_name='audienciapublica', + name='hora_fim', + field=models.CharField(blank=True, max_length=5, verbose_name='Horário Fim(hh:mm)'), + ), + ] diff --git a/sapl/audiencia/models.py b/sapl/audiencia/models.py index 96ad8272a..477a442d9 100644 --- a/sapl/audiencia/models.py +++ b/sapl/audiencia/models.py @@ -71,7 +71,7 @@ class AudienciaPublica(models.Model): hora_inicio = models.CharField( max_length=5, verbose_name=_('Horário Início(hh:mm)')) hora_fim = models.CharField( - max_length=5, verbose_name=_('Horário Fim(hh:mm)')) + max_length=5, blank=True, verbose_name=_('Horário Fim(hh:mm)')) observacao = models.TextField( max_length=500, blank=True, verbose_name=_('Observação')) audiencia_cancelada = models.BooleanField( diff --git a/sapl/audiencia/tests/test_audiencia.py b/sapl/audiencia/tests/test_audiencia.py index 710d70dff..5640b3d0d 100644 --- a/sapl/audiencia/tests/test_audiencia.py +++ b/sapl/audiencia/tests/test_audiencia.py @@ -3,6 +3,8 @@ from django.utils.translation import ugettext as _ from model_mommy import mommy from sapl.audiencia import forms +from sapl.audiencia.models import TipoAudienciaPublica +from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa @pytest.mark.django_db(transaction=False) def test_valida_campos_obrigatorios_audiencia_form(): @@ -15,11 +17,25 @@ def test_valida_campos_obrigatorios_audiencia_form(): assert errors['nome'] == [_('Este campo é obrigatório.')] assert errors['tema'] == [_('Este campo é obrigatório.')] assert errors['tipo'] == [_('Este campo é obrigatório.')] - assert errors['tipo_materia'] == [_('Este campo é obrigatório.')] - assert errors['numero_materia'] == [_('Este campo é obrigatório.')] - assert errors['ano_materia'] == [_('Este campo é obrigatório.')] assert errors['data'] == [_('Este campo é obrigatório.')] assert errors['hora_inicio'] == [_('Este campo é obrigatório.')] - assert errors['hora_fim'] == [_('Este campo é obrigatório.')] - assert len(errors) == 9 + assert len(errors) == 5 + + +@pytest.mark.django_db(transaction=False) +def test_audiencia_form_hora_invalida(): + tipo_materia = mommy.make(TipoMateriaLegislativa) + + tipo = mommy.make(TipoAudienciaPublica) + + form = forms.AudienciaForm(data={'nome': 'Nome da Audiencia', + 'tema': 'Tema da Audiencia', + 'tipo': tipo, + 'data': '2016-10-01', + 'hora_inicio': '10:00', + 'hora_fim': '9:00', + }) + assert not form.is_valid() + + \ No newline at end of file diff --git a/sapl/audiencia/views.py b/sapl/audiencia/views.py index 2c16b0919..bdfc6993c 100644 --- a/sapl/audiencia/views.py +++ b/sapl/audiencia/views.py @@ -6,15 +6,17 @@ from sapl.crud.base import RP_DETAIL, RP_LIST, Crud from .forms import AudienciaForm from .models import AudienciaPublica + def index(request): return HttpResponse("Audiência Pública") + class AudienciaCrud(Crud): model = AudienciaPublica public = [RP_LIST, RP_DETAIL, ] class BaseMixin(Crud.BaseMixin): - list_field_names = ['materia', 'tipo', 'numero', 'nome', + list_field_names = ['numero', 'nome', 'tipo', 'materia', 'data'] ordering = 'nome', 'numero', 'tipo', 'data' diff --git a/sapl/base/forms.py b/sapl/base/forms.py index de6b50403..b71d161da 100644 --- a/sapl/base/forms.py +++ b/sapl/base/forms.py @@ -45,7 +45,7 @@ STATUS_USER_CHOICE = [ def get_roles(): roles = [(g.id, g.name) for g in Group.objects.all().order_by('name') - if g.name != 'Votante' and g.name != 'Autor'] + if g.name != 'Votante'] return roles @@ -62,8 +62,6 @@ class UsuarioCreateForm(ModelForm): user_active = forms.ChoiceField(required=False, choices=YES_NO_CHOICES, label="Usuário ativo?", initial='True') - #ROLES = [(g.id, g.name) for g in Group.objects.all().order_by('name')] - roles = forms.MultipleChoiceField( required=True, widget=forms.CheckboxSelectMultiple(), choices=get_roles) @@ -673,6 +671,11 @@ class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet): label='Ano da Matéria', choices=RANGE_ANOS) + @property + def qs(self): + parent = super(RelatorioMateriasTramitacaoilterSet, self).qs + return parent.distinct().order_by('-ano', 'tipo', '-numero') + class Meta: model = MateriaLegislativa fields = ['ano', 'tipo', 'tramitacao__unidade_tramitacao_local', @@ -738,7 +741,7 @@ class RelatorioMateriasPorAutorFilterSet(django_filters.FilterSet): @property def qs(self): parent = super(RelatorioMateriasPorAutorFilterSet, self).qs - return parent.distinct().order_by('-ano', '-numero') + return parent.distinct().filter(autoria__primeiro_autor=True).order_by('autoria__autor', '-autoria__primeiro_autor', 'tipo', '-ano', '-numero') class Meta: model = MateriaLegislativa diff --git a/sapl/base/migrations/0018_auto_20180801_1652.py b/sapl/base/migrations/0018_auto_20180801_1652.py new file mode 100644 index 000000000..d2f758105 --- /dev/null +++ b/sapl/base/migrations/0018_auto_20180801_1652.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-08-01 19:52 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0017_appconfig_cronometro_consideracoes'), + ] + + operations = [ + migrations.AlterField( + model_name='autor', + name='nome', + field=models.CharField(blank=True, max_length=120, verbose_name='Nome do Autor'), + ), + ] diff --git a/sapl/base/migrations/0019_auto_20180815_1025.py b/sapl/base/migrations/0019_auto_20180815_1025.py new file mode 100644 index 000000000..e96759e52 --- /dev/null +++ b/sapl/base/migrations/0019_auto_20180815_1025.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-08-15 13:25 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0018_auto_20180801_1652'), + ] + + operations = [ + migrations.AlterField( + model_name='autor', + name='tipo', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='base.TipoAutor', verbose_name='Tipo do Autor'), + ), + ] diff --git a/sapl/base/models.py b/sapl/base/models.py index bbc03b6c7..df8ae7c3f 100644 --- a/sapl/base/models.py +++ b/sapl/base/models.py @@ -5,6 +5,7 @@ from django.db import models from django.db.models.signals import post_migrate from django.db.utils import DEFAULT_DB_ALIAS from django.utils.translation import ugettext_lazy as _ + from sapl.utils import (LISTA_DE_UFS, YES_NO_CHOICES, get_settings_auth_user_model, models_with_gr_for_model) @@ -178,7 +179,8 @@ class Autor(models.Model): on_delete=models.SET_NULL, null=True) - tipo = models.ForeignKey(TipoAutor, verbose_name=_('Tipo do Autor')) + tipo = models.ForeignKey(TipoAutor, verbose_name=_('Tipo do Autor'), + on_delete=models.PROTECT) content_type = models.ForeignKey( ContentType, @@ -188,7 +190,7 @@ class Autor(models.Model): autor_related = GenericForeignKey('content_type', 'object_id') nome = models.CharField( - max_length=60, blank=True, verbose_name=_('Nome do Autor')) + max_length=120, blank=True, verbose_name=_('Nome do Autor')) cargo = models.CharField(max_length=50, blank=True) @@ -199,23 +201,17 @@ class Autor(models.Model): ordering = ('nome',) def __str__(self): - if self.autor_related: return str(self.autor_related) else: - if str(self.cargo): - return _('%(nome)s - %(cargo)s') % { - 'nome': self.nome, 'cargo': self.cargo} - else: - return str(self.nome) - """if str(self.tipo) == 'Parlamentar' and self.parlamentar: - return self.parlamentar.nome_parlamentar - elif str(self.tipo) == 'Comissao' and self.comissao: - return str(self.comissao) - elif str(self.tipo) == 'Partido' and self.partido: - return str(self.partido) - else: - """ + if self.nome: + if self.cargo: + return '{} - {}'.format(self.nome, self.cargo) + else: + return str(self.nome) + if self.user: + return str(self.user.username) + return '?' def cria_models_tipo_autor(app_config=None, verbosity=2, interactive=True, diff --git a/sapl/base/templatetags/common_tags.py b/sapl/base/templatetags/common_tags.py index 73d71faa8..71f63e130 100644 --- a/sapl/base/templatetags/common_tags.py +++ b/sapl/base/templatetags/common_tags.py @@ -44,6 +44,15 @@ def split(value, arg): return value.split(arg) +@register.filter +def to_str(arg): + return str(arg) + +@register.filter +def get_last_item_from_list(list,arg): + return list[arg] + + @register.filter def sort_by_keys(value, key): transformed = [] @@ -63,6 +72,16 @@ def sort_by_keys(value, key): return transformed +@register.filter +def paginacao_limite_inferior(pagina): + return (int(pagina) - 1) * 10 + + +@register.filter +def paginacao_limite_superior(pagina): + return int(pagina) * 10 + + @register.filter def lookup(d, key): return d[key] if key in d else [] diff --git a/sapl/base/views.py b/sapl/base/views.py index b41492f4e..870fbbaa5 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -393,7 +393,7 @@ class RelatorioMateriasTramitacaoView(FilterView): context = super(RelatorioMateriasTramitacaoView, self).get_context_data(**kwargs) - context['title'] = _('Matérias por Ano, Autor e Tipo') + context['title'] = _('Matérias em Tramitação') qs = context['object_list'] qs = qs.filter(em_tramitacao=True) diff --git a/sapl/comissoes/forms.py b/sapl/comissoes/forms.py index db1822e4a..fc81b3510 100644 --- a/sapl/comissoes/forms.py +++ b/sapl/comissoes/forms.py @@ -242,7 +242,7 @@ class ComissaoForm(forms.ModelForm): if not self.is_valid(): return self.cleaned_data - if len(self.cleaned_data['nome']) > 50: + if len(self.cleaned_data['nome']) > 100: msg = _('Nome da Comissão deve ter no máximo 50 caracteres.') raise ValidationError(msg) if self.cleaned_data['data_extincao']: diff --git a/sapl/compilacao/models.py b/sapl/compilacao/models.py index f677cf97a..28b963038 100644 --- a/sapl/compilacao/models.py +++ b/sapl/compilacao/models.py @@ -69,8 +69,15 @@ class BaseModel(models.Model): def save(self, force_insert=False, force_update=False, using=None, update_fields=None, clean=True): - if clean: + # método clean não pode ser chamado no caso do save que está sendo + # executado é o save de revision_pre_delete_signal + import inspect + funcs = list(filter(lambda x: x == 'revision_pre_delete_signal', + map(lambda x: x[3], inspect.stack()))) + + if clean and not funcs: self.clean() + return models.Model.save( self, force_insert=force_insert, @@ -1458,7 +1465,7 @@ class Dispositivo(BaseModel, TimestampedMixin): tipo_dispositivo_id=self.tipo_dispositivo.pk)) else: # contagem continua restrita a articulacao - proxima_articulacao = self.get_proximo_nivel_zero() + proxima_articulacao = self.select_next_root() if proxima_articulacao is None: irmaos = list(Dispositivo.objects.filter( @@ -1550,25 +1557,15 @@ class Dispositivo(BaseModel, TimestampedMixin): irmao.clean() irmao.save() - def get_proximo_nivel_zero(self): - proxima_articulacao = Dispositivo.objects.order_by('ordem').filter( - ordem__gt=self.ordem, - nivel=0, - ta_id=self.ta_id).first() - return proxima_articulacao - - def get_nivel_zero_anterior(self): - anterior_articulacao = Dispositivo.objects.order_by('ordem').filter( - ordem__lt=self.ordem, - nivel=0, - ta_id=self.ta_id).last() - return anterior_articulacao - - def get_niveis_zero(self): - niveis_zero = Dispositivo.objects.order_by('ordem').filter( - nivel=0, - ta_id=self.ta_id) - return niveis_zero + def select_roots(self): + return Dispositivo.objects.order_by( + 'ordem').filter(nivel=0, ta_id=self.ta_id) + + def select_next_root(self): + return self.select_roots().filter(ordem__gt=self.ordem).first() + + def select_prev_root(self): + return self.select_roots().filter(ordem__lt=self.ordem).last() # metodo obsoleto, foi acrescentado o campo auto_inserido no modelo def is_relative_auto_insert__obsoleto(self, perfil_pk=None): diff --git a/sapl/compilacao/views.py b/sapl/compilacao/views.py index a68c456a1..dac910cc2 100644 --- a/sapl/compilacao/views.py +++ b/sapl/compilacao/views.py @@ -15,7 +15,7 @@ from django.db import connection, transaction from django.db.models import Q from django.db.utils import IntegrityError from django.http.response import (HttpResponse, HttpResponseRedirect, - JsonResponse) + JsonResponse, Http404) from django.shortcuts import get_object_or_404, redirect from django.utils.dateparse import parse_date from django.utils.encoding import force_text @@ -51,7 +51,6 @@ from sapl.compilacao.utils import (DISPOSITIVO_SELECT_RELATED, from sapl.crud.base import Crud, CrudAux, CrudListView, make_pagination from sapl.settings import BASE_DIR - TipoNotaCrud = CrudAux.build(TipoNota, 'tipo_nota') TipoVideCrud = CrudAux.build(TipoVide, 'tipo_vide') TipoPublicacaoCrud = CrudAux.build(TipoPublicacao, 'tipo_publicacao') @@ -104,7 +103,7 @@ class IntegracaoTaView(TemplateView): ) % self.model._meta.verbose_name_plural) return redirect('/') - def get(self, request, *args, **kwargs): + def get(self, request, *args, **kwargs): try: if settings.DEBUG or not TipoDispositivo.objects.exists(): @@ -211,8 +210,12 @@ class CompMixin(PermissionRequiredMixin): @property def ta(self): - ta = TextoArticulado.objects.get( - pk=self.kwargs.get('ta_id', self.kwargs.get('pk', 0))) + try: + ta = TextoArticulado.objects.get( + pk=self.kwargs.get('ta_id', self.kwargs.get('pk', 0))) + except TextoArticulado.DoesNotExist: + raise Http404() + return ta def get_context_data(self, **kwargs): @@ -1237,9 +1240,9 @@ class TextEditView(CompMixin, TemplateView): 'filhos': [], 'alts': [], 'pai': None, - 'st': None, # dispositivo substituido - 'sq': None, # dispositivo subsequente - 'da': None, # dispositivo atualizador + 'st': None, # dispositivo substituido + 'sq': None, # dispositivo subsequente + 'da': None, # dispositivo atualizador 'td': tds[d.tipo_dispositivo_id], # tipo do dispositivo 'na': self.nota_alteracao(d, lista_ta_publicado)\ if d.ta_id == ta_id else None @@ -1388,7 +1391,7 @@ class ActionsCommonsMixin: pkfilho = dp.pk dp = dp.dispositivo_pai - proxima_articulacao = dp.get_proximo_nivel_zero() + proxima_articulacao = dp.select_next_root() if proxima_articulacao is not None: parents = Dispositivo.objects.filter( @@ -1480,30 +1483,36 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin): ).first() data = {} - if base_anterior: - data = self.get_json_for_refresh(base_anterior) + if not base_anterior or base == base.get_raiz(): + base_anterior = base.select_prev_root() + if not base_anterior: + base_anterior = base + data = self.get_json_for_refresh(base_anterior) + + if base == base_anterior: + data['pk'] = base.pk + self.set_message(data, 'danger', _( + 'Base Inicial não pode ser removida!'), modal=True) else: - base_anterior = base.get_nivel_zero_anterior() - data = self.get_json_for_refresh(base_anterior) - - data['pai'] = [base.get_raiz().pk] + if base != base.get_raiz(): + data['pai'] = [base.get_raiz().pk] - if ta_base.id != int(self.kwargs['ta_id']): - data['pai'] = [base.dispositivo_atualizador.pk] - data['pk'] = base.dispositivo_atualizador.pk + if ta_base.id != int(self.kwargs['ta_id']): + data['pai'] = [base.dispositivo_atualizador.pk] + data['pk'] = base.dispositivo_atualizador.pk - try: - with transaction.atomic(): - message = str(self.remover_dispositivo(base, bloco)) - if message: - self.set_message(data, 'warning', message, modal=True) - else: - self.set_message(data, 'success', _( - 'Exclusão efetuada com sucesso!'), modal=True) - ta_base.reagrupar_ordem_de_dispositivos() - except Exception as e: - data['pk'] = self.kwargs['dispositivo_id'] - self.set_message(data, 'danger', str(e), modal=True) + try: + with transaction.atomic(): + message = str(self.remover_dispositivo(base, bloco)) + if message: + self.set_message(data, 'warning', message, modal=True) + else: + self.set_message(data, 'success', _( + 'Exclusão efetuada com sucesso!'), modal=True) + ta_base.reagrupar_ordem_de_dispositivos() + except Exception as e: + data['pk'] = self.kwargs['dispositivo_id'] + self.set_message(data, 'danger', str(e), modal=True) return data @@ -1536,7 +1545,7 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin): print(e) base.delete() else: - proxima_articulacao = base.get_proximo_nivel_zero() + proxima_articulacao = base.select_next_root() if not bloco: # tranferir filhos para primeiro pai possível acima da base # de exclusão @@ -1694,7 +1703,7 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin): base_adicao = {} - nivel_zero_anterior = base.get_nivel_zero_anterior() + nivel_zero_anterior = base.select_prev_root() if nivel_zero_anterior: nivel_zero_anterior = nivel_zero_anterior.ordem else: @@ -2374,7 +2383,7 @@ class ActionDispositivoCreateMixin(ActionsCommonsMixin): if dp.nivel == 0: - proxima_articulacao = dp.get_proximo_nivel_zero() + proxima_articulacao = dp.select_next_root() if not proxima_articulacao: filhos_continuos = list(Dispositivo.objects.filter( diff --git a/sapl/crud/base.py b/sapl/crud/base.py index 8bb8c3794..739fc4f37 100644 --- a/sapl/crud/base.py +++ b/sapl/crud/base.py @@ -851,7 +851,9 @@ class CrudDeleteView(PermissionRequiredContainerCrudMixin, é referenciado por outros registros:
\ ' messages.add_message(request, diff --git a/sapl/legacy/management/commands/migracao_25_31.py b/sapl/legacy/management/commands/migracao_25_31.py index e3864ad0f..ba9a80224 100644 --- a/sapl/legacy/management/commands/migracao_25_31.py +++ b/sapl/legacy/management/commands/migracao_25_31.py @@ -7,5 +7,14 @@ class Command(BaseCommand): help = 'Migração de dados do SAPL 2.5 para o SAPL 3.1' + def add_arguments(self, parser): + parser.add_argument( + '-a', + action='store_true', + default=False, + dest='apagar_do_legado', + help='Apagar entradas migradas do legado', + ) + def handle(self, *args, **options): - migrar(interativo=False) + migrar(apagar_do_legado=options['apagar_do_legado']) diff --git a/sapl/legacy/migracao.py b/sapl/legacy/migracao.py index dd54a103e..c4c183b84 100644 --- a/sapl/legacy/migracao.py +++ b/sapl/legacy/migracao.py @@ -18,7 +18,7 @@ def adornar_msg(msg): return '\n{1}\n{0}\n{1}'.format(msg, '#' * len(msg)) -def migrar(interativo=False): +def migrar(apagar_do_legado=False): if TAG_MARCO in REPO.tags: info('A migração já está feita.') return @@ -26,7 +26,7 @@ def migrar(interativo=False): 'Antes de migrar ' 'é necessário fazer a exportação de documentos do zope') management.call_command('migrate') - migrar_dados() + migrar_dados(apagar_do_legado) migrar_usuarios(REPO.working_dir) migrar_documentos(REPO) gravar_marco() diff --git a/sapl/legacy/migracao_dados.py b/sapl/legacy/migracao_dados.py index 4c953d5f5..24bc9948d 100644 --- a/sapl/legacy/migracao_dados.py +++ b/sapl/legacy/migracao_dados.py @@ -8,7 +8,6 @@ from datetime import date from functools import lru_cache, partial from itertools import groupby from operator import xor -from subprocess import PIPE, call import git import pkg_resources @@ -31,12 +30,10 @@ from unipath import Path from sapl.base.models import AppConfig as AppConf from sapl.base.models import Autor, TipoAutor, cria_models_tipo_autor from sapl.comissoes.models import Comissao, Composicao, Participacao, Reuniao -from sapl.legacy import scripts from sapl.legacy.models import NormaJuridica as OldNormaJuridica from sapl.legacy.models import TipoNumeracaoProtocolo -from sapl.legacy_migration_settings import (DATABASES, DIR_DADOS_MIGRACAO, - DIR_REPO, NOME_BANCO_LEGADO, - PROJECT_DIR) +from sapl.legacy_migration_settings import (DIR_DADOS_MIGRACAO, DIR_REPO, + NOME_BANCO_LEGADO) from sapl.materia.models import (AcompanhamentoMateria, MateriaLegislativa, Proposicao, StatusTramitacao, TipoDocumento, TipoMateriaLegislativa, TipoProposicao, @@ -141,10 +138,6 @@ models_novos_para_antigos = { for model in field_renames} models_novos_para_antigos[Composicao] = models_novos_para_antigos[Participacao] -content_types = {model: ContentType.objects.get( - app_label=model._meta.app_label, model=model._meta.model_name) - for model in field_renames} - campos_novos_para_antigos = { model._meta.get_field(nome_novo): nome_antigo for model, renames in field_renames.items() @@ -226,10 +219,6 @@ class ForeignKeyFaltando(ObjectDoesNotExist): 'Uma FK aponta para um registro inexistente' def __init__(self, field, valor, old): - if (field.related_model.__name__ == 'Comissao' - and old.__class__.__name__ == 'ReuniaoComissao' - and valor == 1): - __import__('pdb').set_trace() self.field = field self.valor = valor self.old = old @@ -546,7 +535,6 @@ PROPAGACOES_DE_EXCLUSAO = [ ('sessao_plenaria', 'ordem_dia', 'cod_sessao_plen'), ('sessao_plenaria', 'expediente_materia', 'cod_sessao_plen'), ('sessao_plenaria', 'expediente_sessao_plenaria', 'cod_sessao_plen'), - ('registro_votacao', 'registro_votacao_parlamentar', 'cod_votacao'), # as consultas no código do sapl 2.5 # votacao_ordem_dia_obter_zsql e votacao_expediente_materia_obter_zsql # indicam que os registros de votação de matérias excluídas não são @@ -562,30 +550,38 @@ PROPAGACOES_DE_EXCLUSAO = [ ('materia_legislativa', 'anexada', 'cod_materia_anexada'), ('materia_legislativa', 'documento_acessorio', 'cod_materia'), ('materia_legislativa', 'numeracao', 'cod_materia'), + ('materia_legislativa', 'expediente_materia', 'cod_materia'), # norma ('norma_juridica', 'vinculo_norma_juridica', 'cod_norma_referente'), ('norma_juridica', 'vinculo_norma_juridica', 'cod_norma_referida'), + ('norma_juridica', 'legislacao_citada', 'cod_norma'), # documento administrativo ('documento_administrativo', 'tramitacao_administrativo', 'cod_documento'), ] +PROPAGACOES_DE_EXCLUSAO_REGISTROS_VOTACAO = [ + ('registro_votacao', 'registro_votacao_parlamentar', 'cod_votacao'), +] -def propaga_exclusoes(): - for tabela_pai, tabela_filha, fk in PROPAGACOES_DE_EXCLUSAO: + +def propaga_exclusoes(propagacoes): + for tabela_pai, tabela_filha, fk in propagacoes: [pk_pai] = get_pk_legado(tabela_pai) - exec_legado(''' + sql = ''' update {} set ind_excluido = 1 where {} not in ( select {} from {} where ind_excluido != 1) - '''.format(tabela_filha, fk, pk_pai, tabela_pai)) + '''.format(tabela_filha, fk, pk_pai, tabela_pai) + exec_legado(sql) def uniformiza_banco(): exec_legado('SET SESSION sql_mode = "";') # desliga checagens do mysql - propaga_exclusoes() + propaga_exclusoes(PROPAGACOES_DE_EXCLUSAO) checa_registros_votacao_ambiguos_e_remove_nao_usados() + propaga_exclusoes(PROPAGACOES_DE_EXCLUSAO_REGISTROS_VOTACAO) garante_coluna_no_legado('proposicao', 'num_proposicao int(11) NULL') @@ -808,7 +804,7 @@ def roda_comando_shell(cmd): assert res == 0, 'O comando falhou: {}'.format(cmd) -def migrar_dados(): +def migrar_dados(apagar_do_legado=False): try: ocorrencias.clear() ocorrencias.default_factory = list @@ -840,7 +836,7 @@ def migrar_dados(): fill_vinculo_norma_juridica() fill_dados_basicos() info('Começando migração: ...') - migrar_todos_os_models() + migrar_todos_os_models(apagar_do_legado) except Exception as e: ocorrencias['traceback'] = str(traceback.format_exc()) raise e @@ -874,7 +870,6 @@ def get_models_a_migrar(): if model in field_renames] # retira reuniões quando não existe na base legada # (só existe no sapl 3.0) - tabelas_legado = [t for (t,) in exec_legado('show tables')] if not EXISTE_REUNIAO_NO_LEGADO: models.remove(Reuniao) # Devido à referência TipoProposicao.tipo_conteudo_related @@ -890,12 +885,12 @@ def get_models_a_migrar(): return models -def migrar_todos_os_models(): +def migrar_todos_os_models(apagar_do_legado): for model in get_models_a_migrar(): - migrar_model(model) + migrar_model(model, apagar_do_legado) -def migrar_model(model): +def migrar_model(model, apagar_do_legado): print('Migrando %s...' % model.__name__) model_legado, tabela_legado, campos_pk_legado = \ @@ -949,12 +944,13 @@ def migrar_model(model): novos.append(new) # guarda para salvar # acumula deleção do registro no legado - sql_delete_legado += 'delete from {} where {};\n'.format( - tabela_legado, - ' and '.join( - '{} = "{}"'.format(campo, - getattr(old, campo)) - for campo in campos_pk_legado)) + if apagar_do_legado: + sql_delete_legado += 'delete from {} where {};\n'.format( + tabela_legado, + ' and '.join( + '{} = "{}"'.format(campo, + getattr(old, campo)) + for campo in campos_pk_legado)) # salva novos registros with reversion.create_revision(): @@ -973,7 +969,7 @@ def migrar_model(model): reinicia_sequence(model, ultima_pk_legado + 1) # apaga registros migrados do legado - if sql_delete_legado: + if apagar_do_legado and sql_delete_legado: exec_legado(sql_delete_legado) @@ -1156,7 +1152,9 @@ def adjust_tipoafastamento(new, old): def set_generic_fk(new, campo_virtual, old): - new.content_type = content_types[campo_virtual.related_model] + model = campo_virtual.related_model + new.content_type = ContentType.objects.get( + app_label=model._meta.app_label, model=model._meta.model_name) new.object_id = get_fk_related(campo_virtual, old) diff --git a/sapl/legacy/migracao_usuarios.py b/sapl/legacy/migracao_usuarios.py index 51b3348c8..d767343a3 100644 --- a/sapl/legacy/migracao_usuarios.py +++ b/sapl/legacy/migracao_usuarios.py @@ -16,6 +16,7 @@ PERFIL_LEGADO_PARA_NOVO = {legado: Group.objects.get(name=novo) ('Operador Protocolo', 'Operador de Protocolo Administrativo'), ('Operador Sessao Plenaria', 'Operador de Sessão Plenária'), ('Parlamentar', 'Votante'), + ('Operador Painel', 'Operador de Painel Eletrônico'), ] } diff --git a/sapl/legacy/scripts/ressucita_dependencias.py b/sapl/legacy/scripts/ressucita_dependencias.py new file mode 100644 index 000000000..793e51483 --- /dev/null +++ b/sapl/legacy/scripts/ressucita_dependencias.py @@ -0,0 +1,59 @@ +import yaml +from unipath import Path + +from sapl.legacy.migracao_dados import DIR_REPO, exec_legado + +fks_legado = ''' + autor cod_parlamentar parlamentar + autor tip_autor tipo_autor + autoria cod_autor autor + expediente_materia cod_materia materia_legislativa + ordem_dia cod_materia materia_legislativa + legislacao_citada cod_norma norma_juridica + oradores cod_parlamentar parlamentar + oradores_expediente cod_parlamentar parlamentar + ordem_dia_presenca cod_parlamentar parlamentar + protocolo cod_autor autor + registro_votacao tip_resultado_votacao tipo_resultado_votacao + registro_votacao_parlamentar cod_parlamentar parlamentar + registro_votacao_parlamentar cod_votacao registro_votacao + sessao_legislativa num_legislatura legislatura + sessao_plenaria_presenca cod_parlamentar parlamentar +''' +fks_legado = [l.split() for l in fks_legado.strip().splitlines()] +fks_legado = {(o, c): t for (o, c, t) in fks_legado} + + +def get_excluido(fk): + campo, valor, tabela_origem = [fk[k] for k in ('campo', 'valor', 'tabela')] + tabela_alvo = fks_legado[(tabela_origem, campo)] + sql = 'select ind_excluido, t.* from {} t where {} = {}'.format( + tabela_alvo, campo, valor) + res = list(exec_legado(sql)) + return tabela_origem, campo, valor, tabela_alvo, res + + +def get_dependencias_a_ressucitar(): + ocorrencias = yaml.load( + Path(DIR_REPO.child('ocorrencias.yaml').read_file())) + fks = ocorrencias['fk'] + excluidos = [get_excluido(fk) for fk in fks] + desexcluir, criar = [ + set([(tabela_alvo, campo, valor) + for tabela_origem, campo, valor, tabela_alvo, res in excluidos + if condicao(res)]) + for condicao in ( + # o registro existe e ind_excluido == 1 + lambda res: res and res[0][0] == 1, + # o registro não existe + lambda res: not res + )] + return desexcluir, criar + + +def get_sqls_desexcluir_criar(desexcluir, criar): + sqls_desexcluir = [ + 'update {} set ind_excluido = 0 where {} = {};'.format( + tabela_alvo, campo, valor) + for tabela_alvo, campo, valor in desexcluir] + return '\n'.join(sqls_desexcluir) diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index 04ee8d0e0..88db58f8b 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -675,6 +675,7 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet): widget=forms.HiddenInput()) ementa = django_filters.CharFilter(lookup_expr='icontains') + indexacao = django_filters.CharFilter(lookup_expr='icontains') em_tramitacao = django_filters.ChoiceFilter(required=False, label='Em tramitação', @@ -751,7 +752,7 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet): [('em_tramitacao', 6), ('o', 6)]) row9 = to_row( - [('materiaassunto__assunto', 12)]) + [('materiaassunto__assunto', 6), ('indexacao', 6)]) row10 = to_row( [('ementa', 12)]) @@ -1294,12 +1295,11 @@ class ProposicaoForm(forms.ModelForm): def clean_texto_original(self): texto_original = self.cleaned_data.get('texto_original', False) - if texto_original: - if texto_original.size > MAX_DOC_UPLOAD_SIZE: - max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024)) - raise ValidationError( + if texto_original and texto_original.size > MAX_DOC_UPLOAD_SIZE: + max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024)) + raise ValidationError( "Arquivo muito grande. ( > {0}MB )".format(max_size)) - return texto_original + return texto_original def gerar_hash(self, inst, receber_recibo): diff --git a/sapl/materia/views.py b/sapl/materia/views.py index f2e3e76db..5f1b81ccf 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -426,6 +426,10 @@ class ProposicaoPendente(PermissionRequiredMixin, ListView): def get_context_data(self, **kwargs): context = super(ProposicaoPendente, self).get_context_data(**kwargs) + context['object_list'] = Proposicao.objects.filter( + data_envio__isnull=False, + data_recebimento__isnull=True, + data_devolucao__isnull=True) paginator = context['paginator'] page_obj = context['page_obj'] context['AppConfig'] = sapl.base.models.AppConfig.objects.all().last() @@ -434,6 +438,8 @@ class ProposicaoPendente(PermissionRequiredMixin, ListView): context['NO_ENTRIES_MSG'] = 'Nenhuma proposição pendente.' context['subnav_template_name'] = 'materia/subnav_prop.yaml' + qr = self.request.GET.copy() + context['filter_url'] = ('&o=' + qr['o']) if 'o' in qr.keys() else '' return context @@ -701,12 +707,12 @@ class ProposicaoCrud(Crud): messages.success(request, _( 'Proposição enviada com sucesso.')) try: - Numero = MateriaLegislativa.objects.filter(tipo=p.tipo.tipo_conteudo_related, + numero = MateriaLegislativa.objects.filter(tipo=p.tipo.tipo_conteudo_related, ano=p.ano).last().numero + 1 messages.success(request, _( '%s : nº %s de %s
Atenção! Este número é apenas um provável ' 'número que pode não corresponder com a realidade' - % (p.tipo, Numero, p.ano))) + % (p.tipo, numero, p.ano))) except ValueError: pass diff --git a/sapl/norma/forms.py b/sapl/norma/forms.py index 610895e09..7d791207a 100644 --- a/sapl/norma/forms.py +++ b/sapl/norma/forms.py @@ -14,7 +14,7 @@ from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.settings import MAX_DOC_UPLOAD_SIZE from sapl.utils import RANGE_ANOS, RangeWidgetOverride -from .models import (AssuntoNorma, NormaJuridica, NormaRelacionada, +from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada, TipoNormaJuridica) @@ -120,6 +120,7 @@ class NormaJuridicaForm(ModelForm): 'assuntos'] widgets = {'assuntos': widgets.CheckboxSelectMultiple} + def clean(self): cleaned_data = super(NormaJuridicaForm, self).clean() @@ -163,11 +164,10 @@ class NormaJuridicaForm(ModelForm): def clean_texto_integral(self): texto_integral = self.cleaned_data.get('texto_integral', False) - if texto_integral: - if texto_integral.size > MAX_DOC_UPLOAD_SIZE: - max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024)) - raise ValidationError( - "Arquivo muito grande. ( > {0}MB )".format(max_size)) + if texto_integral and texto_integral.size > MAX_DOC_UPLOAD_SIZE: + max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024)) + raise ValidationError( + "Arquivo muito grande. ( > {0}MB )".format(max_size)) return texto_integral def save(self, commit=False): @@ -175,9 +175,40 @@ class NormaJuridicaForm(ModelForm): norma.timestamp = timezone.now() norma.materia = self.cleaned_data['materia'] norma = super(NormaJuridicaForm, self).save(commit=True) + return norma +class AnexoNormaJuridicaForm(ModelForm): + class Meta: + model = AnexoNormaJuridica + fields = ['norma', 'anexo_arquivo'] + widgets = { + 'norma': forms.HiddenInput(), + } + + def clean(self): + cleaned_data = super(AnexoNormaJuridicaForm, self).clean() + if not self.is_valid(): + return cleaned_data + anexo_arquivo = self.cleaned_data.get('anexo_arquivo', False) + if anexo_arquivo and anexo_arquivo.size > MAX_DOC_UPLOAD_SIZE: + max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024)) + raise ValidationError( + "Arquivo muito grande. ( > {0}MB )".format(max_size)) + return cleaned_data + + def save(self, commit=False): + anexo = self.instance + anexo.ano = self.cleaned_data['norma'].ano + anexo = super(AnexoNormaJuridicaForm, self).save(commit=True) + anexo.norma = self.cleaned_data['norma'] + anexo.anexo_arquivo = self.cleaned_data['anexo_arquivo'] + anexo.save() + return anexo + + + class NormaRelacionadaForm(ModelForm): tipo = forms.ModelChoiceField( diff --git a/sapl/norma/migrations/0012_anexonormajuridica.py b/sapl/norma/migrations/0012_anexonormajuridica.py new file mode 100644 index 000000000..2ba9ebdca --- /dev/null +++ b/sapl/norma/migrations/0012_anexonormajuridica.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-08-06 19:48 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import sapl.norma.models +import sapl.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('norma', '0011_auto_20180220_1859'), + ] + + operations = [ + migrations.CreateModel( + name='AnexoNormaJuridica', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('anexo_arquivo', models.FileField(blank=True, null=True, upload_to=sapl.norma.models.norma_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Arquivo Anexo')), + ('ano', models.PositiveSmallIntegerField(choices=[(2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano')), + ('norma', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='norma', to='norma.NormaJuridica', verbose_name='Norma Juridica')), + ], + options={ + 'verbose_name_plural': 'Anexos da Norma Juridica', + 'verbose_name': 'Anexo da Norma Juridica', + }, + ), + ] diff --git a/sapl/norma/models.py b/sapl/norma/models.py index b045ea1f9..ff7b44dca 100644 --- a/sapl/norma/models.py +++ b/sapl/norma/models.py @@ -141,9 +141,14 @@ class NormaJuridica(models.Model): norma_relacionada=self.id) return (principais, relacionadas) + def get_anexos_norma_juridica(self): + anexos = AnexoNormaJuridica.objects.filter( + norma=self.id) + return anexos + + def __str__(self): - return _('%(tipo)s nº %(numero)s de %(data)s') % { - 'tipo': self.tipo, + return _('nº %(numero)s de %(data)s') % { 'numero': self.numero, 'data': defaultfilters.date(self.data, "d \d\e F \d\e Y")} @@ -252,3 +257,28 @@ class NormaRelacionada(models.Model): ' - Relacionada: %(norma_relacionada)s') % { 'norma_principal': self.norma_principal, 'norma_relacionada': self.norma_relacionada} + + +@reversion.register() +class AnexoNormaJuridica(models.Model): + norma = models.ForeignKey( + NormaJuridica, + related_name='norma', + on_delete=models.PROTECT, + verbose_name=_('Norma Juridica')) + anexo_arquivo = models.FileField( + blank=True, + null=True, + upload_to=norma_upload_path, + verbose_name=_('Arquivo Anexo'), + validators=[restringe_tipos_de_arquivo_txt]) + ano = models.PositiveSmallIntegerField(verbose_name=_('Ano'), + choices=RANGE_ANOS) + + class Meta: + verbose_name = _('Anexo da Norma Juridica') + verbose_name_plural = _('Anexos da Norma Juridica') + + def __str__(self): + return _('Anexo: %(anexo)s da norma %(norma)s') % { + 'anexo': self.anexo_arquivo, 'norma': self.norma} diff --git a/sapl/norma/urls.py b/sapl/norma/urls.py index 93081c4fc..d943f71e8 100644 --- a/sapl/norma/urls.py +++ b/sapl/norma/urls.py @@ -1,6 +1,6 @@ from django.conf.urls import include, url -from sapl.norma.views import (AssuntoNormaCrud, NormaCrud, NormaPesquisaView, +from sapl.norma.views import (AnexoNormaJuridicaCrud,AssuntoNormaCrud, NormaCrud, NormaPesquisaView, NormaRelacionadaCrud, NormaTaView, TipoNormaCrud, TipoVinculoNormaJuridicaCrud, recuperar_norma, recuperar_numero_norma) @@ -12,11 +12,11 @@ app_name = AppConfig.name urlpatterns = [ url(r'^norma/', include(NormaCrud.get_urls() + - NormaRelacionadaCrud.get_urls())), + NormaRelacionadaCrud.get_urls() + + AnexoNormaJuridicaCrud.get_urls())), # Integração com Compilação url(r'^norma/(?P[0-9]+)/ta$', NormaTaView.as_view(), name='norma_ta'), - url(r'^sistema/norma/tipo/', include(TipoNormaCrud.get_urls())), url(r'^sistema/norma/assunto/', include(AssuntoNormaCrud.get_urls())), url(r'^sistema/norma/vinculo/', include( diff --git a/sapl/norma/views.py b/sapl/norma/views.py index 9c98e783e..87b842802 100644 --- a/sapl/norma/views.py +++ b/sapl/norma/views.py @@ -1,4 +1,5 @@ +import re import weasyprint from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.exceptions import ObjectDoesNotExist @@ -18,9 +19,9 @@ from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux, MasterDetailCrud, make_pagination) from sapl.utils import show_results_filter_set -from .forms import (NormaFilterSet, NormaJuridicaForm, +from .forms import (AnexoNormaJuridicaForm, NormaFilterSet, NormaJuridicaForm, NormaPesquisaSimplesForm, NormaRelacionadaForm) -from .models import (AssuntoNorma, NormaJuridica, NormaRelacionada, +from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada, TipoNormaJuridica, TipoVinculoNormaJuridica) # LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '') @@ -71,7 +72,7 @@ class NormaPesquisaView(FilterView): def get_queryset(self): qs = super().get_queryset() - qs.select_related('tipo', 'materia') + qs = qs.extra({'norma_i': "CAST(regexp_replace(numero,'[^0-9]','', 'g') AS INTEGER)", 'norma_letra': "regexp_replace(numero,'[^a-zA-Z]','', 'g')"}).order_by('-data', '-norma_i', '-norma_letra') return qs @@ -97,6 +98,39 @@ class NormaPesquisaView(FilterView): return context +class AnexoNormaJuridicaCrud(MasterDetailCrud): + model = AnexoNormaJuridica + parent_field = 'norma' + help_topic = 'anexonormajuridica' + public = [RP_LIST, RP_DETAIL] + + class BaseMixin(MasterDetailCrud.BaseMixin): + list_field_names = ['id','anexo_arquivo'] + + class CreateView(MasterDetailCrud.CreateView): + form_class = AnexoNormaJuridicaForm + layout_key = 'AnexoNormaJuridica' + + def get_initial(self): + initial = super(MasterDetailCrud.CreateView, self).get_initial() + initial['norma'] = NormaJuridica.objects.get(id=self.kwargs['pk']) + return initial + + class UpdateView(MasterDetailCrud.UpdateView): + form_class = AnexoNormaJuridicaForm + layout_key = 'AnexoNormaJuridica' + + def get_initial(self): + initial = super(UpdateView, self).get_initial() + initial['norma'] = self.object.norma + initial['anexo_arquivo'] = self.object.anexo_arquivo + initial['ano'] = self.object.ano + return initial + + class DetailView(MasterDetailCrud.DetailView): + form_class = AnexoNormaJuridicaForm + layout_key = 'AnexoNormaJuridica' + class NormaTaView(IntegracaoTaView): model = NormaJuridica @@ -201,14 +235,12 @@ def recuperar_norma(request): def recuperar_numero_norma(request): tipo = TipoNormaJuridica.objects.get(pk=request.GET['tipo']) ano = request.GET.get('ano', '') - param = {'tipo': tipo} param['ano'] = ano if ano else timezone.now().year - norma = NormaJuridica.objects.filter(**param).extra( - {'numero_id': "CAST(numero as INTEGER)"}).order_by( - 'tipo', 'ano','numero_id').values_list('numero', 'ano').last() + norma = NormaJuridica.objects.filter(**param).order_by( + 'tipo', 'ano', 'numero').values_list('numero', 'ano').last() if norma: - response = JsonResponse({'numero': int(norma[0]) + 1, + response = JsonResponse({'numero': int(re.sub("[^0-9].*", '', norma[0])) + 1, 'ano': norma[1]}) else: response = JsonResponse( diff --git a/sapl/painel/views.py b/sapl/painel/views.py index 847518898..88d0aa785 100644 --- a/sapl/painel/views.py +++ b/sapl/painel/views.py @@ -82,10 +82,114 @@ def votacao_aberta(request): return votacoes_abertas.first(), None +def votacao(context,context_vars): + + parlamentar = context_vars['votante'].parlamentar + parlamentar_presente = False + if parlamentar.id in context_vars['presentes']: + parlamentar_presente = True + context_vars.update({'parlamentar': parlamentar}) + else: + context.update({'error_message': + 'Não há presentes na Sessão com a ' + 'matéria em votação.'}) + + if parlamentar_presente: + voto = [] + if context_vars['ordem_dia']: + voto = VotoParlamentar.objects.filter( + ordem=context_vars['ordem_dia']) + elif context_vars['expediente']: + voto = VotoParlamentar.objects.filter( + expediente=context_vars['expediente']) + + if voto: + try: + voto = voto.get(parlamentar=context_vars['parlamentar']) + context.update({'voto_parlamentar': voto.voto}) + except ObjectDoesNotExist: + context.update( + {'voto_parlamentar': 'Voto não ' + 'computado.'}) + else: + context.update({'error_message': + 'Você não está presente na ' + 'Ordem do Dia/Expediente em votação.'}) + return context, context_vars + +def sessao_votacao(context,context_vars): + pk = context_vars['sessao'].pk + context.update({'sessao_id': pk}) + context.update({'sessao': context_vars['sessao'], + 'data': context_vars['sessao'].data_inicio, + 'hora': context_vars['sessao'].hora_inicio}) + + # Inicializa presentes + presentes = [] + ordem_dia = get_materia_aberta(pk) + expediente = get_materia_expediente_aberta(pk) + errors_msgs = {'materia':'Não há nenhuma matéria aberta.', + 'registro':'A votação para esta matéria já encerrou.', + 'tipo':'A matéria aberta não é do tipo votação nominal.'} + + materia_aberta = None + if ordem_dia: + materia_aberta = ordem_dia + presentes = PresencaOrdemDia.objects.filter( + sessao_plenaria_id=pk).values_list( + 'parlamentar_id', flat=True).distinct() + elif expediente: + materia_aberta = expediente + presentes = SessaoPlenariaPresenca.objects.filter( + sessao_plenaria_id=pk).values_list( + 'parlamentar_id', flat=True).distinct() + + context_vars.update({'ordem_dia': ordem_dia, + 'expediente':expediente, + 'presentes': presentes}) + + # Verifica votação aberta + # Se aberta, verifica se é nominal. ID nominal == 2 + erro = None + if not materia_aberta: + erro = 'materia' + elif materia_aberta.registro_aberto: + erro = 'registro' + elif materia_aberta.tipo_votacao != VOTACAO_NOMINAL: + erro = 'tipo' + + if not erro: + context.update({'materia': materia_aberta.materia, + 'ementa': materia_aberta.materia.ementa}) + context, context_vars = votacao(context, context_vars) + else: + context.update({'error_message': errors_msgs[erro]}) + + return context, context_vars + + +def can_vote(context, context_vars, request): + context.update({'permissao': True}) + + # Pega sessão + sessao, msg = votacao_aberta(request) + context_vars.update({'sessao':sessao}) + if sessao and not msg: + context, context_vars = sessao_votacao(context, context_vars) + elif not sessao and msg: + return HttpResponseRedirect('/') + else: + context.update( + {'error_message': 'Não há nenhuma sessão com matéria aberta.'}) + return context, context_vars + + def votante_view(request): # Pega o votante relacionado ao usuário template_name = 'painel/voto_nominal.html' context = {} + context_vars = {} + try: votante = Votante.objects.get(user=request.user) except ObjectDoesNotExist: @@ -95,96 +199,12 @@ def votante_view(request): }) return render(request, template_name, context) - + context_vars = {'votante': votante} context = {'head_title': str(_('Votação Individual'))} # Verifica se usuário possui permissão para votar if 'parlamentares.can_vote' in request.user.get_all_permissions(): - context.update({'permissao': True}) - - # Pega sessão - sessao, msg = votacao_aberta(request) - - if sessao and not msg: - pk = sessao.pk - context.update({'sessao_id': pk}) - context.update({'sessao': sessao, - 'data': sessao.data_inicio, - 'hora': sessao.hora_inicio}) - - # Inicializa presentes - presentes = [] - - # Verifica votação aberta - # Se aberta, verifica se é nominal. ID nominal == 2 - ordem_dia = get_materia_aberta(pk) - expediente = get_materia_expediente_aberta(pk) - - materia_aberta = None - if ordem_dia: - materia_aberta = ordem_dia - presentes = PresencaOrdemDia.objects.filter( - sessao_plenaria_id=pk).values_list( - 'parlamentar_id', flat=True).distinct() - elif expediente: - materia_aberta = expediente - presentes = SessaoPlenariaPresenca.objects.filter( - sessao_plenaria_id=pk).values_list( - 'parlamentar_id', flat=True).distinct() - - if materia_aberta: - if not materia_aberta.registro_aberto: - if materia_aberta.tipo_votacao == VOTACAO_NOMINAL: - context.update({'materia': materia_aberta.materia, - 'ementa': materia_aberta.materia.ementa}) - - parlamentar = votante.parlamentar - parlamentar_presente = False - if parlamentar.id in presentes: - parlamentar_presente = True - else: - context.update({'error_message': - 'Não há presentes na Sessão com a ' - 'matéria em votação.'}) - - if parlamentar_presente: - voto = [] - if ordem_dia: - voto = VotoParlamentar.objects.filter( - ordem=ordem_dia) - elif expediente: - voto = VotoParlamentar.objects.filter( - expediente=expediente) - - if voto: - try: - voto = voto.get(parlamentar=parlamentar) - context.update({'voto_parlamentar': voto.voto}) - except ObjectDoesNotExist: - context.update( - {'voto_parlamentar': 'Voto não ' - 'computado.'}) - else: - context.update({'error_message': - 'Você não está presente na ' - 'Ordem do Dia/Expediente em votação.'}) - else: - context.update( - {'error_message': 'A matéria aberta não é do tipo ' - 'votação nominal.'}) - else: - context.update( - {'error_message': 'A votação para esta matéria já encerrou.'}) - else: - context.update( - {'error_message': 'Não há nenhuma matéria aberta.'}) - - elif not sessao and msg: - return HttpResponseRedirect('/') - - else: - context.update( - {'error_message': 'Não há nenhuma sessão com matéria aberta.'}) + context, context_vars = can_vote(context, context_vars, request) else: context.update({'permissao': False, @@ -192,36 +212,36 @@ def votante_view(request): # Salva o voto if request.method == 'POST': - if ordem_dia: + if context_vars['ordem_dia']: try: voto = VotoParlamentar.objects.get( - parlamentar=parlamentar, - ordem=ordem_dia) + parlamentar=context_vars['parlamentar'], + ordem=context_vars['ordem_dia']) except ObjectDoesNotExist: voto = VotoParlamentar.objects.create( - parlamentar=parlamentar, + parlamentar=context_vars['parlamentar'], voto=request.POST['voto'], user=request.user, ip=get_client_ip(request), - ordem=ordem_dia) + ordem=context_vars['ordem_dia']) else: voto.voto = request.POST['voto'] voto.ip = get_client_ip(request) voto.user = request.user voto.save() - elif expediente: + elif context_vars['expediente']: try: voto = VotoParlamentar.objects.get( - parlamentar=parlamentar, - expediente=expediente) + parlamentar=context_vars['parlamentar'], + expediente=context_vars['expediente']) except ObjectDoesNotExist: voto = VotoParlamentar.objects.create( - parlamentar=parlamentar, + parlamentar=context_vars['parlamentar'], voto=request.POST['voto'], user=request.user, ip=get_client_ip(request), - expediente=expediente) + expediente=context_vars['expediente']) else: voto.voto = request.POST['voto'] voto.ip = get_client_ip(request) diff --git a/sapl/parlamentares/forms.py b/sapl/parlamentares/forms.py index 36ce94c3a..326d41b3a 100644 --- a/sapl/parlamentares/forms.py +++ b/sapl/parlamentares/forms.py @@ -99,6 +99,19 @@ class MandatoForm(ModelForm): raise ValidationError(_("Data fim mandato fora do intervalo de" " legislatura informada")) + data_expedicao_diploma = data['data_expedicao_diploma'] + if (data_expedicao_diploma and + data_expedicao_diploma > data_inicio_mandato): + raise ValidationError(_("A data da expedição do diploma deve ser anterior " + "a data de início do mandato")) + + coligacao = data['coligacao'] + if coligacao and not coligacao.legislatura == legislatura: + raise ValidationError(_("A coligação selecionada não está cadastrada " + "na mesma legislatura que o presente mandato, " + "favor verificar a coligação ou fazer o cadastro " + "de uma nova coligação na legislatura correspondente")) + existe_mandato = Mandato.objects.filter( parlamentar=data['parlamentar'], legislatura=data['legislatura']).exists() @@ -318,18 +331,32 @@ class FrenteForm(ModelForm): model = Frente fields = '__all__' + def clean(self): + frente = super(FrenteForm, self).clean() + cd = self.cleaned_data + if not self.is_valid(): + return self.cleaned_data + + if cd['data_criacao'] >= cd['data_extincao']: + raise ValidationError(_("Data Dissolução não pode ser anterior a Data Criação")) + + return cd + @transaction.atomic def save(self, commit=True): frente = super(FrenteForm, self).save(commit) - content_type = ContentType.objects.get_for_model(Frente) - object_id = frente.pk - tipo = TipoAutor.objects.get(descricao__icontains='Frente') - Autor.objects.create( - content_type=content_type, - object_id=object_id, - tipo=tipo, - nome=frente.nome - ) + + if not self.instance.pk: + frente = super(FrenteForm, self).save(commit) + content_type = ContentType.objects.get_for_model(Frente) + object_id = frente.pk + tipo = TipoAutor.objects.get(descricao__icontains='Frente') + Autor.objects.create( + content_type=content_type, + object_id=object_id, + tipo=tipo, + nome=frente.nome + ) return frente diff --git a/sapl/parlamentares/migrations/0024_auto_20180814_1237.py b/sapl/parlamentares/migrations/0024_auto_20180814_1237.py new file mode 100644 index 000000000..43873cc68 --- /dev/null +++ b/sapl/parlamentares/migrations/0024_auto_20180814_1237.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-08-14 15:37 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0023_auto_20180626_1524'), + ] + + operations = [ + migrations.AlterField( + model_name='mandato', + name='titular', + field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], db_index=True, default=True, verbose_name='Parlamentar Titular'), + ), + ] diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index 19d4fa57c..43c4e2215 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -443,7 +443,7 @@ class Mandato(models.Model): db_index=True, default=True, choices=YES_NO_CHOICES, - verbose_name=_('Vereador Titular')) + verbose_name=_('Parlamentar Titular')) observacao = models.TextField( blank=True, verbose_name=_('Observação')) diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index 78678c3fc..ad4a8f5e5 100644 --- a/sapl/parlamentares/views.py +++ b/sapl/parlamentares/views.py @@ -87,8 +87,7 @@ class FrenteList(MasterDetailCrud): CreateView, UpdateView, DeleteView = None, None, None class BaseMixin(Crud.PublicMixin, MasterDetailCrud.BaseMixin): - list_field_names = ['nome', 'data_criacao'] - + list_field_names = ['nome', 'data_criacao', 'data_extincao'] @classmethod def url_name(cls, suffix): return '%s_parlamentar_%s' % (cls.model._meta.model_name, suffix) @@ -282,7 +281,8 @@ class FrenteCrud(CrudAux): model = Frente help_topic = 'tipo_situa_militar' public = [RP_DETAIL, RP_LIST] - list_field_names = ['nome', 'data_criacao', 'parlamentares'] + list_field_names = ['nome', 'data_criacao', 'data_extincao', 'parlamentares'] + class CreateView(Crud.CreateView): form_class = FrenteForm @@ -290,6 +290,10 @@ class FrenteCrud(CrudAux): def form_valid(self, form): return super(Crud.CreateView, self).form_valid(form) + class UpdateView(Crud.UpdateView): + form_class = FrenteForm + + class MandatoCrud(MasterDetailCrud): model = Mandato diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index 3350c81cf..67d43fe51 100644 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -550,9 +550,16 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin, qs = self.get_queryset() + qs = qs.prefetch_related("documentoacessorioadministrativo_set", + "tramitacaoadministrativo_set", + "tipo", + "tramitacaoadministrativo_set__status", + "tramitacaoadministrativo_set__unidade_tramitacao_local", + "tramitacaoadministrativo_set__unidade_tramitacao_destino") + if status_tramitacao and unidade_destino: lista = filtra_tramitacao_adm_destino_and_status(status_tramitacao, - unidade_destino) + unidade_destino) qs = qs.filter(id__in=lista).distinct() elif status_tramitacao: @@ -566,11 +573,6 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin, if 'o' in self.request.GET and not self.request.GET['o']: qs = qs.order_by('-ano', '-numero') - qs = qs.prefetch_related("documentoacessorioadministrativo_set", - "tramitacaoadministrativo_set", - "tramitacaoadministrativo_set__status", - "tramitacaoadministrativo_set__unidade_tramitacao_local", - "tramitacaoadministrativo_set__unidade_tramitacao_destino") kwargs.update({ 'queryset': qs, @@ -607,10 +609,10 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin, self.filterset.form.fields['o'].label = _('Ordenação') + length = self.object_list.count() context = self.get_context_data(filter=self.filterset, - object_list=self.object_list, filter_url=url, - numero_res=len(self.object_list) + numero_res=length ) context['show_results'] = show_results_filter_set( diff --git a/sapl/redireciona_urls/urls.py b/sapl/redireciona_urls/urls.py index 63afd8b6b..f8a9aa685 100644 --- a/sapl/redireciona_urls/urls.py +++ b/sapl/redireciona_urls/urls.py @@ -2,20 +2,22 @@ from django.conf.urls import url from .apps import AppConfig from .views import (RedirecionaAtasList, RedirecionaComissao, + RedirecionaComposicaoComissao, RedirecionaHistoricoTramitacoesList, RedirecionaMateriaLegislativaDetail, RedirecionaMateriaLegislativaList, RedirecionaMateriasPorAnoAutorTipo, RedirecionaMateriasPorAutor, RedirecionaMesaDiretoraView, RedirecionaNormasJuridicasDetail, - RedirecionaNormasJuridicasList, RedirecionaParlamentar, - RedirecionaPautaSessao, RedirecionaPresencaParlamentares, + RedirecionaNormasJuridicasList, + RedirecionaNormasJuridicasTextoIntegral, + RedirecionaParlamentar, RedirecionaPautaSessao, + RedirecionaPresencaParlamentares, RedirecionaRelatoriosList, RedirecionaRelatoriosMateriasEmTramitacaoList, RedirecionaSAPLIndex, RedirecionaSessaoPlenaria) app_name = AppConfig.name - urlpatterns = [ url(r'^default_index_html$', RedirecionaSAPLIndex.as_view(), @@ -26,6 +28,9 @@ urlpatterns = [ url(r'^consultas/comissao/comissao_', RedirecionaComissao.as_view(), name='redireciona_comissao'), + url(r'^consultas/comissao/composicao/composicao_index_html', + RedirecionaComposicaoComissao.as_view(), + name='redireciona_composicaio_comissao'), url(r'^consultas/pauta_sessao/pauta_sessao_', RedirecionaPautaSessao.as_view(), name='redireciona_pauta_sessao_'), @@ -44,6 +49,9 @@ urlpatterns = [ url(r'^consultas/norma_juridica/norma_juridica_mostrar_proc', RedirecionaNormasJuridicasDetail.as_view(), name='redireciona_norma_juridica_detail'), + url(r'^sapl_documentos/norma_juridica/(?P[0-9]+)_texto_integral', + RedirecionaNormasJuridicasTextoIntegral.as_view(), + name='redireciona_norma_juridica_texto_integral'), url(r'^relatorios_administrativos/relatorios_administrativos_index_html$', RedirecionaRelatoriosList.as_view(), name='redireciona_relatorios_list'), @@ -51,6 +59,9 @@ urlpatterns = [ RedirecionaRelatoriosMateriasEmTramitacaoList.as_view(), name='redireciona_relatorio_materia_por_tramitacao'), url(r'tramitacaoMaterias/materia_mostrar_proc$', + RedirecionaMateriaLegislativaDetail.as_view(), + name='redireciona_materialegislativa_detail_tramitacao'), + url(r'consultas/materia/materia_mostrar_proc$', RedirecionaMateriaLegislativaDetail.as_view(), name='redireciona_materialegislativa_detail'), url(r'^generico/materia_pesquisar_', @@ -71,4 +82,4 @@ urlpatterns = [ url(r'propositurasAnoAutorTipo', RedirecionaMateriasPorAnoAutorTipo.as_view(), name='redireciona_materia_por_ano_autor_tipo_list'), -] +] \ No newline at end of file diff --git a/sapl/redireciona_urls/views.py b/sapl/redireciona_urls/views.py index 369ff7518..ade47c9de 100644 --- a/sapl/redireciona_urls/views.py +++ b/sapl/redireciona_urls/views.py @@ -1,14 +1,14 @@ from django.core.urlresolvers import NoReverseMatch, reverse from django.views.generic import RedirectView +from sapl.audiencia.apps import AppConfig as audienciaConfig from sapl.base.apps import AppConfig as atasConfig from sapl.comissoes.apps import AppConfig as comissoesConfig from sapl.materia.apps import AppConfig as materiaConfig from sapl.norma.apps import AppConfig as normaConfig +from sapl.norma.models import NormaJuridica from sapl.parlamentares.apps import AppConfig as parlamentaresConfig from sapl.sessao.apps import AppConfig as sessaoConfig -from sapl.audiencia.apps import AppConfig as audienciaConfig - from .exceptions import UnknownUrlNameError EMPTY_STRING = '' @@ -142,6 +142,33 @@ class RedirecionaComissao(RedirectView): return url +class RedirecionaComposicaoComissao(RedirectView): + permanent = True + + def get_redirect_url(self): + url = EMPTY_STRING + pk_composicao = self.request.GET.get( + 'cod_periodo_comp_sel', EMPTY_STRING) + pk_comissao = self.request.GET.get('cod_comissao', EMPTY_STRING) + + if pk_comissao: + kwargs = {'pk': pk_comissao} + + try: + url = reverse(comissao_detail, kwargs=kwargs) + except NoReverseMatch: + raise UnknownUrlNameError(comissao_detail) + else: + try: + url = reverse(comissao_list) + except NoReverseMatch: + raise UnknownUrlNameError(comissao_list) + + url = has_iframe(url, self.request) + + return url + + class RedirecionaPautaSessao(RedirectView): permanent = True @@ -418,6 +445,23 @@ class RedirecionaNormasJuridicasDetail(RedirectView): return url +class RedirecionaNormasJuridicasTextoIntegral(RedirectView): + permanent = False + + def get_redirect_url(self, **kwargs): + url = EMPTY_STRING + try: + norma = NormaJuridica.objects.get(pk=kwargs['norma_id']) + if norma: + url = norma.texto_integral.url + except Exception as e: + raise e + + url = has_iframe(url, self.request) + + return url + + class RedirecionaNormasJuridicasList(RedirectView): permanent = True diff --git a/sapl/rules/apps.py b/sapl/rules/apps.py index 606190110..dbdfce8ce 100644 --- a/sapl/rules/apps.py +++ b/sapl/rules/apps.py @@ -1,15 +1,15 @@ from builtins import LookupError import django -import reversion from django.apps import apps from django.contrib.auth import get_user_model from django.contrib.auth.management import _get_all_permissions from django.core import exceptions from django.db import models, router from django.db.utils import DEFAULT_DB_ALIAS -from django.utils.translation import ugettext_lazy as _ from django.utils.translation import string_concat +from django.utils.translation import ugettext_lazy as _ +import reversion from sapl.rules import (SAPL_GROUP_ADMINISTRATIVO, SAPL_GROUP_COMISSOES, SAPL_GROUP_GERAL, SAPL_GROUP_MATERIA, SAPL_GROUP_NORMA, diff --git a/sapl/rules/map_rules.py b/sapl/rules/map_rules.py index f98da8c0e..bba114149 100644 --- a/sapl/rules/map_rules.py +++ b/sapl/rules/map_rules.py @@ -135,6 +135,7 @@ rules_group_norma = { 'rules': [ (norma.NormaJuridica, __base__), (norma.NormaRelacionada, __base__), + (norma.AnexoNormaJuridica, __base__), # Publicacao está com permissão apenas para norma e não para matéria # e proposições apenas por análise do contexto, não é uma limitação diff --git a/sapl/sessao/forms.py b/sapl/sessao/forms.py index 06dd40830..cc68df531 100644 --- a/sapl/sessao/forms.py +++ b/sapl/sessao/forms.py @@ -112,6 +112,24 @@ class BancadaForm(ModelForm): if not self.is_valid(): return self.cleaned_data + data = self.cleaned_data + + legislatura = data['legislatura'] + + data_criacao = data['data_criacao'] + if data_criacao: + if (data_criacao < legislatura.data_inicio or + data_criacao > legislatura.data_fim): + raise ValidationError(_("Data de criação da bancada fora do intervalo" + " de legislatura informada")) + + data_extincao = data['data_extincao'] + if data_extincao: + if (data_extincao < legislatura.data_inicio or + data_extincao > legislatura.data_fim): + raise ValidationError(_("Data fim da bancada fora do intervalo de" + " legislatura informada")) + if self.cleaned_data['data_extincao']: if (self.cleaned_data['data_extincao'] < self.cleaned_data['data_criacao']): diff --git a/sapl/sessao/tests/test_sessao.py b/sapl/sessao/tests/test_sessao.py index d83c56e86..336c62ad0 100644 --- a/sapl/sessao/tests/test_sessao.py +++ b/sapl/sessao/tests/test_sessao.py @@ -1,4 +1,5 @@ import pytest +from datetime import datetime from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ from model_mommy import mommy @@ -87,9 +88,16 @@ def test_valida_campos_obrigatorios_bancada_form(): assert len(errors) == 3 +def data(valor): + return datetime.strptime(valor, '%Y-%m-%d').date() + + @pytest.mark.django_db(transaction=False) def test_bancada_form_valido(): - legislatura = mommy.make(Legislatura) + legislatura = mommy.make(Legislatura, + data_inicio=data('2017-11-10'), + data_fim=data('2017-12-31'), + ) partido = mommy.make(Partido) form = forms.BancadaForm(data={'legislatura': str(legislatura.pk), @@ -105,7 +113,10 @@ def test_bancada_form_valido(): @pytest.mark.django_db(transaction=False) def test_bancada_form_datas_invalidas(): - legislatura = mommy.make(Legislatura) + legislatura = mommy.make(Legislatura, + data_inicio=data('2017-11-10'), + data_fim=data('2017-12-31'), + ) partido = mommy.make(Partido) form = forms.BancadaForm(data={'legislatura': str(legislatura.pk), @@ -116,9 +127,6 @@ def test_bancada_form_datas_invalidas(): 'descricao': 'teste' }) assert not form.is_valid() - assert form.errors['__all__'] == [_('Data de extinção não pode ser menor ' - 'que a de criação')] - @pytest.mark.django_db(transaction=False) def test_expediente_materia_form_valido(): diff --git a/sapl/settings.py b/sapl/settings.py index 72cf086cc..f29011464 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -94,7 +94,10 @@ INSTALLED_APPS = ( ) + SAPL_APPS # FTS = Full Text Search -HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' +# Desabilita a indexação textual até encontramos uma solução para a issue +# https://github.com/interlegis/sapl/issues/2055 +#HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' +HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.BaseSignalProcessor' SEARCH_BACKEND = 'haystack.backends.whoosh_backend.WhooshEngine' SEARCH_URL = ('PATH', PROJECT_DIR.child('whoosh')) @@ -218,7 +221,7 @@ EMAIL_SEND_USER = config('EMAIL_SEND_USER', cast=str, default='') DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', cast=str, default='') SERVER_EMAIL = config('SERVER_EMAIL', cast=str, default='') -MAX_DOC_UPLOAD_SIZE = 20 * 1024 * 1024 # 20MB +MAX_DOC_UPLOAD_SIZE = 50 * 1024 * 1024 # 50MB MAX_IMAGE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB # Internationalization diff --git a/sapl/templates/base.html b/sapl/templates/base.html index c6a5d223b..3e1dc3a39 100644 --- a/sapl/templates/base.html +++ b/sapl/templates/base.html @@ -203,7 +203,7 @@ {{ endereco }}
CEP: {{ cep }} | Telefone: {{ telefone }}
- {% trans 'Site da Câmara' %} | + {% trans 'Site' %} | diff --git a/sapl/templates/base/RelatorioMateriasPorAutor_filter.html b/sapl/templates/base/RelatorioMateriasPorAutor_filter.html index ec26ecd63..bf0b7702f 100644 --- a/sapl/templates/base/RelatorioMateriasPorAutor_filter.html +++ b/sapl/templates/base/RelatorioMateriasPorAutor_filter.html @@ -15,56 +15,55 @@ - + - + {% for key, value in qtdes.items %} - + {% endfor %} -
QUADRO GERAL
QUADRO GERAL
Tipo MatériaTipo Matéria Quantidade
{{key.sigla}} - {{key}}{{key.sigla}} - {{key}} {{value}}
- - - - - - - - - - - {% for materia in object_list %} - - - - - - - {% endfor %} - + {% for materia in object_list %} + {% ifchanged materia.autoria_set.first.autor %} + + + + + + + + + + + {% endifchanged %} + + + + + + + + + {% endfor %}
MatériaEmentaAutorCoautor(es)
- {{materia.tipo.sigla}} {{materia.numero}}/{{materia.ano}} - {{materia.ementa}} - {% for autor in materia.autoria_set.all %} - {% if autor.primeiro_autor %} - {{autor.autor}}
- {% endif %} - {% endfor %} -
- {% for autor in materia.autoria_set.all %} - {% if not autor.primeiro_autor %} - {{autor.autor}}
- {% endif %} - {% endfor %} -
Autor: {{ materia.autoria_set.first.autor }}
MatériaEmentaCoautor(es)
+ {{materia.tipo.sigla}} {{materia.numero}}/{{materia.ano}} + {% autoescape off %}{{materia.ementa}}{% endautoescape %} + {% if materia.autoria_set.first != materia.autoria_set.last %} + {% for autor in materia.autoria_set.all %} + {% if not autor.primeiro_autor %} + {{ autor.autor }}
+ {% endif %} + {% endfor %} + {% endif %} +
+ {% endif %} {% endblock base_content %} diff --git a/sapl/templates/compilacao/dispositivo_form.html b/sapl/templates/compilacao/dispositivo_form.html index 0e1d1f0f0..15d383600 100644 --- a/sapl/templates/compilacao/dispositivo_form.html +++ b/sapl/templates/compilacao/dispositivo_form.html @@ -29,7 +29,7 @@