diff --git a/docker-compose.yml b/docker-compose.yml index ac3425c9f..3691de798 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ sapldb: ports: - "5432:5432" sapl: - image: interlegis/sapl:3.1.137 + image: interlegis/sapl:3.1.138 restart: always environment: ADMIN_PASSWORD: interlegis diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 556c5d112..bb93e9f0e 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -9,7 +9,7 @@ django-compressor==2.0 django-crispy-forms==1.6.1 django-extensions==1.9.8 django-extra-views==0.11.0 -django-filter==0.15.3 +django-filter==1.0.0 django-floppyforms==1.6.2 django-model-utils==3.1.1 django-sass-processor==0.5.8 diff --git a/sapl/api/forms.py b/sapl/api/forms.py index c36a0c11f..b9ad11aca 100644 --- a/sapl/api/forms.py +++ b/sapl/api/forms.py @@ -5,9 +5,8 @@ from django.forms.fields import CharField, MultiValueField 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 DateFilter, MethodFilter, ModelChoiceFilter +from django_filters.filters import CharFilter, ModelChoiceFilter, DateFilter from rest_framework import serializers -from rest_framework.compat import django_filters from rest_framework.filters import FilterSet from sapl.base.models import Autor, TipoAutor @@ -16,9 +15,9 @@ from sapl.utils import generic_relations_for_model class SaplGenericRelationSearchFilterSet(FilterSet): - q = MethodFilter() + q = CharFilter(method='filter_q') - def filter_q(self, queryset, value): + def filter_q(self, queryset, name, value): query = value.split(' ') if query: @@ -87,12 +86,12 @@ class SearchForFieldField(MultiValueField): return None -class SearchForFieldFilter(django_filters.filters.MethodFilter): +class SearchForFieldFilter(CharFilter): field_class = SearchForFieldField class AutorChoiceFilterSet(SaplGenericRelationSearchFilterSet): - q = MethodFilter() + q = CharFilter(method='filter_q') tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all()) class Meta: @@ -101,18 +100,18 @@ class AutorChoiceFilterSet(SaplGenericRelationSearchFilterSet): 'tipo', 'nome', ] - def filter_q(self, queryset, value): + def filter_q(self, queryset, name,value): return SaplGenericRelationSearchFilterSet.filter_q( self, queryset, value).distinct('nome').order_by('nome') class AutorSearchForFieldFilterSet(AutorChoiceFilterSet): - q = SearchForFieldFilter() + q = SearchForFieldFilter(method='filter_q') class Meta(AutorChoiceFilterSet.Meta): pass - def filter_q(self, queryset, value): + def filter_q(self, queryset, name, value): value[0] = value[0].split(',') value[1] = value[1].split(',') @@ -128,7 +127,7 @@ class AutorSearchForFieldFilterSet(AutorChoiceFilterSet): class AutoresPossiveisFilterSet(FilterSet): logger = logging.getLogger(__name__) data_relativa = DateFilter(method='filter_data_relativa') - tipo = MethodFilter() + tipo = CharFilter(method='filter_tipo') class Meta: model = Autor @@ -137,10 +136,11 @@ class AutoresPossiveisFilterSet(FilterSet): def filter_data_relativa(self, queryset, name, value): return queryset - def filter_tipo(self, queryset, value): - + def filter_tipo(self, queryset, name, value): + try: - self.logger.debug("Tentando obter TipoAutor correspondente à pk {}.".format(value)) + self.logger.debug( + "Tentando obter TipoAutor correspondente à pk {}.".format(value)) tipo = TipoAutor.objects.get(pk=value) except: self.logger.error("TipoAutor(pk={}) inexistente.".format(value)) diff --git a/sapl/base/forms.py b/sapl/base/forms.py index ccd2c132b..36267dbc3 100644 --- a/sapl/base/forms.py +++ b/sapl/base/forms.py @@ -23,6 +23,7 @@ from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column, from sapl.audiencia.models import AudienciaPublica,TipoAudienciaPublica from sapl.comissoes.models import Reuniao, Comissao from sapl.materia.models import (MateriaLegislativa, UnidadeTramitacao, StatusTramitacao) +from sapl.norma.models import (NormaJuridica) from sapl.parlamentares.models import SessaoLegislativa from sapl.sessao.models import SessaoPlenaria from sapl.settings import MAX_IMAGE_UPLOAD_SIZE @@ -654,14 +655,13 @@ class AutorFormForAdmin(AutorForm): class RelatorioAtasFilterSet(django_filters.FilterSet): - filter_overrides = {models.DateField: { - 'filter_class': django_filters.DateFromToRangeFilter, - 'extra': lambda f: { - 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), - 'widget': RangeWidgetOverride} - }} - class Meta: + filter_overrides = {models.DateField: { + 'filter_class': django_filters.DateFromToRangeFilter, + 'extra': lambda f: { + 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), + 'widget': RangeWidgetOverride} + }} model = SessaoPlenaria fields = ['data_inicio'] @@ -688,16 +688,92 @@ class RelatorioAtasFilterSet(django_filters.FilterSet): ) -class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet): +class RelatorioNormasMesFilterSet(django_filters.FilterSet): + + ano = django_filters.ChoiceFilter(required=True, + label='Ano da Norma', + choices=RANGE_ANOS) filter_overrides = {models.DateField: { 'filter_class': django_filters.DateFromToRangeFilter, 'extra': lambda f: { - 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), + 'label': '%s (%s)' % (f.verbose_name, _('Ano')), 'widget': RangeWidgetOverride} }} + + class Meta: + model = NormaJuridica + fields = ['ano'] + + def __init__(self, *args, **kwargs): + super(RelatorioNormasMesFilterSet, self).__init__( + *args, **kwargs) + + self.filters['ano'].label = 'Ano' + self.form.fields['ano'].required = True + + row1 = to_row([('ano', 12)]) + + self.form.helper = FormHelper() + self.form.helper.form_method = 'GET' + self.form.helper.layout = Layout( + Fieldset(_('Normas por mês do ano.'), + row1, form_actions(label='Pesquisar')) + ) + + @property + def qs(self): + parent = super(RelatorioNormasMesFilterSet, self).qs + return parent.distinct().order_by('data') + + +class RelatorioNormasVigenciaFilterSet(django_filters.FilterSet): + + ano = django_filters.ChoiceFilter(required=True, + label='Ano da Norma', + choices=RANGE_ANOS) + + vigencia = forms.ChoiceField( + label=_('Vigência'), + choices=[(True, "Vigente"), (False, "Não vigente")], + widget=forms.RadioSelect(), + required=True) + + + def __init__(self, *args, **kwargs): + super(RelatorioNormasVigenciaFilterSet, self).__init__( + *args, **kwargs) + + self.filters['ano'].label = 'Ano' + self.form.fields['ano'].required = True + self.form.fields['vigencia'] = self.vigencia + + row1 = to_row([('ano', 12)]) + row2 = to_row([('vigencia', 12)]) + + self.form.helper = FormHelper() + self.form.helper.form_method = 'GET' + self.form.helper.layout = Layout( + Fieldset(_('Normas por vigência.'), + row1, row2, + form_actions(label='Pesquisar')) + ) + + @property + def qs(self): + return qs_override_django_filter(self) + + +class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet): + class Meta: + filter_overrides = {models.DateField: { + 'filter_class': django_filters.DateFromToRangeFilter, + 'extra': lambda f: { + 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), + 'widget': RangeWidgetOverride} + }} model = SessaoPlenaria fields = ['data_inicio'] @@ -724,19 +800,18 @@ class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet): class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet): - filter_overrides = {models.DateField: { - 'filter_class': django_filters.DateFromToRangeFilter, - 'extra': lambda f: { - 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), - 'widget': RangeWidgetOverride} - }} - @property def qs(self): parent = super(RelatorioHistoricoTramitacaoFilterSet, self).qs return parent.distinct().prefetch_related('tipo').order_by('-ano', 'tipo', 'numero') class Meta: + filter_overrides = {models.DateField: { + 'filter_class': django_filters.DateFromToRangeFilter, + 'extra': lambda f: { + 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), + 'widget': RangeWidgetOverride} + }} model = MateriaLegislativa fields = ['tipo', 'tramitacao__unidade_tramitacao_local', 'tramitacao__status', 'tramitacao__data_tramitacao'] @@ -764,19 +839,18 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet): class RelatorioDataFimPrazoTramitacaoFilterSet(django_filters.FilterSet): - filter_overrides = {models.DateField: { - 'filter_class': django_filters.DateFromToRangeFilter, - 'extra': lambda f: { - 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), - 'widget': RangeWidgetOverride} - }} - @property def qs(self): parent = super(RelatorioDataFimPrazoTramitacaoFilterSet, self).qs return parent.distinct().prefetch_related('tipo').order_by('-ano', 'tipo', 'numero') class Meta: + filter_overrides = {models.DateField: { + 'filter_class': django_filters.DateFromToRangeFilter, + 'extra': lambda f: { + 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), + 'widget': RangeWidgetOverride} + }} model = MateriaLegislativa fields = ['tipo', 'tramitacao__unidade_tramitacao_local', 'tramitacao__status', 'tramitacao__data_fim_prazo'] @@ -936,13 +1010,6 @@ class RelatorioMateriasPorAnoAutorTipoFilterSet(django_filters.FilterSet): class RelatorioMateriasPorAutorFilterSet(django_filters.FilterSet): - filter_overrides = {models.DateField: { - 'filter_class': django_filters.DateFromToRangeFilter, - 'extra': lambda f: { - 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), - 'widget': RangeWidgetOverride} - }} - autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput()) @property @@ -952,6 +1019,12 @@ class RelatorioMateriasPorAutorFilterSet(django_filters.FilterSet): .order_by('autoria__autor', '-autoria__primeiro_autor', 'tipo', '-ano', '-numero') class Meta: + filter_overrides = {models.DateField: { + 'filter_class': django_filters.DateFromToRangeFilter, + 'extra': lambda f: { + 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), + 'widget': RangeWidgetOverride} + }} model = MateriaLegislativa fields = ['tipo', 'data_apresentacao'] @@ -1061,7 +1134,8 @@ class ConfiguracoesAppForm(ModelForm): 'cronometro_consideracoes', 'mostrar_brasao_painel', 'receber_recibo_proposicao', - 'assinatura_ata'] + 'assinatura_ata', + 'estatisticas_acesso_normas'] def __init__(self, *args, **kwargs): super(ConfiguracoesAppForm, self).__init__(*args, **kwargs) diff --git a/sapl/base/migrations/0027_appconfig_relatorios_atos.py b/sapl/base/migrations/0027_appconfig_relatorios_atos.py new file mode 100644 index 000000000..afd3382e1 --- /dev/null +++ b/sapl/base/migrations/0027_appconfig_relatorios_atos.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.8 on 2018-12-11 20:25 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0026_auto_20181126_1727'), + ] + + operations = [ + migrations.AddField( + model_name='appconfig', + name='relatorios_atos', + field=models.CharField(choices=[('S', 'Sim'), ('N', 'Não')], default='N', max_length=1, verbose_name='Relatórios de atos acessados'), + ), + ] diff --git a/sapl/base/migrations/0028_appconfig_estatisticas_acesso_normas.py b/sapl/base/migrations/0028_appconfig_estatisticas_acesso_normas.py new file mode 100644 index 000000000..7a4af06de --- /dev/null +++ b/sapl/base/migrations/0028_appconfig_estatisticas_acesso_normas.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.8 on 2018-12-18 17:03 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0027_appconfig_relatorios_atos'), + ] + + operations = [ + migrations.AddField( + model_name='appconfig', + name='estatisticas_acesso_normas', + field=models.CharField(choices=[('S', 'Sim'), ('N', 'Não')], default='N', max_length=1, verbose_name='Estatísticas de acesso a normas'), + ), + ] diff --git a/sapl/base/migrations/0029_remove_appconfig_relatorios_atos.py b/sapl/base/migrations/0029_remove_appconfig_relatorios_atos.py new file mode 100644 index 000000000..fa06b23ac --- /dev/null +++ b/sapl/base/migrations/0029_remove_appconfig_relatorios_atos.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.8 on 2018-12-18 18:40 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0028_appconfig_estatisticas_acesso_normas'), + ] + + operations = [ + migrations.RemoveField( + model_name='appconfig', + name='relatorios_atos', + ), + ] diff --git a/sapl/base/models.py b/sapl/base/models.py index 5caf8b2c0..343a8db9b 100644 --- a/sapl/base/models.py +++ b/sapl/base/models.py @@ -12,6 +12,9 @@ from sapl.utils import (LISTA_DE_UFS, YES_NO_CHOICES, TIPO_DOCUMENTO_ADMINISTRATIVO = (('O', _('Ostensiva')), ('R', _('Restritiva'))) +RELATORIO_ATOS_ACESSADOS = (('S', _('Sim')), + ('N', _('Não'))) + SEQUENCIA_NUMERACAO = (('A', _('Sequencial por ano')), ('L', _('Sequencial por legislatura')), ('U', _('Sequencial único'))) @@ -84,6 +87,11 @@ class AppConfig(models.Model): verbose_name=_('Visibilidade dos Documentos Administrativos'), choices=TIPO_DOCUMENTO_ADMINISTRATIVO, default='O') + estatisticas_acesso_normas = models.CharField( + max_length=1, + verbose_name=_('Estatísticas de acesso a normas'), + choices=RELATORIO_ATOS_ACESSADOS, default='N') + sequencia_numeracao = models.CharField( max_length=1, verbose_name=_('Sequência de numeração'), diff --git a/sapl/base/urls.py b/sapl/base/urls.py index 93a5b1cd3..ae4add258 100644 --- a/sapl/base/urls.py +++ b/sapl/base/urls.py @@ -23,7 +23,11 @@ from .views import (AlterarSenha, AppConfigCrud, CasaLegislativaCrud, RelatorioMateriasPorAutorView, RelatorioMateriasTramitacaoView, RelatorioPresencaSessaoView, - RelatorioReuniaoView, SaplSearchView) + RelatorioReuniaoView, SaplSearchView, + RelatorioNormasPublicadasMesView, + RelatorioNormasVigenciaView, + EstatisticasAcessoNormas, + RelatoriosListView) app_name = AppConfig.name @@ -84,10 +88,16 @@ urlpatterns = [ url(r'^sistema/app-config/', include(AppConfigCrud.get_urls())), # TODO mover estas telas para a app 'relatorios' - url(r'^sistema/relatorios/$', TemplateView.as_view( - template_name='base/relatorios_list.html'), name='relatorios_list'), + url(r'^sistema/relatorios/$', + RelatoriosListView.as_view(), name='relatorios_list'), url(r'^sistema/relatorios/materia-por-autor$', RelatorioMateriasPorAutorView.as_view(), name='materia_por_autor'), + url(r'^sistema/relatorios/relatorio-por-mes$', + RelatorioNormasPublicadasMesView.as_view(), name='normas_por_mes'), + url(r'^sistema/relatorios/relatorio-por-vigencia$', + RelatorioNormasVigenciaView.as_view(), name='normas_por_vigencia'), + url(r'^sistema/relatorios/estatisticas-acesso$', + EstatisticasAcessoNormas.as_view(), name='estatisticas_acesso'), url(r'^sistema/relatorios/materia-por-ano-autor-tipo$', RelatorioMateriasPorAnoAutorTipoView.as_view(), name='materia_por_ano_autor_tipo'), diff --git a/sapl/base/views.py b/sapl/base/views.py index ca122ce9f..599d6b05f 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -1,3 +1,5 @@ +import collections +import datetime import logging import os @@ -30,6 +32,7 @@ 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.norma.models import (NormaJuridica, NormaEstatisticas) from sapl.sessao.models import (PresencaOrdemDia, SessaoPlenaria, SessaoPlenariaPresenca) from sapl.utils import (parlamentares_ativos, @@ -45,7 +48,8 @@ from .forms import (AlterarSenhaForm, CasaLegislativaForm, RelatorioMateriasTramitacaoilterSet, RelatorioPresencaSessaoFilterSet, RelatorioReuniaoFilterSet, UsuarioCreateForm, - UsuarioEditForm) + UsuarioEditForm, RelatorioNormasMesFilterSet, + RelatorioNormasVigenciaFilterSet) from .models import AppConfig, CasaLegislativa @@ -276,6 +280,20 @@ class AutorCrud(CrudAux): return url_reverse +class RelatoriosListView(TemplateView): + template_name='base/relatorios_list.html' + + def get_context_data(self, **kwargs): + context = super(TemplateView, self).get_context_data(**kwargs) + estatisticas_acesso_normas = AppConfig.objects.first().estatisticas_acesso_normas + if estatisticas_acesso_normas == 'S': + context['estatisticas_acesso_normas'] = True + else: + context['estatisticas_acesso_normas'] = False + + return context + + class RelatorioAtasView(FilterView): model = SessaoPlenaria filterset_class = RelatorioAtasFilterSet @@ -744,6 +762,145 @@ class RelatorioMateriasPorAutorView(FilterView): return context +class RelatorioNormasPublicadasMesView(FilterView): + model = NormaJuridica + filterset_class = RelatorioNormasMesFilterSet + template_name = 'base/RelatorioNormaMes_filter.html' + + def get_context_data(self, **kwargs): + context = super(RelatorioNormasPublicadasMesView, + self).get_context_data(**kwargs) + context['title'] = _('Normas') + + # Verifica se os campos foram preenchidos + 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) + context['ano'] = self.request.GET['ano'] + + normas_mes = collections.OrderedDict() + meses = {1: 'Janeiro', 2: 'Fevereiro', 3:'Março', 4: 'Abril', 5: 'Maio', 6:'Junho', + 7: 'Julho', 8: 'Agosto', 9:'Setembro', 10:'Outubro', 11:'Novembro', 12:'Dezembro'} + for norma in context['object_list']: + if not meses[norma.data.month] in normas_mes: + normas_mes[meses[norma.data.month]] = [] + normas_mes[meses[norma.data.month]].append(norma) + + context['normas_mes'] = normas_mes + + quant_normas_mes = {} + for key in normas_mes.keys(): + quant_normas_mes[key] = len(normas_mes[key]) + + context['quant_normas_mes'] = quant_normas_mes + + return context + + +class RelatorioNormasVigenciaView(FilterView): + model = NormaJuridica + filterset_class = RelatorioNormasVigenciaFilterSet + template_name = 'base/RelatorioNormasVigencia_filter.html' + + def get_filterset_kwargs(self, filterset_class): + super(RelatorioNormasVigenciaView, + self).get_filterset_kwargs(filterset_class) + + kwargs = {'data': self.request.GET or None} + qs = self.get_queryset().order_by('data').distinct() + if kwargs['data']: + ano = kwargs['data']['ano'] + vigencia = kwargs['data']['vigencia'] + qs = qs.filter(ano=ano) + if vigencia == 'True': + qs_dt_not_null = qs.filter(data_vigencia__isnull=True) + qs = (qs_dt_not_null | qs.filter(data_vigencia__gte=datetime.datetime.now().date())).distinct() + else: + qs = qs.filter(data_vigencia__lt=datetime.datetime.now().date()) + + kwargs.update({ + 'queryset': qs + }) + return kwargs + + + def get_context_data(self, **kwargs): + context = super(RelatorioNormasVigenciaView, + self).get_context_data(**kwargs) + context['title'] = _('Normas por vigência') + + # Verifica se os campos foram preenchidos + if not self.filterset.form.is_valid(): + return context + + normas_totais = NormaJuridica.objects.filter(ano=self.request.GET['ano']) + + context['quant_total'] = len(normas_totais) + if self.request.GET['vigencia'] == 'True': + context['vigencia'] = 'Vigente' + context['quant_vigente'] = len(context['object_list']) + context['quant_nao_vigente'] = context['quant_total'] - context['quant_vigente'] + else: + context['vigencia'] = 'Não vigente' + context['quant_nao_vigente'] = len(context['object_list']) + context['quant_vigente'] = context['quant_total'] - context['quant_nao_vigente'] + + qr = self.request.GET.copy() + context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' + + context['show_results'] = show_results_filter_set(qr) + context['ano'] = self.request.GET['ano'] + + return context + + +class EstatisticasAcessoNormas(FilterView): + model = NormaJuridica + filterset_class = RelatorioNormasMesFilterSet + template_name = 'base/EstatisticasAcessoNormas_filter.html' + + def get_context_data(self, **kwargs): + context = super(EstatisticasAcessoNormas, + self).get_context_data(**kwargs) + context['title'] = _('Normas') + + # Verifica se os campos foram preenchidos + 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) + context['ano'] = self.request.GET['ano'] + + normas_mes = collections.OrderedDict() + meses = {1: 'Janeiro', 2: 'Fevereiro', 3:'Março', 4: 'Abril', 5: 'Maio', 6:'Junho', + 7: 'Julho', 8: 'Agosto', 9:'Setembro', 10:'Outubro', 11:'Novembro', 12:'Dezembro'} + for norma in context['object_list']: + if not meses[norma.data.month] in normas_mes: + normas_mes[meses[norma.data.month]] = [] + norma_est = [norma, len(NormaEstatisticas.objects.filter(norma=norma))] + normas_mes[meses[norma.data.month]].append(norma_est) + + meses_sem_acesso = [] + # Ordena por acesso e limita em 5 + for n in normas_mes: + sorted_by_value = sorted(normas_mes[n], key=lambda kv: kv[1], reverse=True) + normas_mes[n] = sorted_by_value[0:5] + if all(v[1]==0 for v in normas_mes[n]): + meses_sem_acesso.append(n) + + context['normas_mes'] = normas_mes + context['meses_sem_acesso'] = meses_sem_acesso + + return context + + class ListarUsuarioView(PermissionRequiredMixin, ListView): model = get_user_model() template_name = 'auth/user_list.html' diff --git a/sapl/comissoes/migrations/0019_auto_20181214_1023.py b/sapl/comissoes/migrations/0019_auto_20181214_1023.py new file mode 100644 index 000000000..669ea20c6 --- /dev/null +++ b/sapl/comissoes/migrations/0019_auto_20181214_1023.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.8 on 2018-12-14 12:23 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0018_auto_20180924_1724'), + ] + + operations = [ + migrations.AlterField( + model_name='reuniao', + name='hora_fim', + field=models.TimeField(blank=True, null=True, verbose_name='Horário de Término (hh:mm)'), + ), + ] diff --git a/sapl/comissoes/models.py b/sapl/comissoes/models.py index f9ffa97fa..2792c80d6 100644 --- a/sapl/comissoes/models.py +++ b/sapl/comissoes/models.py @@ -221,6 +221,7 @@ class Reuniao(models.Model): null=True, verbose_name=_('Horário de Início (hh:mm)')) hora_fim = models.TimeField( + blank=True, null=True, verbose_name=_('Horário de Término (hh:mm)')) local_reuniao = models.CharField( diff --git a/sapl/comissoes/tests/test_comissoes.py b/sapl/comissoes/tests/test_comissoes.py index d2f8b0bd1..3b45bf337 100644 --- a/sapl/comissoes/tests/test_comissoes.py +++ b/sapl/comissoes/tests/test_comissoes.py @@ -139,7 +139,6 @@ def test_valida_campos_obrigatorios_reuniao_form(): assert errors['nome'] == [_('Este campo é obrigatório.')] assert errors['data'] == [_('Este campo é obrigatório.')] assert errors['hora_inicio'] == [_('Este campo é obrigatório.')] - assert errors['hora_fim'] == [_('Este campo é obrigatório.')] - assert len(errors) == 7 + assert len(errors) == 6 diff --git a/sapl/compilacao/templatetags/compilacao_filters.py b/sapl/compilacao/templatetags/compilacao_filters.py index e56478bae..a7fb2eada 100644 --- a/sapl/compilacao/templatetags/compilacao_filters.py +++ b/sapl/compilacao/templatetags/compilacao_filters.py @@ -83,6 +83,9 @@ def nota_automatica(dispositivo, ta_pub_list): if dispositivo.ta_publicado: d = dispositivo.dispositivo_atualizador.dispositivo_pai + if d.auto_inserido: + d = d.dispositivo_pai + ta_publicado = ta_pub_list[dispositivo.ta_publicado_id] if\ ta_pub_list else dispositivo.ta_publicado diff --git a/sapl/compilacao/views.py b/sapl/compilacao/views.py index a82f63556..7d28f619a 100644 --- a/sapl/compilacao/views.py +++ b/sapl/compilacao/views.py @@ -1319,6 +1319,9 @@ class TextEditView(CompMixin, TemplateView): if dispositivo.ta_publicado_id: d = dispositivo.dispositivo_atualizador.dispositivo_pai + if d.auto_inserido: + d = d.dispositivo_pai + ta_publicado = lista_ta_publicado[dispositivo.ta_publicado_id] if\ lista_ta_publicado else dispositivo.ta_publicado diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index c3ce1f36f..454cd9890 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -127,6 +127,9 @@ class MateriaSimplificadaForm(ModelForm): 'numero_protocolo', 'regime_tramitacao', 'em_tramitacao', 'ementa', 'tipo_apresentacao', 'texto_original'] + widgets = { + 'numero_protocolo': forms.TextInput(attrs={'readonly': True}), + } def __init__(self, *args, **kwargs): @@ -754,13 +757,6 @@ class AnexadaForm(ModelForm): class MateriaLegislativaFilterSet(django_filters.FilterSet): - filter_overrides = {models.DateField: { - 'filter_class': django_filters.DateFromToRangeFilter, - 'extra': lambda f: { - 'label': '%s (%s)' % (f.verbose_name, _('Inicial Final')), - 'widget': RangeWidgetOverride} - }} - ano = django_filters.ChoiceFilter(required=False, label='Ano da Matéria', choices=ANO_CHOICES) @@ -791,6 +787,12 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet): o = MateriaPesquisaOrderingFilter() class Meta: + filter_overrides = {models.DateField: { + 'filter_class': django_filters.DateFromToRangeFilter, + 'extra': lambda f: { + 'label': '%s (%s)' % (f.verbose_name, _('Inicial Final')), + 'widget': RangeWidgetOverride} + }} model = MateriaLegislativa fields = ['numero', 'numero_protocolo', @@ -1029,14 +1031,13 @@ class AutoriaMultiCreateForm(Form): class AcessorioEmLoteFilterSet(django_filters.FilterSet): - filter_overrides = {models.DateField: { - 'filter_class': django_filters.DateFromToRangeFilter, - 'extra': lambda f: { - 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), - 'widget': RangeWidgetOverride} - }} - class Meta: + filter_overrides = {models.DateField: { + 'filter_class': django_filters.DateFromToRangeFilter, + 'extra': lambda f: { + 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), + 'widget': RangeWidgetOverride} + }} model = MateriaLegislativa fields = ['tipo', 'data_apresentacao'] @@ -1060,14 +1061,13 @@ class AcessorioEmLoteFilterSet(django_filters.FilterSet): class PrimeiraTramitacaoEmLoteFilterSet(django_filters.FilterSet): - filter_overrides = {models.DateField: { - 'filter_class': django_filters.DateFromToRangeFilter, - 'extra': lambda f: { - 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), - 'widget': RangeWidgetOverride} - }} - class Meta: + filter_overrides = {models.DateField: { + 'filter_class': django_filters.DateFromToRangeFilter, + 'extra': lambda f: { + 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), + 'widget': RangeWidgetOverride} + }} model = MateriaLegislativa fields = ['tipo', 'data_apresentacao'] @@ -1092,14 +1092,13 @@ class PrimeiraTramitacaoEmLoteFilterSet(django_filters.FilterSet): class TramitacaoEmLoteFilterSet(django_filters.FilterSet): - filter_overrides = {models.DateField: { - 'filter_class': django_filters.DateFromToRangeFilter, - 'extra': lambda f: { - 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), - 'widget': RangeWidgetOverride} - }} - class Meta: + filter_overrides = {models.DateField: { + 'filter_class': django_filters.DateFromToRangeFilter, + 'extra': lambda f: { + 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), + 'widget': RangeWidgetOverride} + }} model = MateriaLegislativa fields = ['tipo', 'data_apresentacao', 'tramitacao__status', 'tramitacao__unidade_tramitacao_destino'] diff --git a/sapl/materia/urls.py b/sapl/materia/urls.py index 030e5386e..e446e6a64 100644 --- a/sapl/materia/urls.py +++ b/sapl/materia/urls.py @@ -26,6 +26,7 @@ from sapl.materia.views import (AcompanhamentoConfirmarView, proposicao_texto, recuperar_materia, ExcluirTramitacaoEmLoteView, RetornarProposicao) from sapl.norma.views import NormaPesquisaSimplesView +from sapl.protocoloadm.views import (FichaPesquisaAdmView, FichaSelecionaAdmView) from .apps import AppConfig @@ -47,6 +48,12 @@ urlpatterns_impressos = [ url(r'^materia/impressos/norma-pesquisa/$', NormaPesquisaSimplesView.as_view(), name='impressos_norma_pesquisa'), + url(r'^materia/impressos/ficha-pesquisa-adm/$', + FichaPesquisaAdmView.as_view(), + name= 'impressos_ficha_pesquisa_adm'), + url(r'^materia/impressos/ficha-seleciona-adm/$', + FichaSelecionaAdmView.as_view(), + name= 'impressos_ficha_seleciona_adm'), ] urlpatterns_materia = [ diff --git a/sapl/norma/forms.py b/sapl/norma/forms.py index 4f6b6a563..7cd8368f7 100644 --- a/sapl/norma/forms.py +++ b/sapl/norma/forms.py @@ -41,13 +41,6 @@ ORDENACAO_CHOICES = [('', '---------'), class NormaFilterSet(django_filters.FilterSet): - filter_overrides = {models.DateField: { - 'filter_class': django_filters.DateFromToRangeFilter, - 'extra': lambda f: { - 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), - 'widget': RangeWidgetOverride} - }} - ano = django_filters.ChoiceFilter(required=False, label='Ano', choices=ANO_CHOICES) @@ -63,6 +56,12 @@ class NormaFilterSet(django_filters.FilterSet): o = NormaPesquisaOrderingFilter() class Meta: + filter_overrides = {models.DateField: { + 'filter_class': django_filters.DateFromToRangeFilter, + 'extra': lambda f: { + 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), + 'widget': RangeWidgetOverride} + }} model = NormaJuridica fields = ['tipo', 'numero', 'ano', 'data', 'data_vigencia', 'data_publicacao', 'ementa', 'assuntos'] diff --git a/sapl/norma/migrations/0017_normaestatisticas.py b/sapl/norma/migrations/0017_normaestatisticas.py new file mode 100644 index 000000000..03009eeec --- /dev/null +++ b/sapl/norma/migrations/0017_normaestatisticas.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.8 on 2018-12-17 18:44 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('norma', '0016_tipovinculonormajuridica_revoga_integramente'), + ] + + operations = [ + migrations.CreateModel( + name='NormaEstatisticas', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('usuario', models.CharField(max_length=50)), + ('horario_acesso', models.DateTimeField(auto_now=True, null=True)), + ('norma', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='norma.NormaJuridica')), + ], + ), + ] diff --git a/sapl/norma/models.py b/sapl/norma/models.py index 6565304ee..80075f113 100644 --- a/sapl/norma/models.py +++ b/sapl/norma/models.py @@ -191,6 +191,18 @@ class NormaJuridica(models.Model): update_fields=update_fields) +class NormaEstatisticas(models.Model): + usuario = models.CharField(max_length=50) + horario_acesso = models.DateTimeField( + blank=True, null=True, + auto_now=True) + norma = models.ForeignKey(NormaJuridica, + on_delete=models.CASCADE) + def __str__(self): + return _('Usuário: %(usuario)s, Norma: %(norma)s') % { + 'usuario': self.usuario, 'norma': self.norma} + + @reversion.register() class AutoriaNorma(models.Model): autor = models.ForeignKey(Autor, diff --git a/sapl/norma/tests/test_norma.py b/sapl/norma/tests/test_norma.py index 6603d7167..5c2a76a6a 100644 --- a/sapl/norma/tests/test_norma.py +++ b/sapl/norma/tests/test_norma.py @@ -7,6 +7,7 @@ from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.norma.forms import (NormaJuridicaForm, NormaPesquisaSimplesForm, NormaRelacionadaForm) from sapl.norma.models import NormaJuridica, TipoNormaJuridica +from sapl.base.models import AppConfig @pytest.mark.django_db(transaction=False) @@ -15,6 +16,7 @@ def test_incluir_norma_submit(admin_client): tipo = mommy.make(TipoNormaJuridica, sigla='T', descricao='Teste') + config = mommy.make(AppConfig) # Testa POST response = admin_client.post(reverse('sapl.norma:normajuridica_create'), diff --git a/sapl/norma/views.py b/sapl/norma/views.py index f2dfb6f2e..f7800c42f 100644 --- a/sapl/norma/views.py +++ b/sapl/norma/views.py @@ -1,6 +1,8 @@ -import re import logging +import re +import sapl +import weasyprint from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.exceptions import ObjectDoesNotExist @@ -13,8 +15,6 @@ 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 -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, @@ -24,7 +24,7 @@ from sapl.utils import show_results_filter_set from .forms import (AnexoNormaJuridicaForm, NormaFilterSet, NormaJuridicaForm, NormaPesquisaSimplesForm, NormaRelacionadaForm, AutoriaNormaForm) from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada, - TipoNormaJuridica, TipoVinculoNormaJuridica, AutoriaNorma) + TipoNormaJuridica, TipoVinculoNormaJuridica, AutoriaNorma, NormaEstatisticas) # LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '') @@ -190,7 +190,13 @@ class NormaCrud(Crud): return reverse('%s:%s' % (namespace, 'norma_pesquisa')) class DetailView(Crud.DetailView): - pass + def get(self, request, *args, **kwargs): + estatisticas_acesso_normas = AppConfig.objects.first().estatisticas_acesso_normas + if estatisticas_acesso_normas == 'S': + NormaEstatisticas.objects.create(usuario=str(self.request.user), + norma_id=kwargs['pk']) + return super().get(request, *args, **kwargs) + class DeleteView(Crud.DeleteView): @@ -225,7 +231,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): diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index f9a37b373..c47d36566 100644 --- a/sapl/parlamentares/views.py +++ b/sapl/parlamentares/views.py @@ -288,7 +288,7 @@ def parlamentares_frente_selected(request): return JsonResponse({'id_list': list(lista_parlamentar_id)}) -class FrenteCrud(CrudAux): +class FrenteCrud(Crud): model = Frente help_topic = 'tipo_situa_militar' public = [RP_DETAIL, RP_LIST] @@ -574,7 +574,7 @@ class ParlamentarCrud(Crud): # Caso encontre UMA filiação nessas condições else: - self.logger.info("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 diff --git a/sapl/protocoloadm/forms.py b/sapl/protocoloadm/forms.py index a621754b7..97cab4eee 100644 --- a/sapl/protocoloadm/forms.py +++ b/sapl/protocoloadm/forms.py @@ -66,13 +66,6 @@ class AcompanhamentoDocumentoForm(ModelForm): class ProtocoloFilterSet(django_filters.FilterSet): - filter_overrides = {models.DateTimeField: { - 'filter_class': django_filters.DateFromToRangeFilter, - 'extra': lambda f: { - 'label': 'Data (%s)' % (_('Inicial - Final')), - 'widget': RangeWidgetOverride} - }} - ano = django_filters.ChoiceFilter(required=False, label='Ano', choices=ANO_CHOICES) @@ -99,6 +92,12 @@ class ProtocoloFilterSet(django_filters.FilterSet): o = AnoNumeroOrderingFilter() class Meta: + filter_overrides = {models.DateTimeField: { + 'filter_class': django_filters.DateFromToRangeFilter, + 'extra': lambda f: { + 'label': 'Data (%s)' % (_('Inicial - Final')), + 'widget': RangeWidgetOverride} + }} model = Protocolo fields = ['numero', 'tipo_documento', @@ -154,13 +153,6 @@ class ProtocoloFilterSet(django_filters.FilterSet): class DocumentoAdministrativoFilterSet(django_filters.FilterSet): - filter_overrides = {models.DateField: { - 'filter_class': django_filters.DateFromToRangeFilter, - 'extra': lambda f: { - 'label': 'Data (%s)' % (_('Inicial - Final')), - 'widget': RangeWidgetOverride} - }} - ano = django_filters.ChoiceFilter(required=False, label='Ano', choices=ANO_CHOICES) @@ -176,6 +168,12 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet): o = AnoNumeroOrderingFilter() class Meta: + filter_overrides = {models.DateField: { + 'filter_class': django_filters.DateFromToRangeFilter, + 'extra': lambda f: { + 'label': 'Data (%s)' % (_('Inicial - Final')), + 'widget': RangeWidgetOverride} + }} model = DocumentoAdministrativo fields = ['tipo', 'numero', @@ -1000,3 +998,80 @@ def filtra_tramitacao_adm_destino_and_status(status, destino): status=status, unidade_tramitacao_destino=destino).distinct().values_list( 'documento_id', flat=True) + +class FichaPesquisaAdmForm(forms.Form): + + logger = logging.getLogger(__name__) + + tipo_documento = forms.ModelChoiceField( + label=TipoDocumentoAdministrativo._meta.verbose_name, + queryset=TipoDocumentoAdministrativo.objects.all(), + empty_label='Selecione') + + data_inicial = forms.DateField( + label='Data Inicial', + widget=forms.DateInput(format='%d/%m/%Y') + ) + + data_final = forms.DateField( + label='Data Final', + widget=forms.DateInput(format='%d/%m/%Y') + ) + + def __init__(self, *args, **kwargs): + super(FichaPesquisaAdmForm, self).__init__(*args, **kwargs) + + row1 = to_row( + [('tipo_documento', 6), + ('data_inicial', 3), + ('data_final', 3)]) + + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset( + ('Formulário de Ficha'), + row1, + form_actions(label='Pesquisar') + ) + ) + + def clean(self): + super(FichaPesquisaAdmForm, self).clean() + + if not self.is_valid(): + return self.cleaned_data + + cleaned_data = self.cleaned_data + + if not self.is_valid(): + return cleaned_data + + if cleaned_data['data_final'] < cleaned_data['data_inicial']: + self.logger.error("A Data Final ({}) não pode ser menor que a Data Inicial ({})." + .format(cleaned_data['data_final'], cleaned_data['data_inicial'])) + raise ValidationError(_( + 'A Data Final não pode ser menor que a Data Inicial')) + + return cleaned_data + + +class FichaSelecionaAdmForm(forms.Form): + documento = forms.ModelChoiceField( + widget=forms.RadioSelect, + queryset=DocumentoAdministrativo.objects.all(), + label='') + + def __init__(self, *args, **kwargs): + super(FichaSelecionaAdmForm, self).__init__(*args, **kwargs) + + row1 = to_row( + [('documento', 12)]) + + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset( + ('Selecione a ficha que deseja imprimir'), + row1, + form_actions(label='Gerar Impresso') + ) + ) \ No newline at end of file diff --git a/sapl/protocoloadm/migrations/0010_auto_20181212_1900.py b/sapl/protocoloadm/migrations/0010_auto_20181212_1900.py new file mode 100644 index 000000000..df5d6c078 --- /dev/null +++ b/sapl/protocoloadm/migrations/0010_auto_20181212_1900.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.8 on 2018-12-12 21:00 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('protocoloadm', '0009_merge'), + ] + + operations = [ + migrations.AlterField( + model_name='protocolo', + name='justificativa_anulacao', + field=models.CharField(blank=True, max_length=260, verbose_name='Motivo'), + ), + ] diff --git a/sapl/protocoloadm/models.py b/sapl/protocoloadm/models.py index aa4e7c0c2..1b061fa18 100644 --- a/sapl/protocoloadm/models.py +++ b/sapl/protocoloadm/models.py @@ -93,7 +93,7 @@ class Protocolo(models.Model): user_anulacao = models.CharField(max_length=20, blank=True) ip_anulacao = models.CharField(max_length=15, blank=True) justificativa_anulacao = models.CharField( - max_length=60, blank=True, verbose_name=_('Motivo')) + max_length=260, blank=True, verbose_name=_('Motivo')) timestamp_anulacao = models.DateTimeField(blank=True, null=True) class Meta: diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index 1d68dfce2..8dd7e9e47 100755 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -29,6 +29,7 @@ from sapl.base.signals import tramitacao_signal from sapl.comissoes.models import Comissao from sapl.crud.base import Crud, CrudAux, MasterDetailCrud, make_pagination from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa +from sapl.materia.views import gerar_pdf_impressos from sapl.parlamentares.models import Legislatura, Parlamentar from sapl.protocoloadm.models import Protocolo from sapl.utils import (create_barcode, get_base_url, get_client_ip, @@ -38,7 +39,7 @@ from sapl.utils import (create_barcode, get_base_url, get_client_ip, from .forms import (AcompanhamentoDocumentoForm, AnularProcoloAdmForm, DocumentoAcessorioAdministrativoForm, DocumentoAdministrativoFilterSet, - DocumentoAdministrativoForm, ProtocoloDocumentForm, + DocumentoAdministrativoForm, FichaPesquisaAdmForm, FichaSelecionaAdmForm, ProtocoloDocumentForm, ProtocoloFilterSet, ProtocoloMateriaForm, TramitacaoAdmEditForm, TramitacaoAdmForm, DesvincularDocumentoForm, DesvincularMateriaForm, @@ -1073,3 +1074,98 @@ class DesvincularMateriaView(PermissionRequiredMixin, FormView): materia.numero_protocolo = None materia.save() return redirect(self.get_success_url()) + + +class ImpressosView(PermissionRequiredMixin, TemplateView): + template_name = 'materia/impressos/impressos.html' + permission_required = ('materia.can_access_impressos', ) + + +class FichaPesquisaAdmView(PermissionRequiredMixin, FormView): + form_class = FichaPesquisaAdmForm + template_name = 'materia/impressos/ficha.html' + permission_required = ('materia.can_access_impressos', ) + + def form_valid(self, form): + tipo_documento = form.data['tipo_documento'] + data_inicial = form.data['data_inicial'] + data_final = form.data['data_final'] + + url = reverse('sapl.materia:impressos_ficha_seleciona_adm') + url = url + '?tipo=%s&data_inicial=%s&data_final=%s' % ( + tipo_documento, data_inicial, data_final) + + return HttpResponseRedirect(url) + + +class FichaSelecionaAdmView(PermissionRequiredMixin, FormView): + logger = logging.getLogger(__name__) + form_class = FichaSelecionaAdmForm + template_name = 'materia/impressos/ficha_seleciona.html' + permission_required = ('materia.can_access_impressos', ) + + def get_context_data(self, **kwargs): + if ('tipo' not in self.request.GET or + 'data_inicial' not in self.request.GET or + 'data_final' not in self.request.GET): + return HttpResponseRedirect(reverse( + 'sapl.materia:impressos_ficha_pesquisa_adm')) + + context = super(FichaSelecionaAdmView, self).get_context_data( + **kwargs) + + tipo = self.request.GET['tipo'] + data_inicial = datetime.strptime( + self.request.GET['data_inicial'], "%d/%m/%Y").date() + data_final = datetime.strptime( + self.request.GET['data_final'], "%d/%m/%Y").date() + + documento_list = DocumentoAdministrativo.objects.filter( + tipo=tipo, + data__range=(data_inicial, data_final)) + context['quantidade'] = len(documento_list) + documento_list = documento_list[:100] + + context['form'].fields['documento'].choices = [ + (d.id, str(d)) for d in documento_list] + + username = self.request.user.username + + if context['quantidade'] > 100: + self.logger.info('user=' + username + '. Sua pesquisa (tipo={}, data_inicial={}, data_final={}) retornou mais do que ' + '100 impressos. Por questões de ' + 'performance, foram retornados ' + 'apenas os 100 primeiros. Caso ' + 'queira outros, tente fazer uma ' + 'pesquisa mais específica'.format(tipo, data_inicial, data_final)) + messages.info(self.request, _('Sua pesquisa retornou mais do que ' + '100 impressos. Por questões de ' + 'performance, foram retornados ' + 'apenas os 100 primeiros. Caso ' + 'queira outros, tente fazer uma ' + 'pesquisa mais específica')) + + return context + + def form_valid(self, form): + context = {} + username = self.request.user.username + + try: + self.logger.debug( + "user=" + username + ". Tentando obter objeto DocumentoAdministrativo com id={}".format(form.data['documento'])) + documento = DocumentoAdministrativo.objects.get( + id=form.data['documento']) + except ObjectDoesNotExist: + self.logger.error( + "user=" + username + ". Este DocumentoAdministrativo não existe (id={}).".format(form.data['documento'])) + mensagem = _('Este Documento Administrativo não existe.') + self.messages.add_message(self.request, messages.INFO, mensagem) + + return self.render_to_response(context) + if len(documento.assunto) > 301: + documento.assunto = documento.assunto[0:300] + '[...]' + context['documento'] = documento + + return gerar_pdf_impressos(self.request, context, + 'materia/impressos/ficha_adm_pdf.html') \ No newline at end of file diff --git a/sapl/rules/map_rules.py b/sapl/rules/map_rules.py index aa691da5e..f9b63fd4b 100644 --- a/sapl/rules/map_rules.py +++ b/sapl/rules/map_rules.py @@ -139,6 +139,7 @@ rules_group_norma = { (norma.NormaRelacionada, __base__), (norma.AnexoNormaJuridica, __base__), (norma.AutoriaNorma, __base__), + (norma.NormaEstatisticas, __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 @@ -242,6 +243,7 @@ rules_group_geral = { (norma.AssuntoNorma, __base__), (norma.TipoNormaJuridica, __base__), (norma.TipoVinculoNormaJuridica, __base__), + (norma.NormaEstatisticas, __base__), (parlamentares.Legislatura, __base__), (parlamentares.SessaoLegislativa, __base__), diff --git a/sapl/sessao/forms.py b/sapl/sessao/forms.py index 3020d93b2..5a3cde342 100644 --- a/sapl/sessao/forms.py +++ b/sapl/sessao/forms.py @@ -23,9 +23,8 @@ from sapl.parlamentares.models import Parlamentar, Legislatura, Mandato from sapl.utils import (RANGE_DIAS_MES, RANGE_MESES, MateriaPesquisaOrderingFilter, autor_label, autor_modal, timezone) - from .models import (Bancada, Bloco, ExpedienteMateria, JustificativaAusencia, - Orador, OradorExpediente, OrdemDia, SessaoPlenaria, + Orador, OradorExpediente, OrdemDia, PresencaOrdemDia, SessaoPlenaria, SessaoPlenariaPresenca, TipoJustificativa, TipoResultadoVotacao, OcorrenciaSessao, RegistroVotacao, RetiradaPauta, TipoRetiradaPauta) @@ -872,7 +871,8 @@ class JustificativaAusenciaForm(ModelForm): ordens = OrdemDia.objects.filter(q) expedientes = ExpedienteMateria.objects.filter(q) legislatura = kwargs['initial']['sessao_plenaria'].legislatura - mandato = Mandato.objects.filter(legislatura=legislatura) + mandato = Mandato.objects.filter( + legislatura=legislatura).order_by('parlamentar__nome_parlamentar') parlamentares = [m.parlamentar for m in mandato] @@ -881,9 +881,14 @@ class JustificativaAusenciaForm(ModelForm): presencas = SessaoPlenariaPresenca.objects.filter( q).order_by('parlamentar__nome_parlamentar') + presencas_ordem = PresencaOrdemDia.objects.filter( + q).order_by('parlamentar__nome_parlamentar') presentes = [p.parlamentar for p in presencas] - setFinal = set(parlamentares) - set(presentes) + presentes_ordem = [p.parlamentar for p in presencas_ordem] + + presentes_ambos = set(presentes).intersection(set(presentes_ordem)) + setFinal = set(parlamentares) - presentes_ambos self.fields['materias_do_expediente'].choices = [ (e.id, e.materia) for e in expedientes] diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index 04cbf85cd..b705e302f 100755 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -2802,6 +2802,7 @@ class PautaSessaoDetailView(DetailView): mat = {'id': m.materia_id, 'ementa': ementa, + 'observacao': m.observacao, 'titulo': titulo, 'numero': numero, 'resultado': resultado, @@ -2865,6 +2866,7 @@ class PautaSessaoDetailView(DetailView): mat = {'id': o.materia_id, 'ementa': ementa, + 'observacao': o.observacao, 'titulo': titulo, 'numero': numero, 'resultado': resultado, diff --git a/sapl/settings.py b/sapl/settings.py index 0c3f9935e..d30b4df3c 100755 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -15,6 +15,7 @@ See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ """ import logging import socket +import sys from decouple import config from dj_database_url import parse as db_url @@ -335,12 +336,16 @@ LOGGING = { } -def excepthook(*args): - logging.getLogger(BASE_DIR.name).error( - 'Uncaught exception:', exc_info=args) +def uncaught_exceptions(type, value, error_traceback): + import traceback + logger = logging.getLogger(__name__) + error_msg = ''.join(traceback.format_tb(error_traceback)) + logger.error(error_msg) + print(error_msg) -# sys.excepthook = excepthook""" +# captura exceções que não foram tratadas +sys.excepthook = uncaught_exceptions PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.PBKDF2PasswordHasher', # default diff --git a/sapl/templates/base.html b/sapl/templates/base.html index d2f4cca33..0291ef7ba 100644 --- a/sapl/templates/base.html +++ b/sapl/templates/base.html @@ -184,7 +184,7 @@ Desenvolvido pelo Interlegis em software livre e aberto. - Release: 3.1.137 + Release: 3.1.138

