diff --git a/docker-compose.yml b/docker-compose.yml index 1ad5543cb..11902a565 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ sapldb: ports: - "5432:5432" sapl: - image: interlegis/sapl:3.1.132 + image: interlegis/sapl:3.1.134 restart: always environment: ADMIN_PASSWORD: interlegis diff --git a/sapl/compilacao/forms.py b/sapl/compilacao/forms.py index 6407f8925..f7889604a 100644 --- a/sapl/compilacao/forms.py +++ b/sapl/compilacao/forms.py @@ -1343,7 +1343,7 @@ class DispositivoRegistroAlteracaoForm(Form): 'dispositivo_alterado', data_sapl_ta='DispositivoSearch', data_field='dispositivo_alterado', - data_type_selection='radio', + data_type_selection='checkbox', template="compilacao/layout/dispositivo_radio.html") layout.append(Fieldset(_('Registro de Alteração - ' @@ -1378,6 +1378,14 @@ class DispositivoRegistroRevogacaoForm(Form): required=False, queryset=Dispositivo.objects.all()) + revogacao_em_bloco = forms.ChoiceField( + label=_( + 'Revogar todos os dispositivos internos dos ' + 'dispositivos abaixo selecionados?'), + choices=YES_NO_CHOICES, + widget=forms.RadioSelect(), + required=True) + dispositivo_search_form = forms.CharField(widget=forms.HiddenInput(), required=False) @@ -1391,11 +1399,12 @@ class DispositivoRegistroRevogacaoForm(Form): 'dispositivo_revogado', data_sapl_ta='DispositivoSearch', data_field='dispositivo_revogado', - data_type_selection='radio', + data_type_selection='checkbox', template="compilacao/layout/dispositivo_radio.html") layout.append(Fieldset(_('Registro de Revogação - ' 'Seleção do Dispositivo a ser Revogado'), + Field(InlineRadios('revogacao_em_bloco')), row_dispositivo, css_class="col-md-12")) layout.append(Field('dispositivo_search_form')) @@ -1439,7 +1448,7 @@ class DispositivoRegistroInclusaoForm(Form): 'dispositivo_base_para_inclusao', data_sapl_ta='DispositivoSearch', data_field='dispositivo_base_para_inclusao', - data_type_selection='radio', + data_type_selection='checkbox', template="compilacao/layout/dispositivo_radio.html") layout.append(Fieldset(_('Registro de Inclusão - ' diff --git a/sapl/compilacao/models.py b/sapl/compilacao/models.py index 3d5b7a679..bb504934b 100644 --- a/sapl/compilacao/models.py +++ b/sapl/compilacao/models.py @@ -187,7 +187,7 @@ class TextoArticulado(TimestampedMixin): ementa = models.TextField(verbose_name=_('Ementa')) observacao = models.TextField(blank=True, verbose_name=_('Observação')) numero = models.CharField( - max_length=8,verbose_name=_('Número')) + max_length=8, verbose_name=_('Número')) ano = models.PositiveSmallIntegerField(verbose_name=_('Ano')) tipo_ta = models.ForeignKey( TipoTextoArticulado, @@ -1386,28 +1386,30 @@ class Dispositivo(BaseModel, TimestampedMixin): return result - def criar_espaco(self, espaco_a_criar, local): + def criar_espaco(self, espaco_a_criar, local=None): if local == 'json_add_next': proximo_bloco = Dispositivo.objects.filter( ordem__gt=self.ordem, nivel__lte=self.nivel, - ta_id=self.ta_id)[:1] + ta_id=self.ta_id).first() elif local == 'json_add_in': - # FIXME: o exclude não deve estar limitado a uma class_css caput e - # sim a qualquer filho de inserção automática proximo_bloco = Dispositivo.objects.filter( ordem__gt=self.ordem, nivel__lte=self.nivel + 1, - ta_id=self.ta_id).exclude( - tipo_dispositivo__class_css='caput')[:1] + ta_id=self.ta_id).exclude(auto_inserido=True).first() + elif local == 'json_add_in_with_auto': + proximo_bloco = Dispositivo.objects.filter( + ordem__gt=self.ordem, + nivel__lte=self.nivel + 1, + ta_id=self.ta_id).first() else: proximo_bloco = Dispositivo.objects.filter( ordem__gte=self.ordem, - ta_id=self.ta_id)[:1] + ta_id=self.ta_id).first() - if proximo_bloco.exists(): - ordem = proximo_bloco[0].ordem + if proximo_bloco: + ordem = proximo_bloco.ordem proximo_bloco = Dispositivo.objects.order_by('-ordem').filter( ordem__gte=ordem, ta_id=self.ta_id) diff --git a/sapl/compilacao/views.py b/sapl/compilacao/views.py index 6d71344eb..a82f63556 100644 --- a/sapl/compilacao/views.py +++ b/sapl/compilacao/views.py @@ -9,10 +9,12 @@ from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ValidationError from django.core.signing import Signer from django.core.urlresolvers import reverse, reverse_lazy from django.db import transaction from django.db.models import Q +from django.db.models.query import QuerySet from django.http.response import (HttpResponse, HttpResponseRedirect, JsonResponse, Http404) from django.shortcuts import get_object_or_404, redirect @@ -1525,6 +1527,13 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin): return data def remover_dispositivo(self, base, bloco): + + if base.tipo_dispositivo.dispositivo_de_alteracao: + bloco = False + for d in base.dispositivos_alterados_set.all(): + d.refresh_from_db() + self.remover_dispositivo(d, bloco) + username = self.request.user.username base_ordem = base.ordem if base.dispositivo_subsequente or base.dispositivo_substituido: @@ -1836,7 +1845,10 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin): dpts = base.dispositivos_alterados_set.all().order_by( '-ordem_bloco_atualizador') for dpt in dpts: - self.remover_dispositivo(dpt, False) + try: + self.remover_dispositivo(dpt, False) + except Exception as e: + print(e) if base.pk: """ @@ -2585,17 +2597,20 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin, return self.json_add_next_registra_inclusao( context, local_add='json_add_in') - def registra_revogacao(self, bloco_alteracao, dispositivo_a_revogar): + def registra_revogacao(self, bloco_alteracao, dsp_a_rev, em_bloco=False): + return self.registra_alteracao( bloco_alteracao, - dispositivo_a_revogar, - revogacao=True + dsp_a_rev, + revogacao=True, + em_bloco=em_bloco ) def registra_alteracao(self, bloco_alteracao, - dispositivo_a_alterar, - revogacao=False): + dsp_a_alterar, + revogacao=False, + em_bloco=False): """ Caracteristicas: 1 - Se é um dispositivo simples e sem subsequente @@ -2621,14 +2636,69 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin, data.update({'pk': bloco_alteracao.pk, 'pai': [bloco_alteracao.pk, ]}) - history = dispositivo_a_alterar.history() + if isinstance(dsp_a_alterar, list): + dsps = Dispositivo.objects.filter(id__in=dsp_a_alterar) + dsps_ids = set() + for d in dsps: + ds = d + while ds.dispositivo_subsequente: + ds = ds.dispositivo_subsequente + dsps_ids.add(ds.pk) + + if em_bloco: + proximo_bloco = Dispositivo.objects.filter( + ordem__gt=ds.ordem, + nivel__lte=ds.nivel, + ta_id=ds.ta_id).first() + + params = { + 'ta_id': ds.ta_id, + 'nivel__gte': ds.nivel, + 'ordem__gte': ds.ordem, + 'dispositivo_subsequente__isnull': True + } + + if proximo_bloco: + params['ordem__lt'] = proximo_bloco.ordem + + bloco = Dispositivo.objects.filter( + **params).values_list('id', 'auto_inserido') + for id, auto in bloco: + if auto: + dsp_pai = Dispositivo.objects.filter( + pk=id + ).values_list('dispositivo_pai', flat=True).first() + if dsp_pai in dsps_ids: + dsps_ids.remove(dsp_pai) + dsps_ids.add(id) + + dsps_ids = Dispositivo.objects.filter( + id__in=dsps_ids + ).values_list('id', flat="True") + for dsp in dsps_ids: + with transaction.atomic(): + data.update( + self.registra_alteracao( + bloco_alteracao, + dsp, + revogacao + ) + ) + if 'message' in data and 'danger' in data['message']['type']: + return data + return data + + dsp_a_alterar = Dispositivo.objects.get( + pk=dsp_a_alterar) - for d in history: + history = dsp_a_alterar.history() + + for d in list(history): if d.inicio_vigencia <= bloco_alteracao.inicio_vigencia: - dispositivo_a_alterar = d + dsp_a_alterar = d break - if (dispositivo_a_alterar.inicio_vigencia > + if (dsp_a_alterar.inicio_vigencia > bloco_alteracao.inicio_vigencia): self.set_message( data, 'danger', @@ -2637,7 +2707,7 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin, 'Alterador!'), time=10000) return data - if dispositivo_a_alterar.tipo_dispositivo.dispositivo_de_articulacao\ + if dsp_a_alterar.tipo_dispositivo.dispositivo_de_articulacao\ and not revogacao: self.set_message( data, 'warning', @@ -2647,13 +2717,13 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin, 'para o dispositivo que se quer alterar.'), modal=True) ndp = Dispositivo.new_instance_based_on( - dispositivo_a_alterar, dispositivo_a_alterar.tipo_dispositivo) - ndp.auto_inserido = dispositivo_a_alterar.auto_inserido - ndp.rotulo = dispositivo_a_alterar.rotulo + dsp_a_alterar, dsp_a_alterar.tipo_dispositivo) + ndp.auto_inserido = dsp_a_alterar.auto_inserido + ndp.rotulo = dsp_a_alterar.rotulo ndp.publicacao = bloco_alteracao.publicacao if not revogacao: - ndp.texto = dispositivo_a_alterar.texto + ndp.texto = dsp_a_alterar.texto else: ndp.texto = Dispositivo.TEXTO_PADRAO_DISPOSITIVO_REVOGADO ndp.dispositivo_de_revogacao = True @@ -2668,15 +2738,15 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin, ndp.inicio_vigencia = bloco_alteracao.inicio_eficacia try: - ordem = dispositivo_a_alterar.criar_espaco( - espaco_a_criar=1, local='json_add_in') + ordem = dsp_a_alterar.criar_espaco( + espaco_a_criar=1, local='json_add_in_with_auto') ndp.ordem = ordem ndp.dispositivo_atualizador = bloco_alteracao ndp.ta_publicado = bloco_alteracao.ta - p = dispositivo_a_alterar - n = dispositivo_a_alterar.dispositivo_subsequente + p = dsp_a_alterar + n = dsp_a_alterar.dispositivo_subsequente ndp.dispositivo_substituido = p ndp.dispositivo_subsequente = n @@ -2711,7 +2781,7 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin, n.dispositivo_substituido = ndp n.save() - filhos_diretos = dispositivo_a_alterar.dispositivos_filhos_set + filhos_diretos = dsp_a_alterar.dispositivos_filhos_set for d in filhos_diretos.all(): d.dispositivo_pai = ndp d.save() @@ -2728,14 +2798,20 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin, self.set_message( data, 'success', _('Dispositivo de Revogação adicionado com sucesso.')) - + # data.update({'pk': ndp.pk, + # 'pai': [bloco_alteracao.pk, ]}) + except ValidationError as ve: + self.set_message( + data, 'danger', + _('O dispositivo ({} - {}) já existe neste bloco.'.format( + ndp.tipo_dispositivo, + ndp.get_nomenclatura_completa())), time=10000) except Exception as e: username = self.request.user.username self.logger.error("user=" + username + ". " + str(e)) - print(e) - - data.update({'pk': ndp.pk, - 'pai': [bloco_alteracao.pk, ]}) + self.set_message( + data, 'danger', + _('Não é foi possível registrar sua solicitação!'), time=10000) return data @@ -2836,17 +2912,16 @@ class DispositivoDinamicEditView( formtype = request.POST['formtype'] if formtype == 'get_form_alteracao': - dispositivo_a_alterar = Dispositivo.objects.get( - pk=request.POST['dispositivo_alterado']) - - data = self.registra_alteracao(d, dispositivo_a_alterar) + data = self.registra_alteracao( + d, request.POST.getlist('dispositivo_alterado[]', [])) elif formtype == 'get_form_revogacao': - dispositivo_a_revogar = Dispositivo.objects.get( - pk=request.POST['dispositivo_revogado']) - - data = self.registra_revogacao(d, dispositivo_a_revogar) + data = self.registra_revogacao( + d, + request.POST.getlist('dispositivo_revogado[]', []), + request.POST.get("revogacao_em_bloco") == "True" + ) if formtype == 'get_form_inclusao': diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 5fec4a2f7..ebce51a5d 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -2179,6 +2179,10 @@ class EtiquetaPesquisaView(PermissionRequiredMixin, FormView): if context['quantidade'] > 20: materias = materias[:20] + for m in materias: + if len(m.ementa) > 100: + m.ementa = m.ementa[0:99] + "[...]" + context['materias'] = materias return gerar_pdf_impressos(self.request, context, @@ -2267,7 +2271,8 @@ class FichaSelecionaView(PermissionRequiredMixin, FormView): self.messages.add_message(self.request, messages.INFO, mensagem) return self.render_to_response(context) - + if len(materia.ementa) > 301: + materia.ementa = materia.ementa[0:300] + '[...]' context['materia'] = materia context['despachos'] = materia.despachoinicial_set.all().values_list( 'comissao__nome', flat=True) diff --git a/sapl/norma/forms.py b/sapl/norma/forms.py index d4f0046a6..674e1b9a8 100644 --- a/sapl/norma/forms.py +++ b/sapl/norma/forms.py @@ -64,7 +64,7 @@ class NormaFilterSet(django_filters.FilterSet): class Meta: model = NormaJuridica - fields = ['tipo', 'numero', 'ano', 'data', + fields = ['tipo', 'numero', 'ano', 'data', 'data_vigencia', 'data_publicacao', 'ementa', 'assuntos'] def __init__(self, *args, **kwargs): @@ -73,13 +73,14 @@ class NormaFilterSet(django_filters.FilterSet): row1 = to_row([('tipo', 4), ('numero', 4), ('ano', 4)]) row2 = to_row([('data', 6), ('data_publicacao', 6)]) row3 = to_row([('ementa', 6), ('assuntos', 6)]) - row4 = to_row([('o',6), ('indexacao', 6)]) + row4 = to_row([('data_vigencia', 12)]) + row5 = to_row([('o',6), ('indexacao', 6)]) self.form.helper = FormHelper() self.form.helper.form_method = 'GET' self.form.helper.layout = Layout( Fieldset(_('Pesquisa de Norma'), - row1, row2, row3, row4, + row1, row2, row3, row4, row5, form_actions(label='Pesquisar')) ) @@ -120,6 +121,7 @@ class NormaJuridicaForm(ModelForm): 'numero_materia', 'ano_materia', 'data_publicacao', + 'data_vigencia', 'veiculo_publicacao', 'pagina_inicio_publicacao', 'pagina_fim_publicacao', diff --git a/sapl/norma/migrations/0015_auto_20181109_1422.py b/sapl/norma/migrations/0015_auto_20181109_1422.py new file mode 100644 index 000000000..e6550dcb8 --- /dev/null +++ b/sapl/norma/migrations/0015_auto_20181109_1422.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.8 on 2018-11-09 16:22 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('norma', '0014_auto_20181008_1655'), + ] + + operations = [ + migrations.AlterField( + model_name='normajuridica', + name='data_vigencia', + field=models.DateField(blank=True, null=True, verbose_name='Data Fim Vigência'), + ), + ] diff --git a/sapl/norma/models.py b/sapl/norma/models.py index 46f34a0ef..fd06b553e 100644 --- a/sapl/norma/models.py +++ b/sapl/norma/models.py @@ -119,7 +119,7 @@ class NormaJuridica(models.Model): assuntos = models.ManyToManyField( AssuntoNorma, blank=True, verbose_name=_('Assuntos')) - data_vigencia = models.DateField(blank=True, null=True) + data_vigencia = models.DateField(blank=True, null=True, verbose_name=_('Data Fim Vigência')) timestamp = models.DateTimeField(null=True) texto_articulado = GenericRelation( diff --git a/sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py b/sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py index a68a252fb..bdbce554e 100644 --- a/sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py +++ b/sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py @@ -352,6 +352,7 @@ def principal(cabecalho_dic, rodape_dic, imagem, sessao, inf_basicas_dic, lst_me tmp += dict_ord_template[ordenacao.oitavo] tmp += dict_ord_template[ordenacao.nono] tmp += dict_ord_template[ordenacao.decimo] + tmp += dict_ord_template[ordenacao.decimo_primeiro] else: tmp += inf_basicas(inf_basicas_dic) diff --git a/sapl/relatorios/views.py b/sapl/relatorios/views.py index 9ca80f616..4fb4785dd 100755 --- a/sapl/relatorios/views.py +++ b/sapl/relatorios/views.py @@ -466,6 +466,29 @@ def get_espelho(mats): return materias +def remove_html_comments(text): + """ + Assume comentários bem formados e + não aninhados como --> + :param text: + :return: + """ + clean_text = text + start = clean_text.find('') + 2 + output_text = [] + for idx, i in enumerate(clean_text): + if not start <= idx <= end: + output_text.append(i) + clean_text = ''.join(output_text) + start = clean_text.find('