From 0df877108b5d57010059671776116d2ee3823757 Mon Sep 17 00:00:00 2001 From: Leandro Roberto Date: Tue, 9 Apr 2019 11:38:44 -0300 Subject: [PATCH 01/59] =?UTF-8?q?impl=20action=20para=20expedientes=20de?= =?UTF-8?q?=20uma=20sess=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/api/views.py | 16 +++++++++++++++- sapl/sessao/models.py | 23 ++++++++++++++++------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/sapl/api/views.py b/sapl/api/views.py index 2eedb9464..8222e8227 100644 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -27,7 +27,7 @@ from sapl.materia.models import Proposicao, TipoMateriaLegislativa,\ from sapl.parlamentares.models import Parlamentar from sapl.protocoloadm.models import DocumentoAdministrativo,\ DocumentoAcessorioAdministrativo, TramitacaoAdministrativo -from sapl.sessao.models import SessaoPlenaria +from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao from sapl.utils import models_with_gr_for_model, choice_anos_com_sessaoplenaria @@ -498,3 +498,17 @@ class _SessaoPlenariaViewSet: serializer = ChoiceSerializer(years, many=True) return Response(serializer.data) + + @action(detail=True) + def expedientes(self, request, *args, **kwargs): + + sessao = self.get_object() + + page = self.paginate_queryset(sessao.expedientesessao_set.all()) + if page is not None: + serializer = SaplApiViewSetConstrutor.get_class_for_model( + ExpedienteSessao).serializer_class(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = self.get_serializer(page, many=True) + return Response(serializer.data) diff --git a/sapl/sessao/models.py b/sapl/sessao/models.py index 7267e78fc..725898b57 100644 --- a/sapl/sessao/models.py +++ b/sapl/sessao/models.py @@ -290,8 +290,11 @@ class TipoExpediente(models.Model): @reversion.register() class ExpedienteSessao(models.Model): # ExpedienteSessaoPlenaria - sessao_plenaria = models.ForeignKey(SessaoPlenaria, - on_delete=models.CASCADE) + sessao_plenaria = models.ForeignKey( + SessaoPlenaria, + on_delete=models.CASCADE, + related_name='expedientesessao_set' + ) tipo = models.ForeignKey(TipoExpediente, on_delete=models.PROTECT) conteudo = models.TextField( blank=True, verbose_name=_('Conteúdo do expediente')) @@ -379,7 +382,7 @@ class OradorExpediente(AbstractOrador): # OradoresExpediente @reversion.register() -class OradorOrdemDia(AbstractOrador): # OradoresOrdemDia +class OradorOrdemDia(AbstractOrador): # OradoresOrdemDia class Meta: verbose_name = _('Orador da Ordem do Dia') @@ -584,10 +587,14 @@ class ResumoOrdenacao(models.Model): oitavo = models.CharField(max_length=30) nono = models.CharField(max_length=30) decimo = models.CharField(max_length=30) - decimo_primeiro = models.CharField(max_length=30,default="Ocorrências da Sessão") - decimo_segundo = models.CharField(max_length=30, default="Votos Nominais Mat Expediente") - decimo_terceiro = models.CharField(max_length=30, default="Votos Nominais Mat Ordem Dia") - decimo_quarto = models.CharField(max_length=30, default="Oradores da Ordem do Dia") + decimo_primeiro = models.CharField( + max_length=30, default="Ocorrências da Sessão") + decimo_segundo = models.CharField( + max_length=30, default="Votos Nominais Mat Expediente") + decimo_terceiro = models.CharField( + max_length=30, default="Votos Nominais Mat Ordem Dia") + decimo_quarto = models.CharField( + max_length=30, default="Oradores da Ordem do Dia") class Meta: verbose_name = _('Ordenação do Resumo de uma Sessão') @@ -596,6 +603,7 @@ class ResumoOrdenacao(models.Model): def __str__(self): return 'Ordenação do Resumo de uma Sessão' + @reversion.register() class TipoRetiradaPauta(models.Model): descricao = models.CharField(max_length=150, verbose_name=_('Descrição')) @@ -687,6 +695,7 @@ class JustificativaAusencia(models.Model): using=using, update_fields=update_fields) + class RetiradaPauta(models.Model): materia = models.ForeignKey(MateriaLegislativa, on_delete=models.CASCADE, From 65dbe439d965f3e28f48d9072a5489ad62764ebe Mon Sep 17 00:00:00 2001 From: Edward Date: Tue, 9 Apr 2019 14:04:06 -0300 Subject: [PATCH 02/59] Update solr.rst --- docs/solr.rst | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/docs/solr.rst b/docs/solr.rst index ae8bbdb63..13bdbf02c 100644 --- a/docs/solr.rst +++ b/docs/solr.rst @@ -2,6 +2,8 @@ Instruções para instalar o Solr ================================ +**O servidor do Solr NÃO DEVE SER EXPOSTO NA INTERNET. Assim como o servidor de bancos de dados Postgres ele deve estar acessível pelo SAPL na rede interna (atrás de NATs/firewalls/proxies/etc).** + Solr é uma plataforma open source de indexação e busca textual utilizada pelo SAPL 3.1 para indexar documentos (normas jurídicas, matérias legislativas e documentos acessórios). Observação: Se a execução do SAPL for mediante containers Docker então use o arquivo *docker-compose.yml* disponível em @@ -45,11 +47,42 @@ Observação: Se a execução do SAPL for mediante containers Docker então use 8) Enquanto o Solr realiza a indexação da base de dados do SAPL, inicie em uma outra tela o SAPL; 9) Após realizados os passos com sucesso, nas telas de busca de Matéria Legislativa e Normas deverá aparecer um botão -de 'Busca Textual' próximo ao botão de busca tradicional. +de 'Pesquisa Textual' na tela de busca tradicional. **Observações:** * Para parar o Solr execute o comando **$SOLR_HOME/bin/solr stop** -* Para reindexar os dados do SAPL execute o comando **python3 manage.py rebuild_index** (isso irá apagar todos os dados -do Solr e indexar tudo novamente). + +* Comandos de manutenção da base textual do Solr: + +1. **python3 manage.py rebuild_index** : Apaga os dados da coleção `sapl` no Solr e reindexa tudo do início; + +2. **python3 manage.py clear_index** : Apaga todos os dados da coleção `sapl` do Solr. **Este comando não irá apagar os dados do BD Postgres, somente os dados do Solr serão apagados.** + +3. **python3 manage.py update_index** : atualiza os dados do Solr: + +3.1. **python3 manage.py update_index --remove** : remove objetos do Solr que não mais existem no BD Postgres (no caso do Postgres e Solr derem dessincronizados). + +3.2. **python3 manage.py update_index --age ** : reindexa os documentos inseridos/alterados nas últimas horas; + +3.3. **python3 manage.py update_index -s YYYY-MM-DDTHH:MM:SS -e YYYY-MM-DDTHH:MM:SS** : reindexa os documentos que foram inseridos/atualizados entre a data inicial (-s) e a data final (-e). Ambos os argumentos de início e fim são opcionais. + + +### FAQ + +1. Uma dúvida quanto a indexação do Solr, pelo que entendi de tempos e tempos tenho que rodar o comando para poder indexar novos arquivos certo? + + Errado. Cada novo documento inserido, atualizado, ou removido do SAPL dispara uma nova indexação somente daquele documento no Solr automaticamente. + +2. O comando **python3 solr_api.py -c sapl -u http://localhost:8983** indexa os novos arquivos? + + Não. Este comando é para construir a coleção do Solr a primeira vez e, por acaso, faz a indexação inicial. Não deve ser usado se a coleção já foi criada. + +3. Ou teria que reindexar do zero com *rebuild_index*? + + Pode acontecer do Postgres e o Solr se dessincronizarem (ex: o Solr ficou fora do ar por um dia e foram inseridos registros no SAPL). Ou por algum motivo se deseja refazer o índice do Solr. Neste caso pode-se refazer a indexação no Solr com o comando : **python3 manage.py rebuild_index** (direto na linha de comando, a partir da pasta raiz do SAPL). Mas existem maneiras de atualizar somente os documentos inseridos/alterados a partir de uma determinada data ao invés de atualizar tudo do zero de novo. + +4. Pergunto isso pois estou querendo criar um script para crontab para indexar esses novos arquivos + +Desnecessário. From 035b7d352374f5739d4c8d67ede1263294ff977c Mon Sep 17 00:00:00 2001 From: Edward Ribeiro Date: Tue, 9 Apr 2019 15:41:18 -0300 Subject: [PATCH 03/59] =?UTF-8?q?Ajusta=20tags=20para=20n=C3=A3o=20estoura?= =?UTF-8?q?r=20ml2pdf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/relatorios/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sapl/relatorios/views.py b/sapl/relatorios/views.py index 117b7a172..5bd3fe6f7 100755 --- a/sapl/relatorios/views.py +++ b/sapl/relatorios/views.py @@ -580,7 +580,9 @@ def get_sessao_plenaria(sessao, casa): # https://github.com/interlegis/sapl/issues/1046 conteudo = re.sub('style=".*?"', '', conteudo) conteudo = re.sub('class=".*?"', '', conteudo) + conteudo = re.sub('align=".*?"', '', conteudo) # OSTicket Ticket #796450 conteudo = re.sub('', '

', conteudo) + conteudo = re.sub('', '
', conteudo) # OSTicket Ticket #796450 conteudo = html.unescape(conteudo) # escape special character '&' From 313979fc5335d3fc769dcfe81f2a8c876f3b367f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Matheus?= Date: Wed, 10 Apr 2019 08:45:23 -0300 Subject: [PATCH 04/59] HOT-FIX: Adiciona migracao para passar no teste --- .../migrations/0036_auto_20190410_0836.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 sapl/sessao/migrations/0036_auto_20190410_0836.py diff --git a/sapl/sessao/migrations/0036_auto_20190410_0836.py b/sapl/sessao/migrations/0036_auto_20190410_0836.py new file mode 100644 index 000000000..981734928 --- /dev/null +++ b/sapl/sessao/migrations/0036_auto_20190410_0836.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-04-10 11:36 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('sessao', '0035_resumoordenacao_decimo_quarto'), + ] + + operations = [ + migrations.AlterField( + model_name='expedientesessao', + name='sessao_plenaria', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='expedientesessao_set', to='sessao.SessaoPlenaria'), + ), + ] From 5adcf9c4843d0af108f6245b56fcec9c2dca78bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Cantu=C3=A1ria?= Date: Wed, 10 Apr 2019 14:20:04 -0300 Subject: [PATCH 05/59] HOT-FIX: Corrige contagem de autores duplicados --- sapl/base/views.py | 7 ++----- sapl/templates/base/autores_duplicados.html | 8 +++----- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/sapl/base/views.py b/sapl/base/views.py index 2312ab721..3226b4926 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -1081,9 +1081,7 @@ class ListarBancadaComissaoAutorExternoView(PermissionRequiredMixin, ListView): def autores_duplicados(): - return [autor.values() for autor in Autor.objects.values( - 'nome', 'tipo__descricao').order_by( - "nome").annotate(count=Count('nome')).filter(count__gt=1)] + return [autor for autor in Autor.objects.values('nome').annotate(count=Count('nome')).filter(count__gt=1)] class ListarAutoresDuplicadosView(PermissionRequiredMixin, ListView): @@ -1097,8 +1095,7 @@ class ListarAutoresDuplicadosView(PermissionRequiredMixin, ListView): return autores_duplicados() def get_context_data(self, **kwargs): - context = super( - ListarAutoresDuplicadosView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) paginator = context['paginator'] page_obj = context['page_obj'] context['page_range'] = make_pagination( diff --git a/sapl/templates/base/autores_duplicados.html b/sapl/templates/base/autores_duplicados.html index a7861aecd..361ff7add 100644 --- a/sapl/templates/base/autores_duplicados.html +++ b/sapl/templates/base/autores_duplicados.html @@ -11,16 +11,14 @@ Autor - Tipo de Autor Quantidade - {% for autor, tipo, quantidade in autores_duplicados %} + {% for autor in autores_duplicados %} - {{ autor }} - {{ tipo }} - {{ quantidade }} + {{ autor.nome }} + {{ autor.count }} {% endfor %} From d55a7a54febd2f1e821c890036b3403736fdc21d Mon Sep 17 00:00:00 2001 From: Victor Fabre Date: Wed, 10 Apr 2019 15:16:21 -0300 Subject: [PATCH 06/59] HOT-FIX: limita caracteres capa processo --- sapl/protocoloadm/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index bb5d12ed1..c400cf9a2 100755 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -1232,8 +1232,8 @@ class FichaSelecionaAdmView(PermissionRequiredMixin, FormView): self.messages.add_message(self.request, messages.INFO, mensagem) return self.render_to_response(context) - if len(documento.assunto) > 301: - documento.assunto = documento.assunto[0:300] + '[...]' + if len(documento.assunto) > 201: + documento.assunto = documento.assunto[0:200] + '[...]' context['documento'] = documento return gerar_pdf_impressos(self.request, context, From 33e7db1a42ee599474fe73e5f751a4b31b70a1dd Mon Sep 17 00:00:00 2001 From: Cesar Carvalho Date: Wed, 10 Apr 2019 15:19:09 -0300 Subject: [PATCH 07/59] Adiciona script de checar os commits entre 3.1.x e master --- scripts/check_commits.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 scripts/check_commits.py diff --git a/scripts/check_commits.py b/scripts/check_commits.py new file mode 100644 index 000000000..f34c6f0c6 --- /dev/null +++ b/scripts/check_commits.py @@ -0,0 +1,27 @@ +import requests + +branch_master = requests.get('https://api.github.com/repos/interlegis/sapl/commits?per_page=100&sha=master') +branch_master = branch_master.json() + +commits_master = [e['commit']['message'] for e in branch_master] + +branch_3_1_x = requests.get('https://api.github.com/repos/interlegis/sapl/commits?per_page=70&sha=3.1.x') +branch_3_1_x = branch_3_1_x.json() + +commits_3_1_x = [(e['commit']['message'], {'sha':e['sha'], 'data':e['commit']['author']['date']}) for e in branch_3_1_x] + +print("\nCommits que estão em 3.1.x, mas não em master:\n") + +# retira os \r para evitar bugs +commits_master = [commit.replace('\r', '') for commit in commits_master] + +for c, s in commits_3_1_x: + # retira os \r para evitar bugs + c = c.replace('\r', '') + if c not in commits_master: + print('---------------------------------------------------------------------') + print('Data: ' + s['data'][:10]) + print(s['sha'][:7] + '-> ' + c) + +print('---------------------------------------------------------------------') + From 661615116b514cc18d42be91761af81cb8e7f38b Mon Sep 17 00:00:00 2001 From: Victor Fabre Date: Wed, 10 Apr 2019 17:16:52 -0300 Subject: [PATCH 08/59] HOT-FIX: Fix #2717 --- sapl/rules/map_rules.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sapl/rules/map_rules.py b/sapl/rules/map_rules.py index d09e65961..d7f3e0b48 100644 --- a/sapl/rules/map_rules.py +++ b/sapl/rules/map_rules.py @@ -118,6 +118,8 @@ rules_group_materia = { (materia.Autoria, __base__, __perms_publicas__), (materia.DespachoInicial, __base__, __perms_publicas__), (materia.DocumentoAcessorio, __base__, __perms_publicas__), + (materia.MateriaAssunto, __base__, __perms_publicas__), + (materia.AssuntoMateria, __base__, __perms_publicas__), (materia.MateriaLegislativa, __base__ + ['can_access_impressos'], __perms_publicas__), From e72f65bb024d9ef903c5e1b56c17521b4bc9418e Mon Sep 17 00:00:00 2001 From: Victor Fabre Date: Fri, 12 Apr 2019 07:43:17 -0300 Subject: [PATCH 09/59] Fix #2658 (#2718) --- sapl/templates/sessao/painel.html | 98 ++++++------------------------- 1 file changed, 17 insertions(+), 81 deletions(-) diff --git a/sapl/templates/sessao/painel.html b/sapl/templates/sessao/painel.html index 9e1ed742b..7d8a4a104 100644 --- a/sapl/templates/sessao/painel.html +++ b/sapl/templates/sessao/painel.html @@ -116,11 +116,11 @@ $(function() { } startTime(); - - $('#discurso').prop('disabled', true); - $('#aparte').prop('disabled', true); - $('#ordem').prop('disabled', true); - $('#consideracoes').prop('disabled', true); + var audioAlertFinish = document.getElementById("audio"); + $('#discurso').prop('disabled', false); + $('#aparte').prop('disabled', false); + $('#ordem').prop('disabled', false); + $('#consideracoes').prop('disabled', false); $('#discurso').runner({ autostart: false, @@ -130,16 +130,10 @@ $(function() { milliseconds: false }).on('runnerFinish', function(eventObject, info){ $.get('/painel/cronometro', { tipo: 'discurso', action: 'stop' } ); - + audioAlertFinish.play(); $('#discursoReset').show(); $('#discurso').runner('stop'); $('#discursoStart').text('Iniciar'); - $('#aparteStart').prop('disabled', false); - $('#aparteReset').prop('disabled', false); - $('#ordemStart').prop('disabled', false); - $('#ordemReset').prop('disabled', false); - $('#consideracoesStart').prop('disabled', false); - $('#consideracoesReset').prop('disabled', false); }); @@ -152,12 +146,6 @@ $(function() { $('#discursoReset').hide(); $('#discurso').runner('start'); $('#discursoStart').text('Parar'); - $('#aparteStart').prop('disabled', false); - $('#aparteReset').prop('disabled', false); - $('#ordemStart').prop('disabled', false); - $('#ordemReset').prop('disabled', false); - $('#consideracoesStart').prop('disabled', false); - $('#consideracoesReset').prop('disabled', false); } else { @@ -166,12 +154,6 @@ $(function() { $('#discursoReset').show(); $('#discurso').runner('stop'); $('#discursoStart').text('Iniciar'); - $('#aparteStart').prop('disabled', false); - $('#aparteReset').prop('disabled', false); - $('#ordemStart').prop('disabled', false); - $('#ordemReset').prop('disabled', false); - $('#consideracoesStart').prop('disabled', false); - $('#consideracoesReset').prop('disabled', false); } }); @@ -191,16 +173,10 @@ $(function() { milliseconds: false }).on('runnerFinish', function(eventObject, info){ $.get('/painel/cronometro', { tipo: 'aparte', action: 'stop' } ); - + audioAlertFinish.play(); $('#aparteReset').show(); $('#aparte').runner('stop'); $('#aparteStart').text('Iniciar'); - $('#discursoStart').prop('disabled', false); - $('#discursoReset').prop('disabled', false); - $('#ordemStart').prop('disabled', false); - $('#ordemReset').prop('disabled', false); - $('#consideracoesStart').prop('disabled', false); - $('#consideracoesReset').prop('disabled', false); }); @@ -212,12 +188,7 @@ $(function() { $('#aparteReset').hide(); $('#aparte').runner('start'); $('#aparteStart').text('Parar'); - $('#discursoStart').prop('disabled', false); - $('#discursoReset').prop('disabled', false); - $('#ordemStart').prop('disabled', false); - $('#ordemReset').prop('disabled', false); - $('#consideracoesStart').prop('disabled', false); - $('#consideracoesReset').prop('disabled', false); + } else { $.get('/painel/cronometro', { tipo: 'aparte', action: 'stop' } ); @@ -225,12 +196,7 @@ $(function() { $('#aparteReset').show(); $('#aparte').runner('stop'); $('#aparteStart').text('Iniciar'); - $('#discursoStart').prop('disabled', false); - $('#discursoReset').prop('disabled', false); - $('#ordemStart').prop('disabled', false); - $('#ordemReset').prop('disabled', false); - $('#consideracoesStart').prop('disabled', false); - $('#consideracoesReset').prop('disabled', false); + } }); @@ -250,16 +216,11 @@ $(function() { milliseconds: false }).on('runnerFinish', function(eventObject, info){ $.get('/painel/cronometro', { tipo: 'ordem', action: 'stop' } ); - + audioAlertFinish.play(); $('#ordemReset').show(); $('#ordem').runner('stop'); $('#ordemStart').text('Iniciar'); - $('#discursoStart').prop('disabled', false); - $('#discursoReset').prop('disabled', false); - $('#aparteStart').prop('disabled', false); - $('#aparteReset').prop('disabled', false); - $('#consideracoesStart').prop('disabled', false); - $('#consideracoesReset').prop('disabled', false); + }); $('#ordemStart').click(function() { @@ -270,12 +231,7 @@ $(function() { $('#ordemReset').hide(); $('#ordem').runner('start'); $('#ordemStart').text('Parar'); - $('#discursoStart').prop('disabled', false); - $('#discursoReset').prop('disabled', false); - $('#aparteStart').prop('disabled', false); - $('#aparteReset').prop('disabled', false); - $('#consideracoesStart').prop('disabled', false); - $('#consideracoesReset').prop('disabled', false); + } else { @@ -284,12 +240,7 @@ $(function() { $('#ordemReset').show(); $('#ordem').runner('stop'); $('#ordemStart').text('Iniciar'); - $('#discursoStart').prop('disabled', false); - $('#discursoReset').prop('disabled', false); - $('#aparteStart').prop('disabled', false); - $('#aparteReset').prop('disabled', false); - $('#consideracoesStart').prop('disabled', false); - $('#consideracoesReset').prop('disabled', false); + } }); @@ -309,16 +260,11 @@ $(function() { milliseconds: false }).on('runnerFinish', function(eventObject, info){ $.get('/painel/cronometro', { tipo: 'consideracoes', action: 'stop' } ); - + audioAlertFinish.play(); $('#consideracoesReset').show(); $('#consideracoes').runner('stop'); $('#consideracoesStart').text('Iniciar'); - $('#discursoStart').prop('disabled', false); - $('#discursoReset').prop('disabled', false); - $('#ordemStart').prop('disabled', false); - $('#ordemReset').prop('disabled', false); - $('#aparteStart').prop('disabled', false); - $('#aparteReset').prop('disabled', false); + }); @@ -330,12 +276,7 @@ $(function() { $('#consideracoesReset').hide(); $('#consideracoes').runner('start'); $('#consideracoesStart').text('Parar'); - $('#discursoStart').prop('disabled', false); - $('#discursoReset').prop('disabled', false); - $('#ordemStart').prop('disabled', false); - $('#ordemReset').prop('disabled', false); - $('#aparteStart').prop('disabled', false); - $('#aparteReset').prop('disabled', false); + } else { $.get('/painel/cronometro', { tipo: 'consideracoes', action: 'stop' } ); @@ -343,12 +284,7 @@ $(function() { $('#consideracoesReset').show(); $('#consideracoes').runner('stop'); $('#consideracoesStart').text('Iniciar'); - $('#discursoStart').prop('disabled', false); - $('#discursoReset').prop('disabled', false); - $('#ordemStart').prop('disabled', false); - $('#ordemReset').prop('disabled', false); - $('#aparteStart').prop('disabled', false); - $('#aparteReset').prop('disabled', false); + } }); From d38ba0145c47c5d71bebfd14cfd9cbdd7c32fbbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Rodrigues?= Date: Fri, 12 Apr 2019 07:48:13 -0300 Subject: [PATCH 10/59] =?UTF-8?q?Evita=20anexos=20de=20mat=C3=A9rias=20em?= =?UTF-8?q?=20ciclo=20(#2670)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Evitar anexo ciclico - Anexada * Evitar anexo cíclico - Anexada em Lote * Evitar anexo ciclico - Anexada * Evitar anexo cíclico - Anexada em Lote * Atualizar materia/forms.py * Atualizar sapl/materia/views.py * Atualizar migrations --- sapl/materia/forms.py | 20 +++++++++++ sapl/materia/views.py | 34 +++++++++++++++++-- .../migrations/0036_auto_20190409_1334.py | 21 ++++++++++++ sapl/templates/materia/em_lote/anexada.html | 6 ++-- 4 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 sapl/sessao/migrations/0036_auto_20190409_1334.py diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index edbeed810..f662c0fec 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -817,6 +817,26 @@ class AnexadaForm(ModelForm): if is_anexada: self.logger.error("Matéria já se encontra anexada.") raise ValidationError(_('Matéria já se encontra anexada')) + + ciclico = False + anexadas_anexada = Anexada.objects.filter(materia_principal=materia_anexada) + + while anexadas_anexada and not ciclico: + anexadas = [] + + for anexa in anexadas_anexada: + + if materia_principal == anexa.materia_anexada: + ciclico = True + else: + for a in Anexada.objects.filter(materia_principal=anexa.materia_anexada): + anexadas.append(a) + + anexadas_anexada = anexadas + + if ciclico: + self.logger.error("A matéria não pode ser anexada por uma de suas anexadas.") + raise ValidationError(_("A matéria não pode ser anexada por uma de suas anexadas.")) cleaned_data['materia_anexada'] = materia_anexada diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 8d7278e5f..aae53ddac 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -2070,11 +2070,39 @@ class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView): qr = self.request.GET.copy() context['object_list'] = context['object_list'].order_by( - 'ano', 'numero') + 'numero', '-ano') principal = MateriaLegislativa.objects.get(pk=self.kwargs['pk']) not_list = [self.kwargs['pk']] + \ [m for m in principal.materia_principal_set.all().values_list('materia_anexada_id', flat=True)] context['object_list'] = context['object_list'].exclude(pk__in=not_list) + + context['temp_object_list'] = context['object_list'] + context['object_list'] = [] + for obj in context['temp_object_list']: + materia_anexada = obj + ciclico = False + anexadas_anexada = Anexada.objects.filter( + materia_principal = materia_anexada + ) + + while anexadas_anexada and not ciclico: + anexadas = [] + + for anexa in anexadas_anexada: + + if principal == anexa.materia_anexada: + ciclico = True + else: + for a in Anexada.objects.filter(materia_principal=anexa.materia_anexada): + anexadas.append(a) + + anexadas_anexada = anexadas + + if not ciclico: + context['object_list'].append(obj) + + context['numero_res'] = len(context['object_list']) + context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' context['show_results'] = show_results_filter_set(qr) @@ -2110,7 +2138,9 @@ class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView): msg = _('Materia(s) anexada(s).') messages.add_message(request, messages.SUCCESS, msg) - return self.get(request, self.kwargs) + + sucess_url = reverse('sapl_index') + 'materia/' + kwargs['pk'] + '/anexada' + return HttpResponseRedirect(sucess_url) class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView): diff --git a/sapl/sessao/migrations/0036_auto_20190409_1334.py b/sapl/sessao/migrations/0036_auto_20190409_1334.py new file mode 100644 index 000000000..81ffca6b1 --- /dev/null +++ b/sapl/sessao/migrations/0036_auto_20190409_1334.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-04-09 16:34 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('sessao', '0035_resumoordenacao_decimo_quarto'), + ] + + operations = [ + migrations.AlterField( + model_name='expedientesessao', + name='sessao_plenaria', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='expedientesessao_set', to='sessao.SessaoPlenaria'), + ), + ] diff --git a/sapl/templates/materia/em_lote/anexada.html b/sapl/templates/materia/em_lote/anexada.html index 76a154ec6..2b1c22c56 100644 --- a/sapl/templates/materia/em_lote/anexada.html +++ b/sapl/templates/materia/em_lote/anexada.html @@ -8,11 +8,11 @@ {% endif %} {% if show_results %} - {% if object_list.count > 0 %} - {% if object_list.count == 1 %} + {% if numero_res > 0 %} + {% if numero_res == 1 %}

{% trans 'Pesquisa concluída com sucesso! Foi encontrada 1 matéria.'%}

{% else %} -

{% blocktrans with object_list.count as total_materias %}Foram encontradas {{total_materias}} matérias.{% endblocktrans %}

+

Foram encontradas {{ numero_res }} matérias.

{% endif %}
{% csrf_token %} From e3806b52b49b20df05a9fb0d9ee8c85a3dbee908 Mon Sep 17 00:00:00 2001 From: Cesar Augusto de Carvalho Date: Fri, 12 Apr 2019 07:48:55 -0300 Subject: [PATCH 11/59] Adiciona campo regime tramitacao como obrigatorio (#2716) --- sapl/materia/forms.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index f662c0fec..af78b20ff 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -1761,7 +1761,7 @@ class ConfirmarProposicaoForm(ProposicaoForm): required=False, widget=widgets.TextInput( attrs={'readonly': 'readonly'})) - regime_tramitacao = forms.ModelChoiceField( + regime_tramitacao = forms.ModelChoiceField(label="Regime de tramitação", required=False, queryset=RegimeTramitacao.objects.all()) gerar_protocolo = forms.ChoiceField( @@ -1827,6 +1827,10 @@ class ConfirmarProposicaoForm(ProposicaoForm): # esta chamada isola o __init__ de ProposicaoForm super(ProposicaoForm, self).__init__(*args, **kwargs) + if self.instance.tipo.content_type.model_class() ==\ + TipoMateriaLegislativa: + self.fields['regime_tramitacao'].required = True + fields = [ Fieldset( _('Dados Básicos'), From 26d4ccf70a6d94937e61ca99e15bbf2e9bacb30d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Rodrigues?= Date: Fri, 12 Apr 2019 08:01:16 -0300 Subject: [PATCH 12/59] =?UTF-8?q?Implementa=20op=C3=A7=C3=B5es=20de=20anex?= =?UTF-8?q?ar=20em=20Documentos=20Administrativos=20(#2630)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adicionar Adicionar Anexado no mód. Doc. Adm. Alterações iniciais - Anexados em Lote * Adicionar Adicionar Anexado em Lote mód. Doc. Adm. Corrigir permissão * Add classe customizada na api para classe Anexado * Update sapl/rules/map_rules.py Co-Authored-By: rjoao * Refatorar * Evitar anexo cíclico - Anexado em Lote * Evitar anexo ciclico - Anexado * Corrigir marcar/desmarcar todos * Atualizar * Corrigir mensagem de erro - ObjectDoesNotExist * Adiciona migração * Corrigir Editar Anexado * Adicionar migrations * Adicionar link documento - Docs p/ Anexar em Lote --- sapl/api/views.py | 16 +- sapl/materia/views.py | 2 +- sapl/protocoloadm/forms.py | 122 ++++++++++++++- .../migrations/0018_auto_20190314_1532.py | 35 +++++ sapl/protocoloadm/models.py | 42 +++++ sapl/protocoloadm/urls.py | 7 +- sapl/protocoloadm/views.py | 146 +++++++++++++++++- sapl/rules/map_rules.py | 1 + .../migrations/0036_auto_20190409_1452.py | 21 +++ .../migrations/0037_merge_20190411_1548.py | 16 ++ sapl/templates/protocoloadm/anexado_list.html | 13 ++ .../protocoloadm/em_lote/anexado.html | 5 +- sapl/templates/protocoloadm/layouts.yaml | 13 ++ sapl/templates/protocoloadm/subnav.yaml | 2 + 14 files changed, 431 insertions(+), 10 deletions(-) create mode 100644 sapl/protocoloadm/migrations/0018_auto_20190314_1532.py create mode 100644 sapl/sessao/migrations/0036_auto_20190409_1452.py create mode 100644 sapl/sessao/migrations/0037_merge_20190411_1548.py create mode 100644 sapl/templates/protocoloadm/anexado_list.html diff --git a/sapl/api/views.py b/sapl/api/views.py index 8222e8227..13045d6bb 100644 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -26,7 +26,7 @@ from sapl.materia.models import Proposicao, TipoMateriaLegislativa,\ MateriaLegislativa, Tramitacao from sapl.parlamentares.models import Parlamentar from sapl.protocoloadm.models import DocumentoAdministrativo,\ - DocumentoAcessorioAdministrativo, TramitacaoAdministrativo + DocumentoAcessorioAdministrativo, TramitacaoAdministrativo, Anexado from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao from sapl.utils import models_with_gr_for_model, choice_anos_com_sessaoplenaria @@ -489,6 +489,20 @@ class _TramitacaoAdministrativoViewSet(BusinessRulesNotImplementedMixin): return qs +@customize(Anexado) +class _AnexadoViewSet(BusinessRulesNotImplementedMixin): + + permission_classes = ( + _DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission, ) + + def get_queryset(self): + qs = super().get_queryset() + + if self.request.user.is_anonymous(): + qs = qs.exclude(documento__restrito=True) + return qs + + @customize(SessaoPlenaria) class _SessaoPlenariaViewSet: diff --git a/sapl/materia/views.py b/sapl/materia/views.py index aae53ddac..464b505ee 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -2136,7 +2136,7 @@ class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView): anexada.data_desanexacao = data_desanexacao anexada.save() - msg = _('Materia(s) anexada(s).') + msg = _('Matéria(s) anexada(s).') messages.add_message(request, messages.SUCCESS, msg) sucess_url = reverse('sapl_index') + 'materia/' + kwargs['pk'] + '/anexada' diff --git a/sapl/protocoloadm/forms.py b/sapl/protocoloadm/forms.py index 915f62445..b47b093dc 100644 --- a/sapl/protocoloadm/forms.py +++ b/sapl/protocoloadm/forms.py @@ -29,7 +29,7 @@ from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, AnoNumeroOrderingFilter, from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo, DocumentoAdministrativo, Protocolo, TipoDocumentoAdministrativo, - TramitacaoAdministrativo) + TramitacaoAdministrativo, Anexado) TIPOS_PROTOCOLO = [('0', 'Recebido'), ('1', 'Enviado'), @@ -781,6 +781,126 @@ class TramitacaoAdmEditForm(TramitacaoAdmForm): return self.cleaned_data +class AnexadoForm(ModelForm): + + logger = logging.getLogger(__name__) + + tipo = forms.ModelChoiceField( + label='Tipo', + required=True, + queryset=TipoDocumentoAdministrativo.objects.all(), + empty_label='Selecione' + ) + + numero = forms.CharField(label='Número', required=True) + + ano = forms.CharField(label='Ano', required=True) + + def __init__(self, *args, **kwargs): + return super(AnexadoForm, self).__init__(*args, **kwargs) + + def clean(self): + super(AnexadoForm, self).clean() + + if not self.is_valid(): + return self.cleaned_data + + cleaned_data = self.cleaned_data + + data_anexacao = cleaned_data['data_anexacao'] + data_desanexacao = cleaned_data['data_desanexacao'] if cleaned_data['data_desanexacao'] else data_anexacao + + if data_anexacao > data_desanexacao: + self.logger.error("A data de anexação não pode ser posterior a data de desanexação.") + raise ValidationError(_("A data de anexação não pode ser posterior a data de desanexação.")) + try: + self.logger.info( + "Tentando obter objeto DocumentoAdministrativo (numero={}, ano={}, tipo={})." + .format(cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo']) + ) + documento_anexado = DocumentoAdministrativo.objects.get( + numero=cleaned_data['numero'], + ano=cleaned_data['ano'], + tipo=cleaned_data['tipo'] + ) + except ObjectDoesNotExist: + msg = _('O {} {}/{} não existe no cadastro de documentos administrativos.' + .format(cleaned_data['tipo'], cleaned_data['numero'], cleaned_data['ano'])) + self.logger.error("O documento a ser anexado não existe no cadastro" + " de documentos administrativos") + raise ValidationError(msg) + + documento_principal = self.instance.documento_principal + if documento_principal == documento_anexado: + self.logger.error("O documento não pode ser anexado a si mesmo.") + raise ValidationError(_("O documento não pode ser anexado a si mesmo")) + + is_anexado = Anexado.objects.filter(documento_principal=documento_principal, + documento_anexado=documento_anexado + ).exclude(pk=self.instance.pk).exists() + + if is_anexado: + self.logger.error("Documento já se encontra anexado.") + raise ValidationError(_('Documento já se encontra anexado')) + + ciclico = False + anexados_anexado = Anexado.objects.filter(documento_principal=documento_anexado) + + while(anexados_anexado and not ciclico): + anexados = [] + + for anexo in anexados_anexado: + + if documento_principal == anexo.documento_anexado: + ciclico = True + else: + for a in Anexado.objects.filter(documento_principal=anexo.documento_anexado): + anexados.append(a) + + anexados_anexado = anexados + + if ciclico: + self.logger.error("O documento não pode ser anexado por um de seus anexados.") + raise ValidationError(_('O documento não pode ser anexado por um de seus anexados')) + + cleaned_data['documento_anexado'] = documento_anexado + + return cleaned_data + + def save(self, commit=False): + anexado = super(AnexadoForm, self).save(commit) + anexado.documento_anexado = self.cleaned_data['documento_anexado'] + anexado.save() + return anexado + + class Meta: + model = Anexado + fields = ['tipo', 'numero', 'ano', 'data_anexacao', 'data_desanexacao'] + + +class AnexadoEmLoteFilterSet(django_filters.FilterSet): + + class Meta(FilterOverridesMetaMixin): + model = DocumentoAdministrativo + fields = ['tipo', 'data'] + + def __init__(self, *args, **kwargs): + super(AnexadoEmLoteFilterSet, self).__init__(*args, **kwargs) + + self.filters['tipo'].label = 'Tipo de Documento*' + self.filters['data'].label = 'Data (Inicial - Final)*' + + row1 = to_row([('tipo', 12)]) + row2 = to_row([('data', 12)]) + + self.form.helper = SaplFormHelper() + self.form.helper.form_method = 'GET' + self.form.helper.layout = Layout( + Fieldset(_('Pesquisa de Documentos'), + row1, row2, form_actions(label='Pesquisar')) + ) + + class DocumentoAdministrativoForm(FileFieldCheckMixin, ModelForm): logger = logging.getLogger(__name__) diff --git a/sapl/protocoloadm/migrations/0018_auto_20190314_1532.py b/sapl/protocoloadm/migrations/0018_auto_20190314_1532.py new file mode 100644 index 000000000..20822400c --- /dev/null +++ b/sapl/protocoloadm/migrations/0018_auto_20190314_1532.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-03-14 18:32 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('protocoloadm', '0017_merge_20190121_1552'), + ] + + operations = [ + migrations.CreateModel( + name='Anexado', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('data_anexacao', models.DateField(verbose_name='Data Anexação')), + ('data_desanexacao', models.DateField(blank=True, null=True, verbose_name='Data Desanexação')), + ('documento_anexado', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documento_anexado_set', to='protocoloadm.DocumentoAdministrativo', verbose_name='Documento Anexado')), + ('documento_principal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documento_principal_set', to='protocoloadm.DocumentoAdministrativo', verbose_name='Documento Principal')), + ], + options={ + 'verbose_name': 'Anexado', + 'verbose_name_plural': 'Anexados', + }, + ), + migrations.AddField( + model_name='documentoadministrativo', + name='anexados', + field=models.ManyToManyField(blank=True, related_name='anexo_de', through='protocoloadm.Anexado', to='protocoloadm.DocumentoAdministrativo'), + ), + ] diff --git a/sapl/protocoloadm/models.py b/sapl/protocoloadm/models.py index e335a5db1..f792a9cdb 100644 --- a/sapl/protocoloadm/models.py +++ b/sapl/protocoloadm/models.py @@ -170,6 +170,18 @@ class DocumentoAdministrativo(models.Model): verbose_name=_('Acesso Restrito'), blank=True) + anexados = models.ManyToManyField( + 'self', + blank=True, + through='Anexado', + symmetrical=False, + related_name='anexo_de', + through_fields=( + 'documento_principal', + 'documento_anexado' + ) + ) + class Meta: verbose_name = _('Documento Administrativo') verbose_name_plural = _('Documentos Administrativos') @@ -317,6 +329,36 @@ class TramitacaoAdministrativo(models.Model): } +@reversion.register() +class Anexado(models.Model): + documento_principal = models.ForeignKey( + DocumentoAdministrativo, related_name='documento_principal_set', + on_delete = models.CASCADE, + verbose_name=_('Documento Principal') + ) + documento_anexado = models.ForeignKey( + DocumentoAdministrativo, related_name='documento_anexado_set', + on_delete = models.CASCADE, + verbose_name=_('Documento Anexado') + ) + data_anexacao = models.DateField(verbose_name=_('Data Anexação')) + data_desanexacao = models.DateField( + blank=True, null=True, verbose_name=_('Data Desanexação') + ) + + class Meta: + verbose_name = _('Anexado') + verbose_name_plural = _('Anexados') + + def __str__(self): + return _('Anexado: %(documento_anexado_tipo)s %(documento_anexado_numero)s' + '/%(documento_anexado_ano)s\n') % { + 'documento_anexado_tipo': self.documento_anexado.tipo, + 'documento_anexado_numero': self.documento_anexado.numero, + 'documento_anexado_ano': self.documento_anexado.ano + } + + @reversion.register() class AcompanhamentoDocumento(models.Model): usuario = models.CharField(max_length=50) diff --git a/sapl/protocoloadm/urls.py b/sapl/protocoloadm/urls.py index 67d5edb65..e5925204d 100644 --- a/sapl/protocoloadm/urls.py +++ b/sapl/protocoloadm/urls.py @@ -21,7 +21,8 @@ from sapl.protocoloadm.views import (AcompanhamentoDocumentoView, atualizar_numero_documento, doc_texto_integral, DesvincularDocumentoView, - DesvincularMateriaView) + DesvincularMateriaView, + AnexadoCrud, DocumentoAnexadoEmLoteView) from .apps import AppConfig @@ -30,6 +31,7 @@ app_name = AppConfig.name urlpatterns_documento_administrativo = [ url(r'^docadm/', include(DocumentoAdministrativoCrud.get_urls() + + AnexadoCrud.get_urls() + TramitacaoAdmCrud.get_urls() + DocumentoAcessorioAdministrativoCrud.get_urls())), @@ -38,6 +40,9 @@ urlpatterns_documento_administrativo = [ url(r'^docadm/texto_integral/(?P\d+)$', doc_texto_integral, name='doc_texto_integral'), + + url(r'^docadm/(?P\d+)/anexado_em_lote', DocumentoAnexadoEmLoteView.as_view(), + name='anexado_em_lote'), ] urlpatterns_protocolo = [ diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index c400cf9a2..0c2e13157 100755 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -17,7 +17,7 @@ from django.http.response import HttpResponseRedirect from django.shortcuts import redirect from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from django.views.generic import ListView, CreateView +from django.views.generic import ListView, CreateView, UpdateView from django.views.generic.base import RedirectView, TemplateView from django.views.generic.edit import FormView from django_filters.views import FilterView @@ -27,7 +27,8 @@ from sapl.base.email_utils import do_envia_email_confirmacao from sapl.base.models import Autor, CasaLegislativa from sapl.base.signals import tramitacao_signal from sapl.comissoes.models import Comissao -from sapl.crud.base import Crud, CrudAux, MasterDetailCrud, make_pagination +from sapl.crud.base import (Crud, CrudAux, MasterDetailCrud, make_pagination, + RP_LIST, RP_DETAIL) from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.materia.views import gerar_pdf_impressos from sapl.parlamentares.models import Legislatura, Parlamentar @@ -44,10 +45,11 @@ from .forms import (AcompanhamentoDocumentoForm, AnularProcoloAdmForm, TramitacaoAdmEditForm, TramitacaoAdmForm, DesvincularDocumentoForm, DesvincularMateriaForm, filtra_tramitacao_adm_destino_and_status, - filtra_tramitacao_adm_destino, filtra_tramitacao_adm_status) + filtra_tramitacao_adm_destino, filtra_tramitacao_adm_status, + AnexadoForm, AnexadoEmLoteFilterSet) from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo, DocumentoAdministrativo, StatusTramitacaoAdministrativo, - TipoDocumentoAdministrativo, TramitacaoAdministrativo) + TipoDocumentoAdministrativo, TramitacaoAdministrativo, Anexado) TipoDocumentoAdministrativoCrud = CrudAux.build( @@ -941,6 +943,142 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin, return self.render_to_response(context) +class AnexadoCrud(MasterDetailCrud): + model = Anexado + parent_field = 'documento_principal' + help_topic = 'documento_anexado' + public = [RP_LIST, RP_DETAIL] + + class BaseMixin(MasterDetailCrud.BaseMixin): + list_field_names = ['documento_anexado', 'data_anexacao'] + + class CreateView(MasterDetailCrud.CreateView): + form_class = AnexadoForm + + class UpdateView(MasterDetailCrud.UpdateView): + form_class = AnexadoForm + + def get_initial(self): + initial = super(UpdateView, self).get_initial() + initial['tipo'] = self.object.documento_anexado.tipo.id + initial['numero'] = self.object.documento_anexado.numero + initial['ano'] = self.object.documento_anexado.ano + return initial + + class DetailView(MasterDetailCrud.DetailView): + + @property + def layout_key(self): + return 'AnexadoDetail' + + +class DocumentoAnexadoEmLoteView(PermissionRequiredMixin, FilterView): + filterset_class = AnexadoEmLoteFilterSet + template_name = 'protocoloadm/em_lote/anexado.html' + permission_required = ('protocoloadm.add_anexado', ) + + def get_context_data(self, **kwargs): + context = super( + DocumentoAnexadoEmLoteView,self + ).get_context_data(**kwargs) + + context['root_pk'] = self.kwargs['pk'] + + context['subnav_template_name'] = 'protocoloadm/subnav.yaml' + + context['title'] = _('Documentos Anexados em Lote') + + # Verifica se os campos foram preenchidos + if not self.request.GET.get('tipo', " "): + msg =_('Por favor, selecione um tipo de documento.') + messages.add_message(self.request, messages.ERROR, msg) + + if not self.request.GET.get('data_0', " ") or not self.request.GET.get('data_1', " "): + msg =_('Por favor, preencha as datas.') + messages.add_message(self.request, messages.ERROR, msg) + + return context + + if not self.request.GET.get('data_0', " ") or not self.request.GET.get('data_1', " "): + msg =_('Por favor, preencha as datas.') + messages.add_message(self.request, messages.ERROR, msg) + return context + + qr = self.request.GET.copy() + context['temp_object_list'] = context['object_list'].order_by( + 'numero', '-ano' + ) + + context['object_list'] = [] + for obj in context['temp_object_list']: + if not obj.pk == int(context['root_pk']): + documento_principal = DocumentoAdministrativo.objects.get(id=context['root_pk']) + documento_anexado = obj + is_anexado = Anexado.objects.filter(documento_principal=documento_principal, + documento_anexado=documento_anexado).exists() + if not is_anexado: + ciclico = False + anexados_anexado = Anexado.objects.filter(documento_principal=documento_anexado) + + while anexados_anexado and not ciclico: + anexados = [] + + for anexo in anexados_anexado: + + if documento_principal == anexo.documento_anexado: + ciclico = True + else: + for a in Anexado.objects.filter(documento_principal=anexo.documento_anexado): + anexados.append(a) + + anexados_anexado = anexados + + if not ciclico: + context['object_list'].append(obj) + + context['numero_res'] = len(context['object_list']) + + context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' + + context['show_results'] = show_results_filter_set(qr) + + return context + + def post(self, request, *args, **kwargs): + marcados = request.POST.getlist('documento_id') + + if len(marcados) == 0: + msg =_('Nenhum documento foi selecionado') + messages.add_message(request, messages.ERROR, msg) + return self.get(request, self.kwargs) + + data_anexacao = datetime.strptime( + request.POST['data_anexacao'], "%d/%m/%Y" + ).date() + + if request.POST['data_desanexacao'] == '': + data_desanexacao = None + else: + data_desanexacao = datetime.strptime( + request.POST['data_desanexacao'], "%d/%m/%Y" + ).date() + + principal = DocumentoAdministrativo.objects.get(pk = kwargs['pk']) + for documento in DocumentoAdministrativo.objects.filter(id__in = marcados): + anexado = Anexado() + anexado.documento_principal = principal + anexado.documento_anexado = documento + anexado.data_anexacao = data_anexacao + anexado.data_desanexacao = data_desanexacao + anexado.save() + + msg = _('Documento(s) anexado(s).') + messages.add_message(request, messages.SUCCESS, msg) + + success_url = reverse('sapl_index') + 'docadm/' + kwargs['pk'] + '/anexado' + return HttpResponseRedirect(success_url) + + class TramitacaoAdmCrud(MasterDetailCrud): model = TramitacaoAdministrativo parent_field = 'documento' diff --git a/sapl/rules/map_rules.py b/sapl/rules/map_rules.py index d7f3e0b48..a82ef2c3f 100644 --- a/sapl/rules/map_rules.py +++ b/sapl/rules/map_rules.py @@ -60,6 +60,7 @@ rules_group_administrativo = { 'can_access_impressos'], __perms_publicas__), # TODO: tratar em sapl.api a questão de ostencivo e restritivo (protocoloadm.DocumentoAdministrativo, __base__, set()), + (protocoloadm.Anexado, __base__, set()), (protocoloadm.DocumentoAcessorioAdministrativo, __base__, set()), (protocoloadm.TramitacaoAdministrativo, __base__, set()), ] diff --git a/sapl/sessao/migrations/0036_auto_20190409_1452.py b/sapl/sessao/migrations/0036_auto_20190409_1452.py new file mode 100644 index 000000000..b174dc877 --- /dev/null +++ b/sapl/sessao/migrations/0036_auto_20190409_1452.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-04-09 17:52 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('sessao', '0035_resumoordenacao_decimo_quarto'), + ] + + operations = [ + migrations.AlterField( + model_name='expedientesessao', + name='sessao_plenaria', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='expedientesessao_set', to='sessao.SessaoPlenaria'), + ), + ] diff --git a/sapl/sessao/migrations/0037_merge_20190411_1548.py b/sapl/sessao/migrations/0037_merge_20190411_1548.py new file mode 100644 index 000000000..2478af6b7 --- /dev/null +++ b/sapl/sessao/migrations/0037_merge_20190411_1548.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-04-11 18:48 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('sessao', '0036_auto_20190410_0836'), + ('sessao', '0036_auto_20190409_1452'), + ] + + operations = [ + ] diff --git a/sapl/templates/protocoloadm/anexado_list.html b/sapl/templates/protocoloadm/anexado_list.html new file mode 100644 index 000000000..1dfe166c6 --- /dev/null +++ b/sapl/templates/protocoloadm/anexado_list.html @@ -0,0 +1,13 @@ +{% extends "crud/list.html" %} +{% load i18n %} +{% load common_tags %} + +{% block more_buttons %} + + {% if perms|get_add_perm:view %} + + {% trans "Adicionar Anexado em Lote" %} + + {% endif %} + +{% endblock more_buttons %} \ No newline at end of file diff --git a/sapl/templates/protocoloadm/em_lote/anexado.html b/sapl/templates/protocoloadm/em_lote/anexado.html index a8ddf6712..dfb0714ca 100644 --- a/sapl/templates/protocoloadm/em_lote/anexado.html +++ b/sapl/templates/protocoloadm/em_lote/anexado.html @@ -54,7 +54,7 @@ - {{documento.tipo.sigla}} {{documento.numero}}/{{documento.ano}} - {{documento.tipo.descricao}} + {{documento.tipo.sigla}} {{documento.numero}}/{{documento.ano}} - {{documento.tipo.descricao}} {% endfor %} @@ -63,6 +63,7 @@ +
{% else %} @@ -75,7 +76,7 @@ {% block extra_js %}