diff --git a/sapl/templates/base/EstatisticasAcessoNormas_filter.html b/sapl/templates/base/EstatisticasAcessoNormas_filter.html new file mode 100644 index 000000000..a8246e22d --- /dev/null +++ b/sapl/templates/base/EstatisticasAcessoNormas_filter.html @@ -0,0 +1,64 @@ +{% extends "crud/list.html" %} +{% load i18n %} +{% load crispy_forms_tags %} + +{% block base_content %} + {% if not show_results %} + {% crispy filter.form %} + {% endif %} + {% if show_results %} +
+ {% trans 'Fazer nova pesquisa' %} +
+



+ PARÂMETROS DE PESQUISA:
+  Ano: {{ ano }}
+
+ {% if normas_mes|length == 0 %} +
+

{% trans 'Não foi encontrada nenhuma norma com os parâmetros buscados.'%}

+ {% elif normas_mes|length == meses_sem_acesso|length %} +
+

{% trans 'Nenhuma norma teve acesso neste ano.'%}

+ {% else %} + {% for mes, normas in normas_mes.items %} +
+ + + + + + +

Mês: {{ mes }}

+ {% if not mes in meses_sem_acesso %} + + + + + + + + + + {% for n in normas %} + {% if n.1 > 0 %} + + + + + + {% endif %} + {% endfor %} + +
NormaEmentaAcessos
+ {{n.0.tipo.descricao}} - {{n.0.tipo.sigla}} {{n.0.numero}}/{{n.0.ano}} + {{n.0.ementa}}
{{n.0.observacao}}
{{n.1}}
+ {% else %} +

