Browse Source

Merge branch '3.1.x' into 2841-gerar-pdf-de-relatorios

pull/2895/head
Edward 6 years ago
committed by GitHub
parent
commit
4e0e9a06d0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      docker-compose.yml
  2. 6
      sapl/api/views.py
  3. 59
      sapl/base/forms.py
  4. 5
      sapl/base/receivers.py
  5. 6
      sapl/base/templatetags/common_tags.py
  6. 32
      sapl/base/tests/test_view_base.py
  7. 170
      sapl/base/views.py
  8. 2
      sapl/comissoes/views.py
  9. 9
      sapl/compilacao/views.py
  10. 39
      sapl/crud/base.py
  11. 23
      sapl/materia/migrations/0056_popula_materiaemtramitacao.py
  12. 25
      sapl/materia/migrations/0057_materiaemtramitacao.py
  13. 12
      sapl/materia/models.py
  14. 5
      sapl/materia/views.py
  15. 4
      sapl/protocoloadm/forms.py
  16. 25
      sapl/protocoloadm/migrations/0022_deduplica_protocolos.py
  17. 19
      sapl/protocoloadm/migrations/0023_auto_20190711_1755.py
  18. 16
      sapl/protocoloadm/migrations/0024_merge_20190821_1418.py
  19. 1
      sapl/protocoloadm/models.py
  20. 54
      sapl/protocoloadm/views.py
  21. 1
      sapl/rules/map_rules.py
  22. 2
      sapl/settings.py
  23. 2
      sapl/templates/base.html
  24. 17
      sapl/templates/base/RelatorioMateriasPorTramitacao_filter.html
  25. 6
      sapl/templates/base/parlamentares_duplicados.html
  26. 8
      sapl/templates/base/protocolos_duplicados.html
  27. 13
      sapl/templates/materia/proposicao_detail.html
  28. 15
      sapl/templates/protocoloadm/comprovante.html
  29. 2
      sapl/templates/protocoloadm/layouts.yaml
  30. 12
      sapl/templates/sessao/votacao/nominal.html
  31. 8
      sapl/templates/sessao/votacao/votacao.html
  32. 2
      setup.py

2
docker-compose.yml

@ -11,7 +11,7 @@ sapldb:
ports:
- "5432:5432"
sapl:
image: interlegis/sapl:3.1.160-RC0
image: interlegis/sapl:3.1.160-RC1
restart: always
environment:
ADMIN_PASSWORD: interlegis

6
sapl/api/views.py

@ -15,6 +15,7 @@ from django_filters.rest_framework.filterset import FilterSet
from django_filters.utils import resolve_field
from rest_framework import serializers as rest_serializers
from rest_framework.decorators import action
from rest_framework.fields import SerializerMethodField
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
@ -94,10 +95,15 @@ class SaplApiViewSetConstrutor():
# Define uma classe padrão para serializer caso não tenha sido
# criada a classe sapl.api.serializers.{model}Serializer
class SaplSerializer(rest_serializers.ModelSerializer):
__str__ = SerializerMethodField()
class Meta:
model = _model
fields = '__all__'
def get___str__(self, obj):
return str(obj)
# Define uma classe padrão para filtro caso não tenha sido
# criada a classe sapl.api.forms.{model}FilterSet
class SaplFilterSet(SaplFilterSetMixin):

59
sapl/base/forms.py

@ -19,27 +19,25 @@ from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
import django_filters
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica
from sapl.audiencia.models import AudienciaPublica
from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import Reuniao, Comissao
from sapl.comissoes.models import Reuniao, Comissao
from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column,
to_row)
from sapl.crispy_layout_mixin import SaplFormHelper
from sapl.materia.models import (MateriaLegislativa, UnidadeTramitacao, StatusTramitacao,
DocumentoAcessorio, TipoMateriaLegislativa)
from sapl.norma.models import (NormaJuridica, NormaEstatisticas)
from sapl.parlamentares.models import SessaoLegislativa, Partido
from sapl.comissoes.models import Reuniao
from sapl.crispy_layout_mixin import (form_actions, to_column, to_row,
SaplFormHelper, SaplFormLayout)
from sapl.materia.models import (DocumentoAcessorio, MateriaEmTramitacao,
MateriaLegislativa, UnidadeTramitacao,
StatusTramitacao)
from sapl.norma.models import NormaJuridica
from sapl.parlamentares.models import Partido, SessaoLegislativa
from sapl.protocoloadm.models import DocumentoAdministrativo
from sapl.sessao.models import SessaoPlenaria
from sapl.settings import MAX_IMAGE_UPLOAD_SIZE
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES,
ChoiceWithoutValidationField, ImageThumbnailFileInput,
RangeWidgetOverride, autor_label, autor_modal,
models_with_gr_for_model, qs_override_django_filter,
from sapl.utils import (autor_label, autor_modal, ChoiceWithoutValidationField,
choice_anos_com_normas, choice_anos_com_materias,
FilterOverridesMetaMixin, FileFieldCheckMixin, AnoNumeroOrderingFilter)
FilterOverridesMetaMixin, FileFieldCheckMixin,
AnoNumeroOrderingFilter, ImageThumbnailFileInput,
models_with_gr_for_model, qs_override_django_filter,
RangeWidgetOverride, RANGE_ANOS, YES_NO_CHOICES)
from .models import AppConfig, CasaLegislativa
@ -619,6 +617,10 @@ class AutorForm(ModelForm):
tipo = cd['tipo']
if 'nome' in cd and \
Autor.objects.filter(nome=cd['nome']).exists():
raise ValidationError("Autor '%s' já existente!" % cd['nome'])
if not tipo.content_type:
if 'nome' not in cd or not cd['nome']:
self.logger.error('Nome do Autor não informado.')
@ -1236,9 +1238,9 @@ class RelatorioAudienciaFilterSet(django_filters.FilterSet):
)
class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet):
class RelatorioMateriasTramitacaoFilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=True,
materia__ano = django_filters.ChoiceFilter(required=True,
label='Ano da Matéria',
choices=choice_anos_com_materias)
@ -1252,22 +1254,25 @@ class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet):
@property
def qs(self):
parent = super(RelatorioMateriasTramitacaoilterSet, self).qs
return parent.distinct().order_by('-ano', 'tipo', '-numero')
parent = super(RelatorioMateriasTramitacaoFilterSet, self).qs
return parent.distinct().order_by(
'-materia__ano', 'materia__tipo', '-materia__numero'
)
class Meta:
model = MateriaLegislativa
fields = ['ano', 'tipo', 'tramitacao__unidade_tramitacao_destino',
model = MateriaEmTramitacao
fields = ['materia__ano', 'materia__tipo',
'tramitacao__unidade_tramitacao_destino',
'tramitacao__status']
def __init__(self, *args, **kwargs):
super(RelatorioMateriasTramitacaoilterSet, self).__init__(
super(RelatorioMateriasTramitacaoFilterSet, self).__init__(
*args, **kwargs)
self.filters['tipo'].label = 'Tipo de Matéria'
self.filters['materia__tipo'].label = 'Tipo de Matéria'
row1 = to_row([('ano', 12)])
row2 = to_row([('tipo', 12)])
row1 = to_row([('materia__ano', 12)])
row2 = to_row([('materia__tipo', 12)])
row3 = to_row([('tramitacao__unidade_tramitacao_destino', 12)])
row4 = to_row([('tramitacao__status', 12)])
@ -1797,5 +1802,5 @@ class RelatorioNormasPorAutorFilterSet(django_filters.FilterSet):
HTML(autor_label),
HTML(autor_modal),
row3,
buttons)
form_actions(label='Pesquisar'))
)

