diff --git a/config/env-sample b/config/env-sample index bde081a92..aeb8c38c4 100644 --- a/config/env-sample +++ b/config/env-sample @@ -5,4 +5,5 @@ EMAIL_USE_TLS = True EMAIL_PORT = 587 EMAIL_HOST = '' EMAIL_HOST_USER = '' +EMAIL_SEND_USER = '' EMAIL_HOST_PASSWORD = '' diff --git a/config/env_dockerfile b/config/env_dockerfile index c83fc88f1..134beb274 100644 --- a/config/env_dockerfile +++ b/config/env_dockerfile @@ -5,4 +5,5 @@ EMAIL_USE_TLS = True EMAIL_PORT = 587 EMAIL_HOST = '' EMAIL_HOST_USER = '' +EMAIL_SEND_USER = '' EMAIL_HOST_PASSWORD = '' diff --git a/docker-compose.yml b/docker-compose.yml index b379cf46c..5b0ee2315 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ sapldb: ports: - "5432:5432" sapl: - image: interlegis/sapl:3.1.140 + image: interlegis/sapl:3.1.142 restart: always environment: ADMIN_PASSWORD: interlegis @@ -21,6 +21,7 @@ sapl: EMAIL_USE_TLS: 'False' EMAIL_HOST: smtp.dominio.net EMAIL_HOST_USER: usuariosmtp + EMAIL_SEND_USER: usuariosmtp EMAIL_HOST_PASSWORD: senhasmtp TZ: America/Sao_Paulo volumes: diff --git a/docs/instalacao31.rst b/docs/instalacao31.rst index e904f8d6c..6e9146ee4 100644 --- a/docs/instalacao31.rst +++ b/docs/instalacao31.rst @@ -147,6 +147,7 @@ Criação da `SECRET_KEY =1.10,<1.11 -django-bootstrap3==7.0.1 +django>=1.11,<2.0 +django-bootstrap3==11.0.0 +django-haystack==2.8.1 +django-filter==2.0.0 +djangorestframework==3.9.0 +dj-database-url==0.5.0 django-bower==5.2.0 django-braces==1.9.0 -django-compressor==2.0 -django-crispy-forms==1.6.1 -django-extensions==1.9.8 -django-extra-views==0.11.0 -django-filter==1.0.0 -django-floppyforms==1.6.2 -django-model-utils==3.1.1 -django-sass-processor==0.5.8 -djangorestframework==3.4.0 -easy-thumbnails==2.5 +django-crispy-forms==1.7.2 +django-floppyforms==1.7.0 +django-extra-views==0.12.0 +django-model-utils==3.1.2 +django-sass-processor==0.7.2 +django-reversion==3.0.2 +django-reversion-compare==0.8.6 +django-speedinfo==1.4.0 +django-extensions==2.1.4 django-image-cropping==1.2 -libsass==0.11.1 -psycopg2-binary==2.7.4 -python-decouple==3.0 -pytz==2016.4 +easy-thumbnails==2.5 +libsass==0.17.0 +python-decouple==3.1 +psycopg2-binary==2.7.6.1 pyyaml==4.2b1 -rtyaml==0.0.3 -textract==1.5.0 +pytz==2018.9 +rtyaml==0.0.5 +python-magic==0.4.15 unipath==1.1 +WeasyPrint==44 +gunicorn==19.9.0 + +textract==1.5.0 pysolr==3.6.0 -python-magic==0.4.12 -gunicorn==19.6.0 -django-reversion==2.0.8 -WeasyPrint==0.42 whoosh==2.7.4 -django-speedinfo==1.3.5 -django-reversion-compare==0.8.4 git+git://github.com/interlegis/trml2pdf.git git+git://github.com/jasperlittle/django-rest-framework-docs -git+git://github.com/rubgombar1/django-admin-bootstrapped.git \ No newline at end of file +git+git://github.com/rubgombar1/django-admin-bootstrapped.git + +#django-compressor==2.2 \ No newline at end of file diff --git a/sapl/.env_test b/sapl/.env_test index 9416d402d..616909348 100644 --- a/sapl/.env_test +++ b/sapl/.env_test @@ -5,4 +5,5 @@ EMAIL_USE_TLS = True EMAIL_PORT = 587 EMAIL_HOST = '' EMAIL_HOST_USER = '' +EMAIL_SEND_USER = '' EMAIL_HOST_PASSWORD = '' diff --git a/sapl/api/forms.py b/sapl/api/forms.py index fecfbf598..0c8a1889f 100644 --- a/sapl/api/forms.py +++ b/sapl/api/forms.py @@ -6,8 +6,8 @@ from django.forms.widgets import MultiWidget, TextInput from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from django_filters.filters import CharFilter, ModelChoiceFilter, DateFilter +from django_filters.rest_framework.filterset import FilterSet from rest_framework import serializers -from rest_framework.filters import FilterSet from sapl.base.models import Autor, TipoAutor from sapl.parlamentares.models import Legislatura diff --git a/sapl/api/views.py b/sapl/api/views.py index b8cafc1dd..336bb23d7 100644 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -1,9 +1,10 @@ import logging + from django.contrib.contenttypes.models import ContentType from django.db.models import Q from django.http import Http404 from django.utils.translation import ugettext_lazy as _ -from rest_framework.filters import DjangoFilterBackend +from django_filters.rest_framework.backends import DjangoFilterBackend from rest_framework.generics import ListAPIView from rest_framework.mixins import ListModelMixin, RetrieveModelMixin from rest_framework.permissions import (AllowAny, IsAuthenticated, @@ -175,7 +176,7 @@ class AutoresProvaveisListView(ListAPIView): serializer_class = ChoiceSerializer def get_queryset(self): - + params = {'content_type__isnull': False} username = self.request.user.username tipo = '' diff --git a/sapl/base/email_utils.py b/sapl/base/email_utils.py index 024045edd..b41c68402 100644 --- a/sapl/base/email_utils.py +++ b/sapl/base/email_utils.py @@ -18,7 +18,7 @@ def load_email_templates(templates, context={}): emails = [] for t in templates: tpl = loader.get_template(t) - email = tpl.render(Context(context)) + email = tpl.render(context) if t.endswith(".html"): email = email.replace('\n', '').replace('\r', '') emails.append(email) diff --git a/sapl/base/templatetags/common_tags.py b/sapl/base/templatetags/common_tags.py index 89f781558..884a6968f 100644 --- a/sapl/base/templatetags/common_tags.py +++ b/sapl/base/templatetags/common_tags.py @@ -1,8 +1,4 @@ -import logging - -from compressor.utils import get_class from django import template -from django.conf import settings from django.template.defaultfilters import stringfilter from sapl.base.models import AppConfig @@ -15,6 +11,15 @@ from sapl.utils import filiacao_data, SEPARADOR_HASH_PROPOSICAO register = template.Library() +def get_class(class_string): + if not hasattr(class_string, '__bases__'): + class_string = str(class_string) + dot = class_string.rindex('.') + mod_name, class_name = class_string[:dot], class_string[dot + 1:] + if class_name: + return getattr(__import__(mod_name, {}, {}, [str('')]), class_name) + + @register.simple_tag def define(arg): return arg @@ -228,7 +233,7 @@ def file_extension(value): def cronometro_to_seconds(value): if not AppConfig.attr('cronometro_' + value): return 0 - + return AppConfig.attr('cronometro_' + value).seconds diff --git a/sapl/crud/base.py b/sapl/crud/base.py index e720b36cd..faa0e1287 100644 --- a/sapl/crud/base.py +++ b/sapl/crud/base.py @@ -1,6 +1,6 @@ import logging + from braces.views import FormMessagesMixin -from compressor.utils.decorators import cached_property from crispy_forms.bootstrap import FieldWithButtons, StrictButton from crispy_forms.helper import FormHelper from crispy_forms.layout import Field, Layout @@ -16,6 +16,7 @@ from django.http.response import Http404 from django.shortcuts import redirect from django.utils.decorators import classonlymethod from django.utils.encoding import force_text +from django.utils.functional import cached_property from django.utils.translation import string_concat from django.utils.translation import ugettext_lazy as _ from django.views.generic import (CreateView, DeleteView, DetailView, ListView, @@ -29,6 +30,7 @@ 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 + ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \ 'list', 'create', 'detail', 'update', 'delete' @@ -558,7 +560,8 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView): fm = model._meta.get_field(fo) except Exception as e: username = self.request.user.username - self.logger.error("user=" + username + ". " + str(e)) + self.logger.error( + "user=" + username + ". " + str(e)) pass if fm and hasattr(fm, 'related_model')\ @@ -824,7 +827,7 @@ class CrudUpdateView(PermissionRequiredContainerCrudMixin, logger = logging.getLogger(__name__) def form_valid(self, form): - + self.object = form.instance try: self.object.modifier = self.request.user @@ -882,12 +885,12 @@ class CrudDeleteView(PermissionRequiredContainerCrudMixin, error_msg2 += '{} - {}, '.format( i._meta.verbose_name, i ) - error_msg2 = error_msg2[:len(error_msg2)-2] + '.' + error_msg2 = error_msg2[:len(error_msg2) - 2] + '.' error_msg += '' - + username = request.user.username self.logger.error("user=" + username + ". Registro não pode ser removido, pois " - "é referenciado por outros registros: " + error_msg2) + "é referenciado por outros registros: " + error_msg2) messages.add_message(request, messages.ERROR, error_msg) diff --git a/sapl/env-backup b/sapl/env-backup index 5824d3eec..214469633 100644 --- a/sapl/env-backup +++ b/sapl/env-backup @@ -5,4 +5,5 @@ EMAIL_USE_TLS = True EMAIL_PORT = 587 EMAIL_HOST = '' EMAIL_HOST_USER = '' +EMAIL_SEND_USER = '' EMAIL_HOST_PASSWORD = '' diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index a043d024d..c89d82336 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -1298,41 +1298,14 @@ class TipoProposicaoForm(ModelForm): class TipoProposicaoSelect(Select): - def render_tipo_option(self, selected_choices, option_value, option_label, - data_has_perfil=False): - if option_value is None: - option_value = '' - option_value = force_text(option_value) - if option_value in selected_choices: - selected_html = mark_safe(' selected="selected"') - if not self.allow_multiple_selected: - # Only allow for a single selection. - selected_choices.remove(option_value) - else: - selected_html = '' - return format_html( - '', - option_value, - selected_html, - str(data_has_perfil), - force_text(option_label)) - - def render_options(self, selected_choices): - # Normalize to strings. - selected_choices = set(force_text(v) for v in selected_choices) - output = [] - output.append( - self.render_tipo_option( - selected_choices, '', self.choices.field.empty_label)) - - for tipo in self.choices.queryset.all(): - output.append( - self.render_tipo_option( - selected_choices, - str(tipo.pk), - str(tipo), - data_has_perfil=tipo.perfis.exists())) - return '\n'.join(output) + def create_option(self, name, value, label, selected, index, subindex=None, attrs=None): + option = super().create_option(name, value, label, selected, + index, subindex=subindex, attrs=attrs) + if value: + tipo = TipoProposicao.objects.get(id=value) + option['attrs']['data-has-perfil'] = str(tipo.perfis.exists()) + + return option class ProposicaoForm(forms.ModelForm): @@ -2046,15 +2019,10 @@ class ConfirmarProposicaoForm(ProposicaoForm): self.instance.results['messages']['success'].append(_( 'Protocolo realizado com sucesso')) - # FIXME qdo protocoloadm estiver homologado, verifique a necessidade - # de redirecionamento para o protocolo. - # complete e libere código abaixo para tal. - - """ self.instance.results['url'] = reverse( - 'sapl.protocoloadm:...', + 'sapl.protocoloadm:protocolo_mostrar', kwargs={'pk': protocolo.pk}) - """ + conteudo_gerado.numero_protocolo = protocolo.numero conteudo_gerado.save() diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 07911f09b..c3036d52e 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -26,7 +26,7 @@ import weasyprint import sapl from sapl.base.email_utils import do_envia_email_confirmacao -from sapl.base.models import Autor, CasaLegislativa +from sapl.base.models import Autor, CasaLegislativa, AppConfig as BaseAppConfig from sapl.base.signals import tramitacao_signal from sapl.comissoes.models import Comissao, Participacao from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_RESTRICT, @@ -783,16 +783,23 @@ class ProposicaoCrud(Crud): msg_error = _('Proposição não possui nenhum tipo de ' 'Texto associado.') else: - p.data_devolucao = None - p.data_envio = timezone.now() - p.save() - if p.texto_articulado.exists(): ta = p.texto_articulado.first() ta.privacidade = STATUS_TA_IMMUTABLE_RESTRICT ta.editing_locked = True ta.save() + receber_recibo = BaseAppConfig.attr( + 'receber_recibo_proposicao') + + if not receber_recibo: + ta = p.texto_articulado.first() + p.hash_code = 'P' + ta.hash() + SEPARADOR_HASH_PROPOSICAO + str(p.pk) + + p.data_devolucao = None + p.data_envio = timezone.now() + p.save() + messages.success(request, _( 'Proposição enviada com sucesso.')) try: @@ -2168,7 +2175,7 @@ class ImpressosView(PermissionRequiredMixin, TemplateView): def gerar_pdf_impressos(request, context, template_name): template = loader.get_template(template_name) - html = template.render(RequestContext(request, context)) + html = template.render(context, request) pdf = weasyprint.HTML(string=html, base_url=request.build_absolute_uri() ).write_pdf() diff --git a/sapl/norma/views.py b/sapl/norma/views.py index 7abee4a1c..1d0f9b28e 100644 --- a/sapl/norma/views.py +++ b/sapl/norma/views.py @@ -1,7 +1,5 @@ import logging import re -import sapl -import weasyprint from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.exceptions import ObjectDoesNotExist @@ -14,7 +12,10 @@ from django.views.generic import TemplateView, UpdateView from django.views.generic.base import RedirectView from django.views.generic.edit import FormView from django_filters.views import FilterView +import weasyprint + from sapl import settings +import sapl from sapl.base.models import AppConfig from sapl.compilacao.views import IntegracaoTaView from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux, @@ -107,7 +108,8 @@ class NormaPesquisaView(FilterView): context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' context['show_results'] = show_results_filter_set(qr) - context['USE_SOLR'] = settings.USE_SOLR if hasattr(settings, 'USE_SOLR') else False + context['USE_SOLR'] = settings.USE_SOLR if hasattr( + settings, 'USE_SOLR') else False return context @@ -199,7 +201,6 @@ class NormaCrud(Crud): ano=timezone.now().year, horario_acesso=timezone.now()) return super().get(request, *args, **kwargs) - class DeleteView(Crud.DeleteView): @@ -219,12 +220,14 @@ class NormaCrud(Crud): username = self.request.user.username try: - self.logger.debug('user=' + username + '. Tentando obter objeto de modelo da esfera da federação.') + self.logger.debug( + 'user=' + username + '. Tentando obter objeto de modelo da esfera da federação.') esfera = sapl.base.models.AppConfig.objects.last( ).esfera_federacao self.initial['esfera_federacao'] = esfera except: - self.logger.error('user=' + username + '. Erro ao obter objeto de modelo da esfera da federação.') + self.logger.error( + 'user=' + username + '. Erro ao obter objeto de modelo da esfera da federação.') pass self.initial['complemento'] = False return self.initial @@ -234,7 +237,7 @@ class NormaCrud(Crud): class ListView(Crud.ListView, RedirectView): def get_redirect_url(self, *args, **kwargs): - namespace = self.model._meta.app_config.name + namespace = self.model._meta.app_config.name return reverse('%s:%s' % (namespace, 'norma_pesquisa')) def get(self, request, *args, **kwargs): @@ -333,6 +336,7 @@ class AutoriaNormaCrud(MasterDetailCrud): }) return initial + class ImpressosView(PermissionRequiredMixin, TemplateView): template_name = 'materia/impressos/impressos.html' permission_required = ('materia.can_access_impressos', ) @@ -340,7 +344,7 @@ class ImpressosView(PermissionRequiredMixin, TemplateView): def gerar_pdf_impressos(request, context, template_name): template = loader.get_template(template_name) - html = template.render(RequestContext(request, context)) + html = template.render(context, request) pdf = weasyprint.HTML(string=html, base_url=request.build_absolute_uri() ).write_pdf() diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index c47d36566..aa636df79 100644 --- a/sapl/parlamentares/views.py +++ b/sapl/parlamentares/views.py @@ -1,6 +1,6 @@ +from datetime import datetime import json import logging -from datetime import datetime from django.contrib import messages from django.contrib.contenttypes.models import ContentType @@ -35,6 +35,7 @@ from .models import (CargoMesa, Coligacao, ComposicaoColigacao, ComposicaoMesa, NivelInstrucao, Parlamentar, Partido, SessaoLegislativa, SituacaoMilitar, TipoAfastamento, TipoDependente, Votante) + CargoMesaCrud = CrudAux.build(CargoMesa, 'cargo_mesa') PartidoCrud = CrudAux.build(Partido, 'partidos') TipoDependenteCrud = CrudAux.build(TipoDependente, 'tipo_dependente') @@ -45,6 +46,7 @@ TipoMilitarCrud = CrudAux.build(SituacaoMilitar, 'tipo_situa_militar') DependenteCrud = MasterDetailCrud.build( Dependente, 'parlamentar', 'dependente') + class SessaoLegislativaCrud(CrudAux): model = SessaoLegislativa @@ -54,6 +56,7 @@ class SessaoLegislativaCrud(CrudAux): class UpdateView(CrudAux.UpdateView): form_class = SessaoLegislativaForm + class VotanteView(MasterDetailCrud): model = Votante parent_field = 'parlamentar' @@ -89,6 +92,7 @@ class FrenteList(MasterDetailCrud): class BaseMixin(Crud.PublicMixin, MasterDetailCrud.BaseMixin): list_field_names = ['nome', 'data_criacao', 'data_extincao'] + @classmethod def url_name(cls, suffix): return '%s_parlamentar_%s' % (cls.model._meta.model_name, suffix) @@ -276,13 +280,16 @@ def parlamentares_frente_selected(request): logger = logging.getLogger(__name__) username = request.user.username try: - logger.info("user=" + username + ". Tentando objet objeto Frente com id={}.".format(request.GET['frente_id'])) + logger.info("user=" + username + + ". Tentando objet objeto Frente com id={}.".format(request.GET['frente_id'])) frente = Frente.objects.get(id=int(request.GET['frente_id'])) except ObjectDoesNotExist: - logger.error("user=" + username + ". Frente buscada (id={}) não existe. Retornada lista vazia.".format(request.GET['frente_id'])) + logger.error("user=" + username + + ". Frente buscada (id={}) não existe. Retornada lista vazia.".format(request.GET['frente_id'])) lista_parlamentar_id = [] else: - logger.info("user=" + username + ". Frente (id={}) encontrada com sucesso.".format(request.GET['frente_id'])) + logger.info("user=" + username + + ". Frente (id={}) encontrada com sucesso.".format(request.GET['frente_id'])) lista_parlamentar_id = frente.parlamentares.all().values_list( 'id', flat=True) return JsonResponse({'id_list': list(lista_parlamentar_id)}) @@ -292,8 +299,14 @@ class FrenteCrud(Crud): model = Frente help_topic = 'tipo_situa_militar' public = [RP_DETAIL, RP_LIST] - list_field_names = ['nome', 'data_criacao', 'data_extincao', 'parlamentares'] + list_field_names = ['nome', 'data_criacao', + 'data_extincao', 'parlamentares'] + class BaseMixin(Crud.BaseMixin): + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['subnav_template_name'] = '' + return context class CreateView(Crud.CreateView): form_class = FrenteForm @@ -305,7 +318,6 @@ class FrenteCrud(Crud): form_class = FrenteForm - class MandatoCrud(MasterDetailCrud): model = Mandato parent_field = 'parlamentar' @@ -360,7 +372,7 @@ class ComposicaoColigacaoCrud(MasterDetailCrud): class LegislaturaCrud(CrudAux): - + model = Legislatura help_topic = 'legislatura' @@ -371,11 +383,13 @@ class LegislaturaCrud(CrudAux): def get_initial(self): username = self.request.user.username try: - self.logger.error("user=" + username + ". Tentando obter última Legislatura.") + self.logger.error("user=" + username + + ". Tentando obter última Legislatura.") ultima_legislatura = Legislatura.objects.latest('numero') numero = ultima_legislatura.numero + 1 except Legislatura.DoesNotExist: - self.logger.error("user=" + username + ". Legislatura não encontrada. Número definido como 1.") + self.logger.error( + "user=" + username + ". Legislatura não encontrada. Número definido como 1.") numero = 1 return {'numero': numero} @@ -476,10 +490,12 @@ class ParlamentarCrud(Crud): def take_legislatura_id(self): username = self.request.user.username try: - self.logger.debug("user=" + username + ". Tentando obter id da legislatura.") + self.logger.debug("user=" + username + + ". Tentando obter id da legislatura.") return int(self.request.GET['pk']) except: - self.logger.error("user=" + username + ". Legislatura não possui ID. Buscando em todas as entradas.") + self.logger.error( + "user=" + username + ". Legislatura não possui ID. Buscando em todas as entradas.") legislaturas = Legislatura.objects.all() for l in legislaturas: if l.atual(): @@ -501,14 +517,17 @@ class ParlamentarCrud(Crud): mandato_titular=F('mandato__titular')).distinct() else: try: - self.logger.debug("user=" + username + ". Tentando obter o mais recente registro do objeto Legislatura.") + self.logger.debug( + "user=" + username + ". Tentando obter o mais recente registro do objeto Legislatura.") l = Legislatura.objects.all().order_by( '-data_inicio').first() except ObjectDoesNotExist: - self.logger.error("user=" + username + ". Objeto não encontrado. Retornando todos os registros.") + self.logger.error( + "user=" + username + ". Objeto não encontrado. Retornando todos os registros.") return Legislatura.objects.all() else: - self.logger.info("user=" + username + ". Objeto encontrado com sucesso.") + self.logger.info("user=" + username + + ". Objeto encontrado com sucesso.") if l is None: return Legislatura.objects.all() return queryset.filter(mandato__legislatura_id=l).annotate( @@ -547,8 +566,8 @@ class ParlamentarCrud(Crud): # ou igual a data de fim da legislatura try: self.logger.debug("user=" + username + ". Tentando obter filiação do parlamentar com (data<={} e data_desfiliacao>={}) " - "ou (data<={} e data_desfiliacao=Null))." - .format(legislatura.data_fim, legislatura.data_fim, legislatura.data_fim)) + "ou (data<={} e data_desfiliacao=Null))." + .format(legislatura.data_fim, legislatura.data_fim, legislatura.data_fim)) filiacao = parlamentar.filiacao_set.get(Q( data__lte=legislatura.data_fim, data_desfiliacao__gte=legislatura.data_fim) | Q( @@ -574,7 +593,8 @@ class ParlamentarCrud(Crud): # Caso encontre UMA filiação nessas condições else: - self.logger.debug("user=" + username + ". Filiação encontrada com sucesso.") + self.logger.debug("user=" + username + + ". Filiação encontrada com sucesso.") row[1] = (filiacao.partido.sigla, None, None) return context @@ -606,13 +626,16 @@ class ParlamentarMateriasView(FormView): parlamentar_pk = kwargs['pk'] username = request.user.username try: - self.logger.debug("user=" + username + ". Tentando obter Autor (object_id={}).".format(parlamentar_pk)) + self.logger.debug( + "user=" + username + ". Tentando obter Autor (object_id={}).".format(parlamentar_pk)) autor = Autor.objects.get( content_type=ContentType.objects.get_for_model(Parlamentar), object_id=parlamentar_pk) except ObjectDoesNotExist: - mensagem = _('Este Parlamentar (pk={}) não é Autor de matéria.'.format(parlamentar_pk)) - self.logger.error("user=" + username + ". Este Parlamentar (pk={}) não é Autor de matéria.".format(parlamentar_pk)) + mensagem = _( + 'Este Parlamentar (pk={}) não é Autor de matéria.'.format(parlamentar_pk)) + self.logger.error( + "user=" + username + ". Este Parlamentar (pk={}) não é Autor de matéria.".format(parlamentar_pk)) messages.add_message(request, messages.ERROR, mensagem) return HttpResponseRedirect( reverse( @@ -700,7 +723,8 @@ class MesaDiretoraView(FormView): sessao_atual = sessoes.filter(data_inicio__year__lte=year).exclude( data_inicio__gt=timezone.now()).order_by('-data_inicio').first() - mesa = sessao_atual.composicaomesa_set.all().order_by('cargo_id') if sessao_atual else [] + mesa = sessao_atual.composicaomesa_set.all().order_by( + 'cargo_id') if sessao_atual else [] cargos_ocupados = [m.cargo for m in mesa] cargos = CargoMesa.objects.all() @@ -756,7 +780,8 @@ def altera_field_mesa(request): else: year = timezone.now().year try: - logger.debug("user=" + username + ". Tentando obter id de sessoes com data_inicio.ano={}.".format(year)) + logger.debug( + "user=" + username + ". Tentando obter id de sessoes com data_inicio.ano={}.".format(year)) sessao_selecionada = sessoes.get(data_inicio__year=year).id except ObjectDoesNotExist: logger.error("user=" + username + ". Id de sessoes com data_inicio.ano={} não encontrado. " @@ -809,24 +834,29 @@ def insere_parlamentar_composicao(request): composicao = ComposicaoMesa() try: - logger.debug("user=" + username + ". Tentando obter SessaoLegislativa com id={}.".format(request.POST['sessao'])) + logger.debug( + "user=" + username + ". Tentando obter SessaoLegislativa com id={}.".format(request.POST['sessao'])) composicao.sessao_legislativa = SessaoLegislativa.objects.get( id=int(request.POST['sessao'])) except MultiValueDictKeyError: - logger.error("user=" + username + ". 'MultiValueDictKeyError', nenhuma sessão foi inserida!") + logger.error( + "user=" + username + ". 'MultiValueDictKeyError', nenhuma sessão foi inserida!") return JsonResponse({'msg': ('Nenhuma sessão foi inserida!', 0)}) try: - logger.debug("user=" + username + ". Tentando obter Parlamentar com id={}.".format(request.POST['parlamentar'])) + logger.debug( + "user=" + username + ". Tentando obter Parlamentar com id={}.".format(request.POST['parlamentar'])) composicao.parlamentar = Parlamentar.objects.get( id=int(request.POST['parlamentar'])) except MultiValueDictKeyError: - logger.error("user=" + username + ". 'MultiValueDictKeyError', nenhum parlamentar foi inserido!") + logger.error( + "user=" + username + ". 'MultiValueDictKeyError', nenhum parlamentar foi inserido!") return JsonResponse({ 'msg': ('Nenhum parlamentar foi inserido!', 0)}) try: - logger.info("user=" + username + ". Tentando obter CargoMesa com id={}.".format(request.POST['cargo'])) + logger.info("user=" + username + + ". Tentando obter CargoMesa com id={}.".format(request.POST['cargo'])) composicao.cargo = CargoMesa.objects.get( id=int(request.POST['cargo'])) parlamentar_ja_inserido = ComposicaoMesa.objects.filter( @@ -839,14 +869,16 @@ def insere_parlamentar_composicao(request): composicao.save() except MultiValueDictKeyError: - logger.error("user=" + username + ". 'MultiValueDictKeyError', nenhum cargo foi inserido!") + logger.error("user=" + username + + ". 'MultiValueDictKeyError', nenhum cargo foi inserido!") return JsonResponse({'msg': ('Nenhum cargo foi inserido!', 0)}) logger.info("user=" + username + ". Parlamentar inserido com sucesso!") return JsonResponse({'msg': ('Parlamentar inserido com sucesso!', 1)}) else: - logger.error("user=" + username + " não tem permissão para esta operação!") + logger.error("user=" + username + + " não tem permissão para esta operação!") return JsonResponse( {'msg': ('Você não tem permissão para esta operação!', 0)}) @@ -864,7 +896,8 @@ def remove_parlamentar_composicao(request): if 'composicao_mesa' in request.POST: try: - logger.debug("user=" + username + ". Tentando obter ComposicaoMesa com id={}.".format(request.POST['composicao_mesa'])) + logger.debug("user=" + username + ". Tentando obter ComposicaoMesa com id={}.".format( + request.POST['composicao_mesa'])) composicao = ComposicaoMesa.objects.get( id=request.POST['composicao_mesa']) except ObjectDoesNotExist: @@ -876,12 +909,14 @@ def remove_parlamentar_composicao(request): composicao.delete() - logger.info("user=" + username + ". ComposicaoMesa com id={} excluido com sucesso!".format(request.POST['composicao_mesa'])) + logger.info("user=" + username + ". ComposicaoMesa com id={} excluido com sucesso!".format( + request.POST['composicao_mesa'])) return JsonResponse( {'msg': ( 'Parlamentar excluido com sucesso!', 1)}) else: - logger.info("user=" + username + ". Nenhum parlamentar escolhido para ser excluído.") + logger.info("user=" + username + + ". Nenhum parlamentar escolhido para ser excluído.") return JsonResponse( {'msg': ( 'Selecione algum parlamentar para ser excluido!', 0)}) @@ -900,8 +935,8 @@ def partido_parlamentar_sessao_legislativa(sessao, parlamentar): logger = logging.getLogger(__name__) try: logger.debug("Tentando obter filiação do parlamentar com (data<={} e data_desfiliacao>={}) " - "ou (data<={} e data_desfiliacao=Null))." - .format(sessao.data_fim, sessao.data_fim, sessao.data_fim)) + "ou (data<={} e data_desfiliacao=Null))." + .format(sessao.data_fim, sessao.data_fim, sessao.data_fim)) logger.info("Tentando obter filiação correspondente.") filiacao = parlamentar.filiacao_set.get(Q( @@ -957,7 +992,8 @@ def altera_field_mesa_public_view(request): else: try: year = timezone.now().year - logger.info("user=" + username + ". Tentando obter sessões com data_inicio.ano = {}.".format(year)) + logger.info("user=" + username + + ". Tentando obter sessões com data_inicio.ano = {}.".format(year)) sessao_selecionada = sessoes.get(data_inicio__year=year).id except ObjectDoesNotExist: logger.error("user=" + username + ". Sessões não encontradas com com data_inicio.ano = {}. " diff --git a/sapl/protocoloadm/forms.py b/sapl/protocoloadm/forms.py index d0c812958..8f476d443 100644 --- a/sapl/protocoloadm/forms.py +++ b/sapl/protocoloadm/forms.py @@ -1,9 +1,9 @@ import logging -from crispy_forms.bootstrap import InlineRadios +from crispy_forms.bootstrap import InlineRadios, Alert from crispy_forms.helper import FormHelper -from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout +from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout, Div from django import forms from django.core.exceptions import (MultipleObjectsReturned, ObjectDoesNotExist, ValidationError) @@ -18,11 +18,12 @@ from sapl.base.models import Autor, TipoAutor from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row from sapl.materia.models import (MateriaLegislativa, TipoMateriaLegislativa, UnidadeTramitacao) +from sapl.protocoloadm.models import Protocolo from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, AnoNumeroOrderingFilter, RangeWidgetOverride, autor_label, autor_modal, choice_anos_com_protocolo, choice_force_optional, choice_anos_com_documentoadministrativo, - FilterOverridesMetaMixin) + FilterOverridesMetaMixin, choice_anos_com_materias) from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo, DocumentoAdministrativo, @@ -344,6 +345,12 @@ class ProtocoloDocumentForm(ModelForm): numero = forms.IntegerField( required=False, label=_('Número de Protocolo (opcional)')) + data_hora_manual = forms.ChoiceField( + label=_('Informar data e hora manualmente?'), + widget=forms.RadioSelect(), + choices=YES_NO_CHOICES, + initial=False) + class Meta: model = Protocolo fields = ['tipo_protocolo', @@ -352,7 +359,9 @@ class ProtocoloDocumentForm(ModelForm): 'assunto', 'interessado', 'observacao', - 'numero' + 'numero', + 'data', + 'hora', ] def __init__(self, *args, **kwargs): @@ -360,30 +369,56 @@ class ProtocoloDocumentForm(ModelForm): row1 = to_row( [(InlineRadios('tipo_protocolo'), 12)]) row2 = to_row( - [('tipo_documento', 6), - ('numero_paginas', 6)]) - row3 = to_row( - [('assunto', 12)]) + [('tipo_documento', 5), + ('numero_paginas', 2), + (Div(), 1), + (InlineRadios('data_hora_manual'), 4), + ]) + row3 = to_row([ + (Div(), 2), + (Alert( + """ + Usuário: {} - {}
+ IP: {} - {}
+ + """.format( + kwargs['initial']['user_data_hora_manual'], + Protocolo._meta.get_field( + 'user_data_hora_manual').help_text, + kwargs['initial']['ip_data_hora_manual'], + Protocolo._meta.get_field( + 'ip_data_hora_manual').help_text, + + ), + dismiss=False, + css_class='alert-info'), 6), + ('data', 2), + ('hora', 2), + ]) row4 = to_row( - [('interessado', 12)]) + [('assunto', 12)]) row5 = to_row( - [('observacao', 12)]) + [('interessado', 12)]) row6 = to_row( + [('observacao', 12)]) + row7 = to_row( [('numero', 12)]) self.helper = FormHelper() self.helper.layout = Layout( Fieldset(_('Identificação de Documento'), row1, - row2, + row2), + Fieldset(_('Protocolo com data e hora informados manualmente'), row3, - row4, - row5, - HTML(" "), - ), + css_id='protocolo_data_hora_manual', + css_class='hidden'), + row4, + row5, + HTML(" "), Fieldset(_('Número do Protocolo (Apenas se quiser que a numeração comece ' 'a partir do número a ser informado)'), - row6, + row7, HTML(" "), form_actions(label=_('Protocolar Documento')) ) @@ -419,10 +454,11 @@ class ProtocoloMateriaForm(ModelForm): ano_materia = forms.CharField( label=_('Ano matéria'), required=False) - vincular_materia = forms.ChoiceField(label=_('Vincular a matéria existente?'), - widget=forms.RadioSelect(), - choices=YES_NO_CHOICES, - initial=False) + vincular_materia = forms.ChoiceField( + label=_('Vincular a matéria existente?'), + widget=forms.RadioSelect(), + choices=YES_NO_CHOICES, + initial=False) numero_paginas = forms.CharField(label=_('Núm. Páginas'), required=True) @@ -435,6 +471,12 @@ class ProtocoloMateriaForm(ModelForm): numero = forms.IntegerField( required=False, label=_('Número de Protocolo (opcional)')) + data_hora_manual = forms.ChoiceField( + label=_('Informar data e hora manualmente?'), + widget=forms.RadioSelect(), + choices=YES_NO_CHOICES, + initial=False) + class Meta: model = Protocolo fields = ['tipo_materia', @@ -446,7 +488,9 @@ class ProtocoloMateriaForm(ModelForm): 'numero_materia', 'ano_materia', 'vincular_materia', - 'numero' + 'numero', + 'data', + 'hora', ] def clean_autor(self): @@ -506,28 +550,55 @@ class ProtocoloMateriaForm(ModelForm): ('tipo_autor', 3), ('autor', 3)]) row2 = to_row( - [(InlineRadios('vincular_materia'), 4), - ('numero_materia', 4), - ('ano_materia', 4), ]) - row3 = to_row( - [('assunto_ementa', 12)]) + [(InlineRadios('vincular_materia'), 3), + ('numero_materia', 2), + ('ano_materia', 2), + (Div(), 1), + (InlineRadios('data_hora_manual'), 4), + ]) + row3 = to_row([ + (Div(), 2), + (Alert( + """ + Usuário: {} - {}
+ IP: {} - {}
+ + """.format( + kwargs['initial']['user_data_hora_manual'], + Protocolo._meta.get_field( + 'user_data_hora_manual').help_text, + kwargs['initial']['ip_data_hora_manual'], + Protocolo._meta.get_field( + 'ip_data_hora_manual').help_text, + + ), + dismiss=False, + css_class='alert-info'), 6), + ('data', 2), + ('hora', 2), + ]) row4 = to_row( - [('observacao', 12)]) + [('assunto_ementa', 12)]) row5 = to_row( + [('observacao', 12)]) + row6 = to_row( [('numero', 12)]) self.helper = FormHelper() self.helper.layout = Layout( Fieldset(_('Identificação da Matéria'), row1, - row2, + row2), + Fieldset(_('Protocolo com data e hora informados manualmente'), row3, - row4, - HTML(" "), - ), + css_id='protocolo_data_hora_manual', + css_class='hidden'), + row4, + row5, + HTML(" "), Fieldset(_('Número do Protocolo (Apenas se quiser que a numeração comece' ' a partir do número a ser informado)'), - row5, + row6, HTML(" "), form_actions(label=_('Protocolar Matéria'))) ) @@ -855,15 +926,15 @@ class DesvincularDocumentoForm(ModelForm): logger = logging.getLogger(__name__) - numero = forms.CharField(required=True, - label=DocumentoAdministrativo._meta. - get_field('numero').verbose_name - ) - ano = forms.ChoiceField(required=True, - label=DocumentoAdministrativo._meta. - get_field('ano').verbose_name, - choices=RANGE_ANOS, - widget=forms.Select(attrs={'class': 'selector'})) + numero = forms.CharField( + required=True, + label=DocumentoAdministrativo._meta.get_field('numero').verbose_name) + + ano = forms.ChoiceField( + required=True, + label=DocumentoAdministrativo._meta.get_field('ano').verbose_name, + choices=choice_anos_com_documentoadministrativo, + widget=forms.Select(attrs={'class': 'selector'})) def clean(self): super(DesvincularDocumentoForm, self).clean() @@ -929,7 +1000,7 @@ class DesvincularMateriaForm(forms.Form): label=_('Número da Matéria')) ano = forms.ChoiceField(required=True, label=_('Ano da Matéria'), - choices=RANGE_ANOS, + choices=choice_anos_com_materias, widget=forms.Select(attrs={'class': 'selector'})) tipo = forms.ModelChoiceField(label=_('Tipo de Matéria'), required=True, diff --git a/sapl/protocoloadm/migrations/0014_auto_20190110_1300.py b/sapl/protocoloadm/migrations/0014_auto_20190110_1300.py new file mode 100644 index 000000000..56cdf8d36 --- /dev/null +++ b/sapl/protocoloadm/migrations/0014_auto_20190110_1300.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.8 on 2019-01-10 15:00 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('protocoloadm', '0013_auto_20190106_1336'), + ] + + operations = [ + migrations.AddField( + model_name='protocolo', + name='ip_data_hora_manual', + field=models.CharField(blank=True, help_text='Endereço IP da estação de trabalho do usuário que está realizando Protocolo e informando data e hora manualmente.', max_length=15, verbose_name='IP'), + ), + migrations.AddField( + model_name='protocolo', + name='user_data_hora_manual', + field=models.CharField(blank=True, help_text='Usuário que está realizando Protocolo e informando data e hora manualmente.', max_length=20, verbose_name='IP'), + ), + migrations.AlterField( + model_name='protocolo', + name='data', + field=models.DateField(blank=True, help_text='Informado manualmente', null=True, verbose_name='Data do Protocolo'), + ), + migrations.AlterField( + model_name='protocolo', + name='hora', + field=models.TimeField(blank=True, help_text='Informado manualmente', null=True, verbose_name='Hora do Protocolo'), + ), + ] diff --git a/sapl/protocoloadm/migrations/0015_protocolo_timestamp_data_hora_manual.py b/sapl/protocoloadm/migrations/0015_protocolo_timestamp_data_hora_manual.py new file mode 100644 index 000000000..85554edad --- /dev/null +++ b/sapl/protocoloadm/migrations/0015_protocolo_timestamp_data_hora_manual.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.8 on 2019-01-10 15:43 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('protocoloadm', '0014_auto_20190110_1300'), + ] + + operations = [ + migrations.AddField( + model_name='protocolo', + name='timestamp_data_hora_manual', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + ] diff --git a/sapl/protocoloadm/migrations/0016_auto_20190110_1345.py b/sapl/protocoloadm/migrations/0016_auto_20190110_1345.py new file mode 100644 index 000000000..71f2b7f7a --- /dev/null +++ b/sapl/protocoloadm/migrations/0016_auto_20190110_1345.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.8 on 2019-01-10 15:45 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('protocoloadm', '0015_protocolo_timestamp_data_hora_manual'), + ] + + operations = [ + migrations.AlterField( + model_name='protocolo', + name='timestamp', + field=models.DateTimeField(blank=True, default=django.utils.timezone.now, null=True), + ), + ] diff --git a/sapl/protocoloadm/models.py b/sapl/protocoloadm/models.py index 6d3d90671..e335a5db1 100644 --- a/sapl/protocoloadm/models.py +++ b/sapl/protocoloadm/models.py @@ -57,13 +57,29 @@ class Protocolo(models.Model): choices=RANGE_ANOS, verbose_name=_('Ano do Protocolo')) - # FIXME: https://github.com/interlegis/sapl/issues/2337 - data = models.DateField(null=True, blank=True) - hora = models.TimeField(null=True, blank=True) + data = models.DateField(null=True, blank=True, + verbose_name=_('Data do Protocolo'), + help_text=_('Informado manualmente')) + hora = models.TimeField(null=True, blank=True, + verbose_name=_('Hora do Protocolo'), + help_text=_('Informado manualmente')) + timestamp_data_hora_manual = models.DateTimeField(default=timezone.now) + user_data_hora_manual = models.CharField( + max_length=20, blank=True, + verbose_name=_('IP'), + help_text=_('Usuário que está realizando Protocolo e informando ' + 'data e hora manualmente.')) + ip_data_hora_manual = models.CharField( + max_length=15, blank=True, + verbose_name=_('IP'), + help_text=_('Endereço IP da estação de trabalho ' + 'do usuário que está realizando Protocolo e informando ' + 'data e hora manualmente.')) # Não foi utilizado auto_now_add=True em timestamp porque # ele usa datetime.now que não é timezone aware. - timestamp = models.DateTimeField(default=timezone.now) + timestamp = models.DateTimeField( + default=timezone.now, null=True, blank=True) tipo_protocolo = models.PositiveIntegerField( blank=True, null=True, verbose_name=_('Tipo de Protocolo')) tipo_processo = models.PositiveIntegerField() diff --git a/sapl/protocoloadm/tests/test_protocoloadm.py b/sapl/protocoloadm/tests/test_protocoloadm.py index 2a4c9f53c..c7e4bc341 100644 --- a/sapl/protocoloadm/tests/test_protocoloadm.py +++ b/sapl/protocoloadm/tests/test_protocoloadm.py @@ -1,4 +1,4 @@ -from datetime import date, timedelta +from datetime import date, timedelta, datetime from django.core.urlresolvers import reverse from django.utils import timezone @@ -392,29 +392,42 @@ def test_documento_administrativo_protocolo_inexistente(): def test_protocolo_documento_form_invalido(): - form = ProtocoloDocumentForm(data={}) + form = ProtocoloDocumentForm( + data={}, + initial={ + 'user_data_hora_manual': '', + 'ip_data_hora_manual': '', + 'data': timezone.localdate(timezone.now()), + 'hora': timezone.localtime(timezone.now())}) assert not form.is_valid() errors = form.errors + assert errors['data_hora_manual'] == [_('Este campo é obrigatório.')] assert errors['tipo_protocolo'] == [_('Este campo é obrigatório.')] assert errors['interessado'] == [_('Este campo é obrigatório.')] assert errors['tipo_documento'] == [_('Este campo é obrigatório.')] assert errors['numero_paginas'] == [_('Este campo é obrigatório.')] assert errors['assunto'] == [_('Este campo é obrigatório.')] - assert len(errors) == 5 + assert len(errors) == 6 def test_protocolo_materia_invalido(): - form = ProtocoloMateriaForm(data={}) + form = ProtocoloMateriaForm(data={}, + initial={ + 'user_data_hora_manual': '', + 'ip_data_hora_manual': '', + 'data': timezone.localdate(timezone.now()), + 'hora': timezone.localtime(timezone.now())}) assert not form.is_valid() errors = form.errors + assert errors['data_hora_manual'] == [_('Este campo é obrigatório.')] assert errors['assunto_ementa'] == [_('Este campo é obrigatório.')] assert errors['tipo_autor'] == [_('Este campo é obrigatório.')] assert errors['tipo_materia'] == [_('Este campo é obrigatório.')] @@ -422,4 +435,4 @@ def test_protocolo_materia_invalido(): assert errors['autor'] == [_('Este campo é obrigatório.')] assert errors['vincular_materia'] == [_('Este campo é obrigatório.')] - assert len(errors) == 6 + assert len(errors) == 7 diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index 8dd7e9e47..f91ee5227 100755 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -492,6 +492,15 @@ class ProtocoloDocumentoView(PermissionRequiredMixin, return reverse('sapl.protocoloadm:protocolo_mostrar', kwargs={'pk': self.object.id}) + def get_initial(self): + initial = super().get_initial() + + initial['user_data_hora_manual'] = self.request.user.username + initial['ip_data_hora_manual'] = get_client_ip(self.request) + initial['data'] = timezone.localdate(timezone.now()) + initial['hora'] = timezone.localtime(timezone.now()) + return initial + def form_valid(self, form): protocolo = form.save(commit=False) username = self.request.user.username @@ -538,6 +547,17 @@ class ProtocoloDocumentoView(PermissionRequiredMixin, return self.render_to_response(self.get_context_data()) protocolo.ano = timezone.now().year protocolo.assunto_ementa = self.request.POST['assunto'] + + if form.cleaned_data['data_hora_manual'] == 'True': + protocolo.timestamp = None + protocolo.user_data_hora_manual = username + protocolo.ip_data_hora_manual = get_client_ip(self.request) + else: + protocolo.data = None + protocolo.hora = None + protocolo.user_data_hora_manual = '' + protocolo.ip_data_hora_manual = '' + protocolo.save() self.object = protocolo return redirect(self.get_success_url()) @@ -659,6 +679,15 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView): return reverse('sapl.protocoloadm:materia_continuar', kwargs={ 'pk': protocolo.pk}) + def get_initial(self): + initial = super().get_initial() + + initial['user_data_hora_manual'] = self.request.user.username + initial['ip_data_hora_manual'] = get_client_ip(self.request) + initial['data'] = timezone.localdate(timezone.now()) + initial['hora'] = timezone.localtime(timezone.now()) + return initial + def form_valid(self, form): protocolo = form.save(commit=False) username = self.request.user.username @@ -719,6 +748,16 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView): protocolo.observacao = self.request.POST['observacao'] protocolo.assunto_ementa = self.request.POST['assunto_ementa'] + if form.cleaned_data['data_hora_manual'] == 'True': + protocolo.timestamp = None + protocolo.user_data_hora_manual = username + protocolo.ip_data_hora_manual = get_client_ip(self.request) + else: + protocolo.data = None + protocolo.hora = None + protocolo.user_data_hora_manual = '' + protocolo.ip_data_hora_manual = '' + protocolo.save() data = form.cleaned_data if data['vincular_materia'] == 'True': @@ -1168,4 +1207,4 @@ class FichaSelecionaAdmView(PermissionRequiredMixin, FormView): context['documento'] = documento return gerar_pdf_impressos(self.request, context, - 'materia/impressos/ficha_adm_pdf.html') \ No newline at end of file + 'materia/impressos/ficha_adm_pdf.html') diff --git a/sapl/relatorios/views.py b/sapl/relatorios/views.py index 0c5dfd377..f4bc8cda2 100755 --- a/sapl/relatorios/views.py +++ b/sapl/relatorios/views.py @@ -1,7 +1,7 @@ +from datetime import datetime as dt import html -import re import logging -from datetime import datetime as dt +import re from django.core.exceptions import ObjectDoesNotExist from django.http import Http404, HttpResponse @@ -581,7 +581,6 @@ def get_sessao_plenaria(sessao, casa): if dic_expedientes: lst_expedientes.append(dic_expedientes) - # Lista das matérias do Expediente, incluindo o resultado das votacoes lst_expediente_materia = [] for expediente_materia in ExpedienteMateria.objects.filter( @@ -612,7 +611,8 @@ def get_sessao_plenaria(sessao, casa): dic_expediente_materia["nom_autor"] = '' autoria = materia.autoria_set.all() - dic_expediente_materia['num_autores'] = 'Autores' if len(autoria) > 1 else 'Autor' + dic_expediente_materia['num_autores'] = 'Autores' if len( + autoria) > 1 else 'Autor' if autoria: for a in autoria: if a.autor.nome: @@ -687,7 +687,7 @@ def get_sessao_plenaria(sessao, casa): numeracao = materia.numeracao_set.first() if numeracao: - + dic_votacao["des_numeracao"] = ( str(numeracao.numero_materia) + '/' + @@ -762,7 +762,6 @@ def get_sessao_plenaria(sessao, casa): lst_ocorrencias.append(o) - return (inf_basicas_dic, lst_mesa, lst_presenca_sessao, @@ -810,10 +809,12 @@ def relatorio_sessao_plenaria(request, pk): imagem = get_imagem(casa) try: - logger.debug("user=" + username + ". Tentando obter SessaoPlenaria com id={}.".format(pk)) + logger.debug("user=" + username + + ". Tentando obter SessaoPlenaria com id={}.".format(pk)) sessao = SessaoPlenaria.objects.get(id=pk) except ObjectDoesNotExist as e: - logger.error("user=" + username + ". Essa SessaoPlenaria não existe (pk={}). ".format(pk) + str(e)) + logger.error("user=" + username + + ". Essa SessaoPlenaria não existe (pk={}). ".format(pk) + str(e)) raise Http404('Essa página não existe') (inf_basicas_dic, @@ -828,11 +829,10 @@ def relatorio_sessao_plenaria(request, pk): lst_oradores, lst_ocorrencias) = get_sessao_plenaria(sessao, casa) - for idx in range(len(lst_expedientes)): txt_expedientes = lst_expedientes[idx]['txt_expediente'] txt_expedientes = TrocaTag(txt_expedientes, '', 6, 6, - 'expedientes', '') lst_expedientes[idx]['txt_expediente'] = txt_expedientes pdf = pdf_sessao_plenaria_gerar.principal( @@ -868,7 +868,7 @@ def get_protocolos(prots): ts.strftime("%H:%m") else: dic['data'] = protocolo.data.strftime("%d/%m/%Y") + ' - Horário:' \ - + protocolo.hora.strftime("%H:%m") + + protocolo.hora.strftime("%H:%m") dic['txt_assunto'] = protocolo.assunto_ementa @@ -979,8 +979,8 @@ def get_etiqueta_protocolos(prots): dic['titulo'] = str(p.numero) + '/' + str(p.ano) - tz_hora = timezone.localtime(p.timestamp) if p.timestamp: + tz_hora = timezone.localtime(p.timestamp) dic['data'] = 'Data: ' + tz_hora.strftime( "%d/%m/%Y") + ' - Horário: ' + tz_hora.strftime("%H:%M") else: @@ -1072,7 +1072,8 @@ 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["tipo_materia"] = materia.tipo.sigla + \ + ' - ' + materia.tipo.descricao dic_expediente_materia["num_ordem"] = str( expediente_materia.numero_ordem) dic_expediente_materia["id_materia"] = str( @@ -1090,7 +1091,8 @@ def get_pauta_sessao(sessao, casa): dic_expediente_materia["nom_autor"] = '' autoria = materia.autoria_set.all() - dic_expediente_materia['num_autores'] = 'Autores' if len(autoria) > 1 else 'Autor' + dic_expediente_materia['num_autores'] = 'Autores' if len( + autoria) > 1 else 'Autor' if autoria: for a in autoria: if a.autor.nome: @@ -1112,7 +1114,8 @@ def get_pauta_sessao(sessao, casa): materia = MateriaLegislativa.objects.filter( id=votacao.materia.id).first() dic_votacao = {} - dic_votacao["tipo_materia"] = materia.tipo.sigla + ' - ' + materia.tipo.descricao + 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) @@ -1124,7 +1127,7 @@ def get_pauta_sessao(sessao, casa): numeracao = Numeracao.objects.filter(materia=votacao.materia).first() if numeracao: dic_votacao["des_numeracao"] = str( - numeracao.numero_materia) + '/' + str(numeracao.ano_materia) + numeracao.numero_materia) + '/' + str(numeracao.ano_materia) turno, tramitacao = get_turno(materia) dic_votacao["des_turno"] = turno diff --git a/sapl/sessao/forms.py b/sapl/sessao/forms.py index 35d49e277..68f6d3222 100644 --- a/sapl/sessao/forms.py +++ b/sapl/sessao/forms.py @@ -3,7 +3,6 @@ from datetime import datetime from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML, Button, Fieldset, Layout from django import forms -from django.contrib import messages from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.db import transaction @@ -12,22 +11,21 @@ from django.forms import ModelForm from django.forms.widgets import CheckboxSelectMultiple from django.utils.translation import ugettext_lazy as _ import django_filters -from floppyforms import widgets from sapl.base.models import Autor, TipoAutor from sapl.crispy_layout_mixin import form_actions, to_row, SaplFormLayout from sapl.materia.forms import MateriaLegislativaFilterSet from sapl.materia.models import (MateriaLegislativa, StatusTramitacao, TipoMateriaLegislativa) -from sapl.parlamentares.models import Parlamentar, Legislatura, Mandato +from sapl.parlamentares.models import Parlamentar, Mandato from sapl.utils import (RANGE_DIAS_MES, RANGE_MESES, MateriaPesquisaOrderingFilter, autor_label, autor_modal, timezone, choice_anos_com_sessaoplenaria) from .models import (Bancada, Bloco, ExpedienteMateria, JustificativaAusencia, Orador, OradorExpediente, OrdemDia, PresencaOrdemDia, SessaoPlenaria, - SessaoPlenariaPresenca, TipoJustificativa, TipoResultadoVotacao, - OcorrenciaSessao, RegistroVotacao, RetiradaPauta, TipoRetiradaPauta) + SessaoPlenariaPresenca, TipoResultadoVotacao, + OcorrenciaSessao, RetiradaPauta, TipoRetiradaPauta) MES_CHOICES = RANGE_MESES diff --git a/sapl/settings.py b/sapl/settings.py index 80bf18273..782cbb0f4 100755 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -22,9 +22,6 @@ from dj_database_url import parse as db_url from easy_thumbnails.conf import Settings as thumbnail_settings from unipath import Path -from .temp_suppress_crispy_form_warnings import \ - SUPRESS_CRISPY_FORM_WARNINGS_LOGGING - host = socket.gethostbyname_ex(socket.gethostname())[0] @@ -78,20 +75,24 @@ INSTALLED_APPS = ( 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - - # more 'django_extensions', + 'djangobower', - 'bootstrap3', # basically for django_admin_bootstrapped + 'bootstrap3', 'crispy_forms', - 'easy_thumbnails', - 'image_cropping', 'floppyforms', - 'haystack', 'sass_processor', + 'rest_framework', + 'django_filters', + + 'easy_thumbnails', + 'image_cropping', + 'reversion', 'reversion_compare', + + 'haystack', 'whoosh', 'speedinfo', @@ -110,7 +111,7 @@ SOLR_URL = config('SOLR_URL', cast=str, default='http://localhost:8983') SOLR_COLLECTION = config('SOLR_COLLECTION', cast=str, default='sapl') if USE_SOLR: - HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' #enable auto-index + HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' # enable auto-index SEARCH_BACKEND = 'haystack.backends.solr_backend.SolrEngine' SEARCH_URL = ('URL', '{}/solr/{}'.format(SOLR_URL, SOLR_COLLECTION)) @@ -118,7 +119,7 @@ if USE_SOLR: HAYSTACK_CONNECTIONS = { 'default': { 'ENGINE': SEARCH_BACKEND, - SEARCH_URL[0]: SEARCH_URL[1], + SEARCH_URL[0]: SEARCH_URL[1], 'BATCH_SIZE': 1000, 'TIMEOUT': 60, }, @@ -166,7 +167,7 @@ REST_FRAMEWORK = { "DEFAULT_PAGINATION_CLASS": "sapl.api.pagination.StandardPagination", "DEFAULT_FILTER_BACKENDS": ( "rest_framework.filters.SearchFilter", - "rest_framework.filters.DjangoFilterBackend", + 'django_filters.rest_framework.DjangoFilterBackend', ), } @@ -282,6 +283,7 @@ DAB_FIELD_RENDERER = \ CRISPY_TEMPLATE_PACK = 'bootstrap3' CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap3' CRISPY_FAIL_SILENTLY = not DEBUG +FLOPPY_FORMS_USE_GIS = False BOWER_COMPONENTS_ROOT = PROJECT_DIR.child("bower") BOWER_INSTALLED_APPS = ( @@ -339,6 +341,22 @@ LOGGING = { } } +PASSWORD_HASHERS = [ + 'django.contrib.auth.hashers.PBKDF2PasswordHasher', # default + 'sapl.hashers.ZopeSHA1PasswordHasher', +] + + +def remove_warnings(): + import warnings + warnings.filterwarnings( + 'ignore', module='floppyforms', + message='Unable to import floppyforms.gis' + ) + + +remove_warnings() + def uncaught_exceptions(type, value, error_traceback): import traceback @@ -350,8 +368,3 @@ def uncaught_exceptions(type, value, error_traceback): # captura exceções que não foram tratadas sys.excepthook = uncaught_exceptions - -PASSWORD_HASHERS = [ - 'django.contrib.auth.hashers.PBKDF2PasswordHasher', # default - 'sapl.hashers.ZopeSHA1PasswordHasher', -] diff --git a/sapl/static/js/app.js b/sapl/static/js/app.js index 48777c0e3..c30f357bd 100644 --- a/sapl/static/js/app.js +++ b/sapl/static/js/app.js @@ -47,6 +47,7 @@ function refreshMask() { $('.dateinput').mask('00/00/0000', {placeholder:"__/__/____"}); $('.hora').mask("00:00", {placeholder:"hh:mm"}); $('.hora_hms').mask("00:00:00", {placeholder:"hh:mm:ss"}); + $('.timeinput').mask("00:00:00", {placeholder:"hh:mm:ss"}); $('.cronometro').mask("00:00:00", {placeholder:"hh:mm:ss"}); } @@ -189,7 +190,7 @@ function OptionalCustomFrontEnd() { if (_label.length === 0) { _label = $('label[for='+this.id+']'); if (_label.length === 0) { - _label = $('').insertBefore(this) + _label = $('