{% trans 'Nenhuma norma deste mês teve acessos.'%}

+

+ {% endif %} +
+ {% endfor %} + {% endif %} + {% endif %} +{% endblock base_content %} diff --git a/sapl/templates/base/RelatorioHistoricoTramitacao_filter.html b/sapl/templates/base/RelatorioHistoricoTramitacao_filter.html index cca46d3ad..1151cd094 100644 --- a/sapl/templates/base/RelatorioHistoricoTramitacao_filter.html +++ b/sapl/templates/base/RelatorioHistoricoTramitacao_filter.html @@ -30,7 +30,7 @@ {{materia.tipo.descricao}} - {{materia.tipo.sigla}} {{materia.numero}}/{{materia.ano}} - {{materia.ementa}} + {{materia.ementa}}
{{materia.observacao}} {% endfor %} diff --git a/sapl/templates/base/RelatorioMateriasPorAutor_filter.html b/sapl/templates/base/RelatorioMateriasPorAutor_filter.html index 6a8ed41a8..35e9aa50c 100644 --- a/sapl/templates/base/RelatorioMateriasPorAutor_filter.html +++ b/sapl/templates/base/RelatorioMateriasPorAutor_filter.html @@ -51,7 +51,7 @@ {{materia.tipo.sigla}} {{materia.numero}}/{{materia.ano}} - {% autoescape off %}{{materia.ementa}}{% endautoescape %} + {% autoescape off %}{{materia.ementa}}
{{materia.observacao}}{% endautoescape %} {% if materia.autoria_set.first != materia.autoria_set.last %} {% for autor in materia.autoria_set.all %} diff --git a/sapl/templates/base/RelatorioNormaMes_filter.html b/sapl/templates/base/RelatorioNormaMes_filter.html new file mode 100644 index 000000000..d4f8d5b30 --- /dev/null +++ b/sapl/templates/base/RelatorioNormaMes_filter.html @@ -0,0 +1,67 @@ +{% extends "crud/list.html" %} +{% load i18n %} +{% load crispy_forms_tags %} + +{% block base_content %} + {% if not show_results %} + {% crispy filter.form %} + {% endif %} + + {% if show_results %} +
+ {% trans 'Fazer nova pesquisa' %} +
+