5
sapl/base/receivers.py

@ -1,6 +1,5 @@
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from sapl.materia.models import Tramitacao
from sapl.protocoloadm.models import TramitacaoAdministrativo
from sapl.base.signals import tramitacao_signal
@ -30,12 +29,12 @@ def handle_tramitacao_signal(sender, **kwargs):
@receiver(post_delete)
def status_tramitacao_materia(sender, instance, **kwargs):
if isinstance(sender, TramitacaoAdministrativo):
if sender == Tramitacao:
if instance.status.indicador == 'F':
materia = instance.materia
materia.em_tramitacao = True
materia.save()
elif isinstance(sender, TramitacaoAdministrativo):
elif sender == TramitacaoAdministrativo:
if instance.status.indicador == 'F':
documento = instance.documento
documento.tramitacao = True

6
sapl/base/templatetags/common_tags.py

@ -249,8 +249,10 @@ def facebook_url(value):
def youtube_id(value):
from urllib.parse import urlparse, parse_qs
u_pars = urlparse(value)
quer_v = parse_qs(u_pars.query).get('v')[0]
return quer_v
quer_v = parse_qs(u_pars.query).get('v')
if quer_v:
return quer_v[0]
return ''
@register.filter

32
sapl/base/tests/test_view_base.py

@ -24,32 +24,6 @@ from sapl.base.views import (protocolos_duplicados, protocolos_com_materias,
bancada_comissao_autor_externo, anexados_ciclicos)
@pytest.mark.django_db(transaction=False)
def test_lista_protocolos_duplicados():
mommy.make(
Protocolo,
numero=15,
ano=2031
)
mommy.make(
Protocolo,
numero=15,
ano=2031
)
mommy.make(
Protocolo,
numero=33,
ano=2033
)
lista_protocolos_duplicados = protocolos_duplicados()
assert len(lista_protocolos_duplicados) == 1
assert lista_protocolos_duplicados[0][1] == 2
assert lista_protocolos_duplicados[0][0].numero == 15
assert lista_protocolos_duplicados[0][0].ano == 2031
@pytest.mark.django_db(transaction=False)
def test_lista_protocolos_com_materias():
mommy.make(
@ -194,13 +168,13 @@ def test_lista_parlamentares_duplicados():
sexo='M'
)
lista_dict_values_parlamentares_duplicados = parlamentares_duplicados()
lista_dict_parlamentares_duplicados = parlamentares_duplicados()
parlamentar_duplicado = list(
lista_dict_values_parlamentares_duplicados[0]
lista_dict_parlamentares_duplicados[0].values()
)
parlamentar_duplicado.sort(key=str)
assert len(lista_dict_values_parlamentares_duplicados) == 1
assert len(lista_dict_parlamentares_duplicados) == 1
assert parlamentar_duplicado == [2, "Nome_Parlamentar_Teste"]

170
sapl/base/views.py

