Browse Source

Merge branch '3.1.x' into votacao-bloco

pull/2416/head
Edward 7 years ago
committed by GitHub
parent
commit
e29aeeac61
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      docker-compose.yml
  2. 2
      requirements/requirements.txt
  3. 24
      sapl/api/forms.py
  4. 136
      sapl/base/forms.py
  5. 20
      sapl/base/migrations/0027_appconfig_relatorios_atos.py
  6. 20
      sapl/base/migrations/0028_appconfig_estatisticas_acesso_normas.py
  7. 19
      sapl/base/migrations/0029_remove_appconfig_relatorios_atos.py
  8. 8
      sapl/base/models.py
  9. 16
      sapl/base/urls.py
  10. 159
      sapl/base/views.py
  11. 20
      sapl/comissoes/migrations/0019_auto_20181214_1023.py
  12. 1
      sapl/comissoes/models.py
  13. 3
      sapl/comissoes/tests/test_comissoes.py
  14. 3
      sapl/compilacao/templatetags/compilacao_filters.py
  15. 3
      sapl/compilacao/views.py
  16. 55
      sapl/materia/forms.py
  17. 7
      sapl/materia/urls.py
  18. 13
      sapl/norma/forms.py
  19. 25
      sapl/norma/migrations/0017_normaestatisticas.py
  20. 12
      sapl/norma/models.py
  21. 2
      sapl/norma/tests/test_norma.py
  22. 16
      sapl/norma/views.py
  23. 4
      sapl/parlamentares/views.py
  24. 103
      sapl/protocoloadm/forms.py
  25. 20
      sapl/protocoloadm/migrations/0010_auto_20181212_1900.py
  26. 2
      sapl/protocoloadm/models.py
  27. 98
      sapl/protocoloadm/views.py
  28. 2
      sapl/rules/map_rules.py
  29. 13
      sapl/sessao/forms.py
  30. 2
      sapl/sessao/views.py
  31. 13
      sapl/settings.py
  32. 2
      sapl/templates/base.html
  33. 64
      sapl/templates/base/EstatisticasAcessoNormas_filter.html
  34. 2
      sapl/templates/base/RelatorioHistoricoTramitacao_filter.html
  35. 2
      sapl/templates/base/RelatorioMateriasPorAutor_filter.html
  36. 67
      sapl/templates/base/RelatorioNormaMes_filter.html
  37. 57
      sapl/templates/base/RelatorioNormasVigencia_filter.html
  38. 3
      sapl/templates/base/layouts.yaml
  39. 14
      sapl/templates/base/relatorios_list.html
  40. 107
      sapl/templates/materia/impressos/ficha_adm_pdf.html
  41. 6
      sapl/templates/materia/impressos/impressos.html
  42. 16
      sapl/templates/protocoloadm/comprovante.html
  43. 7
      sapl/templates/protocoloadm/protocolo_filter.html
  44. 2
      sapl/templates/sessao/blocos_ata/expedientes.html
  45. 8
      sapl/templates/sessao/blocos_ata/identificacao_basica.html
  46. 4
      sapl/templates/sessao/blocos_ata/lista_presenca.html
  47. 15
      sapl/templates/sessao/blocos_ata/lista_presenca_ordem_dia.html
  48. 2
      sapl/templates/sessao/blocos_ata/materias_expediente.html
  49. 37
      sapl/templates/sessao/blocos_ata/materias_ordem_dia.html
  50. 2
      sapl/templates/sessao/blocos_ata/mesa_diretora.html
  51. 4
      sapl/templates/sessao/blocos_ata/ocorrencias_da_sessao.html
  52. 2
      sapl/templates/sessao/blocos_ata/oradores_expediente.html
  53. 10
      sapl/templates/sessao/blocos_ata/oradores_explicacoes.html
  54. 4
      sapl/templates/sessao/pauta_sessao_detail.html
  55. 2
      setup.py

2
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

2
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

24
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))

136
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)

20
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'),
),
]

20
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'),
),
]

19
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',
),
]

8
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'),

16
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'),

159
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'

20
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)'),
),
]

