diff --git a/docker-compose.yml b/docker-compose.yml index c049fc404..5f0d9c6fa 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,5 @@ sapldb: - image: postgres:9.6.8-alpine + image: postgres:10.5-alpine restart: always environment: POSTGRES_PASSWORD: sapl @@ -11,7 +11,7 @@ sapldb: ports: - "5432:5432" sapl: - image: interlegis/sapl:3.1.118 + image: interlegis/sapl:3.1.126 restart: always environment: ADMIN_PASSWORD: interlegis diff --git a/release.sh b/release.sh index cc3a172b5..d8301aafe 100755 --- a/release.sh +++ b/release.sh @@ -13,11 +13,15 @@ function bump_version { sed -e s/$VERSION/$NEXT_VERSION/g setup.py > tmp2 mv tmp2 setup.py + + + sed -e s/$VERSION/$NEXT_VERSION/g sapl/templates/base.html > tmp3 + mv tmp3 sapl/templates/base.html } function commit_and_push { echo "committing..." - git add docker-compose.yml setup.py + git add docker-compose.yml setup.py sapl/templates/base.html git commit -m "Release: $NEXT_VERSION" git tag $NEXT_VERSION diff --git a/sapl/api/views.py b/sapl/api/views.py index 3af0aae25..ebb3b86f4 100755 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -20,7 +20,7 @@ from sapl.api.serializers import (AutorChoiceSerializer, AutorSerializer, from sapl.base.models import Autor, TipoAutor from sapl.materia.models import MateriaLegislativa from sapl.sessao.models import SessaoPlenaria -from sapl.utils import SaplGenericRelation, sapl_logger +from sapl.utils import SaplGenericRelation class ModelChoiceView(ListAPIView): @@ -142,19 +142,15 @@ class AutorListView(ListAPIView): try: tr = int(self.request.GET.get ('tr', AutorListView.TR_AUTOR_CHOICE_SERIALIZER)) - - assert tr in ( - AutorListView.TR_AUTOR_CHOICE_SERIALIZER, - AutorListView.TR_AUTOR_SERIALIZER), sapl_logger.info( - _("Tipo do Resultado a ser fornecido não existe!")) + if tr not in (AutorListView.TR_AUTOR_CHOICE_SERIALIZER, + AutorListView.TR_AUTOR_SERIALIZER): + return AutorListView.TR_AUTOR_CHOICE_SERIALIZER except Exception as e: logger.error("- " + str(e)) return AutorListView.TR_AUTOR_CHOICE_SERIALIZER - else: - return tr + return tr def get(self, request, *args, **kwargs): - if self.tr == AutorListView.TR_AUTOR_SERIALIZER: self.serializer_class = AutorSerializer self.permission_classes = (IsAuthenticated,) diff --git a/sapl/materia/email_utils.py b/sapl/base/email_utils.py similarity index 56% rename from sapl/materia/email_utils.py rename to sapl/base/email_utils.py index 3dc6b220d..737449e57 100755 --- a/sapl/materia/email_utils.py +++ b/sapl/base/email_utils.py @@ -8,7 +8,8 @@ from django.utils import timezone from sapl.base.models import CasaLegislativa from sapl.settings import EMAIL_SEND_USER -from .models import AcompanhamentoMateria +from sapl.materia.models import AcompanhamentoMateria +from sapl.protocoloadm.models import AcompanhamentoDocumento def load_email_templates(templates, context={}): @@ -61,56 +62,73 @@ def enviar_emails(sender, recipients, messages): fail_silently=False) -def criar_email_confirmacao(base_url, casa_legislativa, materia, hash_txt=''): +def criar_email_confirmacao(base_url, casa_legislativa, doc_mat, tipo, hash_txt=''): if not casa_legislativa: raise ValueError("Casa Legislativa é obrigatória") - if not materia: - raise ValueError("Matéria é obrigatória") + if not doc_mat: + if tipo == "materia": + msg = "Matéria é obrigatória" + else: + msg = "Documento é obrigatório" + raise ValueError(msg) # FIXME i18n - casa_nome = (casa_legislativa.nome + ' de ' + - casa_legislativa.municipio + '-' + - casa_legislativa.uf) + casa_nome = ("{} de {} - {}".format(casa_legislativa.nome, + casa_legislativa.municipio, + casa_legislativa.uf)) + + if tipo == "materia": + doc_mat_url = reverse('sapl.materia:materialegislativa_detail', + kwargs={'pk': doc_mat.id}) + confirmacao_url = reverse('sapl.materia:acompanhar_confirmar', + kwargs={'pk': doc_mat.id}) + ementa = doc_mat.ementa + autores = [autoria.autor.nome for autoria in doc_mat.autoria_set.all()] + else: + doc_mat_url = reverse('sapl.protocoloadm:documentoadministrativo_detail', + kwargs={'pk': doc_mat.id}) + confirmacao_url = reverse('sapl.protocoloadm:acompanhar_confirmar', + kwargs={'pk': doc_mat.id}) + ementa = doc_mat.assunto + autores = "" - materia_url = reverse('sapl.materia:materialegislativa_detail', - kwargs={'pk': materia.id}) - confirmacao_url = reverse('sapl.materia:acompanhar_confirmar', - kwargs={'pk': materia.id}) - autores = [] - for autoria in materia.autoria_set.all(): - autores.append(autoria.autor.nome) templates = load_email_templates(['email/acompanhar.txt', 'email/acompanhar.html'], {"casa_legislativa": casa_nome, "logotipo": casa_legislativa.logotipo, - "descricao_materia": materia.ementa, + "descricao_materia": ementa, "autoria": autores, "hash_txt": hash_txt, "base_url": base_url, - "materia": str(materia), - "materia_url": materia_url, + "materia": str(doc_mat), + "materia_url": doc_mat_url, "confirmacao_url": confirmacao_url, }) return templates -def do_envia_email_confirmacao(base_url, casa, materia, destinatario): +def do_envia_email_confirmacao(base_url, casa, tipo, doc_mat, destinatario): # # Envia email de confirmacao para atualizações de tramitação # sender = EMAIL_SEND_USER # FIXME i18n - subject = "[SAPL] " + str(materia) + " - Ative o Acompanhamento da Materia" + if tipo == "materia": + msg = " - Ative o Acompanhamento da Matéria" + else: + msg = " - Ative o Acompanhamento de Documento" + subject = "[SAPL] {} {}".format(str(doc_mat), msg) messages = [] recipients = [] email_texts = criar_email_confirmacao(base_url, casa, - materia, + doc_mat, + tipo, destinatario.hash,) recipients.append(destinatario.email) messages.append({ @@ -123,30 +141,41 @@ def do_envia_email_confirmacao(base_url, casa, materia, destinatario): enviar_emails(sender, recipients, messages) -def criar_email_tramitacao(base_url, casa_legislativa, materia, status, +def criar_email_tramitacao(base_url, casa_legislativa, tipo, doc_mat, status, unidade_destino, hash_txt=''): if not casa_legislativa: raise ValueError("Casa Legislativa é obrigatória") - if not materia: - raise ValueError("Matéria é obrigatória") + if not doc_mat: + if tipo == "materia": + msg = "Matéria é obrigatória" + else: + msg = "Documento é obrigatório" + raise ValueError(msg) # FIXME i18n - casa_nome = (casa_legislativa.nome + ' de ' + - casa_legislativa.municipio + '-' + - casa_legislativa.uf) - - url_materia = reverse('sapl.materia:tramitacao_list', - kwargs={'pk': materia.id}) - url_excluir = reverse('sapl.materia:acompanhar_excluir', - kwargs={'pk': materia.id}) + casa_nome = ("{} de {} - {}".format(casa_legislativa.nome, + casa_legislativa.municipio, + casa_legislativa.uf)) + if tipo == "materia": + doc_mat_url = reverse('sapl.materia:tramitacao_list', + kwargs={'pk': doc_mat.id}) + url_excluir = reverse('sapl.materia:acompanhar_excluir', + kwargs={'pk': doc_mat.id}) + + ementa = doc_mat.ementa + autores = [autoria.autor.nome for autoria in doc_mat.autoria_set.all()] + tramitacao = doc_mat.tramitacao_set.last() - autores = [] - for autoria in materia.autoria_set.all(): - autores.append(autoria.autor.nome) - - tramitacao = materia.tramitacao_set.last() + else: + doc_mat_url = reverse('sapl.protocoloadm:tramitacaoadministrativo_list', + kwargs={'pk': doc_mat.id}) + url_excluir = reverse('sapl.protocoloadm:acompanhar_excluir', + kwargs={'pk': doc_mat.id}) + autores = "" + ementa = doc_mat.assunto + tramitacao = doc_mat.tramitacaoadministrativo_set.last() templates = load_email_templates(['email/tramitacao.txt', 'email/tramitacao.html'], @@ -154,34 +183,42 @@ def criar_email_tramitacao(base_url, casa_legislativa, materia, status, "data_registro": dt.strftime( timezone.now(), "%d/%m/%Y"), - "cod_materia": materia.id, + "cod_materia": doc_mat.id, "logotipo": casa_legislativa.logotipo, - "descricao_materia": materia.ementa, + "descricao_materia": ementa, "autoria": autores, "data": tramitacao.data_tramitacao, "status": status, "localizacao": unidade_destino, "texto_acao": tramitacao.texto, "hash_txt": hash_txt, - "materia": str(materia), + "materia": str(doc_mat), "base_url": base_url, - "materia_url": url_materia, + "materia_url": doc_mat_url, "excluir_url": url_excluir}) return templates -def do_envia_email_tramitacao(base_url, materia, status, unidade_destino): +def do_envia_email_tramitacao(base_url, tipo, doc_mat, status, unidade_destino): # # Envia email de tramitacao para usuarios cadastrados # - destinatarios = AcompanhamentoMateria.objects.filter(materia=materia, - confirmado=True) + if tipo == "materia": + destinatarios = AcompanhamentoMateria.objects.filter(materia=doc_mat, + confirmado=True) + else: + destinatarios = AcompanhamentoDocumento.objects.filter(documento=doc_mat, + confirmado=True) + casa = CasaLegislativa.objects.first() sender = EMAIL_SEND_USER - # FIXME i18n - subject = "[SAPL] " + str(materia) + \ - " - Acompanhamento de Materia Legislativa" + # FIXME i18nn + if tipo == "materia": + msg = " - Acompanhamento de Matéria Legislativa" + else: + msg = " - Acompanhamento de Documento" + subject = "[SAPL] {} {}".format(str(doc_mat), msg) connection = get_connection() connection.open() @@ -190,10 +227,11 @@ def do_envia_email_tramitacao(base_url, materia, status, unidade_destino): try: email_texts = criar_email_tramitacao(base_url, casa, - materia, + tipo, + doc_mat, status, unidade_destino, - destinatario.hash,) + destinatario.hash) email = EmailMultiAlternatives( subject, diff --git a/sapl/base/receivers.py b/sapl/base/receivers.py new file mode 100644 index 000000000..b2176be14 --- /dev/null +++ b/sapl/base/receivers.py @@ -0,0 +1,42 @@ +from django.db.models.signals import post_delete, post_save +from django.dispatch import receiver + +from sapl.materia.models import Tramitacao +from sapl.protocoloadm.models import TramitacaoAdministrativo +from sapl.base.signals import tramitacao_signal +from sapl.utils import get_base_url + +from sapl.base.email_utils import do_envia_email_tramitacao + + +@receiver(tramitacao_signal) +def handle_tramitacao_signal(sender, **kwargs): + tramitacao = kwargs.get("post") + request = kwargs.get("request") + if 'protocoloadm' in str(sender): + doc_mat = tramitacao.documento + tipo = "documento" + elif 'materia' in str(sender): + tipo = "materia" + doc_mat = tramitacao.materia + + do_envia_email_tramitacao( + get_base_url(request), + tipo, + doc_mat, + tramitacao.status, + tramitacao.unidade_tramitacao_destino) + + +@receiver(post_delete) +def status_tramitacao_materia(sender, instance, **kwargs): + if isinstance(sender, TramitacaoAdministrativo): + if instance.status.indicador == 'F': + materia = instance.materia + materia.em_tramitacao = True + materia.save() + elif isinstance(sender, TramitacaoAdministrativo): + if instance.status.indicador == 'F': + documento = instance.documento + documento.tramitacao = True + documento.save() diff --git a/sapl/base/search_indexes.py b/sapl/base/search_indexes.py index ed811cebb..479ecb426 100755 --- a/sapl/base/search_indexes.py +++ b/sapl/base/search_indexes.py @@ -1,30 +1,25 @@ -import logging import os.path import re import string -import textract from django.db.models import F, Q, Value from django.db.models.fields import TextField -from django.db.models.fields.files import FieldFile from django.db.models.functions import Concat from django.template import loader from haystack.constants import Indexable from haystack.fields import CharField from haystack.indexes import SearchIndex from haystack.utils import get_model_ct_tuple +import textract from textract.exceptions import ExtensionNotSupported from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC, - STATUS_TA_PUBLIC, Dispositivo, - TextoArticulado) + STATUS_TA_PUBLIC, Dispositivo) from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa from sapl.norma.models import NormaJuridica -from sapl.settings import BASE_DIR, SOLR_URL +from sapl.settings import SOLR_URL from sapl.utils import RemoveTag -logger = logging.getLogger(BASE_DIR.name) - class TextExtractField(CharField): @@ -68,7 +63,6 @@ class TextExtractField(CharField): msg = 'Erro inesperado processando arquivo: %s' % ( arquivo.path) print(msg) - logger.error(msg) def file_extractor(self, arquivo): if not os.path.exists(arquivo.path) or \ @@ -89,7 +83,6 @@ class TextExtractField(CharField): return self.whoosh_extraction(arquivo) except ExtensionNotSupported as e: print(str(e)) - logger.error(str(e)) except Exception as e2: print(str(e2)) self.print_error(arquivo) diff --git a/sapl/materia/signals.py b/sapl/base/signals.py similarity index 100% rename from sapl/materia/signals.py rename to sapl/base/signals.py diff --git a/sapl/base/templatetags/common_tags.py b/sapl/base/templatetags/common_tags.py index 71f63e130..440f747a4 100755 --- a/sapl/base/templatetags/common_tags.py +++ b/sapl/base/templatetags/common_tags.py @@ -198,6 +198,19 @@ def url(value): return True return False +@register.filter +def audio_url(value): + return True if url(value) and value.endswith("mp3") else False + + +@register.filter +def video_url(value): + return True if url(value) and value.endswith("mp4") else False + +@register.filter +def file_extension(value): + import pathlib + return pathlib.Path(value).suffix.replace('.', '') @register.filter def cronometro_to_seconds(value): diff --git a/sapl/base/templatetags/menus.py b/sapl/base/templatetags/menus.py index f90a6940f..289cd20e6 100755 --- a/sapl/base/templatetags/menus.py +++ b/sapl/base/templatetags/menus.py @@ -1,9 +1,8 @@ -import yaml from django import template from django.core.urlresolvers import reverse from django.utils.translation import ugettext_lazy as _ +import yaml -from sapl.utils import sapl_logger register = template.Library() @@ -85,7 +84,7 @@ def nav_run(context, path=None): menu = yaml.load(rendered) resolve_urls_inplace(menu, root_pk, rm, context) except Exception as e: - sapl_logger.error(_("""Erro na conversão do yaml %s. App: %s. + print(_("""Erro na conversão do yaml %s. App: %s. Erro: %s """) % ( diff --git a/sapl/base/views.py b/sapl/base/views.py index f3d14cb30..b63be5c77 100755 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -14,8 +14,8 @@ from django.template import TemplateDoesNotExist from django.template.loader import get_template from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode -from django.utils.translation import ugettext_lazy as _ from django.utils.translation import string_concat +from django.utils.translation import ugettext_lazy as _ from django.views.generic import (CreateView, DeleteView, FormView, ListView, UpdateView) from django.views.generic.base import RedirectView, TemplateView @@ -23,16 +23,16 @@ from django_filters.views import FilterView from haystack.views import SearchView from sapl import settings +from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica from sapl.base.forms import AutorForm, AutorFormForAdmin, TipoAutorForm from sapl.base.models import Autor, TipoAutor -from sapl.crud.base import CrudAux, make_pagination -from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica from sapl.comissoes.models import Reuniao, Comissao +from sapl.crud.base import CrudAux, make_pagination from sapl.materia.models import (Autoria, MateriaLegislativa, TipoMateriaLegislativa, StatusTramitacao, UnidadeTramitacao) from sapl.sessao.models import (PresencaOrdemDia, SessaoPlenaria, SessaoPlenariaPresenca) -from sapl.utils import (parlamentares_ativos, sapl_logger, +from sapl.utils import (parlamentares_ativos, show_results_filter_set) from .forms import (AlterarSenhaForm, CasaLegislativaForm, @@ -53,9 +53,11 @@ def filtra_url_materias_em_tramitacao(qr, qs, campo_url, local_ou_status): id_materias = [] filtro_url = qr[campo_url] if local_ou_status == 'local': - id_materias = [item.id for item in qs if item.tramitacao_set.order_by('-id').first().unidade_tramitacao_destino_id == int(filtro_url)] + id_materias = [item.id for item in qs if item.tramitacao_set.order_by( + '-id').first().unidade_tramitacao_destino_id == int(filtro_url)] elif local_ou_status == 'status': - id_materias = [item.id for item in qs if item.tramitacao_set.order_by('-id').first().status_id == int(filtro_url)] + id_materias = [item.id for item in qs if item.tramitacao_set.order_by( + '-id').first().status_id == int(filtro_url)] return qs.filter(em_tramitacao=True, id__in=id_materias) @@ -197,8 +199,7 @@ class AutorCrud(CrudAux): fail_silently=False) except: logger.error('- Erro no envio de email na edição de Autores.') - sapl_logger.error( - _('Erro no envio de email na edição de Autores.')) + print(_('Erro no envio de email na edição de Autores.')) return url_reverse class CreateView(CrudAux.CreateView): @@ -250,7 +251,7 @@ class AutorCrud(CrudAux): send_mail(assunto, mensagem, remetente, destinatario, fail_silently=False) except: - sapl_logger.error( + print( _('Erro no envio de email na criação de Autores.')) logger.error('- Erro no envio de email na criação de Autores.') @@ -276,8 +277,8 @@ class RelatorioAtasView(FilterView): context['show_results'] = show_results_filter_set(qr) context['periodo'] = ( - self.request.GET['data_inicio_0'] + - ' - ' + self.request.GET['data_inicio_1']) + self.request.GET['data_inicio_0'] + + ' - ' + self.request.GET['data_inicio_1']) return context @@ -401,20 +402,23 @@ class RelatorioHistoricoTramitacaoView(FilterView): context['show_results'] = show_results_filter_set(qr) context['data_tramitacao'] = (self.request.GET['tramitacao__data_tramitacao_0'] + ' - ' + - self.request.GET['tramitacao__data_tramitacao_1']) + self.request.GET['tramitacao__data_tramitacao_1']) if self.request.GET['tipo']: tipo = self.request.GET['tipo'] - context['tipo'] = (str(TipoMateriaLegislativa.objects.get(id=tipo))) + context['tipo'] = ( + str(TipoMateriaLegislativa.objects.get(id=tipo))) else: context['tipo'] = '' if self.request.GET['tramitacao__status']: tramitacao_status = self.request.GET['tramitacao__status'] - context['tramitacao__status'] = (str(StatusTramitacao.objects.get(id=tramitacao_status))) + context['tramitacao__status'] = ( + str(StatusTramitacao.objects.get(id=tramitacao_status))) else: context['tramitacao__status'] = '' if self.request.GET['tramitacao__unidade_tramitacao_local']: context['tramitacao__unidade_tramitacao_local'] = \ - (str(UnidadeTramitacao.objects.get(id=self.request.GET['tramitacao__unidade_tramitacao_local']))) + (str(UnidadeTramitacao.objects.get( + id=self.request.GET['tramitacao__unidade_tramitacao_local']))) else: context['tramitacao__unidade_tramitacao_destino'] = '' @@ -441,22 +445,26 @@ class RelatorioDataFimPrazoTramitacaoView(FilterView): self.request.GET['tramitacao__data_fim_prazo_1']) if self.request.GET['tipo']: tipo = self.request.GET['tipo'] - context['tipo'] = (str(TipoMateriaLegislativa.objects.get(id=tipo))) + context['tipo'] = ( + str(TipoMateriaLegislativa.objects.get(id=tipo))) else: context['tipo'] = '' if self.request.GET['tramitacao__status']: tramitacao_status = self.request.GET['tramitacao__status'] - context['tramitacao__status'] = (str(StatusTramitacao.objects.get(id=tramitacao_status))) + context['tramitacao__status'] = ( + str(StatusTramitacao.objects.get(id=tramitacao_status))) else: context['tramitacao__status'] = '' if self.request.GET['tramitacao__unidade_tramitacao_local']: context['tramitacao__unidade_tramitacao_local'] = \ - (str(UnidadeTramitacao.objects.get(id=self.request.GET['tramitacao__unidade_tramitacao_local']))) + (str(UnidadeTramitacao.objects.get( + id=self.request.GET['tramitacao__unidade_tramitacao_local']))) else: context['tramitacao__unidade_tramitacao_destino'] = '' return context + class RelatorioReuniaoView(FilterView): model = Reuniao filterset_class = RelatorioReuniaoFilterSet @@ -476,7 +484,7 @@ class RelatorioReuniaoView(FilterView): if not self.filterset.form.is_valid(): return context qr = self.request.GET.copy() - + context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' context['show_results'] = show_results_filter_set(qr) @@ -489,6 +497,7 @@ class RelatorioReuniaoView(FilterView): return context + class RelatorioAudienciaView(FilterView): model = AudienciaPublica filterset_class = RelatorioAudienciaFilterSet @@ -522,7 +531,6 @@ class RelatorioAudienciaView(FilterView): return context - class RelatorioMateriasTramitacaoView(FilterView): model = MateriaLegislativa filterset_class = RelatorioMateriasTramitacaoilterSet @@ -541,9 +549,11 @@ class RelatorioMateriasTramitacaoView(FilterView): qs = qs.filter(em_tramitacao=True) if qr.get('tramitacao__unidade_tramitacao_destino'): - qs = filtra_url_materias_em_tramitacao(qr, qs, 'tramitacao__unidade_tramitacao_destino', 'local') + qs = filtra_url_materias_em_tramitacao( + qr, qs, 'tramitacao__unidade_tramitacao_destino', 'local') if qr.get('tramitacao__status'): - qs = filtra_url_materias_em_tramitacao(qr, qs, 'tramitacao__status', 'status') + qs = filtra_url_materias_em_tramitacao( + qr, qs, 'tramitacao__status', 'status') context['object_list'] = qs @@ -557,17 +567,19 @@ class RelatorioMateriasTramitacaoView(FilterView): context['ano'] = (self.request.GET['ano']) if self.request.GET['tipo']: tipo = self.request.GET['tipo'] - context['tipo'] = (str(TipoMateriaLegislativa.objects.get(id=tipo))) + context['tipo'] = ( + str(TipoMateriaLegislativa.objects.get(id=tipo))) else: context['tipo'] = '' if self.request.GET['tramitacao__status']: tramitacao_status = self.request.GET['tramitacao__status'] - context['tramitacao__status'] = (str(StatusTramitacao.objects.get(id=tramitacao_status))) + context['tramitacao__status'] = ( + str(StatusTramitacao.objects.get(id=tramitacao_status))) else: context['tramitacao__status'] = '' if self.request.GET['tramitacao__unidade_tramitacao_destino']: - context['tramitacao__unidade_tramitacao_destino'] = (str(UnidadeTramitacao.objects.get(id= - self.request.GET['tramitacao__unidade_tramitacao_destino']))) + context['tramitacao__unidade_tramitacao_destino'] = (str(UnidadeTramitacao.objects.get( + id=self.request.GET['tramitacao__unidade_tramitacao_destino']))) else: context['tramitacao__unidade_tramitacao_destino'] = '' context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' @@ -694,7 +706,8 @@ class RelatorioMateriasPorAutorView(FilterView): context['show_results'] = show_results_filter_set(qr) if self.request.GET['tipo']: tipo = int(self.request.GET['tipo']) - context['tipo'] = (str(TipoMateriaLegislativa.objects.get(id=tipo))) + context['tipo'] = ( + str(TipoMateriaLegislativa.objects.get(id=tipo))) else: context['tipo'] = '' if self.request.GET['autoria__autor']: @@ -703,8 +716,8 @@ class RelatorioMateriasPorAutorView(FilterView): else: context['autor'] = '' context['periodo'] = ( - self.request.GET['data_apresentacao_0'] + - ' - ' + self.request.GET['data_apresentacao_1']) + self.request.GET['data_apresentacao_0'] + + ' - ' + self.request.GET['data_apresentacao_1']) return context diff --git a/sapl/comissoes/forms.py b/sapl/comissoes/forms.py index be73d4075..28fe692d2 100755 --- a/sapl/comissoes/forms.py +++ b/sapl/comissoes/forms.py @@ -78,9 +78,9 @@ class PeriodoForm(forms.ModelForm): if not data_fim: data_fim = data_inicio - legislatura = Legislatura.objects.filter(Q(data_inicio__lte=data_inicio, + legislatura = Legislatura.objects.filter(data_inicio__lte=data_inicio, data_fim__gte=data_fim, - )) + ) if not legislatura: logger.error(' - O período informado ' diff --git a/sapl/comissoes/migrations/0018_auto_20180924_1724.py b/sapl/comissoes/migrations/0018_auto_20180924_1724.py new file mode 100644 index 000000000..5f0a81fd5 --- /dev/null +++ b/sapl/comissoes/migrations/0018_auto_20180924_1724.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.8 on 2018-09-24 20:24 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0017_auto_20180717_0827'), + ] + + operations = [ + migrations.AlterField( + model_name='cargocomissao', + name='unico', + field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=True, verbose_name='Único'), + ), + migrations.AlterField( + model_name='comissao', + name='unidade_deliberativa', + field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Unidade Deliberativa'), + ), + ] diff --git a/sapl/comissoes/models.py b/sapl/comissoes/models.py index 232f777b1..f9ffa97fa 100755 --- a/sapl/comissoes/models.py +++ b/sapl/comissoes/models.py @@ -75,7 +75,8 @@ class Comissao(models.Model): verbose_name=_('E-mail')) unidade_deliberativa = models.BooleanField( choices=YES_NO_CHOICES, - verbose_name=_('Unidade Deliberativa')) + verbose_name=_('Unidade Deliberativa'), + default=False) ativa = models.BooleanField( default=False, choices=YES_NO_CHOICES, @@ -121,7 +122,7 @@ class Periodo(models.Model): # PeriodoCompComissao class CargoComissao(models.Model): nome = models.CharField(max_length=50, verbose_name=_('Cargo')) unico = models.BooleanField( - choices=YES_NO_CHOICES, verbose_name=_('Único')) + choices=YES_NO_CHOICES, verbose_name=_('Único'), default=True) class Meta: verbose_name = _('Cargo de Comissão') diff --git a/sapl/compilacao/apps.py b/sapl/compilacao/apps.py index 96bd10b87..fae56b631 100755 --- a/sapl/compilacao/apps.py +++ b/sapl/compilacao/apps.py @@ -1,15 +1,10 @@ -import logging from django import apps from django.conf import settings from django.db import connection, models from django.db.utils import DEFAULT_DB_ALIAS, IntegrityError -from django.utils.translation import ugettext_lazy as _ from django.utils.translation import string_concat - -from sapl.settings import BASE_DIR - -logger = logging.getLogger(BASE_DIR.name) +from django.utils.translation import ugettext_lazy as _ class AppConfig(apps.AppConfig): @@ -42,7 +37,7 @@ class AppConfig(apps.AppConfig): cursor.execute(line) except IntegrityError as e: if not settings.DEBUG: - logger.error( + print( string_concat( _('Ocorreu erro na importação: '), line, @@ -77,10 +72,9 @@ class AppConfig(apps.AppConfig): tipo.save() except IntegrityError as e: if not settings.DEBUG: - logger.error( - string_concat( - _('Ocorreu erro na criação tipo de ta: '), - str(e))) + print(string_concat( + _('Ocorreu erro na criação tipo de ta: '), + str(e))) def init_compilacao_base(app_config, verbosity=2, interactive=True, diff --git a/sapl/compilacao/migrations/0008_auto_20180924_1724.py b/sapl/compilacao/migrations/0008_auto_20180924_1724.py new file mode 100644 index 000000000..ab2befec6 --- /dev/null +++ b/sapl/compilacao/migrations/0008_auto_20180924_1724.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.8 on 2018-09-24 20:24 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('compilacao', '0007_auto_20180911_1600'), + ] + + operations = [ + migrations.AlterField( + model_name='tipodispositivo', + name='contagem_continua', + field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Contagem contínua'), + ), + ] diff --git a/sapl/compilacao/migrations/0009_auto_20180926_1015.py b/sapl/compilacao/migrations/0009_auto_20180926_1015.py new file mode 100644 index 000000000..813358623 --- /dev/null +++ b/sapl/compilacao/migrations/0009_auto_20180926_1015.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-09-26 13:15 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('compilacao', '0008_auto_20180924_1724'), + ] + + operations = [ + migrations.AlterField( + model_name='tipodispositivo', + name='class_css', + field=models.CharField(blank=True, max_length=256, verbose_name='Classe CSS'), + ), + ] diff --git a/sapl/compilacao/migrations/0010_auto_20181004_1939.py b/sapl/compilacao/migrations/0010_auto_20181004_1939.py new file mode 100644 index 000000000..76b2de609 --- /dev/null +++ b/sapl/compilacao/migrations/0010_auto_20181004_1939.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-10-04 22:39 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('compilacao', '0009_auto_20180926_1015'), + ] + + operations = [ + migrations.AlterField( + model_name='textoarticulado', + name='numero', + field=models.CharField(max_length=8, verbose_name='Número'), + ), + ] diff --git a/sapl/compilacao/models.py b/sapl/compilacao/models.py index ff6cbdab1..3d5b7a679 100755 --- a/sapl/compilacao/models.py +++ b/sapl/compilacao/models.py @@ -186,7 +186,8 @@ class TextoArticulado(TimestampedMixin): data = models.DateField(blank=True, null=True, verbose_name=_('Data')) ementa = models.TextField(verbose_name=_('Ementa')) observacao = models.TextField(blank=True, verbose_name=_('Observação')) - numero = models.PositiveIntegerField(verbose_name=_('Número')) + numero = models.CharField( + max_length=8,verbose_name=_('Número')) ano = models.PositiveSmallIntegerField(verbose_name=_('Ano')) tipo_ta = models.ForeignKey( TipoTextoArticulado, @@ -266,10 +267,13 @@ class TextoArticulado(TimestampedMixin): user.has_perm( 'compilacao.change_your_dispositivo_edicao_dinamica')) - def has_view_permission(self, request): + def has_view_permission(self, request=None): if self.privacidade in (STATUS_TA_IMMUTABLE_PUBLIC, STATUS_TA_PUBLIC): return True + if not request: + return False + if request.user in self.owners.all(): return True @@ -599,7 +603,7 @@ class TipoDispositivo(BaseModel): max_length=50, unique=True, verbose_name=_('Nome')) class_css = models.CharField( blank=True, - max_length=20, + max_length=256, verbose_name=_('Classe CSS')) rotulo_prefixo_html = models.TextField( blank=True, @@ -654,7 +658,7 @@ class TipoDispositivo(BaseModel): blank=True, verbose_name=_('Sufixo html da nota automática')) contagem_continua = models.BooleanField( - choices=YES_NO_CHOICES, verbose_name=_('Contagem contínua')) + choices=YES_NO_CHOICES, verbose_name=_('Contagem contínua'), default=False) dispositivo_de_articulacao = models.BooleanField( choices=YES_NO_CHOICES, default=False, diff --git a/sapl/compilacao/views.py b/sapl/compilacao/views.py index b9943acca..d76a0c897 100755 --- a/sapl/compilacao/views.py +++ b/sapl/compilacao/views.py @@ -1,6 +1,5 @@ from collections import OrderedDict from datetime import timedelta -import logging import sys from braces.views import FormMessagesMixin @@ -11,9 +10,8 @@ from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.contenttypes.models import ContentType from django.core.signing import Signer from django.core.urlresolvers import reverse, reverse_lazy -from django.db import connection, transaction +from django.db import transaction from django.db.models import Q -from django.db.utils import IntegrityError from django.http.response import (HttpResponse, HttpResponseRedirect, JsonResponse, Http404) from django.shortcuts import get_object_or_404, redirect @@ -58,8 +56,6 @@ VeiculoPublicacaoCrud = CrudAux.build(VeiculoPublicacao, 'veiculo_publicacao') TipoDispositivoCrud = CrudAux.build( TipoDispositivo, 'tipo_dispositivo') -logger = logging.getLogger(BASE_DIR.name) - def choice_models_in_extenal_views(): integrations_view_names = get_integrations_view_names() @@ -119,7 +115,7 @@ class IntegracaoTaView(TemplateView): tipo_ta.save() except Exception as e: - logger.error( + print( string_concat( _('Ocorreu erro na importação do arquivo base dos Tipos de' 'Dispositivos, entre outras informações iniciais.'), @@ -903,6 +899,8 @@ class TextView(CompMixin, ListView): def get(self, request, *args, **kwargs): if 'print' in request.GET: self.template_name = 'compilacao/text_list__print_version.html' + if 'embedded' in request.GET: + self.template_name = 'compilacao/text_list__embedded.html' return ListView.get(self, request, *args, **kwargs) def get_context_data(self, **kwargs): @@ -2615,13 +2613,7 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin, history = dispositivo_a_alterar.history() for d in history: - """FIXME: A comparação "<" deverá ser mudada para - "<=" caso seja necessário permitir duas alterações - com mesmo inicio_vigencia no mesmo dispositivo. Neste Caso, - a sequencia correta ficará a cargo dos reposicionamentos e - (a ser implementado) entre dispositivos de mesmo nível, - """ - if d.inicio_vigencia < bloco_alteracao.inicio_vigencia: + if d.inicio_vigencia <= bloco_alteracao.inicio_vigencia: dispositivo_a_alterar = d break diff --git a/sapl/crud/base.py b/sapl/crud/base.py index 53677e982..ce28dd5c2 100755 --- a/sapl/crud/base.py +++ b/sapl/crud/base.py @@ -1,4 +1,3 @@ -import logging from braces.views import FormMessagesMixin from compressor.utils.decorators import cached_property @@ -30,9 +29,6 @@ from sapl.rules.map_rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL, from sapl.settings import BASE_DIR from sapl.utils import normalize - -logger = logging.getLogger(BASE_DIR.name) - ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \ 'list', 'create', 'detail', 'update', 'delete' @@ -588,7 +584,7 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView): # print(ordering) except Exception as e: - logger.error(string_concat(_( + print(string_concat(_( 'ERRO: construção da tupla de ordenação.'), str(e))) # print(queryset.query) diff --git a/sapl/legacy/management/commands/ressucitar_deps.py b/sapl/legacy/management/commands/ressuscitar_deps.py similarity index 54% rename from sapl/legacy/management/commands/ressucitar_deps.py rename to sapl/legacy/management/commands/ressuscitar_deps.py index 70900e887..ea219330c 100755 --- a/sapl/legacy/management/commands/ressucitar_deps.py +++ b/sapl/legacy/management/commands/ressuscitar_deps.py @@ -1,12 +1,12 @@ from django.core.management.base import BaseCommand -from sapl.legacy.scripts.ressucita_dependencias import adiciona_ressucitar +from sapl.legacy.scripts.ressuscita_dependencias import adiciona_ressuscitar class Command(BaseCommand): - help = 'Ressucita dependências apagadas ' \ + help = 'Ressuscita dependências apagadas ' \ 'que são necessárias para migrar outros registros' def handle(self, *args, **options): - adiciona_ressucitar() + adiciona_ressuscitar() diff --git a/sapl/legacy/migracao.py b/sapl/legacy/migracao.py index c4c183b84..94fd25930 100755 --- a/sapl/legacy/migracao.py +++ b/sapl/legacy/migracao.py @@ -70,12 +70,13 @@ def scrap_sde(url, usuario, senha=None): {'__ac_name': usuario, '__ac_password': senha}) assert res.status_code == 200 - url_proposicao = '{}/sapl_documentos/proposicao/{}/renderXML?xsl=__default__' # noqa + url_proposicao_tmpl = '{}/sapl_documentos/proposicao/{}/renderXML?xsl=__default__' # noqa total = Proposicao.objects.count() for num, proposicao in enumerate(Proposicao.objects.all()): pk = proposicao.pk - res = session.get(url_proposicao.format(url, pk)) - print("pk: {} status: {} (progresso: {:.2%})".format( - pk, res.status_code, num / total)) + url_proposicao = url_proposicao_tmpl.format(url, pk) + res = session.get(url_proposicao) + print("pk: {} status: {} {} (progresso: {:.2%})".format( + pk, res.status_code, url_proposicao, num / total)) if res.status_code == 200: salva_conteudo_do_sde(proposicao, res.content) diff --git a/sapl/legacy/migracao_dados.py b/sapl/legacy/migracao_dados.py index 40b8a1ddc..1def7f9c3 100755 --- a/sapl/legacy/migracao_dados.py +++ b/sapl/legacy/migracao_dados.py @@ -33,7 +33,8 @@ from sapl.comissoes.models import Comissao, Composicao, Participacao, Reuniao from sapl.legacy.models import NormaJuridica as OldNormaJuridica from sapl.legacy.models import TipoNumeracaoProtocolo from sapl.legacy_migration_settings import (DIR_DADOS_MIGRACAO, DIR_REPO, - NOME_BANCO_LEGADO) + NOME_BANCO_LEGADO, PYTZ_TIMEZONE, + SIGLA_CASA) from sapl.materia.models import (AcompanhamentoMateria, DocumentoAcessorio, MateriaLegislativa, Proposicao, StatusTramitacao, TipoDocumento, @@ -50,7 +51,6 @@ from sapl.sessao.models import (ExpedienteMateria, ExpedienteSessao, OrdemDia, from sapl.utils import normalize from .scripts.normaliza_dump_mysql import normaliza_dump_mysql -from .timezonesbrasil import get_timezone # YAML SETUP ############################################################### @@ -539,6 +539,12 @@ PROPAGACOES_DE_EXCLUSAO = [ ('parlamentar', 'mandato', 'cod_parlamentar'), ('parlamentar', 'composicao_mesa', 'cod_parlamentar'), ('parlamentar', 'composicao_comissao', 'cod_parlamentar'), + # no 2.5 os parlamentares excluídos não são listados na presença da sessão + ('parlamentar', 'sessao_plenaria_presenca', 'cod_parlamentar'), + # ... nem na presença da ordem do dia + ('parlamentar', 'ordem_dia_presenca', 'cod_parlamentar'), + # ... nem na mesa da sessão + ('parlamentar', 'mesa_sessao_plenaria', 'cod_parlamentar'), # coligacao ('coligacao', 'composicao_coligacao', 'cod_coligacao'), @@ -553,6 +559,9 @@ PROPAGACOES_DE_EXCLUSAO = [ ('sessao_plenaria', 'expediente_sessao_plenaria', 'cod_sessao_plen'), ('sessao_plenaria', 'sessao_plenaria_presenca', 'cod_sessao_plen'), ('sessao_plenaria', 'ordem_dia_presenca', 'cod_sessao_plen'), + ('sessao_plenaria', 'mesa_sessao_plenaria', 'cod_sessao_plen'), + ('sessao_plenaria', 'oradores', 'cod_sessao_plen'), + ('sessao_plenaria', 'oradores_expediente', 'cod_sessao_plen'), # as consultas no código do sapl 2.5 # votacao_ordem_dia_obter_zsql e votacao_expediente_materia_obter_zsql @@ -575,6 +584,8 @@ PROPAGACOES_DE_EXCLUSAO = [ ('materia_legislativa', 'despacho_inicial', 'cod_materia'), ('materia_legislativa', 'legislacao_citada', 'cod_materia'), ('materia_legislativa', 'relatoria', 'cod_materia'), + ('materia_legislativa', 'materia_assunto', 'cod_materia'), + # norma ('norma_juridica', 'vinculo_norma_juridica', 'cod_norma_referente'), @@ -612,6 +623,36 @@ def corrige_unidades_tramitacao_destino_vazia_como_anterior(): '''.format(tabela_tramitacao)) +def apaga_ref_a_mats_e_docs_inexistentes_em_proposicoes(): + # as referencias a matérias e documentos apagados não aparecem no 3.1 + # além do que, se ressuscitássemos essas matérias e docs, + # não seria possível apagá-los, + # pois é impossível para um usuário não autor acessar as proposicões + # para apagar a referências antes + exec_legado(''' + update proposicao set cod_materia = NULL where cod_materia not in ( + select cod_materia from materia_legislativa + where ind_excluido <> 1); + ''') + props_sem_mats = list(primeira_coluna(exec_legado(''' + select cod_proposicao from proposicao p inner join tipo_proposicao t + on p.tip_proposicao = t.tip_proposicao + where t.ind_mat_ou_doc = 'M' and cod_mat_ou_doc not in ( + select cod_materia from materia_legislativa + where ind_excluido <> 1) + '''))) + props_sem_docs = list(primeira_coluna(exec_legado(''' + select cod_proposicao from proposicao p inner join tipo_proposicao t + on p.tip_proposicao = t.tip_proposicao + where t.ind_mat_ou_doc = 'D' and cod_mat_ou_doc not in ( + select cod_documento from documento_acessorio + where ind_excluido <> 1); + '''))) + exec_legado_em_subconjunto(''' + update proposicao set cod_mat_ou_doc = NULL + where cod_proposicao in {}''', props_sem_mats + props_sem_docs) + + def uniformiza_banco(): propaga_exclusoes(PROPAGACOES_DE_EXCLUSAO) checa_registros_votacao_ambiguos_e_remove_nao_usados() @@ -705,6 +746,14 @@ sessao_plenaria_presenca | dat_sessao = NULL | dat_sessao = 0 anula_tipos_origem_externa_invalidos() corrige_unidades_tramitacao_destino_vazia_como_anterior() + # matérias inexistentes não são mostradas em norma jurídica => apagamos + exec_legado('''update norma_juridica set cod_materia = NULL + where cod_materia not in ( + select cod_materia from materia_legislativa + where ind_excluido <> 1);''') + + apaga_ref_a_mats_e_docs_inexistentes_em_proposicoes() + class Record: pass @@ -790,19 +839,6 @@ def reinicia_sequence(model, id): REPO = git.Repo.init(DIR_REPO) -# configura timezone de migração -match = re.match('sapl_cm_(.*)', NOME_BANCO_LEGADO) -sigla_casa = match.group(1) -PATH_TABELA_TIMEZONES = DIR_DADOS_MIGRACAO.child('tabela_timezones.yaml') -with open(PATH_TABELA_TIMEZONES, 'r') as arq: - tabela_timezones = yaml.load(arq) -municipio, uf, nome_timezone = tabela_timezones[sigla_casa] -if nome_timezone: - timezone = pytz.timezone(nome_timezone) -else: - timezone = get_timezone(municipio, uf) - - def populate_renamed_fields(new, old): renames = field_renames[type(new)] @@ -822,16 +858,17 @@ def populate_renamed_fields(new, old): and value in [None, 'None']): value = '' - # adiciona timezone faltante aos campos com tempo - # os campos TIMESTAMP do mysql são gravados em UTC - # os DATETIME e TIME não têm timezone - def campo_tempo_sem_timezone(tipo): - return (field_type == tipo - and value and not value.tzinfo) - if campo_tempo_sem_timezone('DateTimeField'): - value = timezone.localize(value) - if campo_tempo_sem_timezone('TimeField'): - value = value.replace(tzinfo=timezone) + # ajusta tempos segundo timezone + # os campos TIMESTAMP do mysql são gravados em UTC + # os DATETIME e TIME não têm timezone + + if field_type == 'DateTimeField' and value: + # as datas armazenadas no legado na verdade são naive + sem_tz = value.replace(tzinfo=None) + value = PYTZ_TIMEZONE.localize(sem_tz).astimezone(pytz.utc) + + if field_type == 'TimeField' and value: + value = value.replace(tzinfo=PYTZ_TIMEZONE) setattr(new, field.name, value) @@ -843,7 +880,7 @@ def roda_comando_shell(cmd): def get_arquivo_ajustes_pre_migracao(): return DIR_DADOS_MIGRACAO.child( - 'ajustes_pre_migracao', '{}.sql'.format(sigla_casa)) + 'ajustes_pre_migracao', '{}.sql'.format(SIGLA_CASA)) def migrar_dados(apagar_do_legado=False): @@ -1026,65 +1063,34 @@ def adjust_acompanhamentomateria(new, old): new.confirmado = True -NOTA_DOCADM = ''' -## NOTA DE MIGRAÇÃO DE DADOS DO SAPL 2.5 ## -O número de protocolo original deste documento era [{num_protocolo}], ano {ano_original}. -'''.strip() # noqa - - def adjust_documentoadministrativo(new, old): if old.num_protocolo: - nota = None - ano_original = new.ano - protocolo = Protocolo.objects.filter( - numero=old.num_protocolo, ano=new.ano) - if not protocolo: - # tentamos encontrar o protocolo no ano seguinte - ano_novo = ano_original + 1 - protocolo = Protocolo.objects.filter(numero=old.num_protocolo, - ano=ano_novo) - if protocolo: - nota = NOTA_DOCADM + ''' -O protocolo vinculado é o de mesmo número, porém do ano seguinte ({ano_novo}), -pois não existe protocolo no sistema com este número no ano {ano_original}. -''' - nota = nota.strip().format(num_protocolo=old.num_protocolo, - ano_original=ano_original, - ano_novo=ano_novo) - msg = 'PROTOCOLO ENCONTRADO APENAS PARA O ANO SEGUINTE!!!!! '\ - 'DocumentoAdministrativo: {cod_documento}, '\ - 'numero_protocolo: {num_protocolo}, '\ - 'ano doc adm: {ano_original}' - warn('protocolo_ano_seguinte', msg, - {'cod_documento': old.cod_documento, - 'num_protocolo': old.num_protocolo, - 'ano_original': ano_original, - 'nota': nota}) - else: - # Se não achamos mesmo no ano anteriro - # colocamos no número externo - new.numero_externo = old.num_protocolo + numero, ano = old.num_protocolo, new.ano + # False < True => o primeiro será o protocolo não anulado + protocolos = Protocolo.objects.filter( + numero=numero, ano=ano).order_by('anulado') + if protocolos: + new.protocolo = protocolos[0] + else: + # Se não achamos o protocolo registramos no número externo + new.numero_externo = numero + + nota = ''' +## NOTA DE MIGRAÇÃO DE DADOS DO SAPL 2.5 ## +O número de protocolo original deste documento era [{numero}], ano [{ano}]. - nota = NOTA_DOCADM + ''' Não existe no sistema nenhum protocolo com estes dados e portanto nenhum protocolo foi vinculado a este documento. Colocamos então o número de protocolo no campo "número externo". ''' - nota = nota.format( - num_protocolo=old.num_protocolo, - ano_original=ano_original) - msg = 'Protocolo {num_protocolo} faltando (referenciado ' \ - 'no documento administrativo {cod_documento})' - warn('protocolo_faltando', msg, - {'num_protocolo': old.num_protocolo, - 'cod_documento': old.cod_documento, - 'nota': nota}) - if protocolo: - assert len(protocolo) == 1, 'mais de um protocolo encontrado' - [new.protocolo] = protocolo - # adiciona nota ao final da observação - if nota: + nota = nota.strip().format(numero=numero, ano=ano) + msg = 'Protocolo {numero} faltando (referenciado ' \ + 'no documento administrativo {cod_documento})' + warn('protocolo_faltando', msg, + {'numero': numero, + 'cod_documento': old.cod_documento, + 'nota': nota}) new.observacao += ('\n\n' if new.observacao else '') + nota @@ -1163,7 +1169,7 @@ def adjust_protocolo_antes_salvar(new, old): def get_arquivo_resolve_registro_votacao(): return DIR_DADOS_MIGRACAO.child( 'ajustes_pre_migracao', - '{}_resolve_registro_votacao_ambiguo.yaml'.format(sigla_casa)) + '{}_resolve_registro_votacao_ambiguo.yaml'.format(SIGLA_CASA)) def get_como_resolver_registro_votacao_ambiguo(): diff --git a/sapl/legacy/migracao_documentos.py b/sapl/legacy/migracao_documentos.py index 66ada19fc..2c9e00842 100755 --- a/sapl/legacy/migracao_documentos.py +++ b/sapl/legacy/migracao_documentos.py @@ -1,4 +1,5 @@ import os +import shutil import re from glob import glob from os.path import join @@ -52,6 +53,12 @@ def mover_documento(repo, origem, destino, ignora_origem_ausente=False): if ignora_origem_ausente and not os.path.exists(origem): print('Origem ignorada ao mover documento: {}'.format(origem)) return + # apaga destino, se houver, e renomeia origem para destino + if os.path.exists(destino): + if os.path.isdir(destino): + shutil.rmtree(destino) + else: + os.remove(destino) os.makedirs(os.path.dirname(destino), exist_ok=True) os.rename(origem, destino) diff --git a/sapl/legacy/scripts/.flake8 b/sapl/legacy/scripts/.flake8 new file mode 100644 index 000000000..977657542 --- /dev/null +++ b/sapl/legacy/scripts/.flake8 @@ -0,0 +1,3 @@ +[flake8] +ignore = E501 + diff --git a/sapl/legacy/scripts/exporta_zope/exporta_zope.py b/sapl/legacy/scripts/exporta_zope/exporta_zope.py index 8f3b1eb4a..682296f4b 100755 --- a/sapl/legacy/scripts/exporta_zope/exporta_zope.py +++ b/sapl/legacy/scripts/exporta_zope/exporta_zope.py @@ -298,6 +298,8 @@ DUMP_FUNCTIONS = { 'Image': dump_file, 'DTML Method': partial(dump_file, get_conteudo=get_conteudo_dtml_method), + 'DTMLMethod': partial(dump_file, + get_conteudo=get_conteudo_dtml_method), 'Folder': partial(dump_folder, enum=enumerate_folder), 'BTreeFolder2': partial(dump_folder, enum=enumerate_btree), 'SDE-Document': partial(dump_sde, tipo='sde.document'), @@ -381,18 +383,30 @@ def _dump_sapl(data_fs_path, documentos_fs_path, destino, salvar, mtimes): sapl = find_sapl(app) # extrai usuários com suas senhas e perfis dump_usuarios(sapl, destino, salvar) + + # extrai folhas XSLT (primeira tentativa) + if 'XSLT' in sapl: + dump_folder(br(sapl['XSLT']), destino, salvar, mtimes) + finally: close_db() app, close_db = get_app(documentos_fs_path) + try: sapl = find_sapl(app) - # extrai folhas XSLT - if 'XSLT' in sapl: - dump_folder(br(sapl['XSLT']), destino, salvar, mtimes) + if sapl == {'id': 'sapl'}: + # em algumas instalações sapl_documentos está direto na raiz + docs = br(app['sapl_documentos']) + else: + # caso mais comum + docs = br(sapl['sapl_documentos']) + + # extrai folhas XSLT (segunda tentativa) + if 'XSLT' in sapl: + dump_folder(br(sapl['XSLT']), destino, salvar, mtimes) # extrai documentos - docs = br(sapl['sapl_documentos']) with logando_nao_identificados(): dump_folder(docs, destino, salvar, mtimes) dump_propriedades(docs, destino, salvar) diff --git a/sapl/legacy/scripts/ressucita_dependencias.py b/sapl/legacy/scripts/ressuscita_dependencias.py similarity index 64% rename from sapl/legacy/scripts/ressucita_dependencias.py rename to sapl/legacy/scripts/ressuscita_dependencias.py index 7b37c1977..5e12efc01 100755 --- a/sapl/legacy/scripts/ressucita_dependencias.py +++ b/sapl/legacy/scripts/ressuscita_dependencias.py @@ -1,3 +1,4 @@ +from collections import OrderedDict from textwrap import dedent import texttable @@ -19,6 +20,7 @@ def stripsplit(ll): def _tab_legado(model): return models_novos_para_antigos[model]._meta.db_table + fks_legado = { (_tab_legado(m), campos_novos_para_antigos[f]): _tab_legado(f.related_model) # noqa for m in models_novos_para_antigos @@ -34,29 +36,41 @@ for tabela_origem, campo, tabela_destino in [ urls = ''' -autor /sistema/autor -cargo_comissao /sistema/comissao/cargo -legislatura /sistema/parlamentar/legislatura -materia_legislativa /materia -norma_juridica /norma -parlamentar /parlamentar -sessao_legislativa /sistema/mesa-diretora/sessao-legislativa -sessao_plenaria /sessao -status_tramitacao /sistema/materia/status-tramitacao -tipo_autor /sistema/autor/tipo -tipo_expediente /sistema/sessao-plenaria/tipo-expediente -tipo_proposicao /sistema/proposicao/tipo -tipo_resultado_votacao /sistema/sessao-plenaria/tipo-resultado-votacao -unidade_tramitacao /sistema/materia/unidade-tramitacao -tipo_documento /sistema/materia/tipo-documento -orgao /sistema/materia/orgao -tipo_sessao_plenaria /sistema/sessao-plenaria/tipo -cargo_mesa /sistema/mesa-diretora/cargo-mesa -documento_administrativo /docadm -tipo_materia_legislativa /sistema/materia/tipo -tipo_norma_juridica /sistema/norma/tipo -comissao /comissao -registro_votacao ????????? +autor /sistema/autor +cargo_comissao /sistema/comissao/cargo +legislatura /sistema/parlamentar/legislatura +materia_legislativa /materia +norma_juridica /norma +parlamentar /parlamentar +sessao_legislativa /sistema/mesa-diretora/sessao-legislativa +sessao_plenaria /sessao +status_tramitacao /sistema/materia/status-tramitacao +tipo_autor /sistema/autor/tipo +tipo_expediente /sistema/sessao-plenaria/tipo-expediente +tipo_proposicao /sistema/proposicao/tipo +tipo_resultado_votacao /sistema/sessao-plenaria/tipo-resultado-votacao +unidade_tramitacao /sistema/materia/unidade-tramitacao +tipo_documento /sistema/materia/tipo-documento +orgao /sistema/materia/orgao +tipo_sessao_plenaria /sistema/sessao-plenaria/tipo +cargo_mesa /sistema/mesa-diretora/cargo-mesa +documento_administrativo /docadm +tipo_materia_legislativa /sistema/materia/tipo +tipo_norma_juridica /sistema/norma/tipo +comissao /comissao +assunto_materia /sistema/assunto-materia +coligacao /sistema/coligacao +nivel_instrucao /sistema/parlamentar/nivel-instrucao +partido /sistema/parlamentar/partido +regime_tramitacao /sistema/materia/regime-tramitacao +tipo_comissao /sistema/comissao/tipo +tipo_documento_administrativo /sistema/tipo-documento-adm +registro_votacao /admin/sessao/registrovotacao +tipo_dependente /sistema/parlamentar/tipo-dependente +origem /sistema/materia/origem +documento_acessorio /materia/documentoacessorio +tipo_fim_relatoria /sistema/materia/tipo-fim-relatoria +tipo_situacao_militar /sistema/parlamentar/tipo-militar ''' urls = dict(stripsplit(urls)) @@ -77,6 +91,7 @@ CAMPOS_ORIGEM_PARA_ALVO = { 'cod_unid_tram_dest': 'cod_unid_tramitacao', 'cod_unid_tram_local': 'cod_unid_tramitacao', 'tip_id_basica': 'tip_materia', + 'cod_local_origem_externa': 'cod_origem', } @@ -180,7 +195,7 @@ Para facilitar sua conferência, seguem os links para as proposições envolvida '''.format(table.draw(), links, sqls) -def get_dependencias_a_ressucitar(slug): +def get_dependencias_a_ressuscitar(slug): ocorrencias = yaml.load( Path(DIR_REPO.child('ocorrencias.yaml').read_file())) fks_faltando = ocorrencias.get('fk') @@ -214,6 +229,21 @@ def get_dependencias_a_ressucitar(slug): return preambulo, desexcluir, criar +# deve ser idempotente pois é usada na criação de autor +# por isso o ON DUPLICATE KEY UPDATE +SQL_INSERT_TIPO_AUTOR = ''' + insert into tipo_autor (tip_autor, des_tipo_autor, ind_excluido) + values ({}, "DESCONHECIDO", 0) ON DUPLICATE KEY UPDATE ind_excluido = 0; + ''' + +# deve ser idempotente pois é usada na criação de comissao +# por isso o ON DUPLICATE KEY UPDATE +SQL_INSERT_TIPO_COMISSAO = ''' + insert into tipo_comissao (tip_comissao, nom_tipo_comissao, sgl_natureza_comissao, sgl_tipo_comissao, des_dispositivo_regimental, ind_excluido) + values ({}, "DESCONHECIDO", "P", "DESC", NULL, 0) + ON DUPLICATE KEY UPDATE ind_excluido = 0; + ''' + SQLS_CRIACAO = [ ('tipo_proposicao', ''' insert into tipo_materia_legislativa ( @@ -232,13 +262,54 @@ SQLS_CRIACAO = [ tip_resultado_votacao, nom_resultado, ind_excluido) values ({}, "DESCONHECIDO", 0); '''), - ('tipo_autor', ''' - insert into tipo_autor (tip_autor, des_tipo_autor, ind_excluido) - values ({}, "DESCONHECIDO", 0); - '''), + ('tipo_autor', SQL_INSERT_TIPO_AUTOR), ('unidade_tramitacao', ''' - insert into unidade_tramitacao (cod_unid_tramitacao, cod_comissao, cod_orgao, cod_parlamentar, ind_excluido) - values ({}, NULL, NULL, NULL, 0); + insert into unidade_tramitacao ( + cod_unid_tramitacao, cod_comissao, cod_orgao, cod_parlamentar, ind_excluido) + values ({}, NULL, NULL, 0, 0); + '''), + ('autor', SQL_INSERT_TIPO_AUTOR.format(0) + ''' + insert into autor ( + cod_autor, cod_partido, cod_comissao, cod_parlamentar, tip_autor, + nom_autor, des_cargo, col_username, ind_excluido) + values ({}, 0, 0, 0, 0, "DESCONHECIDO", "DESCONHECIDO", NULL, 0); + '''), + ('tipo_documento', ''' + insert into tipo_documento (tip_documento, des_tipo_documento, ind_excluido) + values ({}, "DESCONHECIDO", 0); + '''), + ('partido', ''' + insert into partido (cod_partido, sgl_partido, nom_partido, dat_criacao, dat_extincao, ind_excluido) + values ({}, "DESC", "DESCONHECIDO", NULL, NULL, 0); + '''), + ('legislatura', ''' + insert into legislatura (num_legislatura, dat_inicio, dat_fim, dat_eleicao, ind_excluido) + values ({}, "1/1/1", "1/1/1", "1/1/1", 0); + '''), + ('cargo_mesa', ''' + insert into cargo_mesa (cod_cargo, des_cargo, ind_unico, ind_excluido) + values ({}, "DESCONHECIDO", 0, 0); + '''), + ('orgao', ''' + insert into orgao (cod_orgao, nom_orgao, sgl_orgao, ind_unid_deliberativa, end_orgao, num_tel_orgao, ind_excluido) + values ({}, "DESCONHECIDO", "DESC", 0, NULL, NULL, 0); + '''), + ('origem', ''' + insert into origem (cod_origem, sgl_origem, nom_origem, ind_excluido) + values ({}, "DESC", "DESCONHECIDO", 0); + '''), + ('tipo_comissao', SQL_INSERT_TIPO_COMISSAO), + ('comissao', SQL_INSERT_TIPO_COMISSAO.format(0) + ''' + insert into comissao (cod_comissao, tip_comissao, nom_comissao, sgl_comissao, dat_criacao, + ind_unid_deliberativa, ind_excluido) + values ({}, 0, "DESCONHECIDO", "DESC", "1-1-1", 0, 0); + '''), + ('parlamentar', ''' + insert into parlamentar (cod_parlamentar, nom_completo, nom_parlamentar, sex_parlamentar, cod_casa, ind_ativo, ind_unid_deliberativa, ind_excluido) + values ({}, "DESCONHECIDO", "DESCONHECIDO", "M", 0, 0, 0, 0); + '''), + ('tipo_sessao_plenaria', ''' + insert into tipo_sessao_plenaria (tip_sessao, nom_sessao, ind_excluido, num_minimo) values ({}, "DESCONHECIDO", 0, 0); '''), ] SQLS_CRIACAO = {k: (dedent(sql.strip()), extras) @@ -282,8 +353,8 @@ def get_sql_criar(tabela_alvo, campo, valor, slug): return sql, links -TEMPLATE_RESSUCITADOS = '''{} -/* RESSUCITADOS +TEMPLATE_RESSUSCITADOS = '''{} +/* RESSUSCITADOS SOBRE REGISTROS QUE ESTAVAM APAGADOS E FORAM RESTAURADOS @@ -307,6 +378,10 @@ def get_url(slug): return 'sapl.{}.leg.br'.format(slug.replace('-', '.')) +def sem_repeticoes_mantendo_ordem(sequencia): + return OrderedDict.fromkeys(sequencia).keys() + + def get_sqls_desexcluir_criar(preambulo, desexcluir, criar, slug): sqls_links = [get_sql(*(args + (slug,))) for itens, get_sql in ((desexcluir, get_sql_desexcluir), @@ -316,13 +391,21 @@ def get_sqls_desexcluir_criar(preambulo, desexcluir, criar, slug): return '' else: sqls, links = zip(*sqls_links) - links = [l for ll in links for l in ll] # flatten + + sqls = [dedent(s.strip()) + ';' + for sql in sqls + for s in sql.split(';') if s.strip()] + sqls = sem_repeticoes_mantendo_ordem(sqls) + + links = (l for ll in links for l in ll) # flatten + links = sem_repeticoes_mantendo_ordem(links) + sqls, links = ['\n'.join(sorted(s)) for s in [sqls, links]] - return TEMPLATE_RESSUCITADOS.format(preambulo, links, sqls) + return TEMPLATE_RESSUSCITADOS.format(preambulo, links, sqls) -def get_ressucitar(slug): - preambulo, desexcluir, criar = get_dependencias_a_ressucitar(slug) +def get_ressuscitar(slug): + preambulo, desexcluir, criar = get_dependencias_a_ressuscitar(slug) return get_sqls_desexcluir_criar(preambulo, desexcluir, criar, slug) @@ -334,8 +417,8 @@ def get_slug(): return siglas_para_slugs[sigla] -def adiciona_ressucitar(): - sqls = get_ressucitar(get_slug()) +def adiciona_ressuscitar(): + sqls = get_ressuscitar(get_slug()) if sqls.strip(): arq_ajustes_pre_migracao = get_arquivo_ajustes_pre_migracao() conteudo = arq_ajustes_pre_migracao.read_file() diff --git a/sapl/legacy_migration_settings.py b/sapl/legacy_migration_settings.py index 96f6a83ad..ce0029937 100755 --- a/sapl/legacy_migration_settings.py +++ b/sapl/legacy_migration_settings.py @@ -1,10 +1,14 @@ import os +import re +import pytz +import yaml from decouple import Config, RepositoryEnv from dj_database_url import parse as db_url from sapl.legacy.scripts.exporta_zope.variaveis_comuns import \ DIR_DADOS_MIGRACAO +from sapl.legacy.timezonesbrasil import get_timezone from .settings import * # flake8: noqa @@ -43,3 +47,18 @@ NOME_BANCO_LEGADO = DATABASES['legacy']['NAME'] DIR_REPO = Path(DIR_DADOS_MIGRACAO, 'repos', NOME_BANCO_LEGADO) MEDIA_ROOT = DIR_REPO + + +# configura timezone de migração +match = re.match('sapl_cm_(.*)', NOME_BANCO_LEGADO) +SIGLA_CASA = match.group(1) +_PATH_TABELA_TIMEZONES = DIR_DADOS_MIGRACAO.child('tabela_timezones.yaml') +with open(_PATH_TABELA_TIMEZONES, 'r') as arq: + tabela_timezones = yaml.load(arq) +municipio, uf, nome_timezone = tabela_timezones[SIGLA_CASA] +if nome_timezone: + PYTZ_TIMEZONE = pytz.timezone(nome_timezone) +else: + PYTZ_TIMEZONE = get_timezone(municipio, uf) + +TIME_ZONE = PYTZ_TIMEZONE.zone diff --git a/sapl/materia/apps.py b/sapl/materia/apps.py index bb4f72f73..2cc3559ae 100755 --- a/sapl/materia/apps.py +++ b/sapl/materia/apps.py @@ -8,4 +8,4 @@ class AppConfig(apps.AppConfig): verbose_name = _('Matéria') def ready(self): - from . import receivers + from sapl.base import receivers diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index 478a94269..82e4ac999 100755 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -259,7 +259,7 @@ class MateriaLegislativaForm(ModelForm): primeiro_autor = True else: primeiro_autor = False - + materia = super(MateriaLegislativaForm, self).save(commit) materia.save() @@ -297,6 +297,20 @@ class UnidadeTramitacaoForm(ModelForm): raise ValidationError(msg) return cleaned_data + def save(self, commit=False): + unidade = super(UnidadeTramitacaoForm, self).save(commit) + cd = self.cleaned_data + + if not cd.get('orgao'): + unidade.orgao = None + if not cd.get('parlamentar'): + unidade.parlamentar = None + if not cd.get('comissao'): + unidade.comissao = None + + unidade.save() + return unidade + class AcompanhamentoMateriaForm(ModelForm): @@ -365,6 +379,11 @@ class RelatoriaForm(ModelForm): class TramitacaoForm(ModelForm): + urgente = forms.ChoiceField(required=True, + choices=YES_NO_CHOICES, + initial=False, + label=_("Urgente?")) + class Meta: model = Tramitacao fields = ['data_tramitacao', @@ -1914,14 +1933,13 @@ class ConfirmarProposicaoForm(ProposicaoForm): else: # numeracao == 'U' ou não informada nm = Protocolo.objects.all().aggregate(Max('numero')) - protocolo = Protocolo() protocolo.numero = (nm['numero__max'] + 1) if nm['numero__max'] else 1 protocolo.ano = timezone.now().year protocolo.tipo_protocolo = '1' - protocolo.interessado = str(proposicao.autor) + protocolo.interessado = str(proposicao.autor)[:200] # tamanho máximo 200 protocolo.autor = proposicao.autor protocolo.assunto_ementa = proposicao.descricao protocolo.numero_paginas = cd['numero_de_paginas'] diff --git a/sapl/materia/migrations/0031_auto_20180924_1724.py b/sapl/materia/migrations/0031_auto_20180924_1724.py new file mode 100644 index 000000000..12d541002 --- /dev/null +++ b/sapl/materia/migrations/0031_auto_20180924_1724.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.8 on 2018-09-24 20:24 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('materia', '0030_tramitacao_timestamp'), + ] + + operations = [ + migrations.AlterField( + model_name='orgao', + name='unidade_deliberativa', + field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Unidade Deliberativa'), + ), + migrations.AlterField( + model_name='tramitacao', + name='urgente', + field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Urgente ?'), + ), + ] diff --git a/sapl/materia/models.py b/sapl/materia/models.py index 6bcb6f8c0..dd9f8ddab 100755 --- a/sapl/materia/models.py +++ b/sapl/materia/models.py @@ -557,7 +557,8 @@ class Orgao(models.Model): sigla = models.CharField(max_length=10, verbose_name=_('Sigla')) unidade_deliberativa = models.BooleanField( choices=YES_NO_CHOICES, - verbose_name=(_('Unidade Deliberativa'))) + verbose_name=(_('Unidade Deliberativa')), + default=False) endereco = models.CharField( max_length=100, blank=True, verbose_name=_('Endereço')) telefone = models.CharField( @@ -929,7 +930,8 @@ class Tramitacao(models.Model): on_delete=models.PROTECT, verbose_name=_('Unidade Destino')) urgente = models.BooleanField(verbose_name=_('Urgente ?'), - choices=YES_NO_CHOICES) + choices=YES_NO_CHOICES, + default=False) turno = models.CharField( max_length=1, blank=True, verbose_name=_('Turno'), choices=TURNO_CHOICES) diff --git a/sapl/materia/tests/test_email_templates.py b/sapl/materia/tests/test_email_templates.py index aac13cbb7..32b2f7ec8 100755 --- a/sapl/materia/tests/test_email_templates.py +++ b/sapl/materia/tests/test_email_templates.py @@ -1,6 +1,6 @@ from django.core import mail -from sapl.materia.email_utils import enviar_emails, load_email_templates +from sapl.base.email_utils import enviar_emails, load_email_templates def test_email_template_loading(): diff --git a/sapl/materia/tests/test_materia_form.py b/sapl/materia/tests/test_materia_form.py index 944cabf00..b67ef2733 100755 --- a/sapl/materia/tests/test_materia_form.py +++ b/sapl/materia/tests/test_materia_form.py @@ -196,8 +196,9 @@ def test_valida_campos_obrigatorios_tramitacao_form(): assert errors['status'] == [_('Este campo é obrigatório.')] assert errors['data_tramitacao'] == [_('Este campo é obrigatório.')] assert errors['unidade_tramitacao_destino'] == [_('Este campo é obrigatório.')] + assert errors['urgente'] == [_('Este campo é obrigatório.')] - assert len(errors) == 5 + assert len(errors) == 6 @pytest.mark.django_db(transaction=False) @@ -213,8 +214,9 @@ def test_valida_campos_obrigatorios_tramitacao_update_form(): assert errors['status'] == [_('Este campo é obrigatório.')] assert errors['data_tramitacao'] == [_('Este campo é obrigatório.')] assert errors['unidade_tramitacao_destino'] == [_('Este campo é obrigatório.')] + assert errors['urgente'] == [_('Este campo é obrigatório.')] - assert len(errors) == 5 + assert len(errors) == 6 @pytest.mark.django_db(transaction=False) diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 8c1fc6d21..2be16e91f 100755 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -47,7 +47,7 @@ from sapl.utils import (YES_NO_CHOICES, autor_label, autor_modal, get_mime_type_from_file_extension, montar_row_autor, show_results_filter_set) -from .email_utils import do_envia_email_confirmacao +from sapl.base.email_utils import do_envia_email_confirmacao from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, AdicionarVariasAutoriasFilterSet, DespachoInicialForm, DocumentoAcessorioForm, EtiquetaPesquisaForm, @@ -66,7 +66,7 @@ from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria, RegimeTramitacao, Relatoria, StatusTramitacao, TipoDocumento, TipoFimRelatoria, TipoMateriaLegislativa, TipoProposicao, Tramitacao, UnidadeTramitacao) -from .signals import tramitacao_signal +from sapl.base.signals import tramitacao_signal AssuntoMateriaCrud = CrudAux.build(AssuntoMateria, 'assunto_materia') @@ -496,10 +496,13 @@ class ReceberProposicao(PermissionRequiredForAppCrudMixin, FormView): form = ReceberProposicaoForm(request.POST) if form.is_valid(): - proposicoes = Proposicao.objects.filter( - data_envio__isnull=False, data_recebimento__isnull=True) + try: + # A ultima parte do código deve ser a pk da Proposicao + id = form.cleaned_data["cod_hash"].split("/")[1] + proposicao = Proposicao.objects.get(id=id, + data_envio__isnull=False, + data_recebimento__isnull=True) - for proposicao in proposicoes: if proposicao.texto_articulado.exists(): ta = proposicao.texto_articulado.first() # FIXME hash para textos articulados @@ -507,7 +510,7 @@ class ReceberProposicao(PermissionRequiredForAppCrudMixin, FormView): else: hasher = gerar_hash_arquivo( proposicao.texto_original.path, - str(proposicao.pk)) \ + str(proposicao.id)) \ if proposicao.texto_original else None if hasher == form.cleaned_data['cod_hash']: return HttpResponseRedirect( @@ -515,8 +518,12 @@ class ReceberProposicao(PermissionRequiredForAppCrudMixin, FormView): kwargs={ 'hash': hasher.split('/')[0][1:], 'pk': proposicao.pk})) - - messages.error(request, _('Proposição não encontrada!')) + except ObjectDoesNotExist: + messages.error(request, _('Proposição não encontrada!')) + except IndexError: + messages.error(request, _('Código de recibo mal formado!')) + except IOError: + messages.error(request, _('Erro abrindo texto original de proposição')) return self.form_invalid(form) def get_success_url(self): @@ -1154,10 +1161,10 @@ class TramitacaoCrud(MasterDetailCrud): msg = _('Tramitação criada, mas e-mail de acompanhamento ' 'de matéria não enviado. Há problemas na configuração ' 'do e-mail.') - logger.error('- Tramitação criada, mas e-mail de acompanhamento ' + logger.warning('- Tramitação criada, mas e-mail de acompanhamento ' 'de matéria não enviado. Há problemas na configuração ' 'do e-mail.') - messages.add_message(self.request, messages.ERROR, msg) + messages.add_message(self.request, messages.WARNING, msg) return HttpResponseRedirect(self.get_success_url()) return super().form_valid(form) @@ -1186,10 +1193,10 @@ class TramitacaoCrud(MasterDetailCrud): msg = _('Tramitação atualizada, mas e-mail de acompanhamento ' 'de matéria não enviado. Há problemas na configuração ' 'do e-mail.') - logger.error('- Tramitação atualizada, mas e-mail de acompanhamento ' + logger.warning('- Tramitação atualizada, mas e-mail de acompanhamento ' 'de matéria não enviado. Há problemas na configuração ' 'do e-mail.') - messages.add_message(self.request, messages.ERROR, msg) + messages.add_message(self.request, messages.WARNING, msg) return HttpResponseRedirect(self.get_success_url()) return super().form_valid(form) @@ -1738,6 +1745,7 @@ class AcompanhamentoMateriaView(CreateView): do_envia_email_confirmacao(base_url, casa, + "materia", materia, destinatario) @@ -1936,9 +1944,9 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView): flag_error = True if flag_error: msg = _('Tramitação criada, mas e-mail de acompanhamento ' - 'de matéria não enviado. Há problemas na configuração ' - 'do e-mail.') - messages.add_message(self.request, messages.ERROR, msg) + 'de matéria não enviado. A não configuração do servidor de e-mail ' + 'impede o envio de aviso de tramitação') + messages.add_message(self.request, messages.WARNING, msg) status = StatusTramitacao.objects.get(id=request.POST['status']) diff --git a/sapl/norma/forms.py b/sapl/norma/forms.py index 8305bf2f8..c18f8c06d 100755 --- a/sapl/norma/forms.py +++ b/sapl/norma/forms.py @@ -6,17 +6,18 @@ from crispy_forms.layout import Fieldset, Layout from django import forms from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.db import models -from django.forms import ModelForm, widgets +from django.forms import ModelForm, widgets, ModelChoiceField from django.utils import timezone from django.utils.translation import ugettext_lazy as _ +from sapl.base.models import Autor, TipoAutor from sapl.crispy_layout_mixin import form_actions, to_row from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.settings import MAX_DOC_UPLOAD_SIZE from sapl.utils import NormaPesquisaOrderingFilter, RANGE_ANOS, RangeWidgetOverride from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada, - TipoNormaJuridica) + TipoNormaJuridica, AutoriaNorma) def ANO_CHOICES(): @@ -134,15 +135,21 @@ class NormaJuridicaForm(ModelForm): if not self.is_valid(): return cleaned_data + + import re + has_digits = re.sub('[^0-9]', '', cleaned_data['numero']) + if not has_digits: + raise ValidationError('Número de norma não pode conter somente letras') + if self.instance.numero != cleaned_data['numero']: norma = NormaJuridica.objects.filter(ano=cleaned_data['ano'], - numero=cleaned_data['numero'], - tipo=cleaned_data['tipo']).exists() + numero=cleaned_data['numero'], + tipo=cleaned_data['tipo']).exists() if norma: raise ValidationError("Já existe uma norma de mesmo Tipo, Ano " "e Número no sistema") if (cleaned_data['tipo_materia'] and - cleaned_data['numero_materia'] and + cleaned_data['numero_materia'] and cleaned_data['ano_materia']): try: logger.info("- Tentando obter objeto MateriaLegislativa.") @@ -196,6 +203,51 @@ class NormaJuridicaForm(ModelForm): return norma +class AutoriaNormaForm(ModelForm): + + tipo_autor = ModelChoiceField(label=_('Tipo Autor'), + required=False, + queryset=TipoAutor.objects.all(), + empty_label=_('Selecione'),) + + data_relativa = forms.DateField( + widget=forms.HiddenInput(), required=False) + + def __init__(self, *args, **kwargs): + super(AutoriaNormaForm, self).__init__(*args, **kwargs) + + row1 = to_row([('tipo_autor', 4), + ('autor', 4), + ('primeiro_autor', 4)]) + + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset(_('Autoria'), + row1, 'data_relativa', form_actions(label='Salvar'))) + + if not kwargs['instance']: + self.fields['autor'].choices = [] + + class Meta: + model = AutoriaNorma + fields = ['tipo_autor', 'autor', 'primeiro_autor', 'data_relativa'] + + def clean(self): + cd = super(AutoriaNormaForm, self).clean() + + if not self.is_valid(): + return self.cleaned_data + + autorias = AutoriaNorma.objects.filter( + norma=self.instance.norma, autor=cd['autor']) + pk = self.instance.pk + + if ((not pk and autorias.exists()) or + (pk and autorias.exclude(pk=pk).exists())): + raise ValidationError(_('Esse Autor já foi cadastrado.')) + + return cd + class AnexoNormaJuridicaForm(ModelForm): class Meta: model = AnexoNormaJuridica diff --git a/sapl/norma/migrations/0014_auto_20181008_1655.py b/sapl/norma/migrations/0014_auto_20181008_1655.py new file mode 100644 index 000000000..da9bf4add --- /dev/null +++ b/sapl/norma/migrations/0014_auto_20181008_1655.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-10-08 19:55 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0021_appconfig_esfera_federacao'), + ('norma', '0013_anexonormajuridica_assunto_anexo'), + ] + + operations = [ + migrations.CreateModel( + name='AutoriaNorma', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('primeiro_autor', models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Primeiro Autor')), + ('autor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='base.Autor', verbose_name='Autor')), + ('norma', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='norma.NormaJuridica', verbose_name='Matéria Legislativa')), + ], + options={ + 'ordering': ('-primeiro_autor', 'autor__nome'), + 'verbose_name': 'Autoria', + 'verbose_name_plural': 'Autorias', + }, + ), + migrations.AddField( + model_name='normajuridica', + name='autores', + field=models.ManyToManyField(through='norma.AutoriaNorma', to='base.Autor'), + ), + migrations.AlterUniqueTogether( + name='autorianorma', + unique_together=set([('autor', 'norma')]), + ), + ] diff --git a/sapl/norma/models.py b/sapl/norma/models.py index e4e3677b3..46f34a0ef 100755 --- a/sapl/norma/models.py +++ b/sapl/norma/models.py @@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from model_utils import Choices import reversion +from sapl.base.models import Autor from sapl.compilacao.models import TextoArticulado from sapl.materia.models import MateriaLegislativa from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, @@ -129,6 +130,12 @@ class NormaJuridica(models.Model): auto_now=True, verbose_name=_('Data')) + autores = models.ManyToManyField( + Autor, + through='AutoriaNorma', + through_fields=('norma', 'autor'), + symmetrical=False) + class Meta: verbose_name = _('Norma Jurídica') verbose_name_plural = _('Normas Jurídicas') @@ -184,6 +191,28 @@ class NormaJuridica(models.Model): update_fields=update_fields) +@reversion.register() +class AutoriaNorma(models.Model): + autor = models.ForeignKey(Autor, + verbose_name=_('Autor'), + on_delete=models.CASCADE) + norma = models.ForeignKey( + NormaJuridica, on_delete=models.CASCADE, + verbose_name=_('Matéria Legislativa')) + primeiro_autor = models.BooleanField(verbose_name=_('Primeiro Autor'), + choices=YES_NO_CHOICES, + default=False) + + class Meta: + verbose_name = _('Autoria') + verbose_name_plural = _('Autorias') + unique_together = (('autor', 'norma'), ) + ordering = ('-primeiro_autor', 'autor__nome') + + def __str__(self): + return _('Autoria: %(autor)s - %(norma)s') % { + 'autor': self.autor, 'norma': self.norma} + @reversion.register() class LegislacaoCitada(models.Model): materia = models.ForeignKey(MateriaLegislativa, on_delete=models.CASCADE) diff --git a/sapl/norma/urls.py b/sapl/norma/urls.py index d943f71e8..800707915 100755 --- a/sapl/norma/urls.py +++ b/sapl/norma/urls.py @@ -3,7 +3,7 @@ from django.conf.urls import include, url from sapl.norma.views import (AnexoNormaJuridicaCrud,AssuntoNormaCrud, NormaCrud, NormaPesquisaView, NormaRelacionadaCrud, NormaTaView, TipoNormaCrud, TipoVinculoNormaJuridicaCrud, recuperar_norma, - recuperar_numero_norma) + recuperar_numero_norma, AutoriaNormaCrud) from .apps import AppConfig @@ -13,7 +13,8 @@ app_name = AppConfig.name urlpatterns = [ url(r'^norma/', include(NormaCrud.get_urls() + NormaRelacionadaCrud.get_urls() + - AnexoNormaJuridicaCrud.get_urls())), + AnexoNormaJuridicaCrud.get_urls() + + AutoriaNormaCrud.get_urls())), # Integração com Compilação url(r'^norma/(?P[0-9]+)/ta$', NormaTaView.as_view(), name='norma_ta'), diff --git a/sapl/norma/views.py b/sapl/norma/views.py index c632997c2..be9ac8d69 100755 --- a/sapl/norma/views.py +++ b/sapl/norma/views.py @@ -22,9 +22,9 @@ from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux, from sapl.utils import show_results_filter_set from .forms import (AnexoNormaJuridicaForm, NormaFilterSet, NormaJuridicaForm, - NormaPesquisaSimplesForm, NormaRelacionadaForm) + NormaPesquisaSimplesForm, NormaRelacionadaForm, AutoriaNormaForm) from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada, - TipoNormaJuridica, TipoVinculoNormaJuridica) + TipoNormaJuridica, TipoVinculoNormaJuridica, AutoriaNorma) # LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '') @@ -240,6 +240,7 @@ class NormaCrud(Crud): initial['tipo_materia'] = norma.materia.tipo initial['ano_materia'] = norma.materia.ano initial['numero_materia'] = norma.materia.numero + initial['esfera_federacao'] = norma.esfera_federacao return initial @@ -280,6 +281,39 @@ def recuperar_numero_norma(request): return response +class AutoriaNormaCrud(MasterDetailCrud): + model = AutoriaNorma + parent_field = 'norma' + help_topic = 'despacho_autoria' + public = [RP_LIST, RP_DETAIL] + list_field_names = ['autor', 'autor__tipo__descricao', 'primeiro_autor'] + + class LocalBaseMixin(): + form_class = AutoriaNormaForm + + @property + def layout_key(self): + return None + + class CreateView(LocalBaseMixin, MasterDetailCrud.CreateView): + + def get_initial(self): + initial = super().get_initial() + norma = NormaJuridica.objects.get(id=self.kwargs['pk']) + initial['data_relativa'] = norma.data + initial['autor'] = [] + return initial + + class UpdateView(LocalBaseMixin, MasterDetailCrud.UpdateView): + + def get_initial(self): + initial = super().get_initial() + initial.update({ + 'data_relativa': self.object.norma.data_apresentacao, + 'tipo_autor': self.object.autor.tipo.id, + }) + return initial + class ImpressosView(PermissionRequiredMixin, TemplateView): template_name = 'materia/impressos/impressos.html' permission_required = ('materia.can_access_impressos', ) diff --git a/sapl/parlamentares/forms.py b/sapl/parlamentares/forms.py index 94ab7b169..6ae314785 100755 --- a/sapl/parlamentares/forms.py +++ b/sapl/parlamentares/forms.py @@ -142,7 +142,7 @@ class LegislaturaForm(ModelForm): pk = self.instance.pk - ultima_legislatura = Legislatura.objects.filter(data_inicio__lte=data_inicio + ultima_legislatura = Legislatura.objects.filter(data_inicio__lt=data_inicio ).order_by('-data_inicio').first() proxima_legislatura = Legislatura.objects.filter(data_fim__gt=data_fim ).order_by('data_fim').first() @@ -348,12 +348,12 @@ class FrenteForm(ModelForm): fields = '__all__' def clean(self): - frente = super(FrenteForm, self).clean() + super(FrenteForm, self).clean() cd = self.cleaned_data if not self.is_valid(): return self.cleaned_data - if cd['data_criacao'] >= cd['data_extincao']: + if cd['data_extincao'] and cd['data_criacao'] >= cd['data_extincao']: raise ValidationError(_("Data Dissolução não pode ser anterior a Data Criação")) return cd diff --git a/sapl/parlamentares/migrations/0025_auto_20180924_1724.py b/sapl/parlamentares/migrations/0025_auto_20180924_1724.py new file mode 100644 index 000000000..958a36b82 --- /dev/null +++ b/sapl/parlamentares/migrations/0025_auto_20180924_1724.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.8 on 2018-09-24 20:24 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0024_auto_20180814_1237'), + ] + + operations = [ + migrations.AlterField( + model_name='cargomesa', + name='unico', + field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=True, verbose_name='Cargo Único'), + ), + ] diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index 43c4e2215..60df66462 100755 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -475,7 +475,7 @@ class CargoMesa(models.Model): descricao = models.CharField( max_length=50, verbose_name=_('Cargo na Mesa')) unico = models.BooleanField( - choices=YES_NO_CHOICES, verbose_name=_('Cargo Único')) + choices=YES_NO_CHOICES, verbose_name=_('Cargo Único'), default=True) class Meta: verbose_name = _('Cargo na Mesa') diff --git a/sapl/protocoloadm/apps.py b/sapl/protocoloadm/apps.py index 8697e58d9..98e28ea36 100755 --- a/sapl/protocoloadm/apps.py +++ b/sapl/protocoloadm/apps.py @@ -6,3 +6,6 @@ class AppConfig(apps.AppConfig): name = 'sapl.protocoloadm' label = 'protocoloadm' verbose_name = _('Protocolo Administrativo') + + def ready(self): + from sapl.base import receivers diff --git a/sapl/protocoloadm/forms.py b/sapl/protocoloadm/forms.py index 1fc917929..2c44d34a8 100755 --- a/sapl/protocoloadm/forms.py +++ b/sapl/protocoloadm/forms.py @@ -3,7 +3,7 @@ import django_filters import logging from crispy_forms.bootstrap import InlineRadios from crispy_forms.helper import FormHelper -from crispy_forms.layout import HTML, Button, Fieldset, Layout +from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout from django import forms from django.core.exceptions import (MultipleObjectsReturned, ObjectDoesNotExist, ValidationError) @@ -20,7 +20,8 @@ from sapl.materia.models import (MateriaLegislativa, TipoMateriaLegislativa, from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, AnoNumeroOrderingFilter, RangeWidgetOverride, autor_label, autor_modal) -from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo, +from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo, + DocumentoAdministrativo, Protocolo, TipoDocumentoAdministrativo, TramitacaoAdministrativo) @@ -40,6 +41,28 @@ EM_TRAMITACAO = [('', '---------'), (0, 'Sim'), (1, 'Não')] +class AcompanhamentoDocumentoForm(ModelForm): + + class Meta: + model = AcompanhamentoDocumento + fields = ['email'] + + def __init__(self, *args, **kwargs): + + row1 = to_row([('email', 10)]) + + row1.append( + Column(form_actions(label='Cadastrar'), css_class='col-md-2') + ) + + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset( + _('Acompanhamento de Documento por e-mail'), row1 + ) + ) + super(AcompanhamentoDocumentoForm, self).__init__(*args, **kwargs) + class ProtocoloFilterSet(django_filters.FilterSet): @@ -920,7 +943,7 @@ class DesvincularMateriaForm(forms.Form): def pega_ultima_tramitacao_adm(): return TramitacaoAdministrativo.objects.values( - 'materia_id').annotate(data_encaminhamento=Max( + 'documento_id').annotate(data_encaminhamento=Max( 'data_encaminhamento'), id=Max('id')).values_list('id', flat=True) @@ -937,7 +960,7 @@ def filtra_tramitacao_adm_destino(destino): return TramitacaoAdministrativo.objects.filter( id__in=lista, unidade_tramitacao_destino=destino).distinct().values_list( - 'materia_id', flat=True) + 'documento_id', flat=True) def filtra_tramitacao_adm_destino_and_status(status, destino): @@ -946,4 +969,4 @@ def filtra_tramitacao_adm_destino_and_status(status, destino): id__in=lista, status=status, unidade_tramitacao_destino=destino).distinct().values_list( - 'materia_id', flat=True) + 'documento_id', flat=True) diff --git a/sapl/protocoloadm/migrations/0007_auto_20180924_1724.py b/sapl/protocoloadm/migrations/0007_auto_20180924_1724.py new file mode 100644 index 000000000..d0f8f3b3b --- /dev/null +++ b/sapl/protocoloadm/migrations/0007_auto_20180924_1724.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.8 on 2018-09-24 20:24 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('protocoloadm', '0006_documentoadministrativo_restrito'), + ] + + operations = [ + migrations.AlterField( + model_name='documentoadministrativo', + name='tramitacao', + field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Em Tramitação?'), + ), + migrations.AlterField( + model_name='protocolo', + name='anulado', + field=models.BooleanField(default=False), + ), + ] diff --git a/sapl/protocoloadm/migrations/0008_acompanhamentodocumento.py b/sapl/protocoloadm/migrations/0008_acompanhamentodocumento.py new file mode 100644 index 000000000..dcc25ed45 --- /dev/null +++ b/sapl/protocoloadm/migrations/0008_acompanhamentodocumento.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-09-27 15:24 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('protocoloadm', '0007_auto_20180924_1724'), + ] + + operations = [ + migrations.CreateModel( + name='AcompanhamentoDocumento', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('usuario', models.CharField(max_length=50)), + ('email', models.EmailField(max_length=100, verbose_name='E-mail')), + ('data_cadastro', models.DateField(auto_now_add=True)), + ('hash', models.CharField(max_length=8)), + ('confirmado', models.BooleanField(default=False)), + ('documento', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='protocoloadm.DocumentoAdministrativo')), + ], + options={ + 'verbose_name_plural': 'Acompanhamentos de Documento', + 'verbose_name': 'Acompanhamento de Documento', + }, + ), + ] diff --git a/sapl/protocoloadm/migrations/0008_auto_20181009_1741.py b/sapl/protocoloadm/migrations/0008_auto_20181009_1741.py new file mode 100644 index 000000000..0ea354a29 --- /dev/null +++ b/sapl/protocoloadm/migrations/0008_auto_20181009_1741.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-10-09 20:41 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('protocoloadm', '0007_auto_20180924_1724'), + ] + + operations = [ + migrations.AlterField( + model_name='protocolo', + name='interessado', + field=models.CharField(blank=True, max_length=200, verbose_name='Interessado'), + ), + ] diff --git a/sapl/protocoloadm/migrations/0009_merge.py b/sapl/protocoloadm/migrations/0009_merge.py new file mode 100644 index 000000000..b59fca714 --- /dev/null +++ b/sapl/protocoloadm/migrations/0009_merge.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-10-10 11:10 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('protocoloadm', '0008_acompanhamentodocumento'), + ('protocoloadm', '0008_auto_20181009_1741'), + ] + + operations = [ + ] diff --git a/sapl/protocoloadm/models.py b/sapl/protocoloadm/models.py index 347b8545e..aa4e7c0c2 100755 --- a/sapl/protocoloadm/models.py +++ b/sapl/protocoloadm/models.py @@ -67,7 +67,7 @@ class Protocolo(models.Model): blank=True, null=True, verbose_name=_('Tipo de Protocolo')) tipo_processo = models.PositiveIntegerField() interessado = models.CharField( - max_length=60, blank=True, verbose_name=_('Interessado')) + max_length=200, blank=True, verbose_name=_('Interessado')) autor = models.ForeignKey(Autor, blank=True, null=True, @@ -89,7 +89,7 @@ class Protocolo(models.Model): blank=True, null=True, verbose_name=_('Número de Páginas')) observacao = models.TextField( blank=True, verbose_name=_('Observação')) - anulado = models.BooleanField() + anulado = models.BooleanField(default=False) user_anulacao = models.CharField(max_length=20, blank=True) ip_anulacao = models.CharField(max_length=15, blank=True) justificativa_anulacao = models.CharField( @@ -135,7 +135,8 @@ class DocumentoAdministrativo(models.Model): blank=True, null=True, verbose_name=_('Data Fim Prazo')) tramitacao = models.BooleanField( verbose_name=_('Em Tramitação?'), - choices=YES_NO_CHOICES) + choices=YES_NO_CHOICES, + default=False) assunto = models.TextField(verbose_name=_('Assunto')) numero_externo = models.PositiveIntegerField( blank=True, @@ -297,3 +298,30 @@ class TramitacaoAdministrativo(models.Model): return _('%(documento)s - %(status)s') % { 'documento': self.documento, 'status': self.status } + +@reversion.register() +class AcompanhamentoDocumento(models.Model): + usuario = models.CharField(max_length=50) + documento = models.ForeignKey(DocumentoAdministrativo, on_delete=models.CASCADE) + email = models.EmailField( + max_length=100, verbose_name=_('E-mail')) + data_cadastro = models.DateField(auto_now_add=True) + hash = models.CharField(max_length=8) + confirmado = models.BooleanField(default=False) + + class Meta: + verbose_name = _('Acompanhamento de Documento') + verbose_name_plural = _('Acompanhamentos de Documento') + + def __str__(self): + if self.data_cadastro is None: + return _('%(documento)s - %(email)s') % { + 'documento': self.documento, + 'email': self.email + } + else: + return _('%(documento)s - %(email)s - Registrado em: %(data)s') % { + 'documento': self.documento, + 'email': self.email, + 'data': str(self.data_cadastro.strftime('%d/%m/%Y')) + } diff --git a/sapl/protocoloadm/tests/test_docadm_email_templates.py b/sapl/protocoloadm/tests/test_docadm_email_templates.py new file mode 100644 index 000000000..ec79098fa --- /dev/null +++ b/sapl/protocoloadm/tests/test_docadm_email_templates.py @@ -0,0 +1,65 @@ +from django.core import mail + +from sapl.base.email_utils import enviar_emails, load_email_templates + + +def test_email_template_loading(): + expected = "Hello Django" + emails = load_email_templates(['email/test_tramitacao.html'], + context={"name": "Django"}) + + # strip \n and \r to compare with expected + actual = emails[0].replace('\n', '').replace('\r', '') + + assert actual == expected + + +def test_html_email_body_with_materia(): + templates = load_email_templates(['email/tramitacao.txt', + 'email/tramitacao.html'], + {"image": 'img/logo.png', + "casa_legislativa": + "Assembléia Parlamentar", + "data_registro": "25/02/2016", + "cod_materia": "1", + "descricao_materia": "Assunto de teste", + "data": "25/02/2016", + "status": "Arquivado", + "texto_acao": "Deliberado", + "hash_txt": "abc01f", + "materia_id": "794", + "base_url": "http://localhost:8000", + "materia_url": + "/docadm/764/acompanhar-documento", + "excluir_url": + "/docadm/764/acompanhar-excluir"}) + + assert len(templates) == 2 + + +def test_enviar_email_distintos(): + NUM_MESSAGES = 10 + messages = [{'recipient': 'user-' + str(i) + '@test.com', + 'subject': 'subject: ' + str(i), + 'txt_message': 'txt: ' + str(i), + 'html_message': '', + } for i in range(NUM_MESSAGES)] + + recipients = [m['recipient'] for m in messages] + + enviar_emails('test@sapl.com', recipients, messages) + assert len(mail.outbox) == NUM_MESSAGES + + +def test_enviar_same_email(): + NUM_MESSAGES = 10 + messages = [{'recipient': 'user-' + str(i) + '@test.com', + 'subject': 'subject: ' + str(i), + 'txt_message': 'txt: ' + str(i), + 'html_message': '', + } for i in range(NUM_MESSAGES)] + + recipients = [m['recipient'] for m in messages] + + enviar_emails('test@sapl.com', recipients, [messages[0]]) + assert len(mail.outbox) == 1 diff --git a/sapl/protocoloadm/urls.py b/sapl/protocoloadm/urls.py index 8ed9216f2..67d5edb65 100755 --- a/sapl/protocoloadm/urls.py +++ b/sapl/protocoloadm/urls.py @@ -1,6 +1,9 @@ from django.conf.urls import include, url -from sapl.protocoloadm.views import (AnularProtocoloAdmView, +from sapl.protocoloadm.views import (AcompanhamentoDocumentoView, + AcompanhamentoConfirmarView, + AcompanhamentoExcluirView, + AnularProtocoloAdmView, ComprovanteProtocoloView, CriarDocumentoProtocolo, DocumentoAcessorioAdministrativoCrud, @@ -56,6 +59,15 @@ urlpatterns_protocolo = [ url(r'^protocoloadm/(?P\d+)/protocolo-mostrar$', ProtocoloMostrarView.as_view(), name='protocolo_mostrar'), + url(r'^docadm/(?P\d+)/acompanhar-documento/$', + AcompanhamentoDocumentoView.as_view(), name='acompanhar_documento'), + url(r'^docadm/(?P\d+)/acompanhar-confirmar$', + AcompanhamentoConfirmarView.as_view(), + name='acompanhar_confirmar'), + url(r'^docadm/(?P\d+)/acompanhar-excluir$', + AcompanhamentoExcluirView.as_view(), + name='acompanhar_excluir'), + url(r'^protocoloadm/(?P\d+)/continuar$', diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index d98b93b4d..5e22f7e8d 100755 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -1,3 +1,6 @@ +from datetime import datetime +from random import choice +from string import ascii_letters, digits from braces.views import FormValidMessageMixin from django.contrib import messages @@ -19,25 +22,30 @@ from django_filters.views import FilterView import sapl import logging -from sapl.base.models import Autor +from sapl.base.models import Autor, CasaLegislativa from sapl.comissoes.models import Comissao from sapl.crud.base import Crud, CrudAux, MasterDetailCrud, make_pagination from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.parlamentares.models import Legislatura, Parlamentar from sapl.protocoloadm.models import Protocolo -from sapl.utils import (create_barcode, get_client_ip, +from sapl.utils import (create_barcode, get_base_url, get_client_ip, get_mime_type_from_file_extension, show_results_filter_set) - -from .forms import (AnularProcoloAdmForm, DocumentoAcessorioAdministrativoForm, +from sapl.base.email_utils import do_envia_email_confirmacao +from .forms import (AcompanhamentoDocumentoForm, AnularProcoloAdmForm, + DocumentoAcessorioAdministrativoForm, DocumentoAdministrativoFilterSet, DocumentoAdministrativoForm, ProtocoloDocumentForm, ProtocoloFilterSet, ProtocoloMateriaForm, - TramitacaoAdmEditForm, TramitacaoAdmForm, DesvincularDocumentoForm, DesvincularMateriaForm, - filtra_tramitacao_adm_destino_and_status, filtra_tramitacao_adm_destino, filtra_tramitacao_adm_status) -from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo, - StatusTramitacaoAdministrativo, + TramitacaoAdmEditForm, TramitacaoAdmForm, + DesvincularDocumentoForm, DesvincularMateriaForm, + filtra_tramitacao_adm_destino_and_status, + filtra_tramitacao_adm_destino, filtra_tramitacao_adm_status) +from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo, + DocumentoAdministrativo, StatusTramitacaoAdministrativo, TipoDocumentoAdministrativo, TramitacaoAdministrativo) +from sapl.base.signals import tramitacao_signal + TipoDocumentoAdministrativoCrud = CrudAux.build( TipoDocumentoAdministrativo, '') @@ -93,6 +101,136 @@ def doc_texto_integral(request, pk): return response raise Http404 +class AcompanhamentoConfirmarView(TemplateView): + + def get_redirect_url(self, email): + msg = _('Este documento está sendo acompanhado pelo e-mail: %s') % ( + email) + messages.add_message(self.request, messages.SUCCESS, msg) + return reverse('sapl.protocoloadm:documentoadministrativo_detail', + kwargs={'pk': self.kwargs['pk']}) + + def get(self, request, *args, **kwargs): + documento_id = kwargs['pk'] + hash_txt = request.GET.get('hash_txt', '') + + try: + acompanhar = AcompanhamentoDocumento.objects.get( + documento_id=documento_id, + hash=hash_txt) + except ObjectDoesNotExist: + raise Http404() + # except MultipleObjectsReturned: + # A melhor solução deve ser permitir que a exceção + # (MultipleObjectsReturned) seja lançada e vá para o log, + # pois só poderá ser causada por um erro de desenvolvimente + + acompanhar.confirmado = True + acompanhar.save() + + return HttpResponseRedirect(self.get_redirect_url(acompanhar.email)) + + +class AcompanhamentoExcluirView(TemplateView): + + def get_success_url(self): + msg = _('Você parou de acompanhar este Documento.') + messages.add_message(self.request, messages.INFO, msg) + return reverse('sapl.protocoloadm:documentoadministrativo_detail', + kwargs={'pk': self.kwargs['pk']}) + + def get(self, request, *args, **kwargs): + materia_id = kwargs['pk'] + hash_txt = request.GET.get('hash_txt', '') + + try: + AcompanhamentoDocumento.objects.get(documento_id=documento_id, + hash=hash_txt).delete() + except ObjectDoesNotExist: + pass + + return HttpResponseRedirect(self.get_success_url()) + + +class AcompanhamentoDocumentoView(CreateView): + template_name = "protocoloadm/acompanhamento_documento.html" + + def get_random_chars(self): + s = ascii_letters + digits + return ''.join(choice(s) for i in range(choice([6, 7]))) + + def get(self, request, *args, **kwargs): + pk = self.kwargs['pk'] + documento = DocumentoAdministrativo.objects.get(id=pk) + + return self.render_to_response( + {'form': AcompanhamentoDocumentoForm(), + 'documento': documento}) + + def post(self, request, *args, **kwargs): + form = AcompanhamentoDocumentoForm(request.POST) + pk = self.kwargs['pk'] + documento = DocumentoAdministrativo.objects.get(id=pk) + + if form.is_valid(): + email = form.cleaned_data['email'] + usuario = request.user + + hash_txt = self.get_random_chars() + + acompanhar = AcompanhamentoDocumento.objects.get_or_create( + documento=documento, + email=form.data['email']) + + # Se o segundo elemento do retorno do get_or_create for True + # quer dizer que o elemento não existia + if acompanhar[1]: + acompanhar = acompanhar[0] + acompanhar.hash = hash_txt + acompanhar.usuario = usuario.username + acompanhar.confirmado = False + acompanhar.save() + + base_url = get_base_url(request) + + destinatario = AcompanhamentoDocumento.objects.get( + documento=documento, + email=email, + confirmado=False) + casa = CasaLegislativa.objects.first() + + do_envia_email_confirmacao(base_url, + casa, + "documento", + documento, + destinatario) + + msg = _('Foi enviado um e-mail de confirmação. Confira sua caixa \ + de mensagens e clique no link que nós enviamos para \ + confirmar o acompanhamento deste documento.') + messages.add_message(request, messages.SUCCESS, msg) + + # Caso esse Acompanhamento já exista + # avisa ao usuário que esse documento já está sendo acompanhado + else: + msg = _('Este e-mail já está acompanhando esse documento.') + messages.add_message(request, messages.INFO, msg) + + return self.render_to_response( + {'form': form, + 'documento': documento, + 'error': _('Esse documento já está\ + sendo acompanhada por este e-mail.')}) + return HttpResponseRedirect(self.get_success_url()) + else: + return self.render_to_response( + {'form': form, + 'documento': documento}) + + def get_success_url(self): + return reverse('sapl.protocoloadm:documentoadministrativo_detail', + kwargs={'pk': self.kwargs['pk']}) + class DocumentoAdministrativoMixin: @@ -585,18 +723,19 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin, kwargs = {'data': self.request.GET or None} - status_tramitacao = self.request.GET.get('tramitacao__status') + status_tramitacao = self.request.GET.get( + 'tramitacaoadministrativo__status') unidade_destino = self.request.GET.get( - 'tramitacao__unidade_tramitacao_destino') + 'tramitacaoadministrativo__unidade_tramitacao_destino') 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") + "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, @@ -702,8 +841,38 @@ class TramitacaoAdmCrud(MasterDetailCrud): 'unidade_tramitacao_local'].widget.attrs['disabled'] = True return context + def form_valid(self, form): + self.object = form.save() + + try: + tramitacao_signal.send(sender=TramitacaoAdministrativo, + post=self.object, + request=self.request) + except Exception as e: + # TODO log error + msg = _('Tramitação criada, mas e-mail de acompanhamento ' + 'de documento não enviado. A não configuração do' + ' servidor de e-mail impede o envio de aviso de tramitação') + messages.add_message(self.request, messages.WARNING, msg) + return HttpResponseRedirect(self.get_success_url()) + return super().form_valid(form) + class UpdateView(MasterDetailCrud.UpdateView): form_class = TramitacaoAdmEditForm + def form_valid(self, form): + self.object = form.save() + try: + tramitacao_signal.send(sender=TramitacaoAdministrativo, + post=self.object, + request=self.request) + except Exception as e: + # TODO log error + msg = _('Tramitação criada, mas e-mail de acompanhamento ' + 'de documento não enviado. A não configuração do' + ' servidor de e-mail impede o envio de aviso de tramitação') + messages.add_message(self.request, messages.WARNING, msg) + return HttpResponseRedirect(self.get_success_url()) + return super().form_valid(form) class ListView(DocumentoAdministrativoMixin, MasterDetailCrud.ListView): diff --git a/sapl/relatorios/templates/pdf_pauta_sessao_gerar.py b/sapl/relatorios/templates/pdf_pauta_sessao_gerar.py index 621b8fb75..15a4147d6 100755 --- a/sapl/relatorios/templates/pdf_pauta_sessao_gerar.py +++ b/sapl/relatorios/templates/pdf_pauta_sessao_gerar.py @@ -122,8 +122,10 @@ def expediente_materia(lst_expediente_materia): tmp += '\n' tmp += 'MatériaEmentaSituação\n' for expediente_materia in lst_expediente_materia: - tmp += '' + str(expediente_materia['num_ordem']) + ' - ' + expediente_materia[ - 'id_materia'] + '\n' + 'Autor: ' + expediente_materia['nom_autor'] + '\n' + tmp += '' + str(expediente_materia['num_ordem']) + ' - ' + \ + expediente_materia["tipo_materia"] + ' No. ' + \ + expediente_materia['id_materia'] + '\n' + 'Autor: ' + \ + expediente_materia['nom_autor'] + '\n' txt_ementa = expediente_materia['txt_ementa'].replace('&', '&') tmp += '' + txt_ementa + '\n' tmp += '' + \ @@ -145,10 +147,14 @@ def votacao(lst_votacao): tmp += '\n' tmp += 'MatériaEmentaSituação\n' for votacao in lst_votacao: - tmp += '' + str(votacao['num_ordem']) + ' - ' + votacao['id_materia'] + '\n' + 'Processo: ' + votacao[ - 'des_numeracao'] + '\n' + 'Turno: ' + votacao['des_turno'] + '\n' + 'Autor: ' + votacao['nom_autor'] + '\n' + tmp += '' + str(votacao['num_ordem']) + ' - ' + \ + votacao["tipo_materia"] + ' No. ' + \ + str(votacao['id_materia']) + '\n' + 'Processo: ' + \ + str(votacao['des_numeracao']) + '\n' + 'Turno: ' + \ + str(votacao['des_turno']) + '\n' + 'Autor: ' + \ + str(votacao['nom_autor']) + '\n' tmp += '' + \ - votacao['txt_ementa'] + '\n' + str(votacao['txt_ementa']) + '\n' tmp += '' + \ str(votacao['des_situacao']) + '\n' diff --git a/sapl/relatorios/views.py b/sapl/relatorios/views.py index 98b290af5..17649acbf 100755 --- a/sapl/relatorios/views.py +++ b/sapl/relatorios/views.py @@ -766,16 +766,7 @@ def get_sessao_plenaria(sessao, casa): def get_turno(dic, materia, sessao_data_inicio): descricao_turno = ' ' descricao_tramitacao = ' ' - tramitacao = Tramitacao.objects.filter(materia=materia, - turno__isnull=False, - data_tramitacao__lte=sessao_data_inicio, - ).exclude(turno__exact='' - ).select_related( - 'materia', - 'status', - 'materia__tipo').order_by( - '-data_tramitacao' - ).first() + tramitacao = None if tramitacao is None: tramitacao = materia.tramitacao_set.last() @@ -784,7 +775,7 @@ def get_turno(dic, materia, sessao_data_inicio): if t[0] == tramitacao.turno: descricao_turno = t[1] break - descricao_tramitacao = tramitacao.status.descricao if tramitacao.status else ' ' + descricao_tramitacao = tramitacao.status.descricao if tramitacao.status else 'Não informada' return (descricao_turno, descricao_tramitacao) @@ -1058,7 +1049,7 @@ def get_pauta_sessao(sessao, casa): inf_basicas_dic["nom_sessao"] = sessao.tipo.nome inf_basicas_dic["num_sessao_plen"] = sessao.numero inf_basicas_dic["num_legislatura"] = sessao.legislatura - inf_basicas_dic["num_sessao_leg"] = sessao.legislatura + inf_basicas_dic["num_sessao_leg"] = sessao.sessao_legislativa.numero inf_basicas_dic["dat_inicio_sessao"] = sessao.data_inicio inf_basicas_dic["hr_inicio_sessao"] = sessao.hora_inicio inf_basicas_dic["dat_fim_sessao"] = sessao.data_fim @@ -1073,6 +1064,7 @@ def get_pauta_sessao(sessao, casa): id=expediente_materia.materia.id).first() dic_expediente_materia = {} + dic_expediente_materia["tipo_materia"] = materia.tipo.sigla + ' - ' + materia.tipo.descricao dic_expediente_materia["num_ordem"] = str( expediente_materia.numero_ordem) dic_expediente_materia["id_materia"] = str( @@ -1125,6 +1117,7 @@ def get_pauta_sessao(sessao, casa): id=votacao.materia.id).first() dic_votacao = {} + dic_votacao["tipo_materia"] = materia.tipo.sigla + ' - ' + materia.tipo.descricao dic_votacao["num_ordem"] = votacao.numero_ordem dic_votacao["id_materia"] = str( materia.numero) + "/" + str(materia.ano) @@ -1133,11 +1126,9 @@ def get_pauta_sessao(sessao, casa): dic_votacao["des_numeracao"] = ' ' - numeracao = Numeracao.objects.filter( - materia=votacao.materia).first() - if numeracao is not None: - numeracao = numeracao.first() - dic_votacao["des_numeracao"] = str( + numeracao = Numeracao.objects.filter(materia=votacao.materia).first() + if numeracao: + dic_votacao["des_numeracao"] = str( numeracao.numero_materia) + '/' + str(numeracao.ano_materia) turno, tramitacao = get_turno(dic_votacao, materia, sessao.data_inicio) diff --git a/sapl/rules/map_rules.py b/sapl/rules/map_rules.py index fa5a5b324..1a2d1fe8b 100755 --- a/sapl/rules/map_rules.py +++ b/sapl/rules/map_rules.py @@ -116,6 +116,7 @@ rules_group_materia = { (materia.Numeracao, __base__), (materia.Tramitacao, __base__), (norma.LegislacaoCitada, __base__), + (norma.AutoriaNorma, __base__), (compilacao.Dispositivo, __base__ + [ 'change_dispositivo_edicao_dinamica', @@ -136,6 +137,7 @@ rules_group_norma = { (norma.NormaJuridica, __base__), (norma.NormaRelacionada, __base__), (norma.AnexoNormaJuridica, __base__), + (norma.AutoriaNorma, __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 @@ -300,6 +302,7 @@ rules_group_anonymous = { 'group': SAPL_GROUP_ANONYMOUS, 'rules': [ (materia.AcompanhamentoMateria, [RP_ADD, RP_DELETE]), + (protocoloadm.AcompanhamentoDocumento, [RP_ADD, RP_DELETE]), ] } diff --git a/sapl/rules/tests/test_rules.py b/sapl/rules/tests/test_rules.py index 07302bae5..e1ed7f7e4 100755 --- a/sapl/rules/tests/test_rules.py +++ b/sapl/rules/tests/test_rules.py @@ -11,6 +11,7 @@ from sapl.compilacao.models import (PerfilEstruturalTextoArticulado, TipoDispositivo, TipoDispositivoRelationship) from sapl.materia.models import AcompanhamentoMateria +from sapl.protocoloadm.models import AcompanhamentoDocumento from sapl.rules import SAPL_GROUPS, map_rules from sapl.test_urls import create_perms_post_migrate from scripts.lista_permissions_in_decorators import \ @@ -61,6 +62,7 @@ __fp__in__test_permission_of_models_in_rules_patterns = { PerfilEstruturalTextoArticulado], map_rules.RP_CHANGE: [AcompanhamentoMateria, + AcompanhamentoDocumento, TipoDispositivo, TipoDispositivoRelationship, PerfilEstruturalTextoArticulado], @@ -71,11 +73,13 @@ __fp__in__test_permission_of_models_in_rules_patterns = { PerfilEstruturalTextoArticulado], map_rules.RP_LIST: [AcompanhamentoMateria, + AcompanhamentoDocumento, TipoDispositivo, TipoDispositivoRelationship, PerfilEstruturalTextoArticulado], map_rules.RP_DETAIL: [AcompanhamentoMateria, + AcompanhamentoDocumento, TipoDispositivo, TipoDispositivoRelationship, PerfilEstruturalTextoArticulado] diff --git a/sapl/sessao/forms.py b/sapl/sessao/forms.py index a319e5447..96e85b9d8 100755 --- a/sapl/sessao/forms.py +++ b/sapl/sessao/forms.py @@ -87,7 +87,8 @@ class SessaoPlenariaForm(ModelForm): sessoes = SessaoPlenaria.objects.filter(numero=num, sessao_legislativa=sl, legislatura=leg, - tipo=tipo).\ + tipo=tipo, + data_inicio__year=abertura.year).\ values_list('id', flat=True) qtd_sessoes = len(sessoes) diff --git a/sapl/sessao/tests/test_sessao.py b/sapl/sessao/tests/test_sessao.py index 065cd58a1..a79822161 100755 --- a/sapl/sessao/tests/test_sessao.py +++ b/sapl/sessao/tests/test_sessao.py @@ -51,17 +51,17 @@ def test_numero_duplicado_sessao_plenaria_form(): legislatura = mommy.make(Legislatura) sessao = mommy.make(SessaoLegislativa) tipo = mommy.make(TipoSessaoPlenaria) - mommy.make(SessaoPlenaria, - legislatura=legislatura, - sessao_legislativa=sessao, - tipo=tipo, - numero=1) + sessao_plenaria = mommy.make(SessaoPlenaria, + legislatura=legislatura, + sessao_legislativa=sessao, + tipo=tipo, + numero=1) form = forms.SessaoPlenariaForm(data={'legislatura': str(legislatura.pk), 'numero': '1', 'tipo': str(tipo.pk), 'sessao_legislativa': str(sessao.pk), - 'data_inicio': '10/11/2017', + 'data_inicio': sessao_plenaria.data_inicio, 'hora_inicio': '10:10' }) diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index 9bc2ee8fb..932e3168e 100755 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -1477,6 +1477,7 @@ class ResumoAtaView(ResumoView): logger.info('- Gerando Resumo.') + class ExpedienteView(FormMixin, DetailView): template_name = 'sessao/expediente.html' form_class = ExpedienteForm @@ -2863,9 +2864,6 @@ class AdicionarVariasMateriasExpediente(PermissionRequiredForAppCrudMixin, expediente = ExpedienteMateria() expediente.sessao_plenaria_id = self.kwargs['pk'] expediente.materia_id = materia.id - # TODO: o campo observacao deve ser uma copia de ML.ementa? - expediente.observacao = MateriaLegislativa.objects.get( - pk=materia.id).ementa if lista_materias_expediente: posicao = lista_materias_expediente.last().numero_ordem + 1 expediente.numero_ordem = posicao @@ -2939,9 +2937,6 @@ class AdicionarVariasMateriasOrdemDia(AdicionarVariasMateriasExpediente): ordem_dia = OrdemDia() ordem_dia.sessao_plenaria_id = self.kwargs['pk'] ordem_dia.materia_id = materia.id - # TODO: o campo observacao deve ser uma copia de ML.ementa? - ordem_dia.observacao = MateriaLegislativa.objects.get( - pk=materia.id).ementa if lista_materias_ordem_dia: posicao = lista_materias_ordem_dia.last().numero_ordem + 1 ordem_dia.numero_ordem = posicao diff --git a/sapl/settings.py b/sapl/settings.py index 9447e84f7..153b7b939 100755 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -13,7 +13,6 @@ Quick-start development settings - unsuitable for production See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ """ -import logging from decouple import config from dj_database_url import parse as db_url @@ -240,7 +239,8 @@ LANGUAGES = ( TIME_ZONE = config('TZ', default='America/Sao_Paulo') if not TIME_ZONE: - raise ValueError('TIMEZONE env variable undefined in .env settings file! Leaving...') + raise ValueError( + 'TIMEZONE env variable undefined in .env settings file! Leaving...') USE_I18N = True USE_L10N = True @@ -348,7 +348,7 @@ def excepthook(*args): logging.getLogger(BASE_DIR.name).error( 'Uncaught exception:', exc_info=args) -# sys.excepthook = excepthook +# sys.excepthook = excepthook""" PASSWORD_HASHERS = [ diff --git a/sapl/static/styles/_header.scss b/sapl/static/styles/_header.scss index e5fa310a3..b04c99494 100755 --- a/sapl/static/styles/_header.scss +++ b/sapl/static/styles/_header.scss @@ -2,9 +2,4 @@ $logo-height: 0.8 * $navbar-height; $logo-margin: ($navbar-height - $logo-height) / 2; -.logo img { - width: $logo-height; - height: $logo-height; - margin: $logo-margin $navbar-padding-horizontal; -} - +$footer-height : 140px; diff --git a/sapl/static/styles/_home_index.scss b/sapl/static/styles/_home_index.scss new file mode 100644 index 000000000..d85ce05a3 --- /dev/null +++ b/sapl/static/styles/_home_index.scss @@ -0,0 +1,188 @@ + +.container-home { + position: relative; + padding: 2em 1.5em 1.5em 1.5em; + max-width: 1000px; + margin: 0 auto; + a:hover { + color: #444; + -webkit-transition: 0.3s ease-in; + -moz-transition: 0.3s ease-in; + -o-transition: 0.3s ease-in + } + + #homeIndex { + text-align: center; + } + + .homeBanner span { + color: white; + font-size: 32px; + font-weight: 600; + display: inline-block; + vertical-align: middle; + padding: 2px 45px 4px; + border: 2px solid; + } + + .homeBanner::after { + display: inline-block; + vertical-align: middle; + height: 100%; + } + + .homeBlock { + display: inline-block; + position: relative; + background-color: #F3F3F3; + width: 190px; + height: 260px; + margin: 3px; + text-align: center; + font-size: 0; + overflow: hidden; + } + + .homeBlock > a { + display: block; + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + } + + .homeBlock::after { + content: ''; + display: inline-block; + vertical-align: middle; + height: 100%; + overflow: visible; + clear: none; + visibility: initial; + } + + .homeContent { + position: relative; + padding: 10px; + text-align: justify; + font-size: 14px; + color: #FFF; + opacity: 0; + transition: opacity 0.5s ease; + display: inline-block; + vertical-align: middle; + } + + .homeContent p { + display: block; + line-height: 13px; + font-size: 80%; + color: white; + } + + .homeIcon { + position: relative; + display: inline-block; + width: 105px; + height: 105px; + border-radius: 50%; + background: #364347; + z-index: 1; + } + + .homeIcon::before { + content: ''; + position: absolute; + width: 100%; + height: 100%; + border-radius: 50%; + background: #364347; + top: 0; + left: 0; + transform: scale(0.95); + transition: transform 0.6s ease; + } + + .homeIcon img { + position: absolute; + margin: auto; + top: 0; + bottom: 0; + right: 0; + left: 0; + transition: opacity 0.4s 0.4s ease; + } + + .homeFront { + position: absolute; + top: 46%; + width: 100%; + font-size: 0; + transform: translateY(-60%); + + } + + .homeFront h2 { + position: absolute; + margin-top: 18px; + font-size: 22px; + font-weight: 700; + color: #595959 !important; + width: 100%; + padding: 0 6%; + z-index: 0; + } + + .homeTitle { + display: block; + height: 32px; + text-align: center; + width: 100%; + opacity: 0; + transition: opacity 0.4s ease; + } + + .homeTitle::before { + content: ''; + display: inline-block; + vertical-align: middle; + height: 100%; + } + + .homeTitle h2 { + display: inline-block; + vertical-align: middle; + max-width: 110px; + font-size: 14px; + color: white !important; + line-height: 1em; + } + + .homeTitle img { + display: inline-block; + vertical-align: middle; + height: 30px; + margin-right: 5px; + } + + .homeBlock:hover .homeIcon::before { + transform: scale(3.6) translateY(7px); + } + + .homeBlock:hover .homeContent{ + opacity: 1; + transition-delay: 0.2s; + } + + .homeBlock:hover .homeIcon img { + opacity: 0; + transition-duration: 0.2s; + transition-delay: 0s; + } + + .homeBlock:hover .homeTitle { + opacity: 1; + } + +} \ No newline at end of file diff --git a/sapl/static/styles/app.scss b/sapl/static/styles/app.scss index 046aa58e4..099392fde 100755 --- a/sapl/static/styles/app.scss +++ b/sapl/static/styles/app.scss @@ -1,73 +1,21 @@ - @import "bootstrap/variables.scss"; + @import "header"; -.vcenter { - display: inline-block; - vertical-align: middle; - float: none; - padding: 10px; -} +@import "home_index"; -nav { - &.navbar { - border-radius: 0; - font-size: 15px; - } - .navbar-nav { - & > li { - & > a { - padding-top: 0px; - padding-bottom: 0px; - line-height: $grid-gutter-width * 2.5; - &:hover { - background-color: $link-hover-color; - } - } - &:nth-child(2) { - & > .dropdown-menu { - right: auto; - } - } - - } - &:last-child { - & > li:last-child { - a { - padding-right: 0px; - } - } - } - } -} -.masthead { - padding: 10px; - .nav { - clear:both; - } - .navbar-brand { - color: $headings-color; - font-size: 24px; - img.img-responsive { - height: 95px; - margin-right: $navbar-padding-horizontal; - } - small { - color: #93A4AA; - font-size: 75%; - line-height: 25px; - } - } +html { + position: relative; + min-height: 100%; } - -.navbar { - margin-bottom: 0; +body { + margin-bottom: 160px; } -.navbar-brand { - padding: 0px; +h1, h2, h3, h4, h5, h6, form, dl, dt, dd, p, div, img, a { + margin: 0; + padding: 0; } -// ADJUST DRUNKEN PARROT STYLES ######################################## h1, .h1 { font-size: 30px; } @@ -87,21 +35,41 @@ h6, .h6 { font-size: 12px; } -.page-header { - margin: 20px 0px 10px; +p { + margin: 0.5em 0; + .control-label { + font-weight: bold; + } } -.btn:hover, .btn:focus { - color: inherit; +label { + margin-bottom: 0; + line-height: 1; +} + +fieldset { + fieldset { + font-size: 95%; + legend { + font-size: 18px; + } + } +} + +.page-header { + margin: 20px 0px 10px; } .caret { - /* Por padrão caret aponta para baixo*/ &.top { transform: rotate(180deg); } } +.btn:hover, .btn:focus { + color: inherit; +} + .btn-default { &.btn-excluir { color: $btn-danger-bg; @@ -113,68 +81,19 @@ h6, .h6 { } } -.controls-file { - padding: 10px; - border: 1px solid #d6e1e5; - border-radius: 4px; - - label.checkbox-inline { - margin: 0px; - display: block; - } -} - -.help-block-danger { - margin: $grid-gutter-width / 2; - padding: $grid-gutter-width / 2; - border: 2px dashed #f00; -} - -.controls-radio-checkbox { - padding: 0px; - border: 1px solid #d6e1e5; - border-radius: 4px; - min-height: 20px; - .help-block { - margin: $grid-gutter-width / 2; - padding: $grid-gutter-width / 2; - border: 2px dashed #d6e1e5; - } - - label { - padding: 5px; - .icons { - top: 5px; - left: 8px; - } - &.checkbox-inline, &.radio-inline { - padding: 8px; - padding-left: 36px; - .icons { - top: 8px; - left: 8px; - } - } +.btn-cancel-iframe { + position: relative; + text-align: right; + opacity: 0.5; + &:hover { + opacity: 1; } - .checkbox, .radio, .checkbox-inline, .radio-inline { - margin: 0; - &:hover { - background-color: #d6e1e5; - } + a { + padding: 10px; + display: inline-block; } } - -// #### CRUD DETAIL ######################################## -p.control-label { - font-weight: bold; -} - -// copied from bootstrap _forms.scss legend -// using @extend would require importing parts of bootstrap again and overriding drunken parrot css -// @import "bootstrap/mixins.scss"; -// @import "bootstrap/forms.scss"; - .legend { display: block; width: 100%; @@ -190,42 +109,27 @@ p.control-label { .grid-gutter-width-right { margin-right: $grid-gutter-width / 2; } -// #### footer ########################################### -// based on http://getbootstrap.com/examples/sticky-footer -$footer-height : 140px; -html { - position: relative; - min-height: 100%; -} -body { - margin-bottom: $footer-height + 20px; -} -.footer { - position: absolute; - bottom: 0; - width: 100%; - /* Set the fixed height of the footer here */ - height: $footer-height; - background: #364347 none repeat scroll 0% 0%; - color: white; - text-align: center; - p { - color: white; - margin-top: 10px; - } - .container { - padding-top: 25px; +.controls-file { + padding: 10px; + border: 1px solid #d6e1e5; + border-radius: 4px; + label.checkbox-inline { + margin: 0px; + display: block; } } -label { - margin-bottom: 0; - line-height: 1; +.help-block-danger { + margin: $grid-gutter-width / 2; + padding: $grid-gutter-width / 2; + border: 2px dashed #f00; } + .control-label { margin: 0; } + .form-control-static { padding-top: 0; min-height: auto; @@ -234,8 +138,6 @@ label { } } - -// #### pagination ######################################## .pagination { padding-top: 25px; } @@ -246,15 +148,6 @@ label { } } -fieldset { - fieldset { - font-size: 95%; - legend { - font-size: 18px; - } - } -} - .avatar-parlamentar { height: 128px; width: 128px; @@ -262,228 +155,120 @@ fieldset { display: table; } -/* INDEX */ -#conteudo { - position: relative; - padding: 2em 1.5em 1.5em 1.5em; - overflow: hidden; - font-size: 100%; - text-align: left; - min-height: 350px; - max-width: 1000px; - margin: 0 auto; -} -#conteudo a:hover { - color: #444; - -webkit-transition: 0.3s ease-in; - -moz-transition: 0.3s ease-in; - -o-transition: 0.3s ease-in -} - -#homeIndex { - text-align: center; -} - -.homeBanner span { - color: white; - font-size: 32px; - font-weight: 600; - display: inline-block; - vertical-align: middle; - padding: 2px 45px 4px; - border: 2px solid; -} - -.homeBanner::after { - display: inline-block; - vertical-align: middle; - height: 100%; -} - -.homeBlock { - display: inline-block; - position: relative; - background-color: #F3F3F3; - width: 190px; - height: 260px; - margin: 3px; - text-align: center; - font-size: 0; - overflow: hidden; -} - -.homeBlock > a { - display: block; - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; -} - -.homeBlock::after { - content: ''; - display: inline-block; - vertical-align: middle; - height: 100%; - overflow: visible; - clear: none; - visibility: initial; -} - -.homeContent { - position: relative; - padding: 10px; - text-align: justify; - font-size: 14px; - color: #FFF; - opacity: 0; - transition: opacity 0.5s ease; - display: inline-block; - vertical-align: middle; -} - -.homeContent p { - display: block; - line-height: 13px; - font-size: 80%; - color: white; -} - -.homeIcon { - position: relative; - display: inline-block; - width: 105px; - height: 105px; - border-radius: 50%; - background: #364347; - z-index: 1; -} -.homeIcon::before { - content: ''; - position: absolute; - width: 100%; - height: 100%; - border-radius: 50%; - background: #364347; - top: 0; - left: 0; - transform: scale(0.95); - transition: transform 0.6s ease; -} - -.homeIcon img { - position: absolute; - margin: auto; - top: 0; - bottom: 0; - right: 0; - left: 0; - transition: opacity 0.4s 0.4s ease; -} - -.homeFront { - position: absolute; - top: 46%; - width: 100%; - font-size: 0; - transform: translateY(-60%); - -} - -.homeFront h2 { - position: absolute; - margin-top: 18px; - font-size: 22px; - font-weight: 700; - color: #595959 !important; - width: 100%; - padding: 0 6%; - z-index: 0; -} - -.homeTitle { - display: block; - height: 32px; - text-align: center; - width: 100%; - opacity: 0; - transition: opacity 0.4s ease; -} - -.homeTitle::before { - content: ''; - display: inline-block; - vertical-align: middle; - height: 100%; -} - -.homeTitle h2 { - display: inline-block; - vertical-align: middle; - max-width: 110px; - font-size: 14px; - color: white !important; - line-height: 1em; -} - -.homeTitle img { - display: inline-block; - vertical-align: middle; - height: 30px; - margin-right: 5px; -} - -.homeBlock:hover .homeIcon::before { - transform: scale(3.6) translateY(7px); -} - -.homeBlock:hover .homeContent{ - opacity: 1; - transition-delay: 0.2s; -} - -.homeBlock:hover .homeIcon img { - opacity: 0; - transition-duration: 0.2s; - transition-delay: 0s; +.masthead { + padding: 10px; + .nav { + clear:both; + } + .navbar-brand { + padding: 0px; + color: $headings-color; + font-size: 24px; + img.img-responsive { + height: 95px; + margin-right: $navbar-padding-horizontal; + } + small { + color: #93A4AA; + font-size: 75%; + line-height: 25px; + } + .vcenter { + display: inline-block; + vertical-align: middle; + float: none; + padding: 10px; + } + } } -.homeBlock:hover .homeTitle { - opacity: 1; -} +nav { + &.navbar { + margin-bottom: 0; + border-radius: 0; + font-size: 15px; + } + .navbar-nav { + & > li { + & > a { + padding-top: 0px; + padding-bottom: 0px; + line-height: $grid-gutter-width * 2.5; + &:hover { + background-color: $link-hover-color; + } + } + &:nth-child(2) { + & > .dropdown-menu { + right: auto; + } + } -h1, h2, h3, h4, h5, h6, form, dl, dt, dd, p, div, img, a { - margin: 0; - padding: 0; + } + &:last-child { + & > li:last-child { + a { + padding-right: 0px; + } + } + } + } } -p { - margin: 0.5em 0; -} -/* FIM INDEX */ +.controls-radio-checkbox { + padding: 0px; + border: 1px solid #d6e1e5; + border-radius: 4px; + min-height: 20px; + .help-block { + margin: $grid-gutter-width / 2; + padding: $grid-gutter-width / 2; + border: 2px dashed #d6e1e5; + } -/* TEMPLATE AJUDA */ -.manual li { - display: list-item; - line-height: 1.5em; - padding-right: 0; -} -.manual li a { - background-color: transparent; - border: none; - border-radius: none; - padding: 0; + label { + padding: 5px; + .icons { + top: 5px; + left: 8px; + } + &.checkbox-inline, &.radio-inline { + padding: 8px; + padding-left: 36px; + .icons { + top: 8px; + left: 8px; + } + } + } + .checkbox, .radio, .checkbox-inline, .radio-inline { + margin: 0; + &:hover { + background-color: #d6e1e5; + } + } } -.manual, .manual ul { +.manual { + &, & ul { padding-left: 1.5em; list-style-type: none; margin-top: 0; font-size: 100%; + } + li { + display: list-item; + line-height: 1.5em; + padding-right: 0; + a { + background-color: transparent; + border: none; + border-radius: none; + padding: 0; + } + } } -/* FIM TEMPLATE AJUDA */ .container-tabaux { .sidebar-tabaux { @@ -558,29 +343,46 @@ p { } } -.btn-cancel-iframe { - position: relative; - text-align: right; - opacity: 0.5; - &:hover { - opacity: 1; +#styleparlamentar { + border: 0px solid #d6e1e5; + border-top-color: rgb(214, 225, 229); + border-right-color: rgb(214, 225, 229); + border-bottom-color: rgb(214, 225, 229); + border-left-color: rgb(214, 225, 229); + border-image-source: initial; + border-image-slice: initial; + border-image-repeat: initial; + font-size: 16px; + line-height: 1.467; + padding: 7px 12px; + height: 40px; + -webkit-appearance: none; + border-radius: 4px; + -webkit-box-shadow: none; + box-shadow: none; + margin-left: 1.0em; +} + +.footer { + background: #364347; + color: white; + text-align: center; + position: absolute; + width: 100%; + bottom: 0px; + p { + color: white; + margin-top: 10px; } - a { - padding: 10px; - display: inline-block; + .container { + padding-top: 25px; } } - @media (max-width: 1199px) { - .masthead { - .navbar-brand { - font-size: 22px; - img.img-responsive { - height: 60px; - width: 60px; - margin-right: $navbar-padding-horizontal / 2; - } + nav { + .container { + width: auto !important; } } .navbar-nav > li > a { @@ -589,14 +391,76 @@ p { } } + @media (max-width: 1091px) { + .container { + width: auto; + } .navbar-nav > li > a { padding-left: $grid-gutter-width / 4; padding-right: $grid-gutter-width / 4; } + .masthead { + .navbar-brand { + font-size: 22px; + img.img-responsive { + height: 60px; + margin-right: $navbar-padding-horizontal / 2; + } + } + } } +@media (max-width: 991px) { + body { + margin: 0; + } + .footer { + position: relative; + } + .caret { + margin-left: 1px; + } + .navbar-nav > li > a { + padding-left: 4px; + padding-right: 4px; + } +} +@media (max-width: 767px) { + nav { + .navbar-nav > li > a { + line-height: 2.5; + } + .navbar-right { + position: absolute; + top: 0; + margin: 10px; + & > li { + vertical-align: top; + display: inline-block; + a { + padding-left: 10px; + padding-right: 10px; + } + } + .pesquisa.open { + ul { + position: absolute; + } + } + .navbar-form { + margin: 8px 0; + } + } + } + .table{ + width: auto; + white-space: normal; + display:block; + overflow-x: auto; + } +} @media (min-width: 1092px) and (max-width: 1199px) { .container { @@ -604,24 +468,8 @@ p { } } -/* Estilização da Listagem de Votos em sessões plenárias */ - -#styleparlamentar { - border: 0px solid #d6e1e5; - border-top-color: rgb(214, 225, 229); - border-right-color: rgb(214, 225, 229); - border-bottom-color: rgb(214, 225, 229); - border-left-color: rgb(214, 225, 229); - border-image-source: initial; - border-image-slice: initial; - border-image-repeat: initial; - font-size: 16px; - line-height: 1.467; - padding: 7px 12px; - height: 40px; - -webkit-appearance: none; - border-radius: 4px; - -webkit-box-shadow: none; - box-shadow: none; - margin-left: 1.0em; +@media print { + a[href]:after { + content: none !important; + } } diff --git a/sapl/static/styles/compilacao.scss b/sapl/static/styles/compilacao.scss index c2e7ed7ad..60500e24c 100755 --- a/sapl/static/styles/compilacao.scss +++ b/sapl/static/styles/compilacao.scss @@ -290,6 +290,7 @@ a:link:after, a:visited:after { margin-top: 0.6em; font-size: 1.15em; } + .page-break { page-break-before: always; } .bloco_alteracao { padding-left: 10%; @@ -303,9 +304,9 @@ a:link:after, a:visited:after { a, table, table td { color: #018 !important; } - } + .dn { /* Notas de Dispositivo*/ font-weight: normal; position: relative; diff --git a/sapl/templates/404.html b/sapl/templates/404.html new file mode 100644 index 000000000..484a2a8f2 --- /dev/null +++ b/sapl/templates/404.html @@ -0,0 +1,171 @@ +{% load i18n staticfiles sass_tags menus %} +{% load common_tags %} + + + + + + + + + {% block head_title %}{% trans 'SAPL - Sistema de Apoio ao Processo Legislativo' %}{% endblock %} + + {% block head_content %} + + + {# Styles #} + + + + + + + + + {# Scripts #} + {# modernizr must be in head (see http://modernizr.com/docs/#installing) #} + {% endblock %} + + + +
+ + {% if not request|has_iframe %} + {% block navigation %} + + {% endblock navigation %} + + {# Header #} + {% block main_header %} +
+ +
+ {% endblock main_header %} + {% else %} +
+ +
+
+
+
+ {% subnav %} +
+
+
+ {% endif %} +
+ +

{% trans 'Página não encontrada! Erro 404' %}

+
+
+ {% block base_content %} + {% endblock %} + {% if not request|has_iframe %} + {% block footer_container %} + +
+ {% endblock footer_container %} + {% endif %} + + {% block foot_js %} + + + + + + + + + + + + + + + + + + + + + {% block extra_js %}{% endblock %} + + + + {% endblock foot_js %} + + diff --git a/sapl/templates/500.html b/sapl/templates/500.html new file mode 100644 index 000000000..ab1afae4f --- /dev/null +++ b/sapl/templates/500.html @@ -0,0 +1,135 @@ +{% load i18n staticfiles sass_tags menus %} +{% load common_tags %} + + + + + + + + + {% block head_title %}{% trans 'SAPL - Sistema de Apoio ao Processo Legislativo' %}{% endblock %} + + {% block head_content %} + + + {# Styles #} + + + + + + + + + {# Scripts #} + {# modernizr must be in head (see http://modernizr.com/docs/#installing) #} + {% endblock %} + + + +
+ + {% block navigation %} + + {% endblock navigation %} + + {# Header #} + {% block main_header %} +
+
+ +
+ {% block sections_nav %} {% subnav %} {% endblock sections_nav %} +
+
+
+ {% endblock main_header %} + +
+ +

{% trans 'Ocorreu um erro inesperado! Erro 500' %}

+
+
+ {% block base_content %} + {% endblock %} + {% block footer_container %} + +
+ {% endblock footer_container %} + + {% block foot_js %} + + + + + + + + + + + + + + + + + + + + + {% block extra_js %}{% endblock %} + + {% endblock foot_js %} + + diff --git a/sapl/templates/base.html b/sapl/templates/base.html index 8c4e24b6c..7e071d372 100755 --- a/sapl/templates/base.html +++ b/sapl/templates/base.html @@ -1,6 +1,6 @@ + {% load i18n staticfiles sass_tags menus %} {% load common_tags %} - @@ -29,7 +29,7 @@
- + {% if not request|has_iframe %} {% block navigation %}
@@ -192,7 +193,7 @@

- Conteúdo e dados sob licença Creative Commons 4.0 Atribuir Fonte - Compartilhar Igual + Conteúdo e dados sob licença Creative Commons 4.0
Atribuir Fonte - Compartilhar Igual

@@ -251,7 +252,7 @@ diff --git a/sapl/templates/comissoes/composicao_list.html b/sapl/templates/comissoes/composicao_list.html index 29071c85d..3f543b0c5 100755 --- a/sapl/templates/comissoes/composicao_list.html +++ b/sapl/templates/comissoes/composicao_list.html @@ -37,7 +37,9 @@ {% endif %} -
+ +
+
diff --git a/sapl/templates/compilacao/dispositivo_form_search_fragment.html b/sapl/templates/compilacao/dispositivo_form_search_fragment.html index a66b59245..95d91a2cf 100755 --- a/sapl/templates/compilacao/dispositivo_form_search_fragment.html +++ b/sapl/templates/compilacao/dispositivo_form_search_fragment.html @@ -64,7 +64,7 @@
{{ dpt.tipo_dispositivo.rotulo_prefixo_html|safe }} - {% if dpt.rotulo or dpt.nivel = 1 %}{{ dpt.rotulo }}{%else%}[{{ dpt|nomenclatura}} {% trans "de" %} {{ dpt.dispositivo_pai.rotulo }}] - {% endif %} + {% if dpt.rotulo or dpt.nivel == 1 %}{{ dpt.rotulo }}{%else%}[{{ dpt|nomenclatura}} {% trans "de" %} {{ dpt.dispositivo_pai.rotulo }}] - {% endif %} {{ dpt.tipo_dispositivo.rotulo_sufixo_html|safe }}
{{ df.tipo_dispositivo.rotulo_prefixo_html|safe }} - {% if df.rotulo or df.nivel = 1%}{{ df.rotulo }}{%else%}[{{ df|nomenclatura}} {% trans "de" %} {{ df.dispositivo_pai.rotulo }}] - {% endif %} + {% if df.rotulo or df.nivel == 1%}{{ df.rotulo }}{%else%}[{{ df|nomenclatura}} {% trans "de" %} {{ df.dispositivo_pai.rotulo }}] - {% endif %} {{ df.tipo_dispositivo.rotulo_sufixo_html|safe }} {{ df.tipo_dispositivo.texto_prefixo_html|safe }}{%if df.texto %}{{ df.texto|safe }}{%else%}{%if not df.tipo_dispositivo.dispositivo_de_articulacao %} {% endif %}{% endif %} {% if df.ta_publicado_id %} diff --git a/sapl/templates/compilacao/layout/dispositivo_radio.html b/sapl/templates/compilacao/layout/dispositivo_radio.html index 93d10eef1..797e8885b 100755 --- a/sapl/templates/compilacao/layout/dispositivo_radio.html +++ b/sapl/templates/compilacao/layout/dispositivo_radio.html @@ -23,7 +23,7 @@
{{ dpt.tipo_dispositivo.rotulo_prefixo_html|safe }} - {% if dpt.rotulo or dpt.nivel = 1 %}{{ dpt.rotulo }}{%else%}[{{ dpt|nomenclatura}} {% trans "de" %} {{ dpt.dispositivo_pai.rotulo }}] - {% endif %} + {% if dpt.rotulo or dpt.nivel == 1 %}{{ dpt.rotulo }}{%else%}[{{ dpt|nomenclatura}} {% trans "de" %} {{ dpt.dispositivo_pai.rotulo }}] - {% endif %} {{ dpt.tipo_dispositivo.rotulo_sufixo_html|safe }} +{% endblock %} + +
+ + {% if object_list %} +
+
+ a + A +
+
+ {% endif %} + + {% for key, values in view.get_vigencias.items %} + {% if forloop.first %} + + + + {% if view.inicio_vigencia and view.fim_vigencia %} + {% blocktrans with inicio_vigencia=view.inicio_vigencia fim_vigencia=view.fim_vigencia%} + Vigência entre {{inicio_vigencia}} e {{fim_vigencia}}. + {% endblocktrans%} + {% else%} + {% blocktrans with inicio_vigencia=dispositivo.inicio_vigencia%} + Vigência a partir de {{inicio_vigencia}}. + {% endblocktrans%} + {% endif %} +
+ {% if view.ta_vigencia %} + {% trans 'Dada por '%}{{ta_pub_list|lookup:view.ta_vigencia}} + {% elif view.ta_vigencia and view.ta_vigencia != 0%} + {% trans 'Dada por '%}{{dispositivo.ta_publicado}} + {% endif %} +
+ {% endif%} + {% endfor %} + {% else %} +
  • + +
  • + {% endif %} + {% endfor %} + + + {% include 'compilacao/text_list_bloco.html'%} +
    + + + + {% if perms.compilacao.add_nota %} + + {% endif %} \ No newline at end of file diff --git a/sapl/templates/compilacao/text_notificacoes.html b/sapl/templates/compilacao/text_notificacoes.html index 78e3a5354..7aa0f96c8 100755 --- a/sapl/templates/compilacao/text_notificacoes.html +++ b/sapl/templates/compilacao/text_notificacoes.html @@ -24,7 +24,7 @@
    {{ dpt.tipo_dispositivo.rotulo_prefixo_html|safe }} - {% if dpt.rotulo or dpt.nivel = 1 %}{{ dpt.rotulo }}{%else%}[{{ dpt|nomenclatura}}{% if dpt.dispositivo_pai_id %} {% trans "de" %} {{ dpt.dispositivo_pai.rotulo }}{% endif %}] - {% endif %} + {% if dpt.rotulo or dpt.nivel == 1 %}{{ dpt.rotulo }}{%else%}[{{ dpt|nomenclatura}}{% if dpt.dispositivo_pai_id %} {% trans "de" %} {{ dpt.dispositivo_pai.rotulo }}{% endif %}] - {% endif %} {{ dpt.tipo_dispositivo.rotulo_sufixo_html|safe }}

    {{ column.verbose_name }}

    - {% if column.text|url %} + {% if column.text|audio_url %} +
    + +
    + {% elif column.text|video_url %} +
    + +
    + {% elif column.text|url %} {% else %}
    {{ column.text|safe|default:"" }}
    diff --git a/sapl/templates/crud/detail_detail.html b/sapl/templates/crud/detail_detail.html index b2312ec51..bddb962f0 100755 --- a/sapl/templates/crud/detail_detail.html +++ b/sapl/templates/crud/detail_detail.html @@ -63,14 +63,18 @@ {% endfor %} {% endfor %} {% endblock detail_content %} -
    - {% if view.detail_set_create_url %} - - {% blocktrans with verbose_name=view.verbose_name_set %} Adicionar {{ verbose_name }} {% endblocktrans %} - - {% endif %} - {% block more_buttons %}{% endblock more_buttons %} -
    + +
    + {% if view.detail_set_create_url %} + + {% blocktrans with verbose_name=view.verbose_name_set %} Adicionar {{ verbose_name }} {% endblocktrans %} + + {% endif %} + {% block more_buttons %}{% endblock more_buttons %} +
    + +
    +
    {% if not rows %}

    {{ NO_ENTRIES_MSG }}

    diff --git a/sapl/templates/email/acompanhar_documento.html b/sapl/templates/email/acompanhar_documento.html new file mode 100644 index 000000000..f90558568 --- /dev/null +++ b/sapl/templates/email/acompanhar_documento.html @@ -0,0 +1,25 @@ +{% load i18n %} +{% load static %} + + +

    {{casa_legislativa}} +
    + Sistema de Apoio ao Processo Legislativo +

    + +

    Registramos seu pedido para acompanhamento por e-mail do documento administrativo identificado a seguir:

    +{{documento}} - {{descricao_documento}}
    +{{assunto}}
    + +

    +

    Para garantia de sua privacidade, solicitamos que ative o recebimento das futuras mensagens clicando no link:

    + +

    + {{base_url}}{{confirmacao_url}}?hash_txt={{hash_txt}} +

    +
    +
    +

    Caso não tenha realizado o cadastramento em nosso sistema, favor desconsiderar a presente mensagem
    +Esta é uma mensagem automática. Por favor, não responda.

    + + diff --git a/sapl/templates/email/acompanhar_documento.txt b/sapl/templates/email/acompanhar_documento.txt new file mode 100644 index 000000000..eab05d7d1 --- /dev/null +++ b/sapl/templates/email/acompanhar_documento.txt @@ -0,0 +1,16 @@ +{{casa_legislativa}} + +Sistema de Apoio ao Processo Legislativo + +>Registramos seu pedido para acompanhamento por e-mail do documento administrativo identificado a seguir: + +{{base_url}}{{documento_url}} - {{documento}} - {{descricao_documento}} + +{{assunto}} + +Para garantia de sua privacidade, solicitamos que ative o recebimento das futuras mensagens acessando no link: + +{{base_url}}{{url_confirmar}}?hash_txt={{hash_txt}} + +Caso não tenha realizado o cadastramento em nosso sistema, favor desconsiderar a presente mensagem +Esta é uma mensagem automática. Por favor, não responda. diff --git a/sapl/templates/email/tramitacao.html b/sapl/templates/email/tramitacao.html index 4b30a1dc5..362fc563a 100755 --- a/sapl/templates/email/tramitacao.html +++ b/sapl/templates/email/tramitacao.html @@ -13,10 +13,12 @@

    {{materia}} - {{descricao_materia}}

    +{% if autoria %} Autoria:
    {% for autor in autoria %} {{ autor }}
    {% endfor %} +{% endif %}

    diff --git a/sapl/templates/email/tramitacao.txt b/sapl/templates/email/tramitacao.txt index f0a06687a..bb68a87a0 100755 --- a/sapl/templates/email/tramitacao.txt +++ b/sapl/templates/email/tramitacao.txt @@ -8,12 +8,12 @@ A seguinte matéria, de seu interesse, sofreu Tramitação registrada em {{data_ Matéria: {{materia}} - {{descricao_materia}} {{url_materia}} - +{% if autoria %} Autoria: {% for autor in autoria %} {{ autor }} {% endfor %} - +{% endif %} Data da ação: {{data}} Status: {{status}} diff --git a/sapl/templates/index.html b/sapl/templates/index.html index 7eab8dbc0..bbcfb97e2 100755 --- a/sapl/templates/index.html +++ b/sapl/templates/index.html @@ -1,164 +1,195 @@ {% extends "base.html" %} {% load i18n staticfiles %} -{% block title%} - - -
    -

    -
    -
    -
    -
    - Mesa Diretora -
    -

    Mesa Diretora

    -
    -
    -
    - +{% block title %} + +{% endblock title %} +{% block base_content%} +
    +
    +
    +
    +
    + Mesa Diretora +

    Mesa Diretora

    -

    - Órgão colegiado, composto de no mínimo três membros efetivos - Presidente e 1° e 2° Secretários - a quem cabe a direção dos trabalhos legislativos. Os parlamentares integrantes da Mesa Diretora são eleitos por seus pares na primeira reunião de instalação do período legislativo, para um mandato de um a dois anos. -

    +
    +
    + +

    Mesa Diretora

    +
    +

    + Órgão colegiado, composto de no mínimo três membros efetivos - Presidente e 1° e 2° Secretários - a + quem cabe a direção dos trabalhos legislativos. Os parlamentares integrantes da Mesa Diretora são + eleitos por seus pares na primeira reunião de instalação do período legislativo, para um mandato de + um + a dois anos. +

    +
    +
    - -
    -
    -
    -
    - Comissões -
    -

    Comissões

    -
    -
    -
    - +
    +
    +
    + Comissões +

    Comissões

    -

    - Órgãos da Casa Legislativa, de natureza técnica especializada e que têm por objetivo prestar melhores esclarecimentos aos parlamentares para a tomada de decisões. Assim, as comissões elaboram estudos, pareceres a respeito de determinados projetos de lei e investigação de irregularidades sobre fato determinado. -

    +
    +
    + +

    Comissões

    +
    +

    + Órgãos da Casa Legislativa, de natureza técnica especializada e que têm por objetivo prestar + melhores + esclarecimentos aos parlamentares para a tomada de decisões. Assim, as comissões elaboram estudos, + pareceres a respeito de determinados projetos de lei e investigação de irregularidades sobre fato + determinado. +

    +
    +
    - -
    -
    -
    -
    - Parlamentares -
    -

    Parlamentares

    -
    -
    -
    - +
    +
    +
    + Parlamentares +

    Parlamentares

    -

    - O Poder Legislativo, exercido pelo sistema de representação, tem nos parlamentares a sua expressão máxima. Devem transformar os anseios de seus representados em ações diretas, na forma de leis ou buscando junto do Executivo obras e atos que beneficiem a sua comunidade. Possuem funções legisladora, administrativa, julgadora e de fiscalização sobre a conduta do Executivo. -

    +
    +
    + +

    Parlamentares

    +
    +

    + O Poder Legislativo, exercido pelo sistema de representação, tem nos parlamentares a sua expressão + máxima. Devem transformar os anseios de seus representados em ações diretas, na forma de leis ou + buscando junto do Executivo obras e atos que beneficiem a sua comunidade. Possuem funções + legisladora, + administrativa, julgadora e de fiscalização sobre a conduta do Executivo. +

    +
    +
    - -
    -
    -
    -
    - Pautas das Sessões -
    -

    Pautas das Sessões

    +
    +
    +
    + Pautas das Sessões +
    +

    Pautas das Sessões

    +
    +
    +
    + +

    Pautas das
    Sessões

    +
    +

    + Utilizadas para se determinar quais matérias serão discutidas e votadas. A responsabilidade pela + elaboração das Pautas, que incluem Expediente e Ordem do Dia, é definida no Regimento Interno que, + em + geral, dá poderes ao Presidente da Casa Legislativa para a sua elaboração. Também, pode ficar a + cargo + de um colégio de líderes dos partidos políticos. +

    +
    +
    -
    -
    - -

    Pautas das
    Sessões

    -
    -

    - Utilizadas para se determinar quais matérias serão discutidas e votadas. A responsabilidade pela elaboração das Pautas, que incluem Expediente e Ordem do Dia, é definida no Regimento Interno que, em geral, dá poderes ao Presidente da Casa Legislativa para a sua elaboração. Também, pode ficar a cargo de um colégio de líderes dos partidos políticos. -

    -
    - -
    -
    -
    -
    - Sessão Plenária -
    -

    Sessão Plenária

    -
    -
    -
    - +
    +
    +
    + Sessão Plenária +

    Sessão Plenária

    -

    - Foro apropriado para a tomada de decisões sobre os projetos de lei e outras matérias legislativas ou administrativas, aprovadas ou rejeitadas em votação pelos parlamentares. É dirigida pela Mesa Diretora de acordo com o Regimento Interno da Casa. As decisões votadas em Plenário são soberanas e prevalecem sobre interesses ou vontades individuais. -

    +
    +
    + +

    Sessão Plenária

    +
    +

    + Foro apropriado para a tomada de decisões sobre os projetos de lei e outras matérias legislativas + ou + administrativas, aprovadas ou rejeitadas em votação pelos parlamentares. É dirigida pela Mesa + Diretora + de acordo com o Regimento Interno da Casa. As decisões votadas em Plenário são soberanas e + prevalecem + sobre interesses ou vontades individuais. +

    +
    +
    - -
    -
    -
    -
    - Matérias Legislativas -
    -

    Matérias Legislativas

    -
    -
    -
    - -

    Matérias
    Legislativas

    -
    -

    - Têm início com o processo de criação de leis e a apresentação de projetos no Poder Legislativo. Na apreciação de matérias, podem haver eventuais conflitos de interpretação ou de entendimento entre o que estabelece o Regimento Interno da Casa e a Lei Orgânica do Município. Nestes casos, prevalece a Lei Orgânica. -

    +
    +
    +
    + Matérias Legislativas +
    +

    Matérias Legislativas

    +
    +
    +
    + +

    Matérias
    Legislativas

    +
    +

    + Têm início com o processo de criação de leis e a apresentação de projetos no Poder Legislativo. Na + apreciação de matérias, podem haver eventuais conflitos de interpretação ou de entendimento entre o + que + estabelece o Regimento Interno da Casa e a Lei Orgânica do Município. Nestes casos, prevalece a Lei + Orgânica. +

    +
    +
    - -
    -
    -
    -
    - Normas Jurídicas -
    -

    Normas Jurídicas

    -
    -
    -
    - +
    +
    +
    + Normas Jurídicas +

    Normas Jurídicas

    -

    - Nos Municípios, referem-se às emendas à Lei Orgânica, às leis complementares, às leis ordinárias, aos decretos legislativos e às resoluções. -

    -
    - -
    - -
    -
    -
    - Relatórios +
    +
    + +

    Normas Jurídicas

    +
    +

    + Nos Municípios, referem-se às emendas à Lei Orgânica, às leis complementares, às leis ordinárias, + aos + decretos legislativos e às resoluções. +

    -

    Relatórios

    +
    -
    -
    - + +
    +
    +
    + Relatórios +

    Relatórios

    -

    - Contém informações estatísticas sobre a produção legislativa dos parlamentares e da Casa, dispostas e agrupadas de diferentes formas de acordo com parâmetros fornecidos. -

    +
    +
    + +

    Relatórios

    +
    +

    + Contém informações estatísticas sobre a produção legislativa dos parlamentares e da Casa, dispostas + e + agrupadas de diferentes formas de acordo com parâmetros fornecidos. +

    +
    +
    - -
    -
    +
    -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/sapl/templates/norma/autorianorma_form.html b/sapl/templates/norma/autorianorma_form.html new file mode 100644 index 000000000..35338c9de --- /dev/null +++ b/sapl/templates/norma/autorianorma_form.html @@ -0,0 +1,47 @@ +{% extends "crud/form.html" %} +{% load i18n %} +{% load crispy_forms_tags %} +{% load common_tags %} + +{% block extra_js %} + +{% endblock %} \ No newline at end of file diff --git a/sapl/templates/norma/layouts.yaml b/sapl/templates/norma/layouts.yaml index 0075317c1..d881ae186 100755 --- a/sapl/templates/norma/layouts.yaml +++ b/sapl/templates/norma/layouts.yaml @@ -70,3 +70,7 @@ NormaRelacionadaDetail: {% trans 'Norma Relacionada' %}: - norma_relacionada - tipo_vinculo + +AutoriaNorma: + {% trans 'Autoria' %}: + - autor primeiro_autor \ No newline at end of file diff --git a/sapl/templates/norma/normajuridica_detail.html b/sapl/templates/norma/normajuridica_detail.html index c2d236a96..0ead31671 100755 --- a/sapl/templates/norma/normajuridica_detail.html +++ b/sapl/templates/norma/normajuridica_detail.html @@ -1,5 +1,5 @@ {% extends "crud/detail.html" %} -{% load i18n common_tags%} +{% load i18n common_tags staticfiles%} {% block detail_content %} {% for fieldset in view.layout_display %} @@ -76,4 +76,30 @@ {% endif %}
    + + {% if object.texto_articulado.exists and object.texto_articulado.first.has_view_permission %} +
    +
    +
    +
    +

    {%trans 'Texto Multivigente' %}

    +
    +
    +
    + {% endif %} {% endblock detail_content %} + +{% block extra_js %} +{% if object.texto_articulado.exists and object.texto_articulado.first.has_view_permission %} + +{% endif %} +{% endblock extra_js %} diff --git a/sapl/templates/norma/subnav.yaml b/sapl/templates/norma/subnav.yaml index 9358787ff..f85433e72 100755 --- a/sapl/templates/norma/subnav.yaml +++ b/sapl/templates/norma/subnav.yaml @@ -7,7 +7,8 @@ check_permission: norma.list_normarelacionada - title: {% trans 'Anexos da Norma' %} url: anexonormajuridica_list - +- title: {% trans 'Autoria' %} + url: autorianorma_list # Opção adicionada para chamar o TextoArticulado da norma. # para integração foram necessárias apenas criar a url norma_ta em urls.py # e a view NormaTaView(IntegracaoTaView) em views.py diff --git a/sapl/templates/protocoloadm/acompanhamento_documento.html b/sapl/templates/protocoloadm/acompanhamento_documento.html new file mode 100644 index 000000000..b6526cc52 --- /dev/null +++ b/sapl/templates/protocoloadm/acompanhamento_documento.html @@ -0,0 +1,21 @@ +{% extends "crud/detail.html" %} +{% load i18n %} +{% load crispy_forms_tags %} +{% block actions %} {% endblock %} +{% block detail_content %} + +

    Acompanhamento de Documento

    +
    +
    +
    Tipo: {{documento.tipo.sigla}} - {{documento.tipo.descricao}}
    +
    Número: {{documento.numero}}
    +
    Ano: {{documento.ano}}
    + +
    +
    +
    Assunto: {{documento.assunto|safe}}
    +
    + +{% if error %}
    {{ error }}
    {% endif %} +{% crispy form %} +{% endblock %} diff --git a/sapl/templates/protocoloadm/documentoadministrativo_filter.html b/sapl/templates/protocoloadm/documentoadministrativo_filter.html index e87962273..6dbd95276 100755 --- a/sapl/templates/protocoloadm/documentoadministrativo_filter.html +++ b/sapl/templates/protocoloadm/documentoadministrativo_filter.html @@ -63,6 +63,9 @@ {% if d.texto_integral %} Texto Integral
    {% endif %} + {% if d.tramitacao %} + Acompanhar Documento + {% endif %} diff --git a/sapl/templates/sessao/blocos_resumo/ocorrencias_da_sessao.html b/sapl/templates/sessao/blocos_resumo/ocorrencias_da_sessao.html index c56a1bb69..c39560e7f 100755 --- a/sapl/templates/sessao/blocos_resumo/ocorrencias_da_sessao.html +++ b/sapl/templates/sessao/blocos_resumo/ocorrencias_da_sessao.html @@ -3,4 +3,4 @@

    {{object.ocorrenciasessao.conteudo|safe}}

    - \ No newline at end of file + diff --git a/sapl/templates/sessao/ocorrencia_sessao.html b/sapl/templates/sessao/ocorrencia_sessao.html index 0daa921d8..472f19ee5 100755 --- a/sapl/templates/sessao/ocorrencia_sessao.html +++ b/sapl/templates/sessao/ocorrencia_sessao.html @@ -29,4 +29,4 @@ initTinymce(null); {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/sapl/templates/sessao/sessaoplenaria_filter.html b/sapl/templates/sessao/sessaoplenaria_filter.html index 40d19bdac..c71f54d21 100755 --- a/sapl/templates/sessao/sessaoplenaria_filter.html +++ b/sapl/templates/sessao/sessaoplenaria_filter.html @@ -34,6 +34,9 @@ Legislatura: {{s.legislatura}}
    Sessão Legislativa: {{s.sessao_legislativa}}
    Tipo: {{s.tipo}}
    + {% if s.upload_ata %} + Ata da Sessão
    + {% endif %} {% endfor %} @@ -54,7 +57,7 @@ var querystring = "{{filter_url|safe}}" // Esse IF garante que já existe uma querystring, ou seja, algo já foi pesquisado - if(querystring.length != 0){ + if(querystring.length != 0){ $('#id_data_inicio__year').on({ change: function(){ window.location.search = jQuery.query.set("data_inicio__year", $('#id_data_inicio__year').val()); @@ -83,4 +86,4 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/sapl/test_general.py b/sapl/test_general.py index da2e17ae6..ef0c3a087 100755 --- a/sapl/test_general.py +++ b/sapl/test_general.py @@ -1,10 +1,12 @@ -import pytest from django.apps import apps from django.db.models import CharField, TextField +from django.db.models.fields import BooleanField from model_mommy import mommy +import pytest from .settings import SAPL_APPS + pytestmark = pytest.mark.django_db sapl_appconfs = [apps.get_app_config(n[5:]) for n in SAPL_APPS] @@ -34,3 +36,19 @@ def test_str_sanity(): msg = '%s.%s.__str__ is broken.' % ( model.__module__, model.__name__) raise AssertionError(msg, exc) + + +def test_booleanfield_configure(): + for app in sapl_appconfs: + for model in app.get_models(): + for field in model._meta.get_fields(): + if not isinstance(field, BooleanField): + continue + assert isinstance(field.default, bool), """ + atributo 'default' não definido em: + Campo: %s + Model: %s + app: %s + """ % (field.name, + model._meta.object_name, + app.name) diff --git a/sapl/utils.py b/sapl/utils.py index 691034789..257489321 100755 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -1,14 +1,11 @@ +from functools import wraps import hashlib -import logging +from operator import itemgetter import os import re -import unicodedata -from functools import wraps -from operator import itemgetter from unicodedata import normalize as unicodedata_normalize +import unicodedata -import django_filters -import magic from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML, Button from django import forms @@ -21,17 +18,15 @@ from django.core.exceptions import ValidationError from django.db.models import Q from django.utils import six, timezone from django.utils.translation import ugettext_lazy as _ +import django_filters from django_filters.filterset import STRICTNESS from easy_thumbnails import source_generators from floppyforms import ClearableFileInput -from reversion.admin import VersionAdmin +import magic from reversion_compare.admin import CompareVersionAdmin from unipath.path import Path from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row -from sapl.settings import BASE_DIR - -sapl_logger = logging.getLogger(BASE_DIR.name) def pil_image(source, exif_orientation=False, **options): @@ -765,5 +760,6 @@ def RemoveTag(texto): return textoSaida + def remover_acentos(string): - return unicodedata.normalize('NFKD', string).encode('ASCII', 'ignore').decode() + return unicodedata.normalize('NFKD', string).encode('ASCII', 'ignore').decode() diff --git a/setup.py b/setup.py index e2ae3d768..9f5b99a69 100755 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ install_requires = [ ] setup( name='interlegis-sapl', - version='3.1.118', + version='3.1.126', packages=find_packages(), include_package_data=True, license='GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007',