@ -13,7 +13,8 @@ from django.core.exceptions import ObjectDoesNotExist, PermissionDenied, Validat
from django.core.mail import send_mail
from django.core.urlresolvers import reverse, reverse_lazy
from django.db import connection
from django.db.models import Count, Q, ProtectedError
from django.db.models import Count, Q, ProtectedError, Max
from django.shortcuts import render
from django.http import Http404, HttpResponseRedirect, JsonResponse
from django.template import TemplateDoesNotExist
from django.template.loader import get_template
@ -39,23 +40,26 @@ from sapl.relatorios.views import (relatorio_materia_em_tramitacao, relatorio_ma
from sapl import settings
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica
from sapl.base.forms import AutorForm, AutorFormForAdmin, TipoAutorForm
from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import Reuniao, Comissao
from sapl.base.forms import AutorForm, AutorFormForAdmin, TipoAutorForm
from sapl.comissoes.models import Comissao, Reuniao
from sapl.crud.base import CrudAux, make_pagination
from sapl.materia.models import (Autoria, MateriaLegislativa, Proposicao, Anexada,
TipoMateriaLegislativa, StatusTramitacao, UnidadeTramitacao,
DocumentoAcessorio, TipoDocumento)
from sapl.norma.models import (NormaJuridica, TipoNormaJuridica, NormaEstatisticas)
from sapl.parlamentares.models import Parlamentar, Legislatura, Mandato, Filiacao, SessaoLegislativa
from sapl.protocoloadm.models import (Protocolo, TipoDocumentoAdministrativo,
from sapl.materia.models import (Anexada, Autoria, DocumentoAcessorio,
MateriaEmTramitacao, MateriaLegislativa, Proposicao,
StatusTramitacao, TipoDocumento,
TipoMateriaLegislativa, UnidadeTramitacao, Tramitacao)
from sapl.norma.models import NormaJuridica, TipoNormaJuridica
from sapl.parlamentares.models import (Filiacao, Legislatura, Mandato, Parlamentar,
SessaoLegislativa)
from sapl.protocoloadm.models import (Anexado, DocumentoAdministrativo, Protocolo,
StatusTramitacaoAdministrativo,
DocumentoAdministrativo, Anexado)
from sapl.sessao.models import (PresencaOrdemDia, SessaoPlenaria,
SessaoPlenariaPresenca, Bancada, TipoSessaoPlenaria)
from sapl.utils import (parlamentares_ativos, gerar_hash_arquivo, SEPARADOR_HASH_PROPOSICAO,
show_results_filter_set, mail_service_configured,
intervalos_tem_intersecao, remover_acentos)
TipoDocumentoAdministrativo)
from sapl.sessao.models import (Bancada, PresencaOrdemDia, SessaoPlenaria,
SessaoPlenariaPresenca, TipoSessaoPlenaria)
from sapl.utils import (gerar_hash_arquivo, intervalos_tem_intersecao,
mail_service_configured, parlamentares_ativos,
SEPARADOR_HASH_PROPOSICAO, show_results_filter_set)
from .forms import (AlterarSenhaForm, CasaLegislativaForm,
ConfiguracoesAppForm, RelatorioAtasFilterSet,
RelatorioAudienciaFilterSet,
@ -63,7 +67,7 @@ from .forms import (AlterarSenhaForm, CasaLegislativaForm,
RelatorioHistoricoTramitacaoFilterSet,
RelatorioMateriasPorAnoAutorTipoFilterSet,
RelatorioMateriasPorAutorFilterSet,
RelatorioMateriasTramitacaoilterSet,
RelatorioMateriasTramitacaoFilterSet,
RelatorioPresencaSessaoFilterSet,
RelatorioReuniaoFilterSet, UsuarioCreateForm,
UsuarioEditForm, RelatorioNormasMesFilterSet,
@ -75,19 +79,6 @@ from .forms import (AlterarSenhaForm, CasaLegislativaForm,
from .models import AppConfig, CasaLegislativa
def filtra_url_materias_em_tramitacao(qr, qs, campo_url, local_ou_status):
id_materias = []
filtro_url = qr[campo_url]
if local_ou_status == 'local':
id_materias = [item.id for item in qs if item.tramitacao_set.order_by(
'-id').first().unidade_tramitacao_destino_id == int(filtro_url)]
elif local_ou_status == 'status':
id_materias = [item.id for item in qs if item.tramitacao_set.order_by(
'-id').first().status_id == int(filtro_url)]
return qs.filter(em_tramitacao=True, id__in=id_materias)
def get_casalegislativa():
return CasaLegislativa.objects.first()
@ -731,62 +722,109 @@ class RelatorioAudienciaView(RelatorioMixin, FilterView):
class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView):
model = MateriaLegislativa
filterset_class = RelatorioMateriasTramitacaoilterSet
model = MateriaEmTramitacao
filterset_class = RelatorioMateriasTramitacaoFilterSet
template_name = 'base/RelatorioMateriasPorTramitacao_filter.html'
relatorio = relatorio_materia_em_tramitacao
paginate_by = 100
total_resultados_tipos = {}
def get_filterset_kwargs(self, filterset_class):
data = super().get_filterset_kwargs(filterset_class)
if data['data']:
qs = data['queryset']
ano_materia = data['data']['materia__ano']
tipo_materia = data['data']['materia__tipo']
unidade_tramitacao_destino = data['data']['tramitacao__unidade_tramitacao_destino']
status_tramitacao = data['data']['tramitacao__status']
kwargs = {}
if ano_materia:
kwargs['materia__ano'] = ano_materia
if tipo_materia:
kwargs['materia__tipo'] = tipo_materia
if unidade_tramitacao_destino:
kwargs['tramitacao__unidade_tramitacao_destino'] = unidade_tramitacao_destino
if status_tramitacao:
kwargs['tramitacao__status'] = status_tramitacao
qs = qs.filter(**kwargs)
data['queryset'] = qs
qtdes = { tipo:0 for tipo in TipoMateriaLegislativa.objects.all() }
for i in qs:
qtdes[i.materia.tipo] += 1
# remove as entradas de valor igual a zero
qtdes = {k:v for k,v in qtdes.items() if v > 0}
self.total_resultados_tipos = qtdes
return data
def get_queryset(self):
qs = super().get_queryset()
qs = qs.select_related('materia__tipo').filter(
materia__em_tramitacao=True
).exclude(
tramitacao__status__indicador='F'
).order_by('-materia__ano', '-materia__numero')
return qs
def get_context_data(self, **kwargs):
context = super(RelatorioMateriasTramitacaoView,
self).get_context_data(**kwargs)
context = super(
RelatorioMateriasTramitacaoView, self
).get_context_data(**kwargs)
context['title'] = _('Matérias em Tramitação')
if not self.filterset.form.is_valid():
return context
qr = self.request.GET.copy()
qs = context['object_list']
qs = qs.filter(em_tramitacao=True)
if qr.get('tramitacao__unidade_tramitacao_destino'):
qs = filtra_url_materias_em_tramitacao(
qr, qs, 'tramitacao__unidade_tramitacao_destino', 'local')
if qr.get('tramitacao__status'):
qs = filtra_url_materias_em_tramitacao(
qr, qs, 'tramitacao__status', 'status')
li = [li1 for li1 in qs if li1.tramitacao_set.last() and li1.tramitacao_set.last().status.indicador != 'F']
context['object_list'] = li
context['qtdes'] = self.total_resultados_tipos
context['ano'] = (self.request.GET['materia__ano'])
qtdes = {}
for tipo in TipoMateriaLegislativa.objects.all():
li = context['object_list']
qtde = sum(1 for i in li if i.tipo_id==tipo.id)
if qtde > 0:
qtdes[tipo] = qtde
context['qtdes'] = qtdes
context['ano'] = (self.request.GET['ano'])
if self.request.GET['tipo']:
tipo = self.request.GET['tipo']
if self.request.GET['materia__tipo']:
tipo = self.request.GET['materia__tipo']
context['tipo'] = (
str(TipoMateriaLegislativa.objects.get(id=tipo)))
str(TipoMateriaLegislativa.objects.get(id=tipo))
)
else:
context['tipo'] = ''
if self.request.GET['tramitacao__status']:
tramitacao_status = self.request.GET['tramitacao__status']
context['tramitacao__status'] = (
str(StatusTramitacao.objects.get(id=tramitacao_status)))
str(StatusTramitacao.objects.get(id=tramitacao_status))
)
else:
context['tramitacao__status'] = ''
if self.request.GET['tramitacao__unidade_tramitacao_destino']:
context['tramitacao__unidade_tramitacao_destino'] = (str(UnidadeTramitacao.objects.get(
id=self.request.GET['tramitacao__unidade_tramitacao_destino'])))
context['tramitacao__unidade_tramitacao_destino'] = (
str(UnidadeTramitacao.objects.get(
id=self.request.GET['tramitacao__unidade_tramitacao_destino']
))
)
else:
context['tramitacao__unidade_tramitacao_destino'] = ''
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
context['show_results'] = show_results_filter_set(qr)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages
)
context['NO_ENTRIES_MSG'] = 'Nenhum encontrado.'
return context
@ -1471,7 +1509,7 @@ class ListarParlMandatosIntersecaoView(PermissionRequiredMixin, ListView):
def parlamentares_duplicados():
return [parlamentar.values() for parlamentar in Parlamentar.objects.values(
return [parlamentar for parlamentar in Parlamentar.objects.values(
'nome_parlamentar').order_by('nome_parlamentar').annotate(count=Count(
'nome_parlamentar')).filter(count__gt=1)]
@ -1656,14 +1694,10 @@ class ListarProtocolosComMateriasView(PermissionRequiredMixin, ListView):
def protocolos_duplicados():
protocolos = {}
for p in Protocolo.objects.order_by('-ano', 'numero'):
key = "{}/{}".format(p.numero, p.ano)
val = protocolos.get(key, list())
val.append(p)
protocolos[key] = val
return [(v[0], len(v)) for (k, v) in protocolos.items() if len(v) > 1]
return [
protocolo for protocolo in Protocolo.objects.values(
'numero', 'ano').order_by('-ano', 'numero').annotate(total=Count('numero')).filter(total__gt=1)
]
class ListarProtocolosDuplicadosView(PermissionRequiredMixin, ListView):

2
sapl/comissoes/views.py

@ -167,7 +167,7 @@ class ComissaoCrud(Crud):
def lista_materias_comissao(comissao_pk):
ts = Tramitacao.objects.order_by(
'materia', '-data_tramitacao', '-id').annotate(
'materia_id', '-data_tramitacao', '-id').annotate(
comissao=F('unidade_tramitacao_destino__comissao')).distinct(
'materia').values_list('materia', 'comissao')

9
sapl/compilacao/views.py

@ -195,8 +195,13 @@ class IntegracaoTaView(TemplateView):
return redirect(to=reverse_lazy('sapl.compilacao:ta_text_edit',
kwargs={'ta_id': ta.pk}))
else:
return redirect(to=reverse_lazy('sapl.compilacao:ta_text',
kwargs={'ta_id': ta.pk}))
return redirect(
to='%s?%s' % (
reverse_lazy('sapl.compilacao:ta_text',
kwargs={'ta_id': ta.pk}),
request.META['QUERY_STRING']
)
)
class Meta:
abstract = True

39
sapl/crud/base.py

@ -4,13 +4,14 @@ from braces.views import FormMessagesMixin
from crispy_forms.bootstrap import FieldWithButtons, StrictButton
from crispy_forms.layout import Field, Layout
from django import forms
from django.conf import settings
from django.conf.urls import url
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from django.db import models
from django.db.models.fields.related import ForeignKey
from django.db.models.fields.related import ForeignKey, ManyToManyField
from django.http.response import Http404
from django.shortcuts import redirect
from django.utils.decorators import classonlymethod
@ -27,10 +28,12 @@ from sapl.crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display
from sapl.crispy_layout_mixin import SaplFormHelper
from sapl.rules.map_rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL,
RP_LIST)
from sapl.settings import BASE_DIR
from sapl.utils import normalize
logger = logging.getLogger(settings.BASE_DIR.name)
ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \
'list', 'create', 'detail', 'update', 'delete'
@ -122,7 +125,6 @@ class SearchMixin(models.Model):
except Exception as e:
username = self.request.user.username
self.logger.error("user=" + username + ". " + str(e))
pass
else:
_self = self
for field in fields:
@ -206,6 +208,7 @@ class PermissionRequiredContainerCrudMixin(PermissionRequiredMixin):
if not self.model.objects.filter(**params).exists():
raise Http404()
elif self.container_field:
container = self.container_field.split('__')
@ -230,14 +233,14 @@ class PermissionRequiredContainerCrudMixin(PermissionRequiredMixin):
return super(PermissionRequiredMixin, self).dispatch(
request, *args, **kwargs)
@cached_property
@property
def container_field(self):
if hasattr(self, 'crud') and not hasattr(self.crud, 'container_field'):
self.crud.container_field = ''
if hasattr(self, 'crud'):
return self.crud.container_field
@cached_property
@property
def container_field_set(self):
if hasattr(self, 'crud') and\
not hasattr(self.crud, 'container_field_set'):
@ -245,7 +248,7 @@ class PermissionRequiredContainerCrudMixin(PermissionRequiredMixin):
if hasattr(self, 'crud'):
return self.crud.container_field_set
@cached_property
@property
def is_contained(self):
return self.container_field_set or self.container_field
@ -600,7 +603,7 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView):
# print(ordering)
except Exception as e:
print(string_concat(_(
logger.error(string_concat(_(
'ERRO: construção da tupla de ordenação.'), str(e)))
# print(queryset.query)
@ -1108,12 +1111,14 @@ class MasterDetailCrud(Crud):
permission_required = RP_LIST,
logger = logging.getLogger(__name__)
def get(self, request, *args, **kwargs):
return Crud.ListView.get(self, request, *args, **kwargs)
@classmethod
def get_url_regex(cls):
return r'^(?P<pk>\d+)/%s$' % cls.model._meta.model_name
def get_context_data(self, **kwargs):
obj = self.crud if hasattr(self, 'crud') else self
context = CrudListView.get_context_data(self, **kwargs)
@ -1133,7 +1138,12 @@ class MasterDetailCrud(Crud):
else:
parent_model = getattr(
self.model, obj.parent_field).field.related_model
self.model, obj.parent_field)
if isinstance(parent_model.field, (
ForeignKey, ManyToManyField)):
parent_model = parent_model.field.related_model
else:
parent_model = parent_model.rel.related_model
params = {'pk': kwargs['root_pk']}
@ -1165,6 +1175,9 @@ class MasterDetailCrud(Crud):
return qs.filter(**kwargs)
def dispatch(self, request, *args, **kwargs):
return PermissionRequiredMixin.dispatch(self, request, *args, **kwargs)
class CreateView(Crud.CreateView):
permission_required = RP_ADD,
logger = logging.getLogger(__name__)
@ -1229,8 +1242,12 @@ class MasterDetailCrud(Crud):
parent_object = getattr(parent_object, field)
else:
parent_model = getattr(
parent_model, obj.parent_field).field.related_model
parent_model = getattr(self.model, obj.parent_field)
if isinstance(parent_model.field, ForeignKey):
parent_model = parent_model.field.related_model
else:
parent_model = parent_model.rel.related_model
parent_object = parent_model.objects.get(**params)
context['root_pk'] = parent_object.pk

23
sapl/materia/migrations/0056_popula_materiaemtramitacao.py

@ -0,0 +1,23 @@
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('materia', '0055_auto_20190816_0943'),
]
operations = [
migrations.RunSQL("""
create or replace view materia_materiaemtramitacao as
select m.id as id,
m.id as materia_id,
t.id as tramitacao_id
from materia_materialegislativa m
inner join materia_tramitacao t on (m.id = t.materia_id)
where t.id = (select max(id) from materia_tramitacao where materia_id = m.id)
order by m.id DESC
"""),
]

25
sapl/materia/migrations/0057_materiaemtramitacao.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-08-27 20:13
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0056_popula_materiaemtramitacao'),
]
operations = [
migrations.CreateModel(
name='MateriaEmTramitacao',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
options={
'db_table': 'materia_materiaemtramitacao',
'managed': False,
},
),
]