1
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(

3
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

3
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

3
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

55
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']

7
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 = [

13
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']

25
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')),
],
),
]

12
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,

2
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'),

16
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):

4
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

103
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')
)
)

20
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'),
),
]

2
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:

98
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')

2
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__),

13
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]

2
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,

13
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

2
sapl/templates/base.html

@ -184,7 +184,7 @@
<small>
Desenvolvido pelo <a href="http://www.interlegis.leg.br/">Interlegis</a> em software livre e aberto.
</small>
<span>Release: 3.1.137</span>
<span>Release: 3.1.138</span>
</p>
</div>
<div class="col-md-4">

64
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 %}
<div class="actions btn-group pull-right" role="group">
<a href="{% url 'sapl.base:estatisticas_acesso' %}" class="btn btn-default">{% trans 'Fazer nova pesquisa' %}</a>
</div>
<br /><br /><br /><br />
<b>PARÂMETROS DE PESQUISA:<br /></b>
&emsp;Ano: {{ ano }} <br />
<br/>
{% if normas_mes|length == 0 %}
<br>
<h3>{% trans 'Não foi encontrada nenhuma norma com os parâmetros buscados.'%}</h3>
{% elif normas_mes|length == meses_sem_acesso|length %}
<br>
<h3>{% trans 'Nenhuma norma teve acesso neste ano.'%}</h3>
{% else %}
{% for mes, normas in normas_mes.items %}
<div style="overflow:auto; ">
<table class="table table-bordered table-hover" style="margin-bottom: 0px;">
<thead class="thead-default">
<tr>
<th><h3 style="text-align:center;">Mês: {{ mes }}</h3></th>
</tr>
</thead>
</table>
{% if not mes in meses_sem_acesso %}
<table class="table table-bordered table-hover" style="width:100%; margin-bottom: 30px;">
<thead class="thead-default" >
<tr class="active">
<th>Norma</th>
<th>Ementa</th>
<th>Acessos</th>
</tr>
</thead>
<tbody>
{% for n in normas %}
{% if n.1 > 0 %}
<tr>
<td><a href="{% url 'sapl.norma:normajuridica_detail' n.0.pk %}">
{{n.0.tipo.descricao}} - {{n.0.tipo.sigla}} {{n.0.numero}}/{{n.0.ano}}
</a></td>
<td>{{n.0.ementa}}<br>{{n.0.observacao}}</td>
<td>{{n.1}}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
{% else %}
<h3 style="text-align:center;">{% trans 'Nenhuma norma deste mês teve acessos.'%}</h3>
<br><br>
{% endif %}
</div>
{% endfor %}
{% endif %}
{% endif %}
{% endblock base_content %}

2
sapl/templates/base/RelatorioHistoricoTramitacao_filter.html

@ -30,7 +30,7 @@
<td><a href="{% url 'sapl.materia:tramitacao_list' materia.pk %}">
{{materia.tipo.descricao}} - {{materia.tipo.sigla}} {{materia.numero}}/{{materia.ano}}
</a></td>
<td>{{materia.ementa}}</td>
<td>{{materia.ementa}}<br>{{materia.observacao}}</td>
</tr>
{% endfor %}
</tbody>

2
sapl/templates/base/RelatorioMateriasPorAutor_filter.html

@ -51,7 +51,7 @@
<td><a href="{% url 'sapl.materia:materialegislativa_detail' materia.pk %}">
{{materia.tipo.sigla}} {{materia.numero}}/{{materia.ano}}
</a></td>
<td>{% autoescape off %}{{materia.ementa}}{% endautoescape %}</td>
<td>{% autoescape off %}{{materia.ementa}}<br>{{materia.observacao}}{% endautoescape %}</td>
<td>
{% if materia.autoria_set.first != materia.autoria_set.last %}
{% for autor in materia.autoria_set.all %}