+ PARÂMETROS DE PESQUISA:
+  Ano: {{ ano }}
+
+ {% if normas_mes|length == 0 %} +
+

{% trans 'Não foi encontrada nenhuma norma com os parâmetros buscados.'%}

+ {% endif %} + {% for mes, normas in normas_mes.items %} +
+ + + + + + +

Mês: {{ mes }}

+ + + + {% for k, v in quant_normas_mes.items %} + {% if k == mes %} + {% if v > 1 %} + + {% else %} + + {% endif %} + {% endif %} + {% endfor %} + + +
Quantidade encontrada no mês: {{ v }} normas.Quantidade encontrada no mês: 1 norma.
+ + + + + + + + + {% for n in normas %} + + + + + {% endfor %} + +
NormaEmenta
+ {{n.tipo.descricao}} - {{n.tipo.sigla}} {{n.numero}}/{{n.ano}} + {{n.ementa}}
{{n.observacao}}
+
+ {% endfor %} + {% endif %} +{% endblock base_content %} diff --git a/sapl/templates/base/RelatorioNormasVigencia_filter.html b/sapl/templates/base/RelatorioNormasVigencia_filter.html new file mode 100644 index 000000000..6412b5b20 --- /dev/null +++ b/sapl/templates/base/RelatorioNormasVigencia_filter.html @@ -0,0 +1,57 @@ +{% extends "crud/list.html" %} +{% load i18n %} +{% load crispy_forms_tags %} + +{% block base_content %} + {% if not show_results %} + {% crispy filter.form %} + {% endif %} + + {% if show_results %} +
+ {% trans 'Fazer nova pesquisa' %} +
+