12
sapl/materia/models.py

@ -1084,3 +1084,15 @@ class Tramitacao(models.Model):
'materia': self.materia,
'status': self.status,
'data': self.data_tramitacao.strftime("%d/%m/%Y")}
class MateriaEmTramitacao(models.Model):
materia = models.ForeignKey(MateriaLegislativa, on_delete=models.DO_NOTHING)
tramitacao = models.ForeignKey(Tramitacao, on_delete=models.DO_NOTHING)
class Meta:
managed = False
db_table = "materia_materiaemtramitacao"
def __str__(self):
return '{}/{}'.format(self.materia, self.tramitacao)

5
sapl/materia/views.py

@ -782,7 +782,8 @@ class ProposicaoCrud(Crud):
def get(self, request, *args, **kwargs):
action = request.GET.get('action', '')
username = request.user.username
user = request.user
username = user.username
if not action:
return Crud.DetailView.get(self, request, *args, **kwargs)
@ -790,7 +791,7 @@ class ProposicaoCrud(Crud):
p = Proposicao.objects.get(id=kwargs['pk'])
msg_error = ''
if p:
if p and p.autor.user == user:
if action == 'send':
if p.data_envio and p.data_recebimento:
msg_error = _('Proposição já foi enviada e recebida.')

4
sapl/protocoloadm/forms.py