67
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 %}
<div class="actions btn-group pull-right" role="group">
<a href="{% url 'sapl.base:normas_por_mes' %}" class="btn btn-default">{% trans 'Fazer nova pesquisa' %}</a>
</div>
<br /><br /><br /><br />
<b>PARÂMETROS DE PESQUISA:<br /></b>
&emsp;Ano: {{ ano }} <br />
<br/>
{% if normas_mes|length == 0 %}
<br>
<h3>{% trans 'Não foi encontrada nenhuma norma com os parâmetros buscados.'%}</h3>
{% endif %}
{% for mes, normas in normas_mes.items %}
<div style="overflow:auto; ">
<table class="table table-bordered table-hover" style="margin-bottom: 0px;">
<thead class="thead-default">
<tr>
<th><h3 style="text-align:center;">Mês: {{ mes }}</h3></th>
</tr>
</thead>
</table>
<table class="table table-bordered table-hover" style="width:100%; margin-bottom: 0px;">
<thead class="thead-default" >
<tr class="active">
{% for k, v in quant_normas_mes.items %}
{% if k == mes %}
{% if v > 1 %}
<th>Quantidade encontrada no mês: {{ v }} normas.</th>
{% else %}
<th>Quantidade encontrada no mês: 1 norma.</th>
{% endif %}
{% endif %}
{% endfor %}
</tr>
</thead>
</table>
<table class="table table-bordered table-hover" style="width:100%; margin-bottom: 30px;">
<thead class="thead-default" >
<tr class="active">
<th>Norma</th>
<th>Ementa</th>
</tr>
</thead>
<tbody>
{% for n in normas %}
<tr>
<td><a href="{% url 'sapl.norma:normajuridica_detail' n.pk %}">
{{n.tipo.descricao}} - {{n.tipo.sigla}} {{n.numero}}/{{n.ano}}
</a></td>
<td>{{n.ementa}}<br>{{n.observacao}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endfor %}
{% endif %}
{% endblock base_content %}

