Browse Source

Merge branch '3.1.x' into fix-relatorios

pull/2433/head
Cesar Augusto de Carvalho 7 years ago
committed by GitHub
parent
commit
011a197de8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      config/env-sample
  2. 1
      config/env_dockerfile
  3. 3
      docker-compose.yml
  4. 1
      docs/instalacao31.rst
  5. 54
      requirements/requirements.txt
  6. 1
      sapl/.env_test
  7. 2
      sapl/api/forms.py
  8. 3
      sapl/api/views.py
  9. 2
      sapl/base/email_utils.py
  10. 13
      sapl/base/templatetags/common_tags.py
  11. 9
      sapl/crud/base.py
  12. 1
      sapl/env-backup
  13. 52
      sapl/materia/forms.py
  14. 19
      sapl/materia/views.py
  15. 18
      sapl/norma/views.py
  16. 96
      sapl/parlamentares/views.py
  17. 137
      sapl/protocoloadm/forms.py
  18. 35
      sapl/protocoloadm/migrations/0014_auto_20190110_1300.py
  19. 21
      sapl/protocoloadm/migrations/0015_protocolo_timestamp_data_hora_manual.py
  20. 21
      sapl/protocoloadm/migrations/0016_auto_20190110_1345.py
  21. 24
      sapl/protocoloadm/models.py
  22. 23
      sapl/protocoloadm/tests/test_protocoloadm.py
  23. 39
      sapl/protocoloadm/views.py
  24. 27
      sapl/relatorios/views.py
  25. 8
      sapl/sessao/forms.py
  26. 45
      sapl/settings.py
  27. 3
      sapl/static/js/app.js
  28. 3
      sapl/static/styles/app.scss
  29. 22
      sapl/temp_suppress_crispy_form_warnings.py
  30. 2
      sapl/templates/base.html
  31. 4
      sapl/templates/compilacao/tipotextoarticulado_list.html
  32. 4
      sapl/templates/crud/list_tabaux.html
  33. 10
      sapl/templates/parlamentares/frente_form.html
  34. 4
      sapl/templates/parlamentares/parlamentares_list.html
  35. 4
      sapl/templates/protocoloadm/comprovante.html
  36. 14
      sapl/templates/protocoloadm/protocolar_documento.html
  37. 8
      sapl/templates/protocoloadm/protocolar_materia.html
  38. 2
      sapl/templates/protocoloadm/protocolo_filter.html
  39. 10
      sapl/templates/protocoloadm/protocolo_mostrar.html
  40. 43
      sapl/utils.py
  41. 58
      setup.py

1
config/env-sample

@ -5,4 +5,5 @@ EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST = ''
EMAIL_HOST_USER = ''
EMAIL_SEND_USER = ''
EMAIL_HOST_PASSWORD = ''

1
config/env_dockerfile

@ -5,4 +5,5 @@ EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST = ''
EMAIL_HOST_USER = ''
EMAIL_SEND_USER = ''
EMAIL_HOST_PASSWORD = ''

3
docker-compose.yml

@ -11,7 +11,7 @@ sapldb:
ports:
- "5432:5432"
sapl:
image: interlegis/sapl:3.1.140
image: interlegis/sapl:3.1.142
restart: always
environment:
ADMIN_PASSWORD: interlegis
@ -21,6 +21,7 @@ sapl:
EMAIL_USE_TLS: 'False'
EMAIL_HOST: smtp.dominio.net
EMAIL_HOST_USER: usuariosmtp
EMAIL_SEND_USER: usuariosmtp
EMAIL_HOST_PASSWORD: senhasmtp
TZ: America/Sao_Paulo
volumes:

1
docs/instalacao31.rst

