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 %}
+
+
+
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 %}
+
+ {% 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 %}
+
+
+ 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 %}
+ | Quantidade encontrada no mês: {{ v }} normas. |
+ {% else %}
+ Quantidade encontrada no mês: 1 norma. |
+ {% endif %}
+ {% endif %}
+ {% endfor %}
+
+
+
+
+
+ {% 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 %}
+
+
+ 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 %}
+
+
+ {% 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
#}
{#