57
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 %}
<div class="actions btn-group pull-right" role="group">
<a href="{% url 'sapl.base:normas_por_vigencia' %}" class="btn btn-default">{% trans 'Fazer nova pesquisa' %}</a>
</div>
<br /><br /><br /><br />
<b>PARÂMETROS DE PESQUISA:<br /></b>
&emsp;Ano: {{ ano }} <br />
&emsp;Vigência: {{ vigencia }} <br />
{% if object_list %}
<br/>
{% if object_list|length > 1 %}
<h3>Foram encontradas {{object_list|length}} normas.</h3>
{% else %}
<h3>Foi encontrada 1 norma.</h3>
{% endif %}
<br/>
<table class="table table-bordered table-hover" style="width:100%">
<thead class="thead-default" >
<tr class="active">
<th>Norma</th>
<th>Ementa</th>
</tr>
</thead>
<tbody>
{% for norma in object_list %}
<tr>
<td><a href="{% url 'sapl.norma:normajuridica_detail' norma.pk %}">
{{norma.tipo.descricao}} - {{norma.tipo.sigla}} {{norma.numero}}/{{norma.ano}}
</a></td>
<td>{{norma.ementa}}<br>{{norma.observacao}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<table class="table table-bordered table-hover" style="margin-top:30px;">
<thead class="thead-default" >
<tr class="active">
<th> Não foi encontrada nenhuma norma com os parâmetros buscados.</th>
</tr>
</thead>
</table>
{% endif %}
<br>
<h3>Estatísticas das normas do ano:</h3><br>
<h3>{{quant_vigente}} vigente(s) / {{quant_nao_vigente}} não vigente(s)</h3>
{% endif %}
{% endblock base_content %}

3
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

14
sapl/templates/base/relatorios_list.html

@ -48,6 +48,20 @@
<td><a href="{% url 'sapl.base:audiencia' %}">Audiência Pública</a></td>
<td> Audiência Pública com o tipo. </td>
</tr>
<tr>
<td><a href="{% url 'sapl.base:normas_por_mes' %}">Normas por mês</a></td>
<td> Normas publicadas por mês. </td>
</tr>
<tr>
<td><a href="{% url 'sapl.base:normas_por_vigencia' %}">Normas por vigência</a></td>
<td> Normas vigentes ou não vigentes. </td>
</tr>
{% if estatisticas_acesso_normas %}
<tr>
<td><a href="{% url 'sapl.base:estatisticas_acesso' %}">Estatísticas de acesso de Normas.</a></td>
<td> Normas por acesso. </td>
</tr>
{% endif %}
</tbody>
</table>
</fieldset>

107
sapl/templates/materia/impressos/ficha_adm_pdf.html

@ -0,0 +1,107 @@
<!DOCTYPE html>
<html><head>
<link rel="stylesheet" href="basicsstyles.css" type="text/css" media="screen">
<link rel="stylesheet" href="printstyles.css" type="text/css" media="print">
<style type="text/css" media="all">
body
{
font-size: small;
font-family: Arial;
line-height: 175%;
background-color: transparent;
margin: 5pt 5pt 0pt 0pt;
}
#voltar
{
position: absolute;
top: 50pt;
left: 500pt;
}
#ementa_texto
{
font-family: Arial;
line-height: 150%;
border-style: none;
text-align: justify;
padding: 0pt 5pt 0pt 0pt;
margin-right:100px;
font-size: small;
height:130px;
}
#titulo
{
font-size: medium;
margin-right:100px;
text-align: center;
}
#despacho_inicial
{
font-family: Arial;
line-height: 150%;
border-style: none;
text-align: justify;
font-size: small;
height:130px;
padding: 0pt 5pt 0pt 0pt;
margin-right:100px;
}
@media print {
#voltar { display: none; }
}
</style>
<body style="margin-left:-50px;margin-right:180px; margin-top: -50px">
<div style="page-break-inside: avoid;">
<justify>
<div id="titulo">
<!-- Informa o processo -->
<strong class="text_pdf">PROCESSO Nº: {{ documento.numero }} / {{documento.ano}}</strong><br>
</div>
<table border=0>
<td height="60pt" valign=top>
<!-- Informa o tipo da matéria -->
<strong class="text_pdf">{{documento.tipo}}:</strong> <span class="text_pdf"> {{documento.numero}} / {{documento.ano}} </span><br>
</td>
</table>
<table border=0>
<td height="60pt" valign=top>
<!-- Informa a Data de Entrada -->
<strong class="text_pdf">Data de entrada:</strong> <span class="text_pdf"> {{documento.data}}</span></br>
</td>
</table>
{% if documento.protocolo%}
<table border=0>
<td height="60pt" valign=top>
<div>
<strong class="text_pdf">Protocolo: </strong><span class="text_pdf">{{materia.protocolo}}</span><br>
</td>
</table>
{% endif %}
<!-- Ementa -->
<table border=0>
<td>
<div id="ementa_texto">
<strong class="text_pdf">Ementa:</strong> <span class="text_pdf">{{documento.assunto}}</span>
</div>
</td>
</table>
</div>
</justify>
</body>

6
sapl/templates/materia/impressos/impressos.html

@ -26,7 +26,11 @@
<ul>
<li><a href="{% url 'sapl.materia:impressos_norma_pesquisa' %}">Pesquisar</a></li>
</ul>
</br>
<h2 class="legend">Capa Documento Administrativo</h2>
<ul>
<li><a href="{% url 'sapl.materia:impressos_ficha_pesquisa_adm' %}">Pesquisar</a></li>
</ul>
{#<h2 class="legend">Guia de Remessa</h2>#}
{# <ul>#}

16
sapl/templates/protocoloadm/comprovante.html

@ -14,15 +14,21 @@
th, td {
padding: 5px;
}
@media print {
.hide-print {
display : none;
}
}
@page {
size: auto; /* auto is the initial value */
margin: 0mm; /* this affects the margin in the printer settings */
}
</style>
<div align="center">
<input type="submit" value="Imprimir" onclick="window.print();" class="btn btn-success"/>
<input type="submit" value="Fechar" onclick="window.close();" class="btn btn-success"/>
<input type="submit" value="Imprimir" onclick="window.print();" class="btn btn-success hide-print"/>
<input type="submit" value="Fechar" onclick="window.close();" class="btn btn-success hide-print"/>
</div>
<br />
<table>
<tr><td colspan="2" align="center">
<img height="90" width="90"

7
sapl/templates/protocoloadm/protocolo_filter.html

@ -62,6 +62,13 @@
<strong>Anulado por: </strong>{{ p.user_anulacao }} - IP {{ p.ip_anulacao }}</br>
<strong>Motivo Anulação: </strong>{{ p.justificativa_anulacao }}</br>
{% endif %}
{% if p.tipo_documento and p.documentoadministrativo_set.first %}
<strong>Documentos vinculados: </strong>
<a href="{% url 'sapl.protocoloadm:documentoadministrativo_detail' p.documentoadministrativo_set.first.pk %}">
{{ p.documentoadministrativo_set.first.tipo }} - {{ p.documentoadministrativo_set.first.numero }} /
{{ p.documentoadministrativo_set.first.ano }}
</a>
{% endif %}
</td>
</tr>
{% endfor %}

2
sapl/templates/sessao/blocos_ata/expedientes.html

@ -1,9 +1,11 @@
<fieldset>
<p align="justify">
{% if expedientes %}
<strong>Expedientes: </strong>
{% for e in expedientes %}
<b>{{e.tipo}}</b>:
{{e.conteudo|striptags|safe}}
{% endfor %}
{% endif %}
</p>
</fieldset>

8
sapl/templates/sessao/blocos_ata/identificacao_basica.html

@ -1,8 +1,8 @@
<fieldset>
<p align="justify">
<strong>Identificação Básica: </strong>
{% for b in basica %}
{{b}} ;
{% endfor %}
<strong>Identificação Básica: </strong>
{% for b in basica %}
{{b}} ;
{% endfor %}
</p>
</fieldset>

4
sapl/templates/sessao/blocos_ata/lista_presenca.html

@ -2,15 +2,19 @@
<fieldset>
<p align="justify">
{% if presenca_sessao %}
<strong>Lista de Presença na Sessão: </strong>
{% for p in presenca_sessao %}
{{p.nome_parlamentar}} / {{ p|filiacao_data_filter:object.data_inicio }} ;
{% endfor %}
{% endif %}
</p>
<p align="justify">
{% if justificativa_ausencia %}
<strong>Justificativas de Ausências na Sessão: </strong>
{% for j in justificativa_ausencia %}
{{j.parlamentar}} / {{ j.tipo_ausencia }} ;
{% endfor %}
{% endif %}
</p>
</fieldset>

15
sapl/templates/sessao/blocos_ata/lista_presenca_ordem_dia.html

@ -1,11 +1,12 @@
{% load common_tags %}
<fieldset>
<p align="justify">
<strong>Lista de Presença na Ordem do Dia: </strong>
{% for p in presenca_ordem %}
{{p.nome_parlamentar}} / {{ p|filiacao_data_filter:object.data_inicio }} ;
{% endfor %}
</p>
<p align="justify">
{% if presenca_ordem %}
<strong>Lista de Presença na Ordem do Dia: </strong>
{% for p in presenca_ordem %}
{{p.nome_parlamentar}} / {{ p|filiacao_data_filter:object.data_inicio }} ;
{% endfor %}
{% endif %}
</p>
</fieldset>

2
sapl/templates/sessao/blocos_ata/materias_expediente.html

@ -1,5 +1,6 @@
<fieldset>
<p align="justify">
{% if materia_expediente %}
<strong>Matérias do Expediente: </strong>
{% for m in materia_expediente %}
<b>{{m.numero}} - {{m.titulo}}</b>
@ -21,5 +22,6 @@
{{m.ementa|safe}}
{{m.resultado}} {{m.resultado_observacao}}</td>
{% endfor %}
{% endif %}
</p>
</fieldset>

37
sapl/templates/sessao/blocos_ata/materias_ordem_dia.html

@ -1,23 +1,24 @@
<fieldset>
<p align="justify">
<strong>Matérias da Ordem do Dia: </strong>
{% for m in materias_ordem %}
<b>{{m.numero}} - {{m.titulo}} </b>
{% if m.turno %}
Turno:{{m.turno}}
{% endif %}
Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }}
{% if m.numero_protocolo %}
Número de Protocolo: {{ m.numero_protocolo }}
{% endif %}
{% if m.numero_processo %}
Processo: {{ m.numero_processo }}
{% endif %}
{{m.ementa|safe}}
{{m.resultado}} {{m.resultado_observacao}}
{% endfor %}
<p align="justify">
{% if materias_ordem %}
<strong>Matérias da Ordem do Dia: </strong>
{% for m in materias_ordem %}
<b>{{m.numero}} - {{m.titulo}} </b>
{% if m.turno %}
Turno:{{m.turno}}
{% endif %}
Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }}
{% if m.numero_protocolo %}
Número de Protocolo: {{ m.numero_protocolo }}
{% endif %}
{% if m.numero_processo %}
Processo: {{ m.numero_processo }}
{% endif %}
{{m.ementa|safe}}
{{m.resultado}} {{m.resultado_observacao}}
{% endfor %}
{% endif %}
</p>
</fieldset>