+ PARÂMETROS DE PESQUISA:
+  Ano: {{ ano }}
+  Vigência: {{ vigencia }}
+ {% if object_list %} +
+ {% if object_list|length > 1 %} +

Foram encontradas {{object_list|length}} normas.

+ {% else %} +

Foi encontrada 1 norma.

+ {% endif %} +
+ + + + + + + + + {% for norma in object_list %} + + + + + {% endfor %} + +
NormaEmenta
+ {{norma.tipo.descricao}} - {{norma.tipo.sigla}} {{norma.numero}}/{{norma.ano}} + {{norma.ementa}}
{{norma.observacao}}
+ {% else %} + + + + + + +
Não foi encontrada nenhuma norma com os parâmetros buscados.
+ {% endif %} +
+

Estatísticas das normas do ano:


+

{{quant_vigente}} vigente(s) / {{quant_nao_vigente}} não vigente(s)

+ {% endif %} +{% endblock base_content %} diff --git a/sapl/templates/base/layouts.yaml b/sapl/templates/base/layouts.yaml index ef1e53d2a..4f6bbd45d 100644 --- a/sapl/templates/base/layouts.yaml +++ b/sapl/templates/base/layouts.yaml @@ -23,6 +23,9 @@ AppConfig: {% trans 'Textos Articulados' %}: - texto_articulado_proposicao texto_articulado_materia texto_articulado_norma + {% trans 'Estatísticas de acesso' %}: + - estatisticas_acesso_normas + {% trans 'Assinaturas' %}: - assinatura_ata diff --git a/sapl/templates/base/relatorios_list.html b/sapl/templates/base/relatorios_list.html index 78192855a..87f8933be 100644 --- a/sapl/templates/base/relatorios_list.html +++ b/sapl/templates/base/relatorios_list.html @@ -48,6 +48,20 @@ Audiência Pública Audiência Pública com o tipo. + + Normas por mês + Normas publicadas por mês. + + + Normas por vigência + Normas vigentes ou não vigentes. + + {% if estatisticas_acesso_normas %} + + Estatísticas de acesso de Normas. + Normas por acesso. + + {% endif %} diff --git a/sapl/templates/materia/impressos/ficha_adm_pdf.html b/sapl/templates/materia/impressos/ficha_adm_pdf.html new file mode 100644 index 000000000..b5de1e0a9 --- /dev/null +++ b/sapl/templates/materia/impressos/ficha_adm_pdf.html @@ -0,0 +1,107 @@ + + + + + + + + + + +
+ +
+ + PROCESSO Nº: {{ documento.numero }} / {{documento.ano}}
+
+ + +
+ + {{documento.tipo}}: {{documento.numero}} / {{documento.ano}}
+
+ + + +
+ + Data de entrada: {{documento.data}}
+
+ + {% if documento.protocolo%} + + +
+
+ Protocolo: {{materia.protocolo}}
+
+ {% endif %} + + + + +
+
+ Ementa: {{documento.assunto}} +
+
+ + +
+ + + diff --git a/sapl/templates/materia/impressos/impressos.html b/sapl/templates/materia/impressos/impressos.html index 71f9f6f6f..5f6001027 100644 --- a/sapl/templates/materia/impressos/impressos.html +++ b/sapl/templates/materia/impressos/impressos.html @@ -26,7 +26,11 @@ - +
+

Capa Documento Administrativo

+ {#

Guia de Remessa

#} {#