@ -302,9 +302,7 @@ class AnularProtocoloAdmForm(ModelForm):
class Meta:
model = Protocolo
fields = ['numero',
'ano',
'justificativa_anulacao',
fields = ['justificativa_anulacao',
'anulado',
'user_anulacao',
'ip_anulacao',

25
sapl/protocoloadm/migrations/0022_deduplica_protocolos.py

@ -0,0 +1,25 @@
from __future__ import unicode_literals
from django.db import migrations
def deduplica_protocolos(apps, schema_editor):
from sapl.base.views import protocolos_duplicados
Protocolo = apps.get_model('protocoloadm', 'Protocolo')
protocolos = protocolos_duplicados()
for protocolo in protocolos:
protocolo_principal = Protocolo.objects.filter(numero=protocolo['numero'], ano=protocolo['ano']).order_by('-id')[0]
Protocolo.objects.filter(numero=protocolo['numero'], ano=protocolo['ano']).exclude(id=protocolo_principal.id).delete()
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0021_merge_20190429_1531'),
]
operations = [
migrations.RunPython(deduplica_protocolos)
]

19
sapl/protocoloadm/migrations/0023_auto_20190711_1755.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-07-11 20:55
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0022_deduplica_protocolos'),
]
operations = [
migrations.AlterUniqueTogether(
name='protocolo',
unique_together=set([('numero', 'ano')]),
),
]

16
sapl/protocoloadm/migrations/0024_merge_20190821_1418.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-08-21 17:18
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0023_auto_20190711_1755'),
('protocoloadm', '0023_merge_20190802_1112'),
]
operations = [
]

1
sapl/protocoloadm/models.py

@ -120,6 +120,7 @@ class Protocolo(models.Model):
permissions = (
('action_anular_protocolo', _('Permissão para Anular Protocolo')),
)
unique_together = ('numero', 'ano',)
def __str__(self):
return _('%(numero)s/%(ano)s') % {

54
sapl/protocoloadm/views.py

@ -34,10 +34,10 @@ from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa, Unid
from sapl.materia.views import gerar_pdf_impressos
from sapl.parlamentares.models import Legislatura, Parlamentar
from sapl.protocoloadm.models import Protocolo
from sapl.relatorios.views import relatorio_doc_administrativos
from sapl.utils import (create_barcode, get_base_url, get_client_ip,
get_mime_type_from_file_extension, lista_anexados,
show_results_filter_set, mail_service_configured)
from sapl.relatorios.views import relatorio_doc_administrativos
from .forms import (AcompanhamentoDocumentoForm, AnularProtocoloAdmForm,
DocumentoAcessorioAdministrativoForm,
@ -373,13 +373,13 @@ class DocumentoAdministrativoCrud(Crud):
return redirect('/')
return super(Crud.DetailView, self).get(args, kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
self.layout_display[0]['rows'][-1][0]['text'] = (
'<a href="%s"></a>' % reverse(
def urlize(self, obj, fieldname):
a = '<a href="%s">%s</a>' % (
reverse(
'sapl.protocoloadm:doc_texto_integral',
kwargs={'pk': self.object.pk}))
return context
kwargs={'pk': obj.pk}),
obj.texto_integral.name.split('/')[-1])
return obj.texto_integral.field.verbose_name, a
class DeleteView(Crud.DeleteView):
@ -700,6 +700,13 @@ class ComprovanteProtocoloView(PermissionRequiredMixin, TemplateView):
autenticacao = str(protocolo.tipo_processo) + \
data + str(protocolo.numero).zfill(6)
if protocolo.tipo_materia:
materia = MateriaLegislativa.objects.filter(
numero_protocolo=protocolo.numero,
ano=protocolo.ano).first()
if materia:
context['materia'] = materia.numero
context.update({"protocolo": protocolo,
"barcode": barcode,
"autenticacao": autenticacao})
@ -950,6 +957,7 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
else:
return self.render_to_response(context)
class AnexadoCrud(MasterDetailCrud):
model = Anexado
parent_field = 'documento_principal'
@ -1019,13 +1027,15 @@ class DocumentoAnexadoEmLoteView(PermissionRequiredMixin, FilterView):
context['object_list'] = []
for obj in context['temp_object_list']:
if not obj.pk == int(context['root_pk']):
documento_principal = DocumentoAdministrativo.objects.get(id=context['root_pk'])
documento_principal = DocumentoAdministrativo.objects.get(
id=context['root_pk'])
documento_anexado = obj
is_anexado = Anexado.objects.filter(documento_principal=documento_principal,
documento_anexado=documento_anexado).exists()
if not is_anexado:
ciclico = False
anexados_anexado = Anexado.objects.filter(documento_principal=documento_anexado)
anexados_anexado = Anexado.objects.filter(
documento_principal=documento_anexado)
while anexados_anexado and not ciclico:
anexados = []
@ -1094,7 +1104,8 @@ class DocumentoAnexadoEmLoteView(PermissionRequiredMixin, FilterView):
msg = _('Documento(s) anexado(s).')
messages.add_message(request, messages.SUCCESS, msg)
success_url = reverse('sapl.protocoloadm:anexado_list', kwargs={'pk': kwargs['pk']})
success_url = reverse('sapl.protocoloadm:anexado_list', kwargs={
'pk': kwargs['pk']})
return HttpResponseRedirect(success_url)
@ -1142,7 +1153,8 @@ class TramitacaoAdmCrud(MasterDetailCrud):
'-timestamp',
'-id').first()
#TODO: Esta checagem foi inserida na issue #2027, mas é mesmo necessária?
# TODO: Esta checagem foi inserida na issue #2027, mas é mesmo
# necessária?
if ultima_tramitacao:
if ultima_tramitacao.unidade_tramitacao_destino:
context['form'].fields[
@ -1230,7 +1242,6 @@ class TramitacaoAdmCrud(MasterDetailCrud):
context['user'] = self.request.user
return context
class DeleteView(MasterDetailCrud.DeleteView):
logger = logging.getLogger(__name__)
@ -1271,7 +1282,8 @@ class TramitacaoAdmCrud(MasterDetailCrud):
if da.tramitacaoadministrativo_set.count() == 0:
da.tramitacao = False
da.save()
TramitacaoAdministrativo.objects.filter(id__in=tramitacoes_deletar).delete()
TramitacaoAdministrativo.objects.filter(
id__in=tramitacoes_deletar).delete()
return HttpResponseRedirect(url)
@ -1464,7 +1476,6 @@ class PrimeiraTramitacaoEmLoteAdmView(PermissionRequiredMixin, FilterView):
logger = logging.getLogger(__name__)
def get_context_data(self, **kwargs):
context = super(PrimeiraTramitacaoEmLoteAdmView,
self).get_context_data(**kwargs)
@ -1517,23 +1528,24 @@ class PrimeiraTramitacaoEmLoteAdmView(PermissionRequiredMixin, FilterView):
form.save()
msg = _('Tramitação completa.')
self.logger.info('user=' + user.username + '. Tramitação completa.')
self.logger.info('user=' + user.username +
'. Tramitação completa.')
messages.add_message(request, messages.SUCCESS, msg)
return self.get_success_url()
return self.form_invalid(form)
def get_success_url(self):
return HttpResponseRedirect(reverse('sapl.protocoloadm:primeira_tramitacao_em_lote_docadm'))
def form_invalid(self, form, *args, **kwargs):
for key, erros in form.errors.items():
if not key == '__all__':
[messages.add_message(self.request, messages.ERROR, form.fields[key].label + ": " + e) for e in erros]
[messages.add_message(
self.request, messages.ERROR, form.fields[key].label + ": " + e) for e in erros]
else:
[messages.add_message(self.request, messages.ERROR, e) for e in erros]
[messages.add_message(self.request, messages.ERROR, e)
for e in erros]
return self.get(self.request, kwargs, {'form': form})
@ -1562,21 +1574,18 @@ class TramitacaoEmLoteAdmView(PrimeiraTramitacaoEmLoteAdmView):
return context
def pega_ultima_tramitacao(self):
return TramitacaoAdministrativo.objects.values(
'documento_id').annotate(data_encaminhamento=Max(
'data_encaminhamento'),
id=Max('id')).values_list('id', flat=True)
def filtra_tramitacao_status(self, status):
lista = self.pega_ultima_tramitacao()
return TramitacaoAdministrativo.objects.filter(
id__in=lista,
status=status).distinct().values_list('documento_id', flat=True)
def filtra_tramitacao_destino(self, destino):
lista = self.pega_ultima_tramitacao()
return TramitacaoAdministrativo.objects.filter(
@ -1584,7 +1593,6 @@ class TramitacaoEmLoteAdmView(PrimeiraTramitacaoEmLoteAdmView):
unidade_tramitacao_destino=destino).distinct().values_list(
'documento_id', flat=True)
def filtra_tramitacao_destino_and_status(self, status, destino):
lista = self.pega_ultima_tramitacao()
return TramitacaoAdministrativo.objects.filter(

1
sapl/rules/map_rules.py

@ -127,6 +127,7 @@ rules_group_materia = {
['can_access_impressos'], __perms_publicas__),
(materia.Numeracao, __base__, __perms_publicas__),
(materia.Tramitacao, __base__, __perms_publicas__),
(materia.MateriaEmTramitacao, __base__, __perms_publicas__),
(norma.LegislacaoCitada, __base__, __perms_publicas__),
(norma.AutoriaNorma, __base__, __perms_publicas__),
(compilacao.Dispositivo, __base__ + [

2
sapl/settings.py

@ -41,7 +41,7 @@ ALLOWED_HOSTS = ['*']
LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/login/?next='
SAPL_VERSION = '3.1.160-RC0'
SAPL_VERSION = '3.1.160-RC1'
if DEBUG:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

2
sapl/templates/base.html

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

17
sapl/templates/base/RelatorioMateriasPorTramitacao_filter.html

@ -44,17 +44,20 @@
</tr>
</thead>
<tbody>
{% for materia in object_list %}
{% for materia_em_tramitacao in object_list %}
<tr>
<td><a href="{% url 'sapl.materia:materialegislativa_detail' materia.pk %}">
{{materia.tipo.descricao}} {{materia.numero}}/{{materia.ano}}
</a></td>
<td>{{materia.tramitacao_set.last.unidade_tramitacao_destino}}</td>
<td>{{materia.tramitacao_set.last.status}}</td>
<td>
<a href="{% url 'sapl.materia:materialegislativa_detail' materia_em_tramitacao.materia.pk %}">
{{ materia_em_tramitacao.materia.tipo.descricao }} {{ materia_em_tramitacao.materia.numero }}/{{ materia_em_tramitacao.materia.ano }}
</a>
</td>
<td>{{ materia_em_tramitacao.materia.tramitacao_set.last.unidade_tramitacao_destino }}</td>
<td>{{ materia_em_tramitacao.materia.tramitacao_set.last.status }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% include 'paginacao.html' %}
<br/>
{% endblock base_content %}

6
sapl/templates/base/parlamentares_duplicados.html

@ -15,12 +15,12 @@
</tr>
</thead>
<tbody>
{% for quantidade, parlamentar in parlamentares_duplicados %}
{% for parlamentar in parlamentares_duplicados %}
<tr>
<td>
<a href="{% url 'sapl.parlamentares:pesquisar_parlamentar' %}?nome_parlamentar={{ parlamentar }}">{{ parlamentar }}</a>
<a href="{% url 'sapl.parlamentares:pesquisar_parlamentar' %}?nome_parlamentar={{ parlamentar.nome_parlamentar }}">{{ parlamentar.nome_parlamentar }}</a>
</td>
<td>{{ quantidade }}</td>
<td>{{ parlamentar.count }}</td>
</tr>
{% endfor %}
</tbody>

8
sapl/templates/base/protocolos_duplicados.html

@ -15,12 +15,14 @@
</tr>
</thead>
<tbody>
{% for protocolo, quantidade in protocolos_duplicados %}
{% for protocolo in protocolos_duplicados %}
<tr>
<td>
<a href="{% url 'sapl.protocoloadm:protocolo' %}?numero={{ protocolo.numero }}&ano={{ protocolo.ano }}">{{ protocolo }}</a>
<a href="{% url 'sapl.protocoloadm:protocolo' %}?numero={{ protocolo.numero }}&ano={{ protocolo.ano }}">
{{ protocolo.numero }}/{{ protocolo.ano }}
</a>
</td>
<td>{{ quantidade }}</td>
<td>{{ protocolo.total }}</td>
</tr>
{% endfor %}
</tbody>

13
sapl/templates/materia/proposicao_detail.html

@ -3,6 +3,7 @@
{% load tz %}
{% block sub_actions %}
{{block.super}}
{% if user == object.autor.user %}
<div class="actions btn-group btn-group-sm {%block sub_actions_pull%}{% endblock%}" role="group">
{% if object.texto_articulado.exists %}
<a class="btn btn-success" href="{% url 'sapl.materia:proposicao_ta' object.pk%}">{% trans "Texto Eletrônico" %}</a>
@ -11,10 +12,11 @@
<a class="btn btn-success" href="{% url 'sapl.materia:proposicao_texto' object.pk %}">{% trans "Texto Original" %}</a>
{% endif %}
</div>
{% endif %}
{% endblock sub_actions%}
{% block editions %}
{% if object.data_envio %}
{% if user == object.autor.user %}
{% if object.data_envio %}
{% block editions_actions_return %}
<div class="actions btn-group" role="group">
<a class="btn btn-outline-primary" onclick="window.open('{% url 'sapl.materia:recibo-proposicao' object.pk %}','Recibo','width=1100, height=600, scrollbars=yes')">{% trans "Recibo de Envio" %}</a>
@ -23,7 +25,6 @@
{% endif %}
</div>
{% endblock %}
{% endif %}
{% else %}
{% block editions_actions_send %}
<div class="actions btn-group" role="group">
@ -35,8 +36,10 @@
</div>
{% endblock %}
{% endif %}
{% endif %}
{% endblock editions %}
{% block detail_content %}
{% if user == object.autor.user %}
<h2 class="legend">{% model_verbose_name 'sapl.materia.models.Proposicao' %}</h2>
<div class="row">
<div class="col-sm-3">
@ -203,4 +206,10 @@
{% endif %}
</div>
{% endif %}
{% else %}
<br><br>
<div class="alert alert-danger alert-dismissible" role="alert">
<h3>Você não tem acesso a essa Proposição porque não é de sua autoria.</h3>
</div>
{% endif %}
{% endblock detail_content %}

15
sapl/templates/protocoloadm/comprovante.html

@ -18,6 +18,9 @@
.hide-print {
display : none;
}
.downsize {
font-size: 12px;
}
}
@page {
size: auto; /* auto is the initial value */
@ -74,7 +77,7 @@
{% if protocolo.tipo_processo == 1 %}
<tr>
<th>Ementa</th>
<td>{{ protocolo.assunto_ementa }}</td>
<td class="downsize">{{ protocolo.assunto_ementa }}</td>
</tr>
<tr>
<th>Autor</th>
@ -102,10 +105,16 @@
</tr>
<tr>
<th>Número Páginas</th>
<td>{{ protocolo.numero_paginas }}</td>
<td>{{ protocolo.numero_paginas|default_if_none:"0" }}</td>
</tr>
{% if materia %}
<tr>
<th>Número da Matéria</th>
<td>{{ materia }}</td>
</tr>
{% endif %}
<tr>
<th>Comprovante emitido por</th>
<th>Emitido por</th>
<td>{{ request.user.username }}</td>
</tr>
</table>

2
sapl/templates/protocoloadm/layouts.yaml

@ -9,7 +9,7 @@ DocumentoAdministrativo:
- data protocolo
- assunto
- interessado tramitacao
- texto_integral
- texto_integral|urlize
- documento_anexado_set__documento_principal|m2m_urlize_for_detail
- documento_principal_set__documento_anexado|m2m_urlize_for_detail
{% trans 'Outras Informações' %}:

12
sapl/templates/sessao/votacao/nominal.html

@ -21,7 +21,6 @@
{% else %}
<fieldset class="form-group">
<legend>Votos</legend>
<div class="row">
{% for parlamentar in parlamentares %}
@ -39,7 +38,6 @@
</div>
<legend>Situação da Votação:</legend>
<div id="soma_votos"></div>
</fieldset>
@ -59,11 +57,17 @@
</div>
<br /><br />
<input type="submit" id="salvar-votacao" name="salvar-votacao" value="Fechar Votação" class="btn btn-primary" />
<div class="row">
<div class="col-md-12">
<div class="form-group row justify-content-between">
<input type="submit" id="cancelar-votacao" name="cancelar-votacao" value="Cancelar Votação" class="btn btn-warning" />
<input type="submit" id="salvar-votacao" name="salvar-votacao" value="Fechar Votação" class="btn btn-primary" />
</div>
</div>
</div>
{% endif %}
</fieldset>
</form>
{% endif %}
{% endblock detail_content %}
{% block extra_js %}

8
sapl/templates/sessao/votacao/votacao.html

@ -71,8 +71,14 @@
</div>
<br /><br />
<input type="submit" id="salvar-votacao" name="salvar-votacao" value="Salvar" class="btn btn-primary" />
<div class="row">
<div class="col-md-12">
<div class="form-group row justify-content-between">
<input type="submit" id="cancelar-votacao" name="cancelar-votacao" value="Cancelar Votação" class="btn btn-warning" />
<input type="submit" id="salvar-votacao" name="salvar-votacao" value="Salvar" class="btn btn-primary" />
</div>
</div>
</div>
</fieldset>
</form>
{% endblock detail_content %}

2
setup.py

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

Loading…
Cancel
Save