2
sapl/templates/sessao/blocos_ata/mesa_diretora.html

@ -1,10 +1,12 @@
<fieldset>
<p align="justify">
{% if mesa %}
<strong>Mesa Diretora: </strong>
{% for m in mesa %}
{{m.cargo}}:
{{m.parlamentar.nome_parlamentar}} / {{ m.parlamentar.filiacao_atual }} ;
{% endfor %}
{% endif %}
</p>
</fieldset>

4
sapl/templates/sessao/blocos_ata/ocorrencias_da_sessao.html

@ -1,6 +1,8 @@
<fieldset>
<p align="justify">
{% if object.ocorrenciasessao.conteudo %}
<strong>Ocorrências da Sessão: </strong>
{{object.ocorrenciasessao.conteudo|striptags|safe}}
</p>
{% endif %}
</p>
</fieldset>

2
sapl/templates/sessao/blocos_ata/oradores_expediente.html

@ -1,5 +1,6 @@
<fieldset>
<p align="justify">
{% if oradores %}
<strong>Oradores do Expediente: </strong>
{% for o in oradores %}
<div><b>{{o.numero_ordem}}</b> - {{o.parlamentar}}</div>
@ -7,6 +8,7 @@
<div>{{o.observacao}}</div>
</br>
{% endfor %}
{% endif %}
</p>
</div>
</fieldset>