@ -147,6 +147,7 @@ Criação da `SECRET_KEY <https://docs.djangoproject.com/es/1.9/ref/settings/#st
EMAIL_PORT = [Insira este parâmetro]
EMAIL_HOST = [Insira este parâmetro]
EMAIL_HOST_USER = [Insira este parâmetro]
EMAIL_SEND_USER = [Insira este parâmetro]
EMAIL_HOST_PASSWORD = [Insira este parâmetro]
DEFAULT_FROM_EMAIL = [Insira este parâmetro]
SERVER_EMAIL = [Insira este parâmetro]

54
requirements/requirements.txt

@ -1,37 +1,39 @@
dj-database-url==0.4.1
django-haystack==2.6.0
django>=1.10,<1.11
django-bootstrap3==7.0.1
django>=1.11,<2.0
django-bootstrap3==11.0.0
django-haystack==2.8.1
django-filter==2.0.0
djangorestframework==3.9.0
dj-database-url==0.5.0
django-bower==5.2.0
django-braces==1.9.0
django-compressor==2.0
django-crispy-forms==1.6.1
django-extensions==1.9.8
django-extra-views==0.11.0
django-filter==1.0.0
django-floppyforms==1.6.2
django-model-utils==3.1.1
django-sass-processor==0.5.8
djangorestframework==3.4.0
easy-thumbnails==2.5
django-crispy-forms==1.7.2
django-floppyforms==1.7.0
django-extra-views==0.12.0
django-model-utils==3.1.2
django-sass-processor==0.7.2
django-reversion==3.0.2
django-reversion-compare==0.8.6
django-speedinfo==1.4.0
django-extensions==2.1.4
django-image-cropping==1.2
libsass==0.11.1
psycopg2-binary==2.7.4
python-decouple==3.0
pytz==2016.4
easy-thumbnails==2.5
libsass==0.17.0
python-decouple==3.1
psycopg2-binary==2.7.6.1
pyyaml==4.2b1
rtyaml==0.0.3
textract==1.5.0
pytz==2018.9
rtyaml==0.0.5
python-magic==0.4.15
unipath==1.1
WeasyPrint==44
gunicorn==19.9.0
textract==1.5.0
pysolr==3.6.0
python-magic==0.4.12
gunicorn==19.6.0
django-reversion==2.0.8
WeasyPrint==0.42
whoosh==2.7.4
django-speedinfo==1.3.5
django-reversion-compare==0.8.4
git+git://github.com/interlegis/trml2pdf.git
git+git://github.com/jasperlittle/django-rest-framework-docs
git+git://github.com/rubgombar1/django-admin-bootstrapped.git
#django-compressor==2.2

1
sapl/.env_test

@ -5,4 +5,5 @@ EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST = ''
EMAIL_HOST_USER = ''
EMAIL_SEND_USER = ''
EMAIL_HOST_PASSWORD = ''

2
sapl/api/forms.py

@ -6,8 +6,8 @@ from django.forms.widgets import MultiWidget, TextInput
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django_filters.filters import CharFilter, ModelChoiceFilter, DateFilter
from django_filters.rest_framework.filterset import FilterSet
from rest_framework import serializers
from rest_framework.filters import FilterSet
from sapl.base.models import Autor, TipoAutor
from sapl.parlamentares.models import Legislatura

3
sapl/api/views.py

@ -1,9 +1,10 @@
import logging
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.http import Http404
from django.utils.translation import ugettext_lazy as _
from rest_framework.filters import DjangoFilterBackend
from django_filters.rest_framework.backends import DjangoFilterBackend
from rest_framework.generics import ListAPIView
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
from rest_framework.permissions import (AllowAny, IsAuthenticated,

2
sapl/base/email_utils.py

@ -18,7 +18,7 @@ def load_email_templates(templates, context={}):
emails = []
for t in templates:
tpl = loader.get_template(t)
email = tpl.render(Context(context))
email = tpl.render(context)
if t.endswith(".html"):
email = email.replace('\n', '').replace('\r', '')
emails.append(email)

13
sapl/base/templatetags/common_tags.py

@ -1,8 +1,4 @@
import logging
from compressor.utils import get_class
from django import template
from django.conf import settings
from django.template.defaultfilters import stringfilter
from sapl.base.models import AppConfig
@ -15,6 +11,15 @@ from sapl.utils import filiacao_data, SEPARADOR_HASH_PROPOSICAO
register = template.Library()
def get_class(class_string):
if not hasattr(class_string, '__bases__'):
class_string = str(class_string)
dot = class_string.rindex('.')
mod_name, class_name = class_string[:dot], class_string[dot + 1:]
if class_name:
return getattr(__import__(mod_name, {}, {}, [str('')]), class_name)
@register.simple_tag
def define(arg):
return arg

9
sapl/crud/base.py

@ -1,6 +1,6 @@
import logging
from braces.views import FormMessagesMixin
from compressor.utils.decorators import cached_property
from crispy_forms.bootstrap import FieldWithButtons, StrictButton
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Field, Layout
@ -16,6 +16,7 @@ from django.http.response import Http404
from django.shortcuts import redirect
from django.utils.decorators import classonlymethod
from django.utils.encoding import force_text
from django.utils.functional import cached_property
from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
from django.views.generic import (CreateView, DeleteView, DetailView, ListView,
@ -29,6 +30,7 @@ from sapl.rules.map_rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL,
from sapl.settings import BASE_DIR
from sapl.utils import normalize
ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \
'list', 'create', 'detail', 'update', 'delete'
@ -558,7 +560,8 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView):
fm = model._meta.get_field(fo)
except Exception as e:
username = self.request.user.username
self.logger.error("user=" + username + ". " + str(e))
self.logger.error(
"user=" + username + ". " + str(e))
pass
if fm and hasattr(fm, 'related_model')\
@ -882,7 +885,7 @@ class CrudDeleteView(PermissionRequiredContainerCrudMixin,
error_msg2 += '{} - {}, '.format(
i._meta.verbose_name, i
)
error_msg2 = error_msg2[:len(error_msg2)-2] + '.'
error_msg2 = error_msg2[:len(error_msg2) - 2] + '.'
error_msg += '</ul>'
username = request.user.username

1
sapl/env-backup

@ -5,4 +5,5 @@ EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST = ''
EMAIL_HOST_USER = ''
EMAIL_SEND_USER = ''
EMAIL_HOST_PASSWORD = ''

52
sapl/materia/forms.py

@ -1298,41 +1298,14 @@ class TipoProposicaoForm(ModelForm):
class TipoProposicaoSelect(Select):
def render_tipo_option(self, selected_choices, option_value, option_label,
data_has_perfil=False):
if option_value is None:
option_value = ''
option_value = force_text(option_value)
if option_value in selected_choices:
selected_html = mark_safe(' selected="selected"')
if not self.allow_multiple_selected:
# Only allow for a single selection.
selected_choices.remove(option_value)
else:
selected_html = ''
return format_html(
'<option value="{}"{} data-has-perfil={}>{}</option>',
option_value,
selected_html,
str(data_has_perfil),
force_text(option_label))
def render_options(self, selected_choices):
# Normalize to strings.
selected_choices = set(force_text(v) for v in selected_choices)
output = []
output.append(
self.render_tipo_option(
selected_choices, '', self.choices.field.empty_label))
for tipo in self.choices.queryset.all():
output.append(
self.render_tipo_option(
selected_choices,
str(tipo.pk),
str(tipo),
data_has_perfil=tipo.perfis.exists()))
return '\n'.join(output)
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
option = super().create_option(name, value, label, selected,
index, subindex=subindex, attrs=attrs)
if value:
tipo = TipoProposicao.objects.get(id=value)
option['attrs']['data-has-perfil'] = str(tipo.perfis.exists())
return option
class ProposicaoForm(forms.ModelForm):
@ -2046,15 +2019,10 @@ class ConfirmarProposicaoForm(ProposicaoForm):
self.instance.results['messages']['success'].append(_(
'Protocolo realizado com sucesso'))
# FIXME qdo protocoloadm estiver homologado, verifique a necessidade
# de redirecionamento para o protocolo.
# complete e libere código abaixo para tal.
"""
self.instance.results['url'] = reverse(
'sapl.protocoloadm:...',
'sapl.protocoloadm:protocolo_mostrar',
kwargs={'pk': protocolo.pk})
"""
conteudo_gerado.numero_protocolo = protocolo.numero
conteudo_gerado.save()

19
sapl/materia/views.py

@ -26,7 +26,7 @@ import weasyprint
import sapl
from sapl.base.email_utils import do_envia_email_confirmacao
from sapl.base.models import Autor, CasaLegislativa
from sapl.base.models import Autor, CasaLegislativa, AppConfig as BaseAppConfig
from sapl.base.signals import tramitacao_signal
from sapl.comissoes.models import Comissao, Participacao
from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_RESTRICT,
@ -783,16 +783,23 @@ class ProposicaoCrud(Crud):
msg_error = _('Proposição não possui nenhum tipo de '
'Texto associado.')
else:
p.data_devolucao = None
p.data_envio = timezone.now()
p.save()
if p.texto_articulado.exists():
ta = p.texto_articulado.first()
ta.privacidade = STATUS_TA_IMMUTABLE_RESTRICT
ta.editing_locked = True
ta.save()
receber_recibo = BaseAppConfig.attr(
'receber_recibo_proposicao')
if not receber_recibo:
ta = p.texto_articulado.first()
p.hash_code = 'P' + ta.hash() + SEPARADOR_HASH_PROPOSICAO + str(p.pk)
p.data_devolucao = None
p.data_envio = timezone.now()
p.save()
messages.success(request, _(
'Proposição enviada com sucesso.'))
try:
@ -2168,7 +2175,7 @@ class ImpressosView(PermissionRequiredMixin, TemplateView):
def gerar_pdf_impressos(request, context, template_name):
template = loader.get_template(template_name)
html = template.render(RequestContext(request, context))
html = template.render(context, request)
pdf = weasyprint.HTML(string=html, base_url=request.build_absolute_uri()
).write_pdf()

18
sapl/norma/views.py

@ -1,7 +1,5 @@
import logging
import re
import sapl
import weasyprint
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ObjectDoesNotExist
@ -14,7 +12,10 @@ from django.views.generic import TemplateView, UpdateView
from django.views.generic.base import RedirectView
from django.views.generic.edit import FormView
from django_filters.views import FilterView
import weasyprint
from sapl import settings
import sapl
from sapl.base.models import AppConfig
from sapl.compilacao.views import IntegracaoTaView
from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux,
@ -107,7 +108,8 @@ class NormaPesquisaView(FilterView):
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
context['show_results'] = show_results_filter_set(qr)
context['USE_SOLR'] = settings.USE_SOLR if hasattr(settings, 'USE_SOLR') else False
context['USE_SOLR'] = settings.USE_SOLR if hasattr(
settings, 'USE_SOLR') else False
return context
@ -200,7 +202,6 @@ class NormaCrud(Crud):
horario_acesso=timezone.now())
return super().get(request, *args, **kwargs)
class DeleteView(Crud.DeleteView):
def get_success_url(self):
@ -219,12 +220,14 @@ class NormaCrud(Crud):
username = self.request.user.username
try:
self.logger.debug('user=' + username + '. Tentando obter objeto de modelo da esfera da federação.')
self.logger.debug(
'user=' + username + '. Tentando obter objeto de modelo da esfera da federação.')
esfera = sapl.base.models.AppConfig.objects.last(
).esfera_federacao
self.initial['esfera_federacao'] = esfera
except:
self.logger.error('user=' + username + '. Erro ao obter objeto de modelo da esfera da federação.')
self.logger.error(
'user=' + username + '. Erro ao obter objeto de modelo da esfera da federação.')
pass
self.initial['complemento'] = False
return self.initial
@ -333,6 +336,7 @@ class AutoriaNormaCrud(MasterDetailCrud):
})
return initial
class ImpressosView(PermissionRequiredMixin, TemplateView):
template_name = 'materia/impressos/impressos.html'
permission_required = ('materia.can_access_impressos', )
@ -340,7 +344,7 @@ class ImpressosView(PermissionRequiredMixin, TemplateView):
def gerar_pdf_impressos(request, context, template_name):
template = loader.get_template(template_name)
html = template.render(RequestContext(request, context))
html = template.render(context, request)
pdf = weasyprint.HTML(string=html, base_url=request.build_absolute_uri()
).write_pdf()

96
sapl/parlamentares/views.py

@ -1,6 +1,6 @@
from datetime import datetime
import json
import logging
from datetime import datetime
from django.contrib import messages
from django.contrib.contenttypes.models import ContentType
@ -35,6 +35,7 @@ from .models import (CargoMesa, Coligacao, ComposicaoColigacao, ComposicaoMesa,
NivelInstrucao, Parlamentar, Partido, SessaoLegislativa,
SituacaoMilitar, TipoAfastamento, TipoDependente, Votante)
CargoMesaCrud = CrudAux.build(CargoMesa, 'cargo_mesa')
PartidoCrud = CrudAux.build(Partido, 'partidos')
TipoDependenteCrud = CrudAux.build(TipoDependente, 'tipo_dependente')
@ -45,6 +46,7 @@ TipoMilitarCrud = CrudAux.build(SituacaoMilitar, 'tipo_situa_militar')
DependenteCrud = MasterDetailCrud.build(
Dependente, 'parlamentar', 'dependente')
class SessaoLegislativaCrud(CrudAux):
model = SessaoLegislativa
@ -54,6 +56,7 @@ class SessaoLegislativaCrud(CrudAux):
class UpdateView(CrudAux.UpdateView):
form_class = SessaoLegislativaForm
class VotanteView(MasterDetailCrud):
model = Votante
parent_field = 'parlamentar'
@ -89,6 +92,7 @@ class FrenteList(MasterDetailCrud):
class BaseMixin(Crud.PublicMixin, MasterDetailCrud.BaseMixin):
list_field_names = ['nome', 'data_criacao', 'data_extincao']
@classmethod
def url_name(cls, suffix):
return '%s_parlamentar_%s' % (cls.model._meta.model_name, suffix)
@ -276,13 +280,16 @@ def parlamentares_frente_selected(request):
logger = logging.getLogger(__name__)
username = request.user.username
try:
logger.info("user=" + username + ". Tentando objet objeto Frente com id={}.".format(request.GET['frente_id']))
logger.info("user=" + username +
". Tentando objet objeto Frente com id={}.".format(request.GET['frente_id']))
frente = Frente.objects.get(id=int(request.GET['frente_id']))
except ObjectDoesNotExist:
logger.error("user=" + username + ". Frente buscada (id={}) não existe. Retornada lista vazia.".format(request.GET['frente_id']))
logger.error("user=" + username +
". Frente buscada (id={}) não existe. Retornada lista vazia.".format(request.GET['frente_id']))
lista_parlamentar_id = []
else:
logger.info("user=" + username + ". Frente (id={}) encontrada com sucesso.".format(request.GET['frente_id']))
logger.info("user=" + username +
". Frente (id={}) encontrada com sucesso.".format(request.GET['frente_id']))
lista_parlamentar_id = frente.parlamentares.all().values_list(
'id', flat=True)
return JsonResponse({'id_list': list(lista_parlamentar_id)})
@ -292,8 +299,14 @@ class FrenteCrud(Crud):
model = Frente
help_topic = 'tipo_situa_militar'
public = [RP_DETAIL, RP_LIST]
list_field_names = ['nome', 'data_criacao', 'data_extincao', 'parlamentares']
list_field_names = ['nome', 'data_criacao',
'data_extincao', 'parlamentares']
class BaseMixin(Crud.BaseMixin):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['subnav_template_name'] = ''
return context
class CreateView(Crud.CreateView):
form_class = FrenteForm
@ -305,7 +318,6 @@ class FrenteCrud(Crud):
form_class = FrenteForm
class MandatoCrud(MasterDetailCrud):
model = Mandato
parent_field = 'parlamentar'
@ -371,11 +383,13 @@ class LegislaturaCrud(CrudAux):
def get_initial(self):
username = self.request.user.username
try:
self.logger.error("user=" + username + ". Tentando obter última Legislatura.")
self.logger.error("user=" + username +
". Tentando obter última Legislatura.")
ultima_legislatura = Legislatura.objects.latest('numero')
numero = ultima_legislatura.numero + 1
except Legislatura.DoesNotExist:
self.logger.error("user=" + username + ". Legislatura não encontrada. Número definido como 1.")
self.logger.error(
"user=" + username + ". Legislatura não encontrada. Número definido como 1.")
numero = 1
return {'numero': numero}
@ -476,10 +490,12 @@ class ParlamentarCrud(Crud):
def take_legislatura_id(self):
username = self.request.user.username
try:
self.logger.debug("user=" + username + ". Tentando obter id da legislatura.")
self.logger.debug("user=" + username +
". Tentando obter id da legislatura.")
return int(self.request.GET['pk'])
except:
self.logger.error("user=" + username + ". Legislatura não possui ID. Buscando em todas as entradas.")
self.logger.error(
"user=" + username + ". Legislatura não possui ID. Buscando em todas as entradas.")
legislaturas = Legislatura.objects.all()
for l in legislaturas:
if l.atual():
@ -501,14 +517,17 @@ class ParlamentarCrud(Crud):
mandato_titular=F('mandato__titular')).distinct()
else:
try:
self.logger.debug("user=" + username + ". Tentando obter o mais recente registro do objeto Legislatura.")
self.logger.debug(
"user=" + username + ". Tentando obter o mais recente registro do objeto Legislatura.")
l = Legislatura.objects.all().order_by(
'-data_inicio').first()
except ObjectDoesNotExist:
self.logger.error("user=" + username + ". Objeto não encontrado. Retornando todos os registros.")
self.logger.error(
"user=" + username + ". Objeto não encontrado. Retornando todos os registros.")
return Legislatura.objects.all()
else:
self.logger.info("user=" + username + ". Objeto encontrado com sucesso.")
self.logger.info("user=" + username +
". Objeto encontrado com sucesso.")
if l is None:
return Legislatura.objects.all()
return queryset.filter(mandato__legislatura_id=l).annotate(
@ -574,7 +593,8 @@ class ParlamentarCrud(Crud):
# Caso encontre UMA filiação nessas condições
else:
self.logger.debug("user=" + username + ". Filiação encontrada com sucesso.")
self.logger.debug("user=" + username +
". Filiação encontrada com sucesso.")
row[1] = (filiacao.partido.sigla, None, None)
return context
@ -606,13 +626,16 @@ class ParlamentarMateriasView(FormView):
parlamentar_pk = kwargs['pk']
username = request.user.username
try:
self.logger.debug("user=" + username + ". Tentando obter Autor (object_id={}).".format(parlamentar_pk))
self.logger.debug(
"user=" + username + ". Tentando obter Autor (object_id={}).".format(parlamentar_pk))
autor = Autor.objects.get(
content_type=ContentType.objects.get_for_model(Parlamentar),
object_id=parlamentar_pk)
except ObjectDoesNotExist:
mensagem = _('Este Parlamentar (pk={}) não é Autor de matéria.'.format(parlamentar_pk))
self.logger.error("user=" + username + ". Este Parlamentar (pk={}) não é Autor de matéria.".format(parlamentar_pk))
mensagem = _(
'Este Parlamentar (pk={}) não é Autor de matéria.'.format(parlamentar_pk))
self.logger.error(
"user=" + username + ". Este Parlamentar (pk={}) não é Autor de matéria.".format(parlamentar_pk))
messages.add_message(request, messages.ERROR, mensagem)
return HttpResponseRedirect(
reverse(
@ -700,7 +723,8 @@ class MesaDiretoraView(FormView):
sessao_atual = sessoes.filter(data_inicio__year__lte=year).exclude(
data_inicio__gt=timezone.now()).order_by('-data_inicio').first()
mesa = sessao_atual.composicaomesa_set.all().order_by('cargo_id') if sessao_atual else []
mesa = sessao_atual.composicaomesa_set.all().order_by(
'cargo_id') if sessao_atual else []
cargos_ocupados = [m.cargo for m in mesa]
cargos = CargoMesa.objects.all()
@ -756,7 +780,8 @@ def altera_field_mesa(request):
else:
year = timezone.now().year
try:
logger.debug("user=" + username + ". Tentando obter id de sessoes com data_inicio.ano={}.".format(year))
logger.debug(
"user=" + username + ". Tentando obter id de sessoes com data_inicio.ano={}.".format(year))
sessao_selecionada = sessoes.get(data_inicio__year=year).id
except ObjectDoesNotExist:
logger.error("user=" + username + ". Id de sessoes com data_inicio.ano={} não encontrado. "
@ -809,24 +834,29 @@ def insere_parlamentar_composicao(request):
composicao = ComposicaoMesa()
try:
logger.debug("user=" + username + ". Tentando obter SessaoLegislativa com id={}.".format(request.POST['sessao']))
logger.debug(
"user=" + username + ". Tentando obter SessaoLegislativa com id={}.".format(request.POST['sessao']))
composicao.sessao_legislativa = SessaoLegislativa.objects.get(
id=int(request.POST['sessao']))
except MultiValueDictKeyError:
logger.error("user=" + username + ". 'MultiValueDictKeyError', nenhuma sessão foi inserida!")
logger.error(
"user=" + username + ". 'MultiValueDictKeyError', nenhuma sessão foi inserida!")
return JsonResponse({'msg': ('Nenhuma sessão foi inserida!', 0)})
try:
logger.debug("user=" + username + ". Tentando obter Parlamentar com id={}.".format(request.POST['parlamentar']))
logger.debug(
"user=" + username + ". Tentando obter Parlamentar com id={}.".format(request.POST['parlamentar']))
composicao.parlamentar = Parlamentar.objects.get(
id=int(request.POST['parlamentar']))
except MultiValueDictKeyError:
logger.error("user=" + username + ". 'MultiValueDictKeyError', nenhum parlamentar foi inserido!")
logger.error(
"user=" + username + ". 'MultiValueDictKeyError', nenhum parlamentar foi inserido!")
return JsonResponse({
'msg': ('Nenhum parlamentar foi inserido!', 0)})
try:
logger.info("user=" + username + ". Tentando obter CargoMesa com id={}.".format(request.POST['cargo']))
logger.info("user=" + username +
". Tentando obter CargoMesa com id={}.".format(request.POST['cargo']))
composicao.cargo = CargoMesa.objects.get(
id=int(request.POST['cargo']))
parlamentar_ja_inserido = ComposicaoMesa.objects.filter(
@ -839,14 +869,16 @@ def insere_parlamentar_composicao(request):
composicao.save()
except MultiValueDictKeyError:
logger.error("user=" + username + ". 'MultiValueDictKeyError', nenhum cargo foi inserido!")
logger.error("user=" + username +
". 'MultiValueDictKeyError', nenhum cargo foi inserido!")
return JsonResponse({'msg': ('Nenhum cargo foi inserido!', 0)})
logger.info("user=" + username + ". Parlamentar inserido com sucesso!")
return JsonResponse({'msg': ('Parlamentar inserido com sucesso!', 1)})
else:
logger.error("user=" + username + " não tem permissão para esta operação!")
logger.error("user=" + username +
" não tem permissão para esta operação!")
return JsonResponse(
{'msg': ('Você não tem permissão para esta operação!', 0)})
@ -864,7 +896,8 @@ def remove_parlamentar_composicao(request):
if 'composicao_mesa' in request.POST:
try:
logger.debug("user=" + username + ". Tentando obter ComposicaoMesa com id={}.".format(request.POST['composicao_mesa']))
logger.debug("user=" + username + ". Tentando obter ComposicaoMesa com id={}.".format(
request.POST['composicao_mesa']))
composicao = ComposicaoMesa.objects.get(
id=request.POST['composicao_mesa'])
except ObjectDoesNotExist:
@ -876,12 +909,14 @@ def remove_parlamentar_composicao(request):
composicao.delete()
logger.info("user=" + username + ". ComposicaoMesa com id={} excluido com sucesso!".format(request.POST['composicao_mesa']))
logger.info("user=" + username + ". ComposicaoMesa com id={} excluido com sucesso!".format(
request.POST['composicao_mesa']))
return JsonResponse(
{'msg': (
'Parlamentar excluido com sucesso!', 1)})
else:
logger.info("user=" + username + ". Nenhum parlamentar escolhido para ser excluído.")
logger.info("user=" + username +
". Nenhum parlamentar escolhido para ser excluído.")
return JsonResponse(
{'msg': (
'Selecione algum parlamentar para ser excluido!', 0)})
@ -957,7 +992,8 @@ def altera_field_mesa_public_view(request):
else:
try:
year = timezone.now().year
logger.info("user=" + username + ". Tentando obter sessões com data_inicio.ano = {}.".format(year))
logger.info("user=" + username +
". Tentando obter sessões com data_inicio.ano = {}.".format(year))
sessao_selecionada = sessoes.get(data_inicio__year=year).id
except ObjectDoesNotExist:
logger.error("user=" + username + ". Sessões não encontradas com com data_inicio.ano = {}. "

137
sapl/protocoloadm/forms.py

@ -1,9 +1,9 @@
import logging
from crispy_forms.bootstrap import InlineRadios
from crispy_forms.bootstrap import InlineRadios, Alert
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout
from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout, Div
from django import forms
from django.core.exceptions import (MultipleObjectsReturned,
ObjectDoesNotExist, ValidationError)
@ -18,11 +18,12 @@ from sapl.base.models import Autor, TipoAutor
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row
from sapl.materia.models import (MateriaLegislativa, TipoMateriaLegislativa,
UnidadeTramitacao)
from sapl.protocoloadm.models import Protocolo
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, AnoNumeroOrderingFilter,
RangeWidgetOverride, autor_label, autor_modal,
choice_anos_com_protocolo, choice_force_optional,
choice_anos_com_documentoadministrativo,
FilterOverridesMetaMixin)
FilterOverridesMetaMixin, choice_anos_com_materias)
from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo,
DocumentoAdministrativo,
@ -344,6 +345,12 @@ class ProtocoloDocumentForm(ModelForm):
numero = forms.IntegerField(
required=False, label=_('Número de Protocolo (opcional)'))
data_hora_manual = forms.ChoiceField(
label=_('Informar data e hora manualmente?'),
widget=forms.RadioSelect(),
choices=YES_NO_CHOICES,
initial=False)
class Meta:
model = Protocolo
fields = ['tipo_protocolo',
@ -352,7 +359,9 @@ class ProtocoloDocumentForm(ModelForm):
'assunto',
'interessado',
'observacao',
'numero'
'numero',
'data',
'hora',
]
def __init__(self, *args, **kwargs):
@ -360,30 +369,56 @@ class ProtocoloDocumentForm(ModelForm):
row1 = to_row(
[(InlineRadios('tipo_protocolo'), 12)])
row2 = to_row(
[('tipo_documento', 6),
('numero_paginas', 6)])
row3 = to_row(
[('assunto', 12)])
[('tipo_documento', 5),
('numero_paginas', 2),
(Div(), 1),
(InlineRadios('data_hora_manual'), 4),
])
row3 = to_row([
(Div(), 2),
(Alert(
"""
Usuário: <strong>{}</strong> - {}<br>
IP: <strong>{}</strong> - {}<br>
""".format(
kwargs['initial']['user_data_hora_manual'],
Protocolo._meta.get_field(
'user_data_hora_manual').help_text,
kwargs['initial']['ip_data_hora_manual'],
Protocolo._meta.get_field(
'ip_data_hora_manual').help_text,
),
dismiss=False,
css_class='alert-info'), 6),
('data', 2),
('hora', 2),
])
row4 = to_row(
[('interessado', 12)])
[('assunto', 12)])
row5 = to_row(
[('observacao', 12)])
[('interessado', 12)])
row6 = to_row(
[('observacao', 12)])
row7 = to_row(
[('numero', 12)])
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(_('Identificação de Documento'),
row1,
row2,
row2),
Fieldset(_('Protocolo com data e hora informados manualmente'),
row3,
css_id='protocolo_data_hora_manual',
css_class='hidden'),
row4,
row5,
HTML("&nbsp;"),
),
Fieldset(_('Número do Protocolo (Apenas se quiser que a numeração comece '
'a partir do número a ser informado)'),
row6,
row7,
HTML("&nbsp;"),
form_actions(label=_('Protocolar Documento'))
)
@ -419,7 +454,8 @@ class ProtocoloMateriaForm(ModelForm):
ano_materia = forms.CharField(
label=_('Ano matéria'), required=False)
vincular_materia = forms.ChoiceField(label=_('Vincular a matéria existente?'),
vincular_materia = forms.ChoiceField(
label=_('Vincular a matéria existente?'),
widget=forms.RadioSelect(),
choices=YES_NO_CHOICES,
initial=False)
@ -435,6 +471,12 @@ class ProtocoloMateriaForm(ModelForm):
numero = forms.IntegerField(
required=False, label=_('Número de Protocolo (opcional)'))
data_hora_manual = forms.ChoiceField(
label=_('Informar data e hora manualmente?'),
widget=forms.RadioSelect(),
choices=YES_NO_CHOICES,
initial=False)
class Meta:
model = Protocolo
fields = ['tipo_materia',
@ -446,7 +488,9 @@ class ProtocoloMateriaForm(ModelForm):
'numero_materia',
'ano_materia',
'vincular_materia',
'numero'
'numero',
'data',
'hora',
]
def clean_autor(self):
@ -506,28 +550,55 @@ class ProtocoloMateriaForm(ModelForm):
('tipo_autor', 3),
('autor', 3)])
row2 = to_row(
[(InlineRadios('vincular_materia'), 4),
('numero_materia', 4),
('ano_materia', 4), ])
row3 = to_row(
[('assunto_ementa', 12)])
[(InlineRadios('vincular_materia'), 3),
('numero_materia', 2),
('ano_materia', 2),
(Div(), 1),
(InlineRadios('data_hora_manual'), 4),
])
row3 = to_row([
(Div(), 2),
(Alert(
"""
Usuário: <strong>{}</strong> - {}<br>
IP: <strong>{}</strong> - {}<br>
""".format(
kwargs['initial']['user_data_hora_manual'],
Protocolo._meta.get_field(
'user_data_hora_manual').help_text,
kwargs['initial']['ip_data_hora_manual'],
Protocolo._meta.get_field(
'ip_data_hora_manual').help_text,
),
dismiss=False,
css_class='alert-info'), 6),
('data', 2),
('hora', 2),
])
row4 = to_row(
[('observacao', 12)])
[('assunto_ementa', 12)])
row5 = to_row(
[('observacao', 12)])
row6 = to_row(
[('numero', 12)])
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(_('Identificação da Matéria'),
row1,
row2,
row2),
Fieldset(_('Protocolo com data e hora informados manualmente'),
row3,
css_id='protocolo_data_hora_manual',
css_class='hidden'),
row4,
row5,
HTML("&nbsp;"),
),
Fieldset(_('Número do Protocolo (Apenas se quiser que a numeração comece'
' a partir do número a ser informado)'),
row5,
row6,
HTML("&nbsp;"),
form_actions(label=_('Protocolar Matéria')))
)
@ -855,14 +926,14 @@ class DesvincularDocumentoForm(ModelForm):
logger = logging.getLogger(__name__)
numero = forms.CharField(required=True,
label=DocumentoAdministrativo._meta.
get_field('numero').verbose_name
)
ano = forms.ChoiceField(required=True,
label=DocumentoAdministrativo._meta.
get_field('ano').verbose_name,
choices=RANGE_ANOS,
numero = forms.CharField(
required=True,
label=DocumentoAdministrativo._meta.get_field('numero').verbose_name)
ano = forms.ChoiceField(
required=True,
label=DocumentoAdministrativo._meta.get_field('ano').verbose_name,
choices=choice_anos_com_documentoadministrativo,
widget=forms.Select(attrs={'class': 'selector'}))
def clean(self):
@ -929,7 +1000,7 @@ class DesvincularMateriaForm(forms.Form):
label=_('Número da Matéria'))
ano = forms.ChoiceField(required=True,
label=_('Ano da Matéria'),
choices=RANGE_ANOS,
choices=choice_anos_com_materias,
widget=forms.Select(attrs={'class': 'selector'}))
tipo = forms.ModelChoiceField(label=_('Tipo de Matéria'),
required=True,

35
sapl/protocoloadm/migrations/0014_auto_20190110_1300.py

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-10 15:00
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0013_auto_20190106_1336'),
]
operations = [
migrations.AddField(
model_name='protocolo',
name='ip_data_hora_manual',
field=models.CharField(blank=True, help_text='Endereço IP da estação de trabalho do usuário que está realizando Protocolo e informando data e hora manualmente.', max_length=15, verbose_name='IP'),
),
migrations.AddField(
model_name='protocolo',
name='user_data_hora_manual',
field=models.CharField(blank=True, help_text='Usuário que está realizando Protocolo e informando data e hora manualmente.', max_length=20, verbose_name='IP'),
),
migrations.AlterField(
model_name='protocolo',
name='data',
field=models.DateField(blank=True, help_text='Informado manualmente', null=True, verbose_name='Data do Protocolo'),
),
migrations.AlterField(
model_name='protocolo',
name='hora',
field=models.TimeField(blank=True, help_text='Informado manualmente', null=True, verbose_name='Hora do Protocolo'),
),
]

21
sapl/protocoloadm/migrations/0015_protocolo_timestamp_data_hora_manual.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-10 15:43
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0014_auto_20190110_1300'),
]
operations = [
migrations.AddField(
model_name='protocolo',
name='timestamp_data_hora_manual',
field=models.DateTimeField(default=django.utils.timezone.now),
),
]

21
sapl/protocoloadm/migrations/0016_auto_20190110_1345.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-10 15:45
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0015_protocolo_timestamp_data_hora_manual'),
]
operations = [
migrations.AlterField(
model_name='protocolo',
name='timestamp',
field=models.DateTimeField(blank=True, default=django.utils.timezone.now, null=True),
),
]

24
sapl/protocoloadm/models.py

@ -57,13 +57,29 @@ class Protocolo(models.Model):
choices=RANGE_ANOS,
verbose_name=_('Ano do Protocolo'))
# FIXME: https://github.com/interlegis/sapl/issues/2337
data = models.DateField(null=True, blank=True)
hora = models.TimeField(null=True, blank=True)
data = models.DateField(null=True, blank=True,
verbose_name=_('Data do Protocolo'),
help_text=_('Informado manualmente'))
hora = models.TimeField(null=True, blank=True,
verbose_name=_('Hora do Protocolo'),
help_text=_('Informado manualmente'))
timestamp_data_hora_manual = models.DateTimeField(default=timezone.now)
user_data_hora_manual = models.CharField(
max_length=20, blank=True,
verbose_name=_('IP'),
help_text=_('Usuário que está realizando Protocolo e informando '
'data e hora manualmente.'))
ip_data_hora_manual = models.CharField(
max_length=15, blank=True,
verbose_name=_('IP'),
help_text=_('Endereço IP da estação de trabalho '
'do usuário que está realizando Protocolo e informando '
'data e hora manualmente.'))
# Não foi utilizado auto_now_add=True em timestamp porque
# ele usa datetime.now que não é timezone aware.
timestamp = models.DateTimeField(default=timezone.now)
timestamp = models.DateTimeField(
default=timezone.now, null=True, blank=True)
tipo_protocolo = models.PositiveIntegerField(
blank=True, null=True, verbose_name=_('Tipo de Protocolo'))
tipo_processo = models.PositiveIntegerField()

23
sapl/protocoloadm/tests/test_protocoloadm.py

@ -1,4 +1,4 @@
from datetime import date, timedelta
from datetime import date, timedelta, datetime
from django.core.urlresolvers import reverse
from django.utils import timezone
@ -392,29 +392,42 @@ def test_documento_administrativo_protocolo_inexistente():
def test_protocolo_documento_form_invalido():
form = ProtocoloDocumentForm(data={})
form = ProtocoloDocumentForm(
data={},
initial={
'user_data_hora_manual': '',
'ip_data_hora_manual': '',
'data': timezone.localdate(timezone.now()),
'hora': timezone.localtime(timezone.now())})
assert not form.is_valid()
errors = form.errors
assert errors['data_hora_manual'] == [_('Este campo é obrigatório.')]
assert errors['tipo_protocolo'] == [_('Este campo é obrigatório.')]
assert errors['interessado'] == [_('Este campo é obrigatório.')]
assert errors['tipo_documento'] == [_('Este campo é obrigatório.')]
assert errors['numero_paginas'] == [_('Este campo é obrigatório.')]
assert errors['assunto'] == [_('Este campo é obrigatório.')]
assert len(errors) == 5
assert len(errors) == 6
def test_protocolo_materia_invalido():
form = ProtocoloMateriaForm(data={})
form = ProtocoloMateriaForm(data={},
initial={
'user_data_hora_manual': '',
'ip_data_hora_manual': '',
'data': timezone.localdate(timezone.now()),
'hora': timezone.localtime(timezone.now())})
assert not form.is_valid()
errors = form.errors
assert errors['data_hora_manual'] == [_('Este campo é obrigatório.')]
assert errors['assunto_ementa'] == [_('Este campo é obrigatório.')]
assert errors['tipo_autor'] == [_('Este campo é obrigatório.')]
assert errors['tipo_materia'] == [_('Este campo é obrigatório.')]
@ -422,4 +435,4 @@ def test_protocolo_materia_invalido():
assert errors['autor'] == [_('Este campo é obrigatório.')]
assert errors['vincular_materia'] == [_('Este campo é obrigatório.')]
assert len(errors) == 6
assert len(errors) == 7

39
sapl/protocoloadm/views.py

@ -492,6 +492,15 @@ class ProtocoloDocumentoView(PermissionRequiredMixin,
return reverse('sapl.protocoloadm:protocolo_mostrar',
kwargs={'pk': self.object.id})
def get_initial(self):
initial = super().get_initial()
initial['user_data_hora_manual'] = self.request.user.username
initial['ip_data_hora_manual'] = get_client_ip(self.request)
initial['data'] = timezone.localdate(timezone.now())
initial['hora'] = timezone.localtime(timezone.now())
return initial
def form_valid(self, form):
protocolo = form.save(commit=False)
username = self.request.user.username
@ -538,6 +547,17 @@ class ProtocoloDocumentoView(PermissionRequiredMixin,
return self.render_to_response(self.get_context_data())
protocolo.ano = timezone.now().year
protocolo.assunto_ementa = self.request.POST['assunto']
if form.cleaned_data['data_hora_manual'] == 'True':
protocolo.timestamp = None
protocolo.user_data_hora_manual = username
protocolo.ip_data_hora_manual = get_client_ip(self.request)
else:
protocolo.data = None
protocolo.hora = None
protocolo.user_data_hora_manual = ''
protocolo.ip_data_hora_manual = ''
protocolo.save()
self.object = protocolo
return redirect(self.get_success_url())
@ -659,6 +679,15 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
return reverse('sapl.protocoloadm:materia_continuar', kwargs={
'pk': protocolo.pk})
def get_initial(self):
initial = super().get_initial()
initial['user_data_hora_manual'] = self.request.user.username
initial['ip_data_hora_manual'] = get_client_ip(self.request)
initial['data'] = timezone.localdate(timezone.now())
initial['hora'] = timezone.localtime(timezone.now())
return initial
def form_valid(self, form):
protocolo = form.save(commit=False)
username = self.request.user.username
@ -719,6 +748,16 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
protocolo.observacao = self.request.POST['observacao']
protocolo.assunto_ementa = self.request.POST['assunto_ementa']
if form.cleaned_data['data_hora_manual'] == 'True':
protocolo.timestamp = None
protocolo.user_data_hora_manual = username
protocolo.ip_data_hora_manual = get_client_ip(self.request)
else:
protocolo.data = None
protocolo.hora = None
protocolo.user_data_hora_manual = ''
protocolo.ip_data_hora_manual = ''
protocolo.save()
data = form.cleaned_data
if data['vincular_materia'] == 'True':

27
sapl/relatorios/views.py

@ -1,7 +1,7 @@
from datetime import datetime as dt
import html
import re
import logging
from datetime import datetime as dt
import re
from django.core.exceptions import ObjectDoesNotExist
from django.http import Http404, HttpResponse
@ -581,7 +581,6 @@ def get_sessao_plenaria(sessao, casa):
if dic_expedientes:
lst_expedientes.append(dic_expedientes)
# Lista das matérias do Expediente, incluindo o resultado das votacoes
lst_expediente_materia = []
for expediente_materia in ExpedienteMateria.objects.filter(
@ -612,7 +611,8 @@ def get_sessao_plenaria(sessao, casa):
dic_expediente_materia["nom_autor"] = ''
autoria = materia.autoria_set.all()
dic_expediente_materia['num_autores'] = 'Autores' if len(autoria) > 1 else 'Autor'
dic_expediente_materia['num_autores'] = 'Autores' if len(
autoria) > 1 else 'Autor'
if autoria:
for a in autoria:
if a.autor.nome:
@ -762,7 +762,6 @@ def get_sessao_plenaria(sessao, casa):
lst_ocorrencias.append(o)
return (inf_basicas_dic,
lst_mesa,
lst_presenca_sessao,
@ -810,10 +809,12 @@ def relatorio_sessao_plenaria(request, pk):
imagem = get_imagem(casa)
try:
logger.debug("user=" + username + ". Tentando obter SessaoPlenaria com id={}.".format(pk))
logger.debug("user=" + username +
". Tentando obter SessaoPlenaria com id={}.".format(pk))
sessao = SessaoPlenaria.objects.get(id=pk)
except ObjectDoesNotExist as e:
logger.error("user=" + username + ". Essa SessaoPlenaria não existe (pk={}). ".format(pk) + str(e))
logger.error("user=" + username +
". Essa SessaoPlenaria não existe (pk={}). ".format(pk) + str(e))
raise Http404('Essa página não existe')
(inf_basicas_dic,
@ -828,7 +829,6 @@ def relatorio_sessao_plenaria(request, pk):
lst_oradores,
lst_ocorrencias) = get_sessao_plenaria(sessao, casa)
for idx in range(len(lst_expedientes)):
txt_expedientes = lst_expedientes[idx]['txt_expediente']
txt_expedientes = TrocaTag(txt_expedientes, '<table', 'table>', 6, 6,
@ -979,8 +979,8 @@ def get_etiqueta_protocolos(prots):
dic['titulo'] = str(p.numero) + '/' + str(p.ano)
tz_hora = timezone.localtime(p.timestamp)
if p.timestamp:
tz_hora = timezone.localtime(p.timestamp)
dic['data'] = '<b>Data: </b>' + tz_hora.strftime(
"%d/%m/%Y") + ' - <b>Horário: </b>' + tz_hora.strftime("%H:%M")
else:
@ -1072,7 +1072,8 @@ def get_pauta_sessao(sessao, casa):
id=expediente_materia.materia.id).first()
dic_expediente_materia = {}
dic_expediente_materia["tipo_materia"] = materia.tipo.sigla + ' - ' + materia.tipo.descricao
dic_expediente_materia["tipo_materia"] = materia.tipo.sigla + \
' - ' + materia.tipo.descricao
dic_expediente_materia["num_ordem"] = str(
expediente_materia.numero_ordem)
dic_expediente_materia["id_materia"] = str(
@ -1090,7 +1091,8 @@ def get_pauta_sessao(sessao, casa):
dic_expediente_materia["nom_autor"] = ''
autoria = materia.autoria_set.all()
dic_expediente_materia['num_autores'] = 'Autores' if len(autoria) > 1 else 'Autor'
dic_expediente_materia['num_autores'] = 'Autores' if len(
autoria) > 1 else 'Autor'
if autoria:
for a in autoria:
if a.autor.nome:
@ -1112,7 +1114,8 @@ def get_pauta_sessao(sessao, casa):
materia = MateriaLegislativa.objects.filter(
id=votacao.materia.id).first()
dic_votacao = {}
dic_votacao["tipo_materia"] = materia.tipo.sigla + ' - ' + materia.tipo.descricao
dic_votacao["tipo_materia"] = materia.tipo.sigla + \
' - ' + materia.tipo.descricao
dic_votacao["num_ordem"] = votacao.numero_ordem
dic_votacao["id_materia"] = str(
materia.numero) + "/" + str(materia.ano)

8
sapl/sessao/forms.py

@ -3,7 +3,6 @@ from datetime import datetime
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Button, Fieldset, Layout
from django import forms
from django.contrib import messages
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db import transaction
@ -12,22 +11,21 @@ from django.forms import ModelForm
from django.forms.widgets import CheckboxSelectMultiple
from django.utils.translation import ugettext_lazy as _
import django_filters
from floppyforms import widgets
from sapl.base.models import Autor, TipoAutor
from sapl.crispy_layout_mixin import form_actions, to_row, SaplFormLayout
from sapl.materia.forms import MateriaLegislativaFilterSet
from sapl.materia.models import (MateriaLegislativa, StatusTramitacao,
TipoMateriaLegislativa)
from sapl.parlamentares.models import Parlamentar, Legislatura, Mandato
from sapl.parlamentares.models import Parlamentar, Mandato
from sapl.utils import (RANGE_DIAS_MES, RANGE_MESES,
MateriaPesquisaOrderingFilter, autor_label,
autor_modal, timezone, choice_anos_com_sessaoplenaria)
from .models import (Bancada, Bloco, ExpedienteMateria, JustificativaAusencia,
Orador, OradorExpediente, OrdemDia, PresencaOrdemDia, SessaoPlenaria,
SessaoPlenariaPresenca, TipoJustificativa, TipoResultadoVotacao,
OcorrenciaSessao, RegistroVotacao, RetiradaPauta, TipoRetiradaPauta)
SessaoPlenariaPresenca, TipoResultadoVotacao,
OcorrenciaSessao, RetiradaPauta, TipoRetiradaPauta)
MES_CHOICES = RANGE_MESES

45
sapl/settings.py

@ -22,9 +22,6 @@ from dj_database_url import parse as db_url
from easy_thumbnails.conf import Settings as thumbnail_settings
from unipath import Path
from .temp_suppress_crispy_form_warnings import \
SUPRESS_CRISPY_FORM_WARNINGS_LOGGING
host = socket.gethostbyname_ex(socket.gethostname())[0]
@ -78,20 +75,24 @@ INSTALLED_APPS = (
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# more
'django_extensions',
'djangobower',
'bootstrap3', # basically for django_admin_bootstrapped
'bootstrap3',
'crispy_forms',
'easy_thumbnails',
'image_cropping',
'floppyforms',
'haystack',
'sass_processor',
'rest_framework',
'django_filters',
'easy_thumbnails',
'image_cropping',
'reversion',
'reversion_compare',
'haystack',
'whoosh',
'speedinfo',
@ -110,7 +111,7 @@ SOLR_URL = config('SOLR_URL', cast=str, default='http://localhost:8983')
SOLR_COLLECTION = config('SOLR_COLLECTION', cast=str, default='sapl')
if USE_SOLR:
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' #enable auto-index
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' # enable auto-index
SEARCH_BACKEND = 'haystack.backends.solr_backend.SolrEngine'
SEARCH_URL = ('URL', '{}/solr/{}'.format(SOLR_URL, SOLR_COLLECTION))
@ -166,7 +167,7 @@ REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS": "sapl.api.pagination.StandardPagination",
"DEFAULT_FILTER_BACKENDS": (
"rest_framework.filters.SearchFilter",
"rest_framework.filters.DjangoFilterBackend",
'django_filters.rest_framework.DjangoFilterBackend',
),
}
@ -282,6 +283,7 @@ DAB_FIELD_RENDERER = \
CRISPY_TEMPLATE_PACK = 'bootstrap3'
CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap3'
CRISPY_FAIL_SILENTLY = not DEBUG
FLOPPY_FORMS_USE_GIS = False
BOWER_COMPONENTS_ROOT = PROJECT_DIR.child("bower")
BOWER_INSTALLED_APPS = (
@ -339,6 +341,22 @@ LOGGING = {
}
}
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.PBKDF2PasswordHasher', # default
'sapl.hashers.ZopeSHA1PasswordHasher',
]
def remove_warnings():
import warnings
warnings.filterwarnings(
'ignore', module='floppyforms',
message='Unable to import floppyforms.gis'
)
remove_warnings()
def uncaught_exceptions(type, value, error_traceback):
import traceback
@ -350,8 +368,3 @@ def uncaught_exceptions(type, value, error_traceback):
# captura exceções que não foram tratadas
sys.excepthook = uncaught_exceptions
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.PBKDF2PasswordHasher', # default
'sapl.hashers.ZopeSHA1PasswordHasher',
]

3
sapl/static/js/app.js

@ -47,6 +47,7 @@ function refreshMask() {
$('.dateinput').mask('00/00/0000', {placeholder:"__/__/____"});
$('.hora').mask("00:00", {placeholder:"hh:mm"});
$('.hora_hms').mask("00:00:00", {placeholder:"hh:mm:ss"});
$('.timeinput').mask("00:00:00", {placeholder:"hh:mm:ss"});
$('.cronometro').mask("00:00:00", {placeholder:"hh:mm:ss"});
}
@ -189,7 +190,7 @@ function OptionalCustomFrontEnd() {
if (_label.length === 0) {
_label = $('label[for='+this.id+']');
if (_label.length === 0) {
_label = $('<label[for='+this.id+']/>').insertBefore(this)
_label = $('<label for='+this.id+'/>').insertBefore(this)
}
}

3
sapl/static/styles/app.scss

@ -451,6 +451,9 @@ nav {
overflow-x: auto;
}
}
.lista-parlamentares .table td {
vertical-align: middle;
}
@media (min-width: 1092px) and (max-width: 1199px) {
.container {

22
sapl/temp_suppress_crispy_form_warnings.py

@ -1,22 +0,0 @@
import copy
import logging
from django.utils.log import DEFAULT_LOGGING
# hack to suppress many annoying warnings from crispy_forms
# Do remove this file and corresponding import in settings
# when crispy_forms is corrected !!!
SUPRESS_CRISPY_FORM_WARNINGS_LOGGING = copy.deepcopy(DEFAULT_LOGGING)
SUPRESS_CRISPY_FORM_WARNINGS_LOGGING['filters']['suppress_deprecated'] = {
'()': 'sapl.temp_suppress_crispy_form_warnings.SuppressDeprecated'
}
SUPRESS_CRISPY_FORM_WARNINGS_LOGGING['handlers']['console']['filters'].append(
'suppress_deprecated')
class SuppressDeprecated(logging.Filter):
def filter(self, record):
msg = record.getMessage()
return not ('crispy_forms' in msg and
'RemovedInDjango19Warning' in msg)

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.140</span>
<span>Release: 3.1.142</span>
</p>
</div>
<div class="col-md-4">

4
sapl/templates/compilacao/tipotextoarticulado_list.html

@ -15,7 +15,7 @@
{% endblock actions %}
<div class="row container-tabaux">
<div class="{% if perms.base.menu_tabelas_auxiliares %}col-sm-9{% endif %}">
<div class="{% if perms.base.view_tabelas_auxiliares %}col-sm-9{% endif %}">
{% if not object_list %}
<p>{{ NO_ENTRIES_MSG }}</p>
{% else %}
@ -40,7 +40,7 @@
{%endif%}
{% include 'paginacao.html'%}
</div>
{% if perms.base.menu_tabelas_auxiliares %}
{% if perms.base.view_tabelas_auxiliares %}
<div class="col-sm-3 sidebar-tabaux">
<h3>{% trans "Tabelas Auxiliares" %}</h3>
{% subnav 'menu_tabelas_auxiliares.yaml'%}

4
sapl/templates/crud/list_tabaux.html

@ -2,10 +2,10 @@
{% load i18n menus%}
{% block base_content %}
<div class="row container-tabaux">
<div class="{% if perms.base.menu_tabelas_auxiliares %}col-sm-9{% endif %}">
<div class="{% if perms.base.view_tabelas_auxiliares %}col-sm-9{% endif %}">
{{block.super}}
</div>
{% if perms.base.menu_tabelas_auxiliares %}
{% if perms.base.view_tabelas_auxiliares %}
<div class="col-sm-3 sidebar-tabaux">
<h3>{% trans "Tabelas Auxiliares" %}</h3>
{% subnav 'menu_tabelas_auxiliares.yaml'%}

10
sapl/templates/parlamentares/frente_form.html

@ -26,7 +26,8 @@
<div class="row-fluid">
<div class="col-md-12" >
<div class="checkbox">
<input id="id_selecionar_ativos" type="checkbox">
<label for="id_selecionar_ativos">
<input id="id_selecionar_ativos" type="checkbox" name="id_selecionar_ativos"/>
<b><h4>Listar somente os parlamentares ativos</h4></b>
</div>
</div>
@ -57,16 +58,15 @@
function selecionar_parlamentares_frente() {
// Seleciona automaticamente todos os parlamentares
// que já estão presentes naquela frente
var update_view = {{ update_view }}
if (update_view == 1) {
{% if object.id %}
var frente_id = {{ object.id }}
$.get("/sistema/frente/parlamentares-frente-selected",
{frente_id: frente_id},
function (data) {
id_list = data['id_list'];
$("div.controls select").val(id_list);
$("#id_parlamentares").val(id_list);
});
}
{% endif %}
}
function atualiza_parlamentares() {

4
sapl/templates/parlamentares/parlamentares_list.html

@ -21,7 +21,7 @@
{% if not rows %}
<p>{{ NO_ENTRIES_MSG }}</p>
{% else %}
<div class="container-table">
<div class="container-table lista-parlamentares">
<div class="result-count">{% blocktrans with verbose_name_plural=view.verbose_name_plural %}Total de {{ verbose_name_plural }}: <strong>{{count}}</strong>{% endblocktrans %}</div>
<table class="table table-striped table-hover table-link-ordering">
<thead>
@ -43,7 +43,9 @@
{% for value, href, obj in value_list %}
{% if forloop.first %}
<td>
{% if obj.fotografia %}
<img class="avatar-parlamentar" src="{% cropped_thumbnail obj "cropping" %}">
{% endif %}
</td>
{% endif %}
<td>

4
sapl/templates/protocoloadm/comprovante.html

@ -65,7 +65,11 @@
</tr>
<tr>
<th>Data / Horário</th>
{% if protocolo.timestamp %}
<td>{{ protocolo.timestamp|date:"d/m/Y" }} - {{ protocolo.timestamp|date:"H:i:s" }}</td>
{% else %}
<td>{{ protocolo.data|date:"d/m/Y" }} - {{ protocolo.hora|date:"H:i:s" }}</td>
{% endif %}
</tr>
{% if protocolo.tipo_processo == 1 %}
<tr>

14
sapl/templates/protocoloadm/protocolar_documento.html

@ -14,3 +14,17 @@
{% block detail_content %}
{% crispy form %}
{% endblock detail_content %}
{% block extra_js %}
<script language="Javascript">
$(document).ready(function() {
$("input[name=data_hora_manual]").change(function(event) {
if (this.value === 'True' && this.checked)
$("#protocolo_data_hora_manual").removeClass('hidden');
else if (this.value === 'False' && this.checked)
$("#protocolo_data_hora_manual").addClass('hidden');
});
$("input[name=data_hora_manual]").trigger('change')
});
</script>
{% endblock %}

8
sapl/templates/protocoloadm/protocolar_materia.html

@ -26,6 +26,14 @@
}
$(document).ready(function() {
$("input[name=data_hora_manual]").change(function(event) {
if (this.value === 'True' && this.checked)
$("#protocolo_data_hora_manual").removeClass('hidden');
else if (this.value === 'False' && this.checked)
$("#protocolo_data_hora_manual").addClass('hidden');
});
$("input[name=data_hora_manual]").trigger('change')
function busca_ementa() {
var vincular_materia = $("#id_vincular_materia_1").prop("checked");
var ano_materia = $("#id_ano_materia").val();

2
sapl/templates/protocoloadm/protocolo_filter.html

@ -47,7 +47,7 @@
{% if p.timestamp%}
<strong>Data Protocolo:</strong> {{ p.timestamp|localtime|date:"d/m/Y"|default_if_none:"Não informado" }} - Horário: {{ p.timestamp|localtime|date:"G:i:s" }}</br>
{% else %}
<strong>Data Protocolo:</strong> {{ p.data|date:"d/m/Y"|default_if_none:"Não informado" }} - Horário: {{ p.hora|date:"G:i:s" }}</br>
<strong>Data Protocolo:</strong> {{ p.data|date:"d/m/Y"|default_if_none:"Não informado" }} - Horário: {{ p.hora|date:"G:i:s" }} - {% if not p.timestamp %} Informado Manualmente por: {{p.user_data_hora_manual}}{% endif %}</br>
{% endif %}
{% if p.tipo_processo == 0 %}
<strong>Interessado:</strong> {{ p.interessado|default_if_none:"Não informado" }}</br>

10
sapl/templates/protocoloadm/protocolo_mostrar.html

@ -4,6 +4,14 @@
{% load crispy_forms_tags %}
{% load static %}
{% block actions %}
{{ block.super }}
<div class="actions btn-group pull-right grid-gutter-width-right " role="group">
<a href="{% url 'sapl.protocoloadm:protocolo' %}" class="btn btn-default">{% trans 'Fazer nova pesquisa' %}</a>
</div>
{% endblock %}
{% block detail_content %}
<strong>Protocolo: </strong>{{ protocolo.numero|stringformat:'06d' }}/{{ protocolo.ano }} -
<a href="{% url 'sapl.relatorios:relatorio_etiqueta_protocolo' protocolo.numero protocolo.ano %}"><img src="{% static 'img/etiqueta.png' %}" alt="Etiqueta Individual"></a></br>
@ -12,7 +20,7 @@
{% if protocolo.timestamp %}
<strong>Data Protocolo:</strong> {{ protocolo.timestamp|localtime|date:"d/m/Y"|default_if_none:"Não informado" }} - Horário: {{ protocolo.timestamp|localtime|date:"G:i:s" }}</br>
{% else %}
<strong>Data Protocolo:</strong> {{ protocolo.data|date:"d/m/Y"|default_if_none:"Não informado" }} - Horário: {{ protocolo.hora|date:"G:i:s" }}</br>
<strong>Data Protocolo:</strong> {{ protocolo.data|date:"d/m/Y"|default_if_none:"Não informado" }} - Horário: {{ protocolo.hora|date:"G:i:s" }} - {% if not protocolo.timestamp %} Informado Manualmente por: {{protocolo.user_data_hora_manual}}{% endif %}</br>
{% endif %}
{% if protocolo.tipo_processo == 0 %}

43
sapl/utils.py

@ -18,10 +18,10 @@ from django.core.exceptions import ValidationError
from django.core.mail import get_connection
from django.db import models
from django.db.models import Q
from django.forms.widgets import SplitDateTimeWidget
from django.utils import six, timezone
from django.utils.translation import ugettext_lazy as _
import django_filters
from django_filters.filterset import STRICTNESS
from easy_thumbnails import source_generators
from floppyforms import ClearableFileInput
import magic
@ -209,24 +209,49 @@ class RangeWidgetOverride(forms.MultiWidget):
def __init__(self, attrs=None):
widgets = (forms.DateInput(format='%d/%m/%Y',
attrs={'class': 'dateinput',
attrs={'class': 'dateinput form-control',
'placeholder': 'Inicial'}),
forms.DateInput(format='%d/%m/%Y',
attrs={'class': 'dateinput',
attrs={'class': 'dateinput form-control',
'placeholder': 'Final'}))
super(RangeWidgetOverride, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return [value.start, value.stop]
return [None, None]
return []
def render(self, name, value, attrs=None, renderer=None):
rendered_widgets = []
for i, x in enumerate(self.widgets):
rendered_widgets.append(
x.render(
'%s_%d' % (name, i), value[i] if value else ''
)
)
def format_output(self, rendered_widgets):
html = '<div class="col-sm-6">%s</div><div class="col-sm-6">%s</div>'\
% tuple(rendered_widgets)
return '<div class="row">%s</div>' % html
class CustomSplitDateTimeWidget(SplitDateTimeWidget):
def render(self, name, value, attrs=None, renderer=None):
rendered_widgets = []
for i, x in enumerate(self.widgets):
x.attrs['class'] += ' form-control'
rendered_widgets.append(
x.render(
'%s_%d' % (name, i), self.decompress(
value)[i] if value else ''
)
)
html = '<div class="col-6">%s</div><div class="col-6">%s</div>'\
% tuple(rendered_widgets)
return '<div class="row">%s</div>' % html
def register_all_models_in_admin(module_name, exclude_list=[]):
appname = module_name.split('.')
appname = appname[1] if appname[0] == 'sapl' else appname[0]
@ -685,9 +710,9 @@ def qs_override_django_filter(self):
valid = self.is_bound and self.form.is_valid()
if self.is_bound and not valid:
if self.strict == STRICTNESS.RAISE_VALIDATION_ERROR:
"""if self.strict == STRICTNESS.RAISE_VALIDATION_ERROR:
raise forms.ValidationError(self.form.errors)
elif bool(self.strict) == STRICTNESS.RETURN_NO_RESULTS:
elif bool(self.strict) == STRICTNESS.RETURN_NO_RESULTS:"""
self._qs = self.queryset.none()
return self._qs
# else STRICTNESS.IGNORE... ignoring
@ -703,9 +728,9 @@ def qs_override_django_filter(self):
try:
value = self.form.fields[name].clean(raw_value)
except forms.ValidationError:
if self.strict == STRICTNESS.RAISE_VALIDATION_ERROR:
"""if self.strict == STRICTNESS.RAISE_VALIDATION_ERROR:
raise
elif bool(self.strict) == STRICTNESS.RETURN_NO_RESULTS:
elif bool(self.strict) == STRICTNESS.RETURN_NO_RESULTS:"""
self._qs = self.queryset.none()
return self._qs
# else STRICTNESS.IGNORE... ignoring

58
setup.py

@ -9,47 +9,49 @@ with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme:
os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
install_requires = [
'dj-database-url==0.4.1',
'django-haystack==2.6.0',
'django>=1.10,<1.11',
'django-bootstrap3==7.0.1',
'django>=1.11,<2.0',
'django-bootstrap3==11.0.0',
'django-haystack==2.8.1',
'django-filter==2.0.0',
'djangorestframework==3.9.0',
'dj-database-url==0.5.0',
'django-bower==5.2.0',
'django-braces==1.9.0',
'django-compressor==2.0',
'django-crispy-forms==1.6.1',
'django-extensions==1.9.8',
'django-extra-views==0.11.0',
'django-filter==0.15.3',
'django-floppyforms==1.6.2',
'django-model-utils==3.1.1',
'django-sass-processor==0.5.8',
'djangorestframework==3.4.0',
'drfdocs',
'django-crispy-forms==1.7.2',
'django-floppyforms==1.7.0',
'django-extra-views==0.12.0',
'django-model-utils==3.1.2',
'django-sass-processor==0.7.2',
'django-reversion==3.0.2',
'django-reversion-compare==0.8.6'
'django-speedinfo==1.4.0',
'django-extensions==2.1.4',
'django-image-cropping==1.2.0',
'easy-thumbnails==2.5',
'django-image-cropping==1.1.0',
'libsass==0.11.1',
'psycopg2==2.7.4',
'python-decouple==3.0',
'pytz==2016.4',
'libsass==0.17.0',
'python-decouple==3.1',
'psycopg2-binary==2.7.6.1',
'pyyaml==4.2b1',
'rtyaml==0.0.3',
'textract==1.5.0',
'pytz==2018.9',
'rtyaml==0.0.5',
'python-magic==0.4.15',
'unipath==1.1',
'WeasyPrint==44',
'gunicorn==19.9.0',
'textract==1.5.0',
'pysolr==3.6.0',
'python-magic==0.4.12',
'gunicorn==19.6.0',
'django-reversion==2.0.8',
'WeasyPrint==0.42',
'whoosh==2.7.4',
'django-speedinfo==1.3.5',
'django-reversion-compare==0.8.4'
# 'git+git://github.com/interlegis/trml2pdf.git',
# 'git+git://github.com/jasperlittle/django-rest-framework-docs'
# 'git+git://github.com/rubgombar1/django-admin-bootstrapped.git''
#'django-compressor==2.2',
]
setup(
name='interlegis-sapl',
version='3.1.140',
version='3.1.142',
packages=find_packages(),
include_package_data=True,
license='GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007',

Loading…
Cancel
Save