10
sapl/templates/sessao/blocos_ata/oradores_explicacoes.html

@ -1,9 +1,11 @@
<fieldset>
<p align="justify">
{% if oradores_explicacoes %}
<strong>Oradores das Explicações Pessoais: </strong>
{% for o in oradores_explicacoes %}
<b>{{o.numero_ordem}}</b> - {{o.parlamentar.nome_parlamentar}} / {{ o.parlamentar.filiacao_atual }} ;
{{o.url_discurso}}
{% endfor %}
{% for o in oradores_explicacoes %}
<b>{{o.numero_ordem}}</b> - {{o.parlamentar.nome_parlamentar}} / {{ o.parlamentar.filiacao_atual }} ;
{{o.url_discurso}}
{% endfor %}
{% endif %}
</p>
</fieldset>

4
sapl/templates/sessao/pauta_sessao_detail.html

@ -34,7 +34,7 @@
<br />
<b>Autor{{ m.autor|length|pluralize:"es" }}</b>: {{ m.autor|join:', ' }}
</td>
<td style="width:70%;">{{m.ementa|safe}}</td>
<td style="width:70%;">{{m.ementa|safe}}<br>{{m.observacao|safe}}</td>
<td style="width:10%;">{{m.situacao}}</td>
</tr>
{% endfor %}
@ -62,7 +62,7 @@
<br />
<b>Autor{{ m.autor|length|pluralize:"es" }}</b>: {{ m.autor|join:', ' }}
</td>
<td style="width:70%;">{{m.ementa|safe}}</td>
<td style="width:70%;">{{m.ementa|safe}}<br>{{m.observacao|safe}}</td>
<td style="width:10%;">{{m.situacao}}</td>
</tr>
{% endfor %}

2
setup.py

@ -52,7 +52,7 @@ install_requires = [
]
setup(
name='interlegis-sapl',
version='3.1.137',
version='3.1.138',
packages=find_packages(),
include_package_data=True,
license='GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007',

Loading…
Cancel
Save