Browse Source

Impl google recaptcha (#3337)

* alterações de IDE

* Refatora forms de acomp via email e recup de senha

Implementa mixin de registro de email para encaminhamento de emails com
google recaptcha.

* Impl registro do Google Recaptcha

* Substitui urllib3 por requests

Co-authored-by: eribeiro <edwardr@senado.leg.br>
pull/3340/head
Leandro Roberto Silva 4 years ago
committed by GitHub
parent
commit
c79de3a89c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 94
      sapl/base/forms.py
  2. 23
      sapl/base/migrations/0043_auto_20210203_1442.py
  3. 30
      sapl/base/models.py
  4. 358
      sapl/base/views.py
  5. 14
      sapl/context_processors.py
  6. 39
      sapl/materia/forms.py
  7. 12
      sapl/materia/views.py
  8. 25
      sapl/protocoloadm/forms.py
  9. 13
      sapl/protocoloadm/views.py
  10. 2
      sapl/settings.py
  11. 19
      sapl/templates/base/layouts.yaml
  12. 11
      sapl/templates/base/login.html
  13. 4
      sapl/templates/base/recuperar_senha_email_form.html
  14. 8
      sapl/templates/materia/acompanhamento_materia.html
  15. 2
      sapl/templates/materia/materialegislativa_detail.html
  16. 2
      sapl/templates/materia/materialegislativa_filter.html
  17. 8
      sapl/templates/protocoloadm/acompanhamento_documento.html
  18. 2
      sapl/templates/protocoloadm/documentoadministrativo_filter.html
  19. 146
      sapl/utils.py

94
sapl/base/forms.py

@ -36,7 +36,8 @@ from sapl.utils import (autor_label, autor_modal, ChoiceWithoutValidationField,
FilterOverridesMetaMixin, FileFieldCheckMixin, FilterOverridesMetaMixin, FileFieldCheckMixin,
AnoNumeroOrderingFilter, ImageThumbnailFileInput, AnoNumeroOrderingFilter, ImageThumbnailFileInput,
models_with_gr_for_model, qs_override_django_filter, models_with_gr_for_model, qs_override_django_filter,
RangeWidgetOverride, RANGE_ANOS, YES_NO_CHOICES) RangeWidgetOverride, RANGE_ANOS, YES_NO_CHOICES,
GoogleRecapthaMixin)
from .models import AppConfig, CasaLegislativa from .models import AppConfig, CasaLegislativa
@ -116,7 +117,8 @@ class UsuarioCreateForm(ModelForm):
data = self.cleaned_data data = self.cleaned_data
if data['password1'] != data['password2']: if data['password1'] != data['password2']:
self.logger.warning('Erro de validação. Senhas informadas são diferentes.') self.logger.warning(
'Erro de validação. Senhas informadas são diferentes.')
raise ValidationError('Senhas informadas são diferentes') raise ValidationError('Senhas informadas são diferentes')
return data return data
@ -235,7 +237,8 @@ class UsuarioEditForm(ModelForm):
self.helper = SaplFormHelper() self.helper = SaplFormHelper()
self.helper.layout = Layout( self.helper.layout = Layout(
'username', 'username',
FieldWithButtons('token', StrictButton('Renovar', id="renovar-token", css_class="btn-outline-primary")), FieldWithButtons('token', StrictButton(
'Renovar', id="renovar-token", css_class="btn-outline-primary")),
rows, rows,
form_actions( form_actions(
more=[ more=[
@ -250,7 +253,8 @@ class UsuarioEditForm(ModelForm):
data = self.cleaned_data data = self.cleaned_data
if data['password1'] and data['password1'] != data['password2']: if data['password1'] and data['password1'] != data['password2']:
self.logger.warning("Erro de validação. Senhas informadas são diferentes.") self.logger.warning(
"Erro de validação. Senhas informadas são diferentes.")
raise ValidationError('Senhas informadas são diferentes') raise ValidationError('Senhas informadas são diferentes')
return data return data
@ -315,7 +319,8 @@ class SessaoLegislativaForm(FileFieldCheckMixin, ModelForm):
if numero <= ult and flag_edit: if numero <= ult and flag_edit:
self.logger.warning( self.logger.warning(
'O número da SessaoLegislativa ({}) é menor ou igual ' 'O número da SessaoLegislativa ({}) é menor ou igual '
'que o de Sessões Legislativas passadas ({})'.format(numero, ult) 'que o de Sessões Legislativas passadas ({})'.format(
numero, ult)
) )
raise ValidationError('O número da Sessão Legislativa não pode ser menor ou igual ' raise ValidationError('O número da Sessão Legislativa não pode ser menor ou igual '
'que o de Sessões Legislativas passadas') 'que o de Sessões Legislativas passadas')
@ -325,7 +330,8 @@ class SessaoLegislativaForm(FileFieldCheckMixin, ModelForm):
self.logger.warning( self.logger.warning(
'A data de início ({}) da SessaoLegislativa está compreendida ' 'A data de início ({}) da SessaoLegislativa está compreendida '
'fora da data início ({}) e fim ({}) da Legislatura ' 'fora da data início ({}) e fim ({}) da Legislatura '
'selecionada'.format(data_inicio, data_inicio_leg, data_fim_leg) 'selecionada'.format(
data_inicio, data_inicio_leg, data_fim_leg)
) )
raise ValidationError('A data de início da Sessão Legislativa deve estar compreendida ' raise ValidationError('A data de início da Sessão Legislativa deve estar compreendida '
'entre a data início e fim da Legislatura selecionada') 'entre a data início e fim da Legislatura selecionada')
@ -342,13 +348,15 @@ class SessaoLegislativaForm(FileFieldCheckMixin, ModelForm):
if data_inicio > data_fim: if data_inicio > data_fim:
self.logger.warning( self.logger.warning(
'Data início ({}) superior à data fim ({}).'.format(data_inicio, data_fim) 'Data início ({}) superior à data fim ({}).'.format(
data_inicio, data_fim)
) )
raise ValidationError( raise ValidationError(
'Data início não pode ser superior à data fim') 'Data início não pode ser superior à data fim')
if data_fim.year > data_inicio.year + 1: if data_fim.year > data_inicio.year + 1:
raise ValidationError('A Sessão Legislativa só pode ter, no máximo, dois anos de período.') raise ValidationError(
'A Sessão Legislativa só pode ter, no máximo, dois anos de período.')
data_inicio_intervalo = cleaned_data['data_inicio_intervalo'] data_inicio_intervalo = cleaned_data['data_inicio_intervalo']
data_fim_intervalo = cleaned_data['data_fim_intervalo'] data_fim_intervalo = cleaned_data['data_fim_intervalo']
@ -357,7 +365,8 @@ class SessaoLegislativaForm(FileFieldCheckMixin, ModelForm):
data_inicio_intervalo > data_fim_intervalo: data_inicio_intervalo > data_fim_intervalo:
self.logger.warning( self.logger.warning(
'Data início de intervalo ({}) superior à ' 'Data início de intervalo ({}) superior à '
'data fim de intervalo ({}).'.format(data_inicio_intervalo, data_fim_intervalo) 'data fim de intervalo ({}).'.format(
data_inicio_intervalo, data_fim_intervalo)
) )
raise ValidationError('Data início de intervalo não pode ser ' raise ValidationError('Data início de intervalo não pode ser '
'superior à data fim de intervalo') 'superior à data fim de intervalo')
@ -766,7 +775,8 @@ class AutorForm(ModelForm):
class AutorFilterSet(django_filters.FilterSet): class AutorFilterSet(django_filters.FilterSet):
nome = django_filters.CharFilter(label=_('Nome do Autor'), lookup_expr='icontains') nome = django_filters.CharFilter(
label=_('Nome do Autor'), lookup_expr='icontains')
class Meta: class Meta:
model = Autor model = Autor
@ -1063,20 +1073,24 @@ class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.form.fields['exibir_ordem_dia'] = forms.BooleanField(required=False, label='Exibir presença das Ordens do Dia') self.form.fields['exibir_ordem_dia'] = forms.BooleanField(
required=False, label='Exibir presença das Ordens do Dia')
self.form.initial['exibir_ordem_dia'] = True self.form.initial['exibir_ordem_dia'] = True
self.form.fields['exibir_somente_titular'] = forms.BooleanField(required=False, label='Exibir somente parlamentares titulares') self.form.fields['exibir_somente_titular'] = forms.BooleanField(
required=False, label='Exibir somente parlamentares titulares')
self.form.initial['exibir_somente_titular'] = False self.form.initial['exibir_somente_titular'] = False
self.form.fields['exibir_somente_ativo'] = forms.BooleanField(required=False, label='Exibir somente parlamentares ativos') self.form.fields['exibir_somente_ativo'] = forms.BooleanField(
required=False, label='Exibir somente parlamentares ativos')
self.form.initial['exibir_somente_ativo'] = False self.form.initial['exibir_somente_ativo'] = False
self.form.fields['legislatura'].required = True self.form.fields['legislatura'].required = True
self.filters['data_inicio'].label = 'Período (Inicial - Final)' self.filters['data_inicio'].label = 'Período (Inicial - Final)'
tipo_sessao_ordinaria = self.filters['tipo'].queryset.filter(nome='Ordinária') tipo_sessao_ordinaria = self.filters['tipo'].queryset.filter(
nome='Ordinária')
if tipo_sessao_ordinaria: if tipo_sessao_ordinaria:
self.form.initial['tipo'] = tipo_sessao_ordinaria.first() self.form.initial['tipo'] = tipo_sessao_ordinaria.first()
@ -1341,7 +1355,6 @@ class RelatorioMateriasTramitacaoFilterSet(django_filters.FilterSet):
label='Autor da Matéria', label='Autor da Matéria',
queryset=Autor.objects.all()) queryset=Autor.objects.all())
@property @property
def qs(self): def qs(self):
parent = super(RelatorioMateriasTramitacaoFilterSet, self).qs parent = super(RelatorioMateriasTramitacaoFilterSet, self).qs
@ -1353,7 +1366,7 @@ class RelatorioMateriasTramitacaoFilterSet(django_filters.FilterSet):
model = MateriaEmTramitacao model = MateriaEmTramitacao
fields = ['materia__ano', 'materia__tipo', fields = ['materia__ano', 'materia__tipo',
'tramitacao__unidade_tramitacao_destino', 'tramitacao__unidade_tramitacao_destino',
'tramitacao__status','materia__autores'] 'tramitacao__status', 'materia__autores']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(RelatorioMateriasTramitacaoFilterSet, self).__init__( super(RelatorioMateriasTramitacaoFilterSet, self).__init__(
@ -1385,7 +1398,7 @@ class RelatorioMateriasTramitacaoFilterSet(django_filters.FilterSet):
self.form.helper.form_method = 'GET' self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout( self.form.helper.layout = Layout(
Fieldset(_('Pesquisa de Matéria em Tramitação'), Fieldset(_('Pesquisa de Matéria em Tramitação'),
row1, row2, row3, row4,row5, row1, row2, row3, row4, row5,
buttons,) buttons,)
) )
@ -1552,6 +1565,18 @@ class ConfiguracoesAppForm(ModelForm):
label=_('Mostrar brasão da Casa no painel?'), label=_('Mostrar brasão da Casa no painel?'),
required=False) required=False)
google_recaptcha_site_key = forms.CharField(
label=AppConfig._meta.get_field(
'google_recaptcha_site_key').verbose_name,
max_length=256,
required=False)
google_recaptcha_secret_key = forms.CharField(
label=AppConfig._meta.get_field(
'google_recaptcha_secret_key').verbose_name,
max_length=256,
required=False)
class Meta: class Meta:
model = AppConfig model = AppConfig
fields = ['documentos_administrativos', fields = ['documentos_administrativos',
@ -1575,7 +1600,9 @@ class ConfiguracoesAppForm(ModelForm):
'estatisticas_acesso_normas', 'estatisticas_acesso_normas',
'escolher_numero_materia_proposicao', 'escolher_numero_materia_proposicao',
'tramitacao_materia', 'tramitacao_materia',
'tramitacao_documento'] 'tramitacao_documento',
'google_recaptcha_site_key',
'google_recaptcha_secret_key']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ConfiguracoesAppForm, self).__init__(*args, **kwargs) super(ConfiguracoesAppForm, self).__init__(*args, **kwargs)
@ -1609,35 +1636,29 @@ class ConfiguracoesAppForm(ModelForm):
return cleaned_data return cleaned_data
class RecuperarSenhaForm(PasswordResetForm): class RecuperarSenhaForm(GoogleRecapthaMixin, PasswordResetForm):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
row1 = to_row(
[('email', 12)])
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Insira o e-mail cadastrado com a sua conta'),
row1,
form_actions(label='Enviar'))
)
super(RecuperarSenhaForm, self).__init__(*args, **kwargs) kwargs['title_label'] = _('Insira o e-mail cadastrado com a sua conta')
kwargs['action_label'] = _('Enviar')
super().__init__(*args, **kwargs)
def clean(self): def clean(self):
super(RecuperarSenhaForm, self).clean()
if not self.is_valid(): super(RecuperarSenhaForm, self).clean()
return self.cleaned_data
email_existente = User.objects.filter( email_existente = get_user_model().objects.filter(
email=self.data['email']).exists() email=self.data['email']).exists()
if not email_existente: if not email_existente:
msg = 'Não existe nenhum usuário cadastrado com este e-mail.' msg = 'Não existe nenhum usuário cadastrado com este e-mail.'
self.logger.warning( self.logger.warning(
'Não existe nenhum usuário cadastrado com este e-mail ({}).'.format(self.data['email']) 'Não existe nenhum usuário cadastrado com este e-mail ({}).'.format(
self.data['email'])
) )
raise ValidationError(msg) raise ValidationError(msg)
@ -1726,7 +1747,8 @@ class AlterarSenhaForm(Form):
if user.is_anonymous: if user.is_anonymous:
self.logger.warning( self.logger.warning(
'Não é possível alterar senha de usuário anônimo ({}).'.format(username) 'Não é possível alterar senha de usuário anônimo ({}).'.format(
username)
) )
raise ValidationError( raise ValidationError(
"Não é possível alterar senha de usuário anônimo") "Não é possível alterar senha de usuário anônimo")

23
sapl/base/migrations/0043_auto_20210203_1442.py

@ -0,0 +1,23 @@
# Generated by Django 2.2.13 on 2021-02-03 17:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0042_auto_20201013_1151'),
]
operations = [
migrations.AddField(
model_name='appconfig',
name='google_recaptcha_secret_key',
field=models.CharField(default='', max_length=256, verbose_name='Chave privada gerada pelo Google Recaptcha'),
),
migrations.AddField(
model_name='appconfig',
name='google_recaptcha_site_key',
field=models.CharField(default='', max_length=256, verbose_name='Chave pública gerada pelo Google Recaptcha'),
),
]

30
sapl/base/models.py

@ -19,11 +19,11 @@ RELATORIO_ATOS_ACESSADOS = (('S', _('Sim')),
('N', _('Não'))) ('N', _('Não')))
SEQUENCIA_NUMERACAO_PROTOCOLO = (('A', _('Sequencial por ano')), SEQUENCIA_NUMERACAO_PROTOCOLO = (('A', _('Sequencial por ano')),
('L', _('Sequencial por legislatura')), ('L', _('Sequencial por legislatura')),
('U', _('Sequencial único'))) ('U', _('Sequencial único')))
SEQUENCIA_NUMERACAO_PROPOSICAO = (('A', _('Sequencial por ano para cada autor')), SEQUENCIA_NUMERACAO_PROPOSICAO = (('A', _('Sequencial por ano para cada autor')),
('B', _('Sequencial por ano indepententemente do autor'))) ('B', _('Sequencial por ano indepententemente do autor')))
ESFERA_FEDERACAO_CHOICES = (('M', _('Municipal')), ESFERA_FEDERACAO_CHOICES = (('M', _('Municipal')),
('E', _('Estadual')), ('E', _('Estadual')),
@ -108,7 +108,7 @@ class AppConfig(models.Model):
max_length=1, max_length=1,
verbose_name=_('Sequência de numeração de protocolos'), verbose_name=_('Sequência de numeração de protocolos'),
choices=SEQUENCIA_NUMERACAO_PROTOCOLO, default='A') choices=SEQUENCIA_NUMERACAO_PROTOCOLO, default='A')
inicio_numeracao_protocolo = models.PositiveIntegerField( inicio_numeracao_protocolo = models.PositiveIntegerField(
verbose_name=_('Início da numeração de protocolo'), verbose_name=_('Início da numeração de protocolo'),
default=1 default=1
@ -179,17 +179,27 @@ class AppConfig(models.Model):
choices=YES_NO_CHOICES, default=False) choices=YES_NO_CHOICES, default=False)
escolher_numero_materia_proposicao = models.BooleanField( escolher_numero_materia_proposicao = models.BooleanField(
verbose_name=_('Indicar número da matéria a ser gerada na proposição?'), verbose_name=_(
'Indicar número da matéria a ser gerada na proposição?'),
choices=YES_NO_CHOICES, default=False) choices=YES_NO_CHOICES, default=False)
tramitacao_materia = models.BooleanField( tramitacao_materia = models.BooleanField(
verbose_name=_('Tramitar matérias anexadas junto com as matérias principais?'), verbose_name=_(
'Tramitar matérias anexadas junto com as matérias principais?'),
choices=YES_NO_CHOICES, default=True) choices=YES_NO_CHOICES, default=True)
tramitacao_documento = models.BooleanField( tramitacao_documento = models.BooleanField(
verbose_name=_('Tramitar documentos anexados junto com os documentos principais?'), verbose_name=_(
'Tramitar documentos anexados junto com os documentos principais?'),
choices=YES_NO_CHOICES, default=True) choices=YES_NO_CHOICES, default=True)
google_recaptcha_site_key = models.CharField(
verbose_name=_('Chave pública gerada pelo Google Recaptcha'),
max_length=256, default='')
google_recaptcha_secret_key = models.CharField(
verbose_name=_('Chave privada gerada pelo Google Recaptcha'),
max_length=256, default='')
class Meta: class Meta:
verbose_name = _('Configurações da Aplicação') verbose_name = _('Configurações da Aplicação')
verbose_name_plural = _('Configurações da Aplicação') verbose_name_plural = _('Configurações da Aplicação')
@ -219,7 +229,8 @@ class TipoAutor(models.Model):
descricao = models.CharField( descricao = models.CharField(
max_length=50, max_length=50,
verbose_name=_('Descrição'), verbose_name=_('Descrição'),
help_text=_('Obs: Não crie tipos de autores semelhante aos tipos fixos. ') help_text=_(
'Obs: Não crie tipos de autores semelhante aos tipos fixos. ')
) )
content_type = models.OneToOneField( content_type = models.OneToOneField(
@ -325,4 +336,3 @@ class AuditLog(models.Model):
self.model_name, self.model_name,
self.username, self.username,
) )

358
sapl/base/views.py

@ -1,62 +1,67 @@
from collections import OrderedDict
import collections import collections
import itertools
import datetime import datetime
import itertools
import logging import logging
import os import os
from collections import OrderedDict
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from django.contrib.auth.tokens import default_token_generator from django.contrib.auth.tokens import default_token_generator
from django.contrib.auth.views import (PasswordResetView,PasswordResetConfirmView, PasswordResetCompleteView, from django.contrib.auth.views import (PasswordResetView, PasswordResetConfirmView, PasswordResetCompleteView,
PasswordResetDoneView) PasswordResetDoneView)
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied, ValidationError from django.core.exceptions import ObjectDoesNotExist, PermissionDenied, ValidationError
from django.core.mail import send_mail from django.core.mail import send_mail
from django.urls import reverse, reverse_lazy
from django.db import connection from django.db import connection
from django.db.models import Count, Q, ProtectedError, Max from django.db.models import Count, Q, ProtectedError, Max
from django.shortcuts import render
from django.http import Http404, HttpResponseRedirect, JsonResponse from django.http import Http404, HttpResponseRedirect, JsonResponse
from django.shortcuts import render, redirect
from django.template import TemplateDoesNotExist from django.template import TemplateDoesNotExist
from django.template.loader import get_template from django.template.loader import get_template
from django.urls import reverse, reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import (CreateView, DetailView, DeleteView, FormView, ListView, UpdateView) from django.views.generic import (
CreateView, DetailView, DeleteView, FormView, ListView, UpdateView)
from django.views.generic.base import RedirectView, TemplateView from django.views.generic.base import RedirectView, TemplateView
from django_filters.views import FilterView from django_filters.views import FilterView
from haystack.views import SearchView
from haystack.query import SearchQuerySet from haystack.query import SearchQuerySet
from haystack.views import SearchView
from sapl.relatorios.views import (relatorio_materia_em_tramitacao, relatorio_materia_por_autor, from rest_framework.authtoken.models import Token
relatorio_materia_por_ano_autor, relatorio_presenca_sessao,
relatorio_historico_tramitacao, relatorio_fim_prazo_tramitacao, relatorio_atas,
relatorio_audiencia, relatorio_normas_mes, relatorio_normas_vigencia,
relatorio_historico_tramitacao_adm, relatorio_reuniao,
relatorio_estatisticas_acesso_normas, relatorio_normas_por_autor,
relatorio_documento_acessorio)
from sapl import settings from sapl import settings
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica
from sapl.base.models import Autor, TipoAutor
from sapl.base.forms import (AutorForm, AutorFormForAdmin, TipoAutorForm, AutorFilterSet, RecuperarSenhaForm, from sapl.base.forms import (AutorForm, AutorFormForAdmin, TipoAutorForm, AutorFilterSet, RecuperarSenhaForm,
NovaSenhaForm) NovaSenhaForm)
from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import Comissao, Reuniao from sapl.comissoes.models import Comissao, Reuniao
from sapl.crud.base import CrudAux, make_pagination from sapl.crud.base import CrudAux, make_pagination
from sapl.materia.models import (Anexada, Autoria, DocumentoAcessorio, MateriaEmTramitacao, MateriaLegislativa, from sapl.materia.models import (Anexada, Autoria, DocumentoAcessorio, MateriaEmTramitacao, MateriaLegislativa,
Proposicao, StatusTramitacao, TipoDocumento, TipoMateriaLegislativa, UnidadeTramitacao, Proposicao, StatusTramitacao, TipoDocumento, TipoMateriaLegislativa, UnidadeTramitacao,
Tramitacao) Tramitacao)
from sapl.norma.models import NormaJuridica, TipoNormaJuridica from sapl.norma.models import NormaJuridica, TipoNormaJuridica
from sapl.parlamentares.models import (Filiacao, Legislatura, Mandato, Parlamentar, SessaoLegislativa) from sapl.parlamentares.models import (
Filiacao, Legislatura, Mandato, Parlamentar, SessaoLegislativa)
from sapl.protocoloadm.models import (Anexado, DocumentoAdministrativo, Protocolo, StatusTramitacaoAdministrativo, from sapl.protocoloadm.models import (Anexado, DocumentoAdministrativo, Protocolo, StatusTramitacaoAdministrativo,
TipoDocumentoAdministrativo) TipoDocumentoAdministrativo)
from sapl.sessao.models import (Bancada, PresencaOrdemDia, SessaoPlenaria, SessaoPlenariaPresenca, TipoSessaoPlenaria) from sapl.relatorios.views import (relatorio_materia_em_tramitacao, relatorio_materia_por_autor,
relatorio_materia_por_ano_autor, relatorio_presenca_sessao,
relatorio_historico_tramitacao, relatorio_fim_prazo_tramitacao, relatorio_atas,
relatorio_audiencia, relatorio_normas_mes, relatorio_normas_vigencia,
relatorio_historico_tramitacao_adm, relatorio_reuniao,
relatorio_estatisticas_acesso_normas, relatorio_normas_por_autor,
relatorio_documento_acessorio)
from sapl.sessao.models import (
Bancada, PresencaOrdemDia, SessaoPlenaria, SessaoPlenariaPresenca, TipoSessaoPlenaria)
from sapl.settings import EMAIL_SEND_USER from sapl.settings import EMAIL_SEND_USER
from sapl.utils import (gerar_hash_arquivo, intervalos_tem_intersecao, mail_service_configured, parlamentares_ativos, from sapl.utils import (gerar_hash_arquivo, intervalos_tem_intersecao, mail_service_configured, parlamentares_ativos,
SEPARADOR_HASH_PROPOSICAO, show_results_filter_set, num_materias_por_tipo) SEPARADOR_HASH_PROPOSICAO, show_results_filter_set, num_materias_por_tipo,
google_recaptcha_configured)
from .forms import (AlterarSenhaForm, CasaLegislativaForm, ConfiguracoesAppForm, RelatorioAtasFilterSet, from .forms import (AlterarSenhaForm, CasaLegislativaForm, ConfiguracoesAppForm, RelatorioAtasFilterSet,
RelatorioAudienciaFilterSet, RelatorioDataFimPrazoTramitacaoFilterSet, RelatorioAudienciaFilterSet, RelatorioDataFimPrazoTramitacaoFilterSet,
RelatorioHistoricoTramitacaoFilterSet, RelatorioMateriasPorAnoAutorTipoFilterSet, RelatorioHistoricoTramitacaoFilterSet, RelatorioMateriasPorAnoAutorTipoFilterSet,
@ -67,8 +72,6 @@ from .forms import (AlterarSenhaForm, CasaLegislativaForm, ConfiguracoesAppForm,
RelatorioNormasPorAutorFilterSet) RelatorioNormasPorAutorFilterSet)
from .models import AppConfig, CasaLegislativa from .models import AppConfig, CasaLegislativa
from rest_framework.authtoken.models import Token
def get_casalegislativa(): def get_casalegislativa():
return CasaLegislativa.objects.first() return CasaLegislativa.objects.first()
@ -87,6 +90,8 @@ class ConfirmarEmailView(TemplateView):
class RecuperarSenhaEmailView(PasswordResetView): class RecuperarSenhaEmailView(PasswordResetView):
logger = logging.getLogger(__name__)
success_url = reverse_lazy('sapl.base:recuperar_senha_finalizado') success_url = reverse_lazy('sapl.base:recuperar_senha_finalizado')
email_template_name = 'base/recuperar_senha_email.html' email_template_name = 'base/recuperar_senha_email.html'
html_email_template_name = 'base/recuperar_senha_email.html' html_email_template_name = 'base/recuperar_senha_email.html'
@ -94,6 +99,24 @@ class RecuperarSenhaEmailView(PasswordResetView):
from_email = EMAIL_SEND_USER from_email = EMAIL_SEND_USER
form_class = RecuperarSenhaForm form_class = RecuperarSenhaForm
def get(self, request, *args, **kwargs):
if not google_recaptcha_configured():
self.logger.warning(_('Google Recaptcha não configurado!'))
messages.error(request, _('Google Recaptcha não configurado!'))
return redirect(request.META.get('HTTP_REFERER', '/'))
return PasswordResetView.get(self, request, *args, **kwargs)
def post(self, request, *args, **kwargs):
if not google_recaptcha_configured():
self.logger.warning(_('Google Recaptcha não configurado!'))
messages.error(request, _('Google Recaptcha não configurado!'))
return redirect(request.META.get('HTTP_REFERER', '/'))
return PasswordResetView.post(self, request, *args, **kwargs)
class RecuperarSenhaFinalizadoView(PasswordResetDoneView): class RecuperarSenhaFinalizadoView(PasswordResetDoneView):
template_name = 'base/recupera_senha_email_enviado.html' template_name = 'base/recupera_senha_email_enviado.html'
@ -330,7 +353,8 @@ class PesquisarAutorView(FilterView):
paginator = context['paginator'] paginator = context['paginator']
page_obj = context['page_obj'] page_obj = context['page_obj']
context['page_range'] = make_pagination(page_obj.number, paginator.num_pages) context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context['NO_ENTRIES_MSG'] = 'Nenhum Autor encontrado!' context['NO_ENTRIES_MSG'] = 'Nenhum Autor encontrado!'
@ -354,19 +378,20 @@ class PesquisarAutorView(FilterView):
filter_url=url, filter_url=url,
numero_res=len(self.object_list)) numero_res=len(self.object_list))
context['show_results'] = show_results_filter_set(self.request.GET.copy()) context['show_results'] = show_results_filter_set(
self.request.GET.copy())
return self.render_to_response(context) return self.render_to_response(context)
class RelatoriosListView(TemplateView): class RelatoriosListView(TemplateView):
template_name='base/relatorios_list.html' template_name = 'base/relatorios_list.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(TemplateView, self).get_context_data(**kwargs) context = super(TemplateView, self).get_context_data(**kwargs)
estatisticas_acesso_normas = AppConfig.objects.first().estatisticas_acesso_normas estatisticas_acesso_normas = AppConfig.objects.first().estatisticas_acesso_normas
context['estatisticas_acesso_normas'] = True if estatisticas_acesso_normas == 'S' else False context['estatisticas_acesso_normas'] = True if estatisticas_acesso_normas == 'S' else False
return context return context
@ -398,10 +423,10 @@ class RelatorioDocumentosAcessoriosView(RelatorioMixin, FilterView):
if not self.filterset.form.is_valid(): if not self.filterset.form.is_valid():
return context return context
query_dict = self.request.GET.copy() query_dict = self.request.GET.copy()
context['show_results'] = show_results_filter_set(query_dict) context['show_results'] = show_results_filter_set(query_dict)
context['tipo_documento'] = str( context['tipo_documento'] = str(
TipoDocumento.objects.get(pk=self.request.GET['tipo']) TipoDocumento.objects.get(pk=self.request.GET['tipo'])
) )
@ -467,15 +492,17 @@ class RelatorioPresencaSessaoView(RelatorioMixin, FilterView):
# Verifica se os campos foram preenchidos # Verifica se os campos foram preenchidos
if not self.filterset.form.is_valid(): if not self.filterset.form.is_valid():
return context return context
cd = self.filterset.form.cleaned_data cd = self.filterset.form.cleaned_data
if not cd['data_inicio'] and not cd['sessao_legislativa'] \ if not cd['data_inicio'] and not cd['sessao_legislativa'] \
and not cd['legislatura']: and not cd['legislatura']:
msg = _("Formulário inválido! Preencha pelo menos algum dos campos Período, Legislatura ou Sessão Legislativa.") msg = _(
"Formulário inválido! Preencha pelo menos algum dos campos Período, Legislatura ou Sessão Legislativa.")
messages.error(self.request, msg) messages.error(self.request, msg)
return context return context
# Caso a data tenha sido preenchida, verifica se foi preenchida corretamente # Caso a data tenha sido preenchida, verifica se foi preenchida
# corretamente
if self.request.GET.get('data_inicio_0') and not self.request.GET.get('data_inicio_1'): if self.request.GET.get('data_inicio_0') and not self.request.GET.get('data_inicio_1'):
msg = _("Formulário inválido! Preencha a data do Período Final.") msg = _("Formulário inválido! Preencha a data do Período Final.")
messages.error(self.request, msg) messages.error(self.request, msg)
@ -497,49 +524,57 @@ class RelatorioPresencaSessaoView(RelatorioMixin, FilterView):
sessao_legislativa_pk = self.request.GET.get('sessao_legislativa') sessao_legislativa_pk = self.request.GET.get('sessao_legislativa')
if sessao_legislativa_pk: if sessao_legislativa_pk:
param0['sessao_plenaria__sessao_legislativa_id'] = sessao_legislativa_pk param0['sessao_plenaria__sessao_legislativa_id'] = sessao_legislativa_pk
sessao_legislativa = SessaoLegislativa.objects.get(id=sessao_legislativa_pk) sessao_legislativa = SessaoLegislativa.objects.get(
id=sessao_legislativa_pk)
context['sessao_legislativa'] = sessao_legislativa context['sessao_legislativa'] = sessao_legislativa
tipo_sessao_plenaria_pk = self.request.GET.get('tipo') tipo_sessao_plenaria_pk = self.request.GET.get('tipo')
context['tipo'] = '' context['tipo'] = ''
if tipo_sessao_plenaria_pk: if tipo_sessao_plenaria_pk:
param0['sessao_plenaria__tipo_id'] = tipo_sessao_plenaria_pk param0['sessao_plenaria__tipo_id'] = tipo_sessao_plenaria_pk
context['tipo'] = TipoSessaoPlenaria.objects.get(id=tipo_sessao_plenaria_pk) context['tipo'] = TipoSessaoPlenaria.objects.get(
id=tipo_sessao_plenaria_pk)
_range = [] _range = []
if ('data_inicio_0' in self.request.GET) and self.request.GET['data_inicio_0'] and \ if ('data_inicio_0' in self.request.GET) and self.request.GET['data_inicio_0'] and \
('data_inicio_1' in self.request.GET) and self.request.GET['data_inicio_1']: ('data_inicio_1' in self.request.GET) and self.request.GET['data_inicio_1']:
where = context['object_list'].query.where where = context['object_list'].query.where
_range = where.children[0].rhs _range = where.children[0].rhs
elif legislatura_pk and not sessao_legislativa_pk: elif legislatura_pk and not sessao_legislativa_pk:
_range = [legislatura.data_inicio, legislatura.data_fim] _range = [legislatura.data_inicio, legislatura.data_fim]
elif sessao_legislativa_pk: elif sessao_legislativa_pk:
_range = [sessao_legislativa.data_inicio, sessao_legislativa.data_fim] _range = [sessao_legislativa.data_inicio,
sessao_legislativa.data_fim]
param0.update({'sessao_plenaria__data_inicio__range': _range}) param0.update({'sessao_plenaria__data_inicio__range': _range})
# Parlamentares com Mandato no intervalo de tempo (Ativos) # Parlamentares com Mandato no intervalo de tempo (Ativos)
parlamentares_qs = parlamentares_ativos(_range[0], _range[1]).order_by('nome_parlamentar') parlamentares_qs = parlamentares_ativos(
_range[0], _range[1]).order_by('nome_parlamentar')
parlamentares_id = parlamentares_qs.values_list('id', flat=True) parlamentares_id = parlamentares_qs.values_list('id', flat=True)
# Presenças de cada Parlamentar em Sessões # Presenças de cada Parlamentar em Sessões
presenca_sessao = SessaoPlenariaPresenca.objects.filter(**param0).values_list('parlamentar_id').annotate(sessao_count=Count('id')) presenca_sessao = SessaoPlenariaPresenca.objects.filter(
**param0).values_list('parlamentar_id').annotate(sessao_count=Count('id'))
# Presenças de cada Ordem do Dia # Presenças de cada Ordem do Dia
presenca_ordem = PresencaOrdemDia.objects.filter(**param0).values_list('parlamentar_id').annotate(sessao_count=Count('id')) presenca_ordem = PresencaOrdemDia.objects.filter(
**param0).values_list('parlamentar_id').annotate(sessao_count=Count('id'))
total_ordemdia = PresencaOrdemDia.objects.filter(**param0).distinct('sessao_plenaria__id').order_by('sessao_plenaria__id').count() total_ordemdia = PresencaOrdemDia.objects.filter(
**param0).distinct('sessao_plenaria__id').order_by('sessao_plenaria__id').count()
total_sessao = context['object_list'].count() total_sessao = context['object_list'].count()
username = self.request.user.username username = self.request.user.username
context['exibir_somente_titular'] = self.request.GET.get('exibir_somente_titular') == 'on' context['exibir_somente_titular'] = self.request.GET.get(
context['exibir_somente_ativo'] = self.request.GET.get('exibir_somente_ativo') == 'on' 'exibir_somente_titular') == 'on'
context['exibir_somente_ativo'] = self.request.GET.get(
'exibir_somente_ativo') == 'on'
# Completa o dicionario as informacoes parlamentar/sessao/ordem # Completa o dicionario as informacoes parlamentar/sessao/ordem
parlamentares_presencas = [] parlamentares_presencas = []
@ -594,17 +629,21 @@ class RelatorioPresencaSessaoView(RelatorioMixin, FilterView):
continue continue
try: try:
self.logger.debug(F'user={username}. Tentando obter presença do parlamentar (pk={p.id}).') self.logger.debug(
F'user={username}. Tentando obter presença do parlamentar (pk={p.id}).')
sessao_count = presenca_sessao.get(parlamentar_id=p.id)[1] sessao_count = presenca_sessao.get(parlamentar_id=p.id)[1]
except ObjectDoesNotExist as e: except ObjectDoesNotExist as e:
self.logger.error(F'user={username}. Erro ao obter presença do parlamentar (pk={p.id}). Definido como 0. {str(e)}') self.logger.error(
F'user={username}. Erro ao obter presença do parlamentar (pk={p.id}). Definido como 0. {str(e)}')
sessao_count = 0 sessao_count = 0
try: try:
# Presenças de cada Ordem do Dia # Presenças de cada Ordem do Dia
self.logger.info(F'user={username}. Tentando obter PresencaOrdemDia para o parlamentar pk={p.id}.') self.logger.info(
F'user={username}. Tentando obter PresencaOrdemDia para o parlamentar pk={p.id}.')
ordemdia_count = presenca_ordem.get(parlamentar_id=p.id)[1] ordemdia_count = presenca_ordem.get(parlamentar_id=p.id)[1]
except ObjectDoesNotExist: except ObjectDoesNotExist:
self.logger.error(F'user={username}. Erro ao obter PresencaOrdemDia para o parlamentar pk={p.id}. Definido como 0.') self.logger.error(
F'user={username}. Erro ao obter PresencaOrdemDia para o parlamentar pk={p.id}. Definido como 0.')
ordemdia_count = 0 ordemdia_count = 0
parlamentar.update({ parlamentar.update({
@ -613,9 +652,11 @@ class RelatorioPresencaSessaoView(RelatorioMixin, FilterView):
}) })
if total_sessao != 0: if total_sessao != 0:
parlamentar.update({'sessao_porc': round(sessao_count * 100 / total_sessao, 2)}) parlamentar.update({'sessao_porc': round(
sessao_count * 100 / total_sessao, 2)})
if total_ordemdia != 0: if total_ordemdia != 0:
parlamentar.update({'ordemdia_porc': round(ordemdia_count * 100 / total_ordemdia, 2)}) parlamentar.update({'ordemdia_porc': round(
ordemdia_count * 100 / total_ordemdia, 2)})
parlamentares_presencas.append(parlamentar) parlamentares_presencas.append(parlamentar)
@ -626,10 +667,12 @@ class RelatorioPresencaSessaoView(RelatorioMixin, FilterView):
context['periodo'] = f"{self.request.GET['data_inicio_0']} - {self.request.GET['data_inicio_1']}" context['periodo'] = f"{self.request.GET['data_inicio_0']} - {self.request.GET['data_inicio_1']}"
context['sessao_legislativa'] = '' context['sessao_legislativa'] = ''
context['legislatura'] = '' context['legislatura'] = ''
context['exibir_ordem'] = self.request.GET.get('exibir_ordem_dia') == 'on' context['exibir_ordem'] = self.request.GET.get(
'exibir_ordem_dia') == 'on'
if sessao_legislativa_pk: if sessao_legislativa_pk:
context['sessao_legislativa'] = SessaoLegislativa.objects.get(id=sessao_legislativa_pk) context['sessao_legislativa'] = SessaoLegislativa.objects.get(
id=sessao_legislativa_pk)
if legislatura_pk: if legislatura_pk:
context['legislatura'] = Legislatura.objects.get(id=legislatura_pk) context['legislatura'] = Legislatura.objects.get(id=legislatura_pk)
# ===================================================================== # =====================================================================
@ -650,7 +693,8 @@ class RelatorioHistoricoTramitacaoView(RelatorioMixin, FilterView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(RelatorioHistoricoTramitacaoView, context = super(RelatorioHistoricoTramitacaoView,
self).get_context_data(**kwargs) self).get_context_data(**kwargs)
context['title'] = _('Histórico de Tramitações de Matérias Legislativas') context['title'] = _(
'Histórico de Tramitações de Matérias Legislativas')
if not self.filterset.form.is_valid(): if not self.filterset.form.is_valid():
return context return context
qr = self.request.GET.copy() qr = self.request.GET.copy()
@ -848,21 +892,22 @@ class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView):
kwargs['tramitacao__status'] = status_tramitacao kwargs['tramitacao__status'] = status_tramitacao
if autor: if autor:
kwargs['materia__autores'] = autor kwargs['materia__autores'] = autor
qs = qs.filter(**kwargs) qs = qs.filter(**kwargs)
data['queryset'] = qs data['queryset'] = qs
self.total_resultados_tipos = num_materias_por_tipo(qs, "materia__tipo") self.total_resultados_tipos = num_materias_por_tipo(
qs, "materia__tipo")
return data return data
def get_queryset(self): def get_queryset(self):
qs = super().get_queryset() qs = super().get_queryset()
qs = qs.select_related('materia__tipo').filter( qs = qs.select_related('materia__tipo').filter(
materia__em_tramitacao=True materia__em_tramitacao=True
).exclude( ).exclude(
tramitacao__status__indicador='F' tramitacao__status__indicador='F'
).order_by('-materia__ano', '-materia__numero') ).order_by('-materia__ano', '-materia__numero')
return qs return qs
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -871,7 +916,7 @@ class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView):
).get_context_data(**kwargs) ).get_context_data(**kwargs)
context['title'] = _('Matérias em Tramitação') context['title'] = _('Matérias em Tramitação')
if not self.filterset.form.is_valid(): if not self.filterset.form.is_valid():
return context return context
@ -887,7 +932,7 @@ class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView):
) )
else: else:
context['tipo'] = '' context['tipo'] = ''
if self.request.GET['tramitacao__status']: if self.request.GET['tramitacao__status']:
tramitacao_status = self.request.GET['tramitacao__status'] tramitacao_status = self.request.GET['tramitacao__status']
context['tramitacao__status'] = ( context['tramitacao__status'] = (
@ -895,7 +940,7 @@ class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView):
) )
else: else:
context['tramitacao__status'] = '' context['tramitacao__status'] = ''
if self.request.GET['tramitacao__unidade_tramitacao_destino']: if self.request.GET['tramitacao__unidade_tramitacao_destino']:
context['tramitacao__unidade_tramitacao_destino'] = ( context['tramitacao__unidade_tramitacao_destino'] = (
str(UnidadeTramitacao.objects.get( str(UnidadeTramitacao.objects.get(
@ -912,7 +957,7 @@ class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView):
) )
else: else:
context['materia__autor'] = '' context['materia__autor'] = ''
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) context['show_results'] = show_results_filter_set(qr)
@ -1044,8 +1089,8 @@ class RelatorioMateriasPorAutorView(RelatorioMixin, FilterView):
else: else:
context['autor'] = '' context['autor'] = ''
context['periodo'] = ( context['periodo'] = (
self.request.GET['data_apresentacao_0'] + self.request.GET['data_apresentacao_0'] +
' - ' + self.request.GET['data_apresentacao_1']) ' - ' + self.request.GET['data_apresentacao_1'])
return context return context
@ -1072,15 +1117,15 @@ class RelatorioNormasPublicadasMesView(RelatorioMixin, FilterView):
context['ano'] = self.request.GET['ano'] context['ano'] = self.request.GET['ano']
normas_mes = collections.OrderedDict() normas_mes = collections.OrderedDict()
meses = {1: 'Janeiro', 2: 'Fevereiro', 3:'Março', 4: 'Abril', 5: 'Maio', 6:'Junho', meses = {1: 'Janeiro', 2: 'Fevereiro', 3: 'Março', 4: 'Abril', 5: 'Maio', 6: 'Junho',
7: 'Julho', 8: 'Agosto', 9:'Setembro', 10:'Outubro', 11:'Novembro', 12:'Dezembro'} 7: 'Julho', 8: 'Agosto', 9: 'Setembro', 10: 'Outubro', 11: 'Novembro', 12: 'Dezembro'}
for norma in context['object_list']: for norma in context['object_list']:
if not meses[norma.data.month] in normas_mes: if not meses[norma.data.month] in normas_mes:
normas_mes[meses[norma.data.month]] = [] normas_mes[meses[norma.data.month]] = []
normas_mes[meses[norma.data.month]].append(norma) normas_mes[meses[norma.data.month]].append(norma)
context['normas_mes'] = normas_mes context['normas_mes'] = normas_mes
quant_normas_mes = {} quant_normas_mes = {}
for key in normas_mes.keys(): for key in normas_mes.keys():
quant_normas_mes[key] = len(normas_mes[key]) quant_normas_mes[key] = len(normas_mes[key])
@ -1107,19 +1152,20 @@ class RelatorioNormasVigenciaView(RelatorioMixin, FilterView):
vigencia = kwargs['data']['vigencia'] vigencia = kwargs['data']['vigencia']
if ano: if ano:
qs = qs.filter(ano=ano) qs = qs.filter(ano=ano)
if vigencia == 'True': if vigencia == 'True':
qs_dt_not_null = qs.filter(data_vigencia__isnull=True) qs_dt_not_null = qs.filter(data_vigencia__isnull=True)
qs = (qs_dt_not_null | qs.filter(data_vigencia__gte=datetime.datetime.now().date())).distinct() qs = (qs_dt_not_null | qs.filter(
data_vigencia__gte=datetime.datetime.now().date())).distinct()
else: else:
qs = qs.filter(data_vigencia__lt=datetime.datetime.now().date()) qs = qs.filter(
data_vigencia__lt=datetime.datetime.now().date())
kwargs.update({ kwargs.update({
'queryset': qs 'queryset': qs
}) })
return kwargs return kwargs
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(RelatorioNormasVigenciaView, context = super(RelatorioNormasVigenciaView,
self).get_context_data(**kwargs) self).get_context_data(**kwargs)
@ -1129,17 +1175,20 @@ class RelatorioNormasVigenciaView(RelatorioMixin, FilterView):
if not self.filterset.form.is_valid(): if not self.filterset.form.is_valid():
return context return context
normas_totais = NormaJuridica.objects.filter(ano=self.request.GET['ano']) normas_totais = NormaJuridica.objects.filter(
ano=self.request.GET['ano'])
context['quant_total'] = len(normas_totais) context['quant_total'] = len(normas_totais)
if self.request.GET['vigencia'] == 'True': if self.request.GET['vigencia'] == 'True':
context['vigencia'] = 'Vigente' context['vigencia'] = 'Vigente'
context['quant_vigente'] = len(context['object_list']) context['quant_vigente'] = len(context['object_list'])
context['quant_nao_vigente'] = context['quant_total'] - context['quant_vigente'] context['quant_nao_vigente'] = context['quant_total'] - \
context['quant_vigente']
else: else:
context['vigencia'] = 'Não vigente' context['vigencia'] = 'Não vigente'
context['quant_nao_vigente'] = len(context['object_list']) context['quant_nao_vigente'] = len(context['object_list'])
context['quant_vigente'] = context['quant_total'] - context['quant_nao_vigente'] context['quant_vigente'] = context['quant_total'] - \
context['quant_nao_vigente']
qr = self.request.GET.copy() qr = self.request.GET.copy()
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
@ -1165,7 +1214,7 @@ class EstatisticasAcessoNormas(TemplateView):
return self.render_to_response(context) return self.render_to_response(context)
context['ano'] = self.request.GET['ano'] context['ano'] = self.request.GET['ano']
query = ''' query = '''
select norma_id, ano, extract(month from horario_acesso) as mes, count(*) select norma_id, ano, extract(month from horario_acesso) as mes, count(*)
from norma_normaestatisticas from norma_normaestatisticas
@ -1178,20 +1227,21 @@ class EstatisticasAcessoNormas(TemplateView):
rows = cursor.fetchall() rows = cursor.fetchall()
normas_mes = collections.OrderedDict() normas_mes = collections.OrderedDict()
meses = {1: 'Janeiro', 2: 'Fevereiro', 3:'Março', 4: 'Abril', 5: 'Maio', 6:'Junho', meses = {1: 'Janeiro', 2: 'Fevereiro', 3: 'Março', 4: 'Abril', 5: 'Maio', 6: 'Junho',
7: 'Julho', 8: 'Agosto', 9:'Setembro', 10:'Outubro', 11:'Novembro', 12:'Dezembro'} 7: 'Julho', 8: 'Agosto', 9: 'Setembro', 10: 'Outubro', 11: 'Novembro', 12: 'Dezembro'}
for row in rows: for row in rows:
if not meses[int(row[2])] in normas_mes: if not meses[int(row[2])] in normas_mes:
normas_mes[meses[int(row[2])]] = [] normas_mes[meses[int(row[2])]] = []
norma_est = [NormaJuridica.objects.get(id=row[0]), row[3]] norma_est = [NormaJuridica.objects.get(id=row[0]), row[3]]
normas_mes[meses[int(row[2])]].append(norma_est) normas_mes[meses[int(row[2])]].append(norma_est)
# Ordena por acesso e limita em 5 # Ordena por acesso e limita em 5
for n in normas_mes: for n in normas_mes:
sorted_by_value = sorted(normas_mes[n], key=lambda kv: kv[1], reverse=True) sorted_by_value = sorted(
normas_mes[n], key=lambda kv: kv[1], reverse=True)
normas_mes[n] = sorted_by_value[0:5] normas_mes[n] = sorted_by_value[0:5]
context['normas_mes'] = normas_mes context['normas_mes'] = normas_mes
is_relatorio = request.GET.get('relatorio') is_relatorio = request.GET.get('relatorio')
@ -1235,19 +1285,19 @@ class ListarInconsistenciasView(PermissionRequiredMixin, ListView):
('filiacoes_sem_data_filiacao', ('filiacoes_sem_data_filiacao',
'Filiações sem data filiação', 'Filiações sem data filiação',
len(filiacoes_sem_data_filiacao()) len(filiacoes_sem_data_filiacao())
) )
) )
tabela.append( tabela.append(
('mandato_sem_data_inicio', ('mandato_sem_data_inicio',
'Mandatos sem data inicial', 'Mandatos sem data inicial',
len(mandato_sem_data_inicio()) len(mandato_sem_data_inicio())
) )
) )
tabela.append( tabela.append(
('parlamentares_duplicados', ('parlamentares_duplicados',
'Parlamentares duplicados', 'Parlamentares duplicados',
len(parlamentares_duplicados()) len(parlamentares_duplicados())
) )
) )
tabela.append( tabela.append(
('parlamentares_mandatos_intersecao', ('parlamentares_mandatos_intersecao',
@ -1258,8 +1308,8 @@ class ListarInconsistenciasView(PermissionRequiredMixin, ListView):
tabela.append( tabela.append(
('parlamentares_filiacoes_intersecao', ('parlamentares_filiacoes_intersecao',
'Parlamentares com filiações em interseção', 'Parlamentares com filiações em interseção',
len(parlamentares_filiacoes_intersecao()) len(parlamentares_filiacoes_intersecao())
) )
) )
tabela.append( tabela.append(
('autores_duplicados', ('autores_duplicados',
@ -1277,7 +1327,7 @@ class ListarInconsistenciasView(PermissionRequiredMixin, ListView):
('legislatura_infindavel', ('legislatura_infindavel',
'Legislaturas sem data fim', 'Legislaturas sem data fim',
len(legislatura_infindavel()) len(legislatura_infindavel())
) )
) )
tabela.append( tabela.append(
('anexadas_ciclicas', ('anexadas_ciclicas',
@ -1293,6 +1343,7 @@ class ListarInconsistenciasView(PermissionRequiredMixin, ListView):
) )
return tabela return tabela
def materias_anexadas_ciclicas(): def materias_anexadas_ciclicas():
ciclos = [] ciclos = []
@ -1306,7 +1357,8 @@ def materias_anexadas_ciclicas():
ma = anexadas.pop() ma = anexadas.pop()
if ma not in visitados: if ma not in visitados:
visitados.append(ma) visitados.append(ma)
anexadas.extend([a.materia_anexada for a in Anexada.objects.filter(materia_principal=ma)]) anexadas.extend(
[a.materia_anexada for a in Anexada.objects.filter(materia_principal=ma)])
else: else:
ciclo_list = visitados + [ma] ciclo_list = visitados + [ma]
ciclos.append(ciclo_list) ciclos.append(ciclo_list)
@ -1320,12 +1372,14 @@ def materias_anexadas_ciclicas():
return ciclos_unique return ciclos_unique
def is_ciclo_unique(ciclo, ciclos_set): def is_ciclo_unique(ciclo, ciclos_set):
if set(ciclo) not in ciclos_set: if set(ciclo) not in ciclos_set:
ciclos_set.append(set(ciclo)) ciclos_set.append(set(ciclo))
return True return True
else: else:
return False return False
def anexados_ciclicos(ofMateriaLegislativa): def anexados_ciclicos(ofMateriaLegislativa):
ciclicos = [] ciclicos = []
@ -1367,7 +1421,8 @@ def anexados_ciclicos(ofMateriaLegislativa):
) )
anexados_temp.extend(anexados_anexado) anexados_temp.extend(anexados_anexado)
else: else:
ciclicos.append((anexado.data_anexacao, anexado.materia_principal, anexado.materia_anexada)) ciclicos.append(
(anexado.data_anexacao, anexado.materia_principal, anexado.materia_anexada))
else: else:
if anexado.documento_anexado not in anexados_total: if anexado.documento_anexado not in anexados_total:
if not principal['documento_principal'] == anexado.documento_anexado.pk: if not principal['documento_principal'] == anexado.documento_anexado.pk:
@ -1377,7 +1432,8 @@ def anexados_ciclicos(ofMateriaLegislativa):
) )
anexados_temp.extend(anexados_anexado) anexados_temp.extend(anexados_anexado)
else: else:
ciclicos.append((anexado.data_anexacao, anexado.documento_principal, anexado.documento_anexado)) ciclicos.append(
(anexado.data_anexacao, anexado.documento_principal, anexado.documento_anexado))
return ciclicos return ciclicos
@ -1425,7 +1481,7 @@ class ListarAnexadasCiclicasView(PermissionRequiredMixin, ListView):
paginator = context['paginator'] paginator = context['paginator']
page_obj = context['page_obj'] page_obj = context['page_obj']
context['page_range'] = make_pagination( context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages page_obj.number, paginator.num_pages
) )
@ -1451,14 +1507,14 @@ class ListarLegislaturaInfindavelView(PermissionRequiredMixin, ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super( context = super(
ListarLegislaturaInfindavelView, self ListarLegislaturaInfindavelView, self
).get_context_data(**kwargs) ).get_context_data(**kwargs)
paginator = context['paginator'] paginator = context['paginator']
page_obj = context['page_obj'] page_obj = context['page_obj']
context['page_range'] = make_pagination( context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages) page_obj.number, paginator.num_pages)
context[ context[
'NO_ENTRIES_MSG' 'NO_ENTRIES_MSG'
] = 'Nenhuma encontrada.' ] = 'Nenhuma encontrada.'
return context return context
@ -1501,14 +1557,14 @@ class ListarBancadaComissaoAutorExternoView(PermissionRequiredMixin, ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super( context = super(
ListarBancadaComissaoAutorExternoView, self ListarBancadaComissaoAutorExternoView, self
).get_context_data(**kwargs) ).get_context_data(**kwargs)
paginator = context['paginator'] paginator = context['paginator']
page_obj = context['page_obj'] page_obj = context['page_obj']
context['page_range'] = make_pagination( context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages) page_obj.number, paginator.num_pages)
context[ context[
'NO_ENTRIES_MSG' 'NO_ENTRIES_MSG'
] = 'Nenhuma encontrada.' ] = 'Nenhuma encontrada.'
return context return context
@ -1534,7 +1590,7 @@ class ListarAutoresDuplicadosView(PermissionRequiredMixin, ListView):
page_obj.number, paginator.num_pages) page_obj.number, paginator.num_pages)
context[ context[
'NO_ENTRIES_MSG' 'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.' ] = 'Nenhum encontrado.'
return context return context
@ -1547,10 +1603,12 @@ def parlamentares_filiacoes_intersecao():
for c in combinacoes: for c in combinacoes:
data_filiacao1 = c[0].data data_filiacao1 = c[0].data
data_desfiliacao1 = c[0].data_desfiliacao if c[0].data_desfiliacao else timezone.now().date() data_desfiliacao1 = c[0].data_desfiliacao if c[0].data_desfiliacao else timezone.now(
).date()
data_filiacao2 = c[1].data data_filiacao2 = c[1].data
data_desfiliacao2 = c[1].data_desfiliacao if c[1].data_desfiliacao else timezone.now().date() data_desfiliacao2 = c[1].data_desfiliacao if c[1].data_desfiliacao else timezone.now(
).date()
if data_filiacao1 and data_filiacao2: if data_filiacao1 and data_filiacao2:
exists = intervalos_tem_intersecao( exists = intervalos_tem_intersecao(
@ -1580,8 +1638,8 @@ class ListarParlFiliacoesIntersecaoView(PermissionRequiredMixin, ListView):
page_obj.number, paginator.num_pages) page_obj.number, paginator.num_pages)
context[ context[
'NO_ENTRIES_MSG' 'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.' ] = 'Nenhum encontrado.'
return context return context
def parlamentares_mandatos_intersecao(): def parlamentares_mandatos_intersecao():
@ -1593,10 +1651,12 @@ def parlamentares_mandatos_intersecao():
for c in combinacoes: for c in combinacoes:
data_inicio_mandato1 = c[0].data_inicio_mandato data_inicio_mandato1 = c[0].data_inicio_mandato
data_fim_mandato1 = c[0].data_fim_mandato if c[0].data_fim_mandato else timezone.now().date() data_fim_mandato1 = c[0].data_fim_mandato if c[0].data_fim_mandato else timezone.now(
).date()
data_inicio_mandato2 = c[1].data_inicio_mandato data_inicio_mandato2 = c[1].data_inicio_mandato
data_fim_mandato2 = c[1].data_fim_mandato if c[1].data_fim_mandato else timezone.now().date() data_fim_mandato2 = c[1].data_fim_mandato if c[1].data_fim_mandato else timezone.now(
).date()
if data_inicio_mandato1 and data_inicio_mandato2: if data_inicio_mandato1 and data_inicio_mandato2:
exists = intervalos_tem_intersecao( exists = intervalos_tem_intersecao(
@ -1627,7 +1687,7 @@ class ListarParlMandatosIntersecaoView(PermissionRequiredMixin, ListView):
page_obj.number, paginator.num_pages) page_obj.number, paginator.num_pages)
context[ context[
'NO_ENTRIES_MSG' 'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.' ] = 'Nenhum encontrado.'
return context return context
@ -1646,7 +1706,7 @@ class ListarParlamentaresDuplicadosView(PermissionRequiredMixin, ListView):
def get_queryset(self): def get_queryset(self):
return parlamentares_duplicados() return parlamentares_duplicados()
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super( context = super(
ListarParlamentaresDuplicadosView, self).get_context_data(**kwargs) ListarParlamentaresDuplicadosView, self).get_context_data(**kwargs)
@ -1656,9 +1716,9 @@ class ListarParlamentaresDuplicadosView(PermissionRequiredMixin, ListView):
page_obj.number, paginator.num_pages) page_obj.number, paginator.num_pages)
context[ context[
'NO_ENTRIES_MSG' 'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.' ] = 'Nenhum encontrado.'
return context return context
def mandato_sem_data_inicio(): def mandato_sem_data_inicio():
return Mandato.objects.filter(data_inicio_mandato__isnull=True).order_by('parlamentar') return Mandato.objects.filter(data_inicio_mandato__isnull=True).order_by('parlamentar')
@ -1669,13 +1729,16 @@ def get_estatistica(request):
normas = NormaJuridica.objects.all() normas = NormaJuridica.objects.all()
datas = [ datas = [
materias.order_by('-data_ultima_atualizacao').values_list('data_ultima_atualizacao', flat=True) materias.order_by(
.exclude(data_ultima_atualizacao__isnull=True).first(), '-data_ultima_atualizacao').values_list('data_ultima_atualizacao', flat=True)
normas.order_by('-data_ultima_atualizacao').values_list('data_ultima_atualizacao', flat=True) .exclude(data_ultima_atualizacao__isnull=True).first(),
.exclude(data_ultima_atualizacao__isnull=True).first() normas.order_by(
'-data_ultima_atualizacao').values_list('data_ultima_atualizacao', flat=True)
.exclude(data_ultima_atualizacao__isnull=True).first()
] ]
max_data = max(datas) if datas[0] and datas[1] else next(iter([i for i in datas if i is not None]), '') max_data = max(datas) if datas[0] and datas[1] else next(
iter([i for i in datas if i is not None]), '')
return JsonResponse({ return JsonResponse({
"data_ultima_atualizacao": max_data, "data_ultima_atualizacao": max_data,
@ -1699,14 +1762,14 @@ class ListarMandatoSemDataInicioView(PermissionRequiredMixin, ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super( context = super(
ListarMandatoSemDataInicioView, self ListarMandatoSemDataInicioView, self
).get_context_data(**kwargs) ).get_context_data(**kwargs)
paginator = context['paginator'] paginator = context['paginator']
page_obj = context['page_obj'] page_obj = context['page_obj']
context['page_range'] = make_pagination( context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages) page_obj.number, paginator.num_pages)
context[ context[
'NO_ENTRIES_MSG' 'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.' ] = 'Nenhum encontrado.'
return context return context
@ -1723,11 +1786,11 @@ class ListarFiliacoesSemDataFiliacaoView(PermissionRequiredMixin, ListView):
def get_queryset(self): def get_queryset(self):
return filiacoes_sem_data_filiacao() return filiacoes_sem_data_filiacao()
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super( context = super(
ListarFiliacoesSemDataFiliacaoView, self ListarFiliacoesSemDataFiliacaoView, self
).get_context_data(**kwargs) ).get_context_data(**kwargs)
paginator = context['paginator'] paginator = context['paginator']
page_obj = context['page_obj'] page_obj = context['page_obj']
context['page_range'] = make_pagination( context['page_range'] = make_pagination(
@ -1762,27 +1825,27 @@ class ListarMatProtocoloInexistenteView(PermissionRequiredMixin, ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super( context = super(
ListarMatProtocoloInexistenteView, self ListarMatProtocoloInexistenteView, self
).get_context_data(**kwargs) ).get_context_data(**kwargs)
paginator = context['paginator'] paginator = context['paginator']
page_obj = context['page_obj'] page_obj = context['page_obj']
context['page_range'] = make_pagination( context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages) page_obj.number, paginator.num_pages)
context[ context[
'NO_ENTRIES_MSG' 'NO_ENTRIES_MSG'
] = 'Nenhuma encontrada.' ] = 'Nenhuma encontrada.'
return context return context
def protocolos_com_materias(): def protocolos_com_materias():
protocolos = {} protocolos = {}
for m in MateriaLegislativa.objects.filter(numero_protocolo__isnull=False).order_by('-ano', 'numero_protocolo'): for m in MateriaLegislativa.objects.filter(numero_protocolo__isnull=False).order_by('-ano', 'numero_protocolo'):
if Protocolo.objects.filter(numero=m.numero_protocolo, ano=m.ano).exists(): if Protocolo.objects.filter(numero=m.numero_protocolo, ano=m.ano).exists():
key = "{}/{}".format(m.numero_protocolo, m.ano) key = "{}/{}".format(m.numero_protocolo, m.ano)
val = protocolos.get(key, list()) val = protocolos.get(key, list())
val.append(m) val.append(m)
protocolos[key] = val protocolos[key] = val
return [(v[0], len(v)) for (k, v) in protocolos.items() if len(v) > 1] return [(v[0], len(v)) for (k, v) in protocolos.items() if len(v) > 1]
@ -1805,7 +1868,7 @@ class ListarProtocolosComMateriasView(PermissionRequiredMixin, ListView):
page_obj.number, paginator.num_pages) page_obj.number, paginator.num_pages)
context[ context[
'NO_ENTRIES_MSG' 'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.' ] = 'Nenhum encontrado.'
return context return context
@ -1835,7 +1898,7 @@ class ListarProtocolosDuplicadosView(PermissionRequiredMixin, ListView):
page_obj.number, paginator.num_pages) page_obj.number, paginator.num_pages)
context[ context[
'NO_ENTRIES_MSG' 'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.' ] = 'Nenhum encontrado.'
return context return context
@ -1958,13 +2021,14 @@ class DeleteUsuarioView(PermissionRequiredMixin, DeleteView):
template_name = "crud/confirm_delete.html" template_name = "crud/confirm_delete.html"
permission_required = ('base.delete_appconfig',) permission_required = ('base.delete_appconfig',)
success_url = reverse_lazy('sapl.base:usuario') success_url = reverse_lazy('sapl.base:usuario')
success_message = "Usuário removido com sucesso!" success_message = "Usuário removido com sucesso!"
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
try: try:
super(DeleteUsuarioView, self).delete(request, *args, **kwargs) super(DeleteUsuarioView, self).delete(request, *args, **kwargs)
except ProtectedError as exception: except ProtectedError as exception:
error_url = reverse_lazy('sapl.base:user_delete', kwargs={'pk': self.kwargs['pk']}) error_url = reverse_lazy('sapl.base:user_delete', kwargs={
'pk': self.kwargs['pk']})
error_message = "O usuário não pode ser removido, pois é referenciado por:<br><ul>" error_message = "O usuário não pode ser removido, pois é referenciado por:<br><ul>"
for e in exception.protected_objects: for e in exception.protected_objects:
@ -1981,7 +2045,7 @@ class DeleteUsuarioView(PermissionRequiredMixin, DeleteView):
@property @property
def cancel_url(self): def cancel_url(self):
return reverse('sapl.base:user_edit', return reverse('sapl.base:user_edit',
kwargs={'pk': self.kwargs['pk']}) kwargs={'pk': self.kwargs['pk']})
class EditUsuarioView(PermissionRequiredMixin, UpdateView): class EditUsuarioView(PermissionRequiredMixin, UpdateView):
@ -2099,13 +2163,15 @@ class AppConfigCrud(CrudAux):
recibo_prop_atual = AppConfig.objects.last().receber_recibo_proposicao recibo_prop_atual = AppConfig.objects.last().receber_recibo_proposicao
recibo_prop_novo = self.request.POST['receber_recibo_proposicao'] recibo_prop_novo = self.request.POST['receber_recibo_proposicao']
if recibo_prop_novo == 'False' and recibo_prop_atual: if recibo_prop_novo == 'False' and recibo_prop_atual:
props = Proposicao.objects.filter(hash_code='', data_recebimento__isnull=True).exclude(data_envio__isnull=True) props = Proposicao.objects.filter(
hash_code='', data_recebimento__isnull=True).exclude(data_envio__isnull=True)
for prop in props: for prop in props:
try: try:
self.gerar_hash(prop) self.gerar_hash(prop)
except ValidationError as e: except ValidationError as e:
form.add_error('receber_recibo_proposicao',e) form.add_error('receber_recibo_proposicao', e)
msg = _("Não foi possível mudar a configuração porque a Proposição {} não possui texto original vinculado!".format(prop)) msg = _(
"Não foi possível mudar a configuração porque a Proposição {} não possui texto original vinculado!".format(prop))
messages.error(self.request, msg) messages.error(self.request, msg)
return super().form_invalid(form) return super().form_invalid(form)
return super().form_valid(form) return super().form_valid(form)
@ -2117,7 +2183,8 @@ class AppConfigCrud(CrudAux):
inst.texto_original.path, str(inst.pk)) inst.texto_original.path, str(inst.pk))
inst.save() inst.save()
except IOError: except IOError:
raise ValidationError("Existem proposicoes com arquivos inexistentes.") raise ValidationError(
"Existem proposicoes com arquivos inexistentes.")
elif inst.texto_articulado.exists(): elif inst.texto_articulado.exists():
ta = inst.texto_articulado.first() ta = inst.texto_articulado.first()
inst.hash_code = 'P' + ta.hash() + SEPARADOR_HASH_PROPOSICAO + str(inst.pk) inst.hash_code = 'P' + ta.hash() + SEPARADOR_HASH_PROPOSICAO + str(inst.pk)
@ -2136,7 +2203,6 @@ class AppConfigCrud(CrudAux):
reverse('sapl.base:appconfig_update', reverse('sapl.base:appconfig_update',
kwargs={'pk': app_config.pk})) kwargs={'pk': app_config.pk}))
class UpdateView(CrudAux.UpdateView): class UpdateView(CrudAux.UpdateView):
template_name = 'base/AppConfig.html' template_name = 'base/AppConfig.html'
@ -2258,6 +2324,7 @@ class LogotipoView(RedirectView):
logo = casa and casa.logotipo and casa.logotipo.name logo = casa and casa.logotipo and casa.logotipo.name
return os.path.join(settings.MEDIA_URL, logo) if logo else STATIC_LOGO return os.path.join(settings.MEDIA_URL, logo) if logo else STATIC_LOGO
def filtro_campos(dicionario): def filtro_campos(dicionario):
chaves_desejadas = ['ementa', chaves_desejadas = ['ementa',
@ -2280,6 +2347,7 @@ def filtro_campos(dicionario):
return dicionario return dicionario
def pesquisa_textual(request): def pesquisa_textual(request):
if 'q' not in request.GET: if 'q' not in request.GET:
@ -2299,17 +2367,17 @@ def pesquisa_textual(request):
try: try:
sec_dict['pk'] = e.object.pk sec_dict['pk'] = e.object.pk
except: except:
# Index and db are out of sync. Object has been deleted from database # Index and db are out of sync. Object has been deleted from
# database
continue continue
dici = filtro_campos(e.object.__dict__) dici = filtro_campos(e.object.__dict__)
sec_dict['objeto'] = str(dici) sec_dict['objeto'] = str(dici)
sec_dict['text'] = str(e.object.ementa) sec_dict['text'] = str(e.object.ementa)
sec_dict['model'] = str(type(e.object)) sec_dict['model'] = str(type(e.object))
json_dict['resultados'].append(sec_dict) json_dict['resultados'].append(sec_dict)
return JsonResponse(json_dict) return JsonResponse(json_dict)
@ -2322,7 +2390,8 @@ class RelatorioHistoricoTramitacaoAdmView(RelatorioMixin, FilterView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(RelatorioHistoricoTramitacaoAdmView, context = super(RelatorioHistoricoTramitacaoAdmView,
self).get_context_data(**kwargs) self).get_context_data(**kwargs)
context['title'] = _('Histórico de Tramitações de Documento Administrativo') context['title'] = _(
'Histórico de Tramitações de Documento Administrativo')
if not self.filterset.form.is_valid(): if not self.filterset.form.is_valid():
return context return context
qr = self.request.GET.copy() qr = self.request.GET.copy()
@ -2361,6 +2430,7 @@ class RelatorioHistoricoTramitacaoAdmView(RelatorioMixin, FilterView):
return context return context
class RelatorioNormasPorAutorView(RelatorioMixin, FilterView): class RelatorioNormasPorAutorView(RelatorioMixin, FilterView):
model = NormaJuridica model = NormaJuridica
filterset_class = RelatorioNormasPorAutorFilterSet filterset_class = RelatorioNormasPorAutorFilterSet
@ -2397,7 +2467,7 @@ class RelatorioNormasPorAutorView(RelatorioMixin, FilterView):
str(TipoNormaJuridica.objects.get(id=tipo))) str(TipoNormaJuridica.objects.get(id=tipo)))
else: else:
context['tipo'] = '' context['tipo'] = ''
if self.request.GET['autorianorma__autor']: if self.request.GET['autorianorma__autor']:
autor = int(self.request.GET['autorianorma__autor']) autor = int(self.request.GET['autorianorma__autor'])
context['autor'] = (str(Autor.objects.get(id=autor))) context['autor'] = (str(Autor.objects.get(id=autor)))

14
sapl/context_processors.py

@ -1,13 +1,14 @@
import logging import logging
from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from sapl.base.views import get_casalegislativa from sapl.utils import google_recaptcha_configured as google_recaptcha_configured_utils
from sapl.utils import mail_service_configured as mail_service_configured_utils from sapl.utils import mail_service_configured as mail_service_configured_utils
def parliament_info(request): def parliament_info(request):
from sapl.base.views import get_casalegislativa
casa = get_casalegislativa() casa = get_casalegislativa()
if casa: if casa:
return casa.__dict__ return casa.__dict__
@ -22,3 +23,12 @@ def mail_service_configured(request):
logger.warning(_('Servidor de email não configurado.')) logger.warning(_('Servidor de email não configurado.'))
return {'mail_service_configured': False} return {'mail_service_configured': False}
return {'mail_service_configured': True} return {'mail_service_configured': True}
def google_recaptcha_configured(request):
if not google_recaptcha_configured_utils():
logger = logging.getLogger(__name__)
logger.warning(_('Google Recaptcha não configurado.'))
return {'google_recaptcha_configured': False}
return {'google_recaptcha_configured': True}

39
sapl/materia/forms.py

@ -7,7 +7,7 @@ from django import forms
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.files.base import File from django.core.files.base import File
from django.db import models, transaction from django.db import transaction
from django.db.models import F, Max, Q from django.db.models import F, Max, Q
from django.forms import ModelChoiceField, ModelForm, widgets from django.forms import ModelChoiceField, ModelForm, widgets
from django.forms.forms import Form from django.forms.forms import Form
@ -15,9 +15,6 @@ from django.forms.models import ModelMultipleChoiceField
from django.forms.widgets import CheckboxSelectMultiple, HiddenInput, Select from django.forms.widgets import CheckboxSelectMultiple, HiddenInput, Select
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.encoding import force_text
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
import django_filters import django_filters
@ -28,15 +25,15 @@ from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC,
STATUS_TA_PRIVATE) STATUS_TA_PRIVATE)
from sapl.crispy_layout_mixin import (form_actions, SaplFormHelper, from sapl.crispy_layout_mixin import (form_actions, SaplFormHelper,
SaplFormLayout, to_column, to_row) SaplFormLayout, to_column, to_row)
from sapl.materia.models import (AssuntoMateria, Autoria, MateriaAssunto, from sapl.materia.models import (AssuntoMateria, MateriaAssunto,
MateriaLegislativa, Orgao, MateriaLegislativa, Orgao,
RegimeTramitacao, StatusTramitacao, RegimeTramitacao, StatusTramitacao,
TipoDocumento, TipoProposicao, TipoDocumento, TipoProposicao,
UnidadeTramitacao, ConfigEtiquetaMateriaLegislativa) ConfigEtiquetaMateriaLegislativa)
from sapl.norma.models import (LegislacaoCitada, NormaJuridica, from sapl.norma.models import (LegislacaoCitada, NormaJuridica,
TipoNormaJuridica) TipoNormaJuridica)
from sapl.parlamentares.models import Legislatura, Partido, Parlamentar from sapl.parlamentares.models import Legislatura, Partido
from sapl.protocoloadm.models import (Anexado, DocumentoAdministrativo, from sapl.protocoloadm.models import (DocumentoAdministrativo,
Protocolo) Protocolo)
from sapl.utils import (autor_label, autor_modal, from sapl.utils import (autor_label, autor_modal,
ChoiceWithoutValidationField, ChoiceWithoutValidationField,
@ -44,8 +41,9 @@ from sapl.utils import (autor_label, autor_modal,
FilterOverridesMetaMixin, gerar_hash_arquivo, FilterOverridesMetaMixin, gerar_hash_arquivo,
lista_anexados, MateriaPesquisaOrderingFilter, lista_anexados, MateriaPesquisaOrderingFilter,
models_with_gr_for_model, qs_override_django_filter, models_with_gr_for_model, qs_override_django_filter,
RangeWidgetOverride, SEPARADOR_HASH_PROPOSICAO, SEPARADOR_HASH_PROPOSICAO,
validar_arquivo, YES_NO_CHOICES) validar_arquivo, YES_NO_CHOICES,
GoogleRecapthaMixin)
from .models import (AcompanhamentoMateria, Anexada, Autoria, from .models import (AcompanhamentoMateria, Anexada, Autoria,
DespachoInicial, DocumentoAcessorio, Numeracao, DespachoInicial, DocumentoAcessorio, Numeracao,
@ -317,7 +315,7 @@ class UnidadeTramitacaoForm(ModelForm):
return unidade return unidade
class AcompanhamentoMateriaForm(ModelForm): class AcompanhamentoMateriaForm(GoogleRecapthaMixin, ModelForm):
class Meta: class Meta:
model = AcompanhamentoMateria model = AcompanhamentoMateria
@ -325,17 +323,10 @@ class AcompanhamentoMateriaForm(ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
row1 = to_row([('email', 12)]) kwargs['title_label'] = _('Acompanhamento de Matéria por e-mail')
kwargs['action_label'] = _('Cadastrar')
self.helper = SaplFormHelper() super().__init__(*args, **kwargs)
self.helper.layout = Layout(
Fieldset(
_('Acompanhamento de Matéria por e-mail'),
row1,
form_actions(label='Cadastrar')
)
)
super(AcompanhamentoMateriaForm, self).__init__(*args, **kwargs)
class DocumentoAcessorioForm(FileFieldCheckMixin, ModelForm): class DocumentoAcessorioForm(FileFieldCheckMixin, ModelForm):
@ -2789,7 +2780,8 @@ class FichaSelecionaForm(forms.Form):
class StatusTramitacaoFilterSet(django_filters.FilterSet): class StatusTramitacaoFilterSet(django_filters.FilterSet):
descricao = django_filters.CharFilter(label=_("Descrição do Status"), method='multifield_filter') descricao = django_filters.CharFilter(
label=_("Descrição do Status"), method='multifield_filter')
class Meta: class Meta:
model = StatusTramitacao model = StatusTramitacao
@ -2806,7 +2798,8 @@ class StatusTramitacaoFilterSet(django_filters.FilterSet):
self.form.helper = SaplFormHelper() self.form.helper = SaplFormHelper()
self.form.helper.form_method = "GET" self.form.helper.form_method = "GET"
self.form.helper.layout = Layout( self.form.helper.layout = Layout(
Fieldset(_("Pesquisa de Status de Tramitacao"), row0, form_actions(label="Pesquisar")) Fieldset(_("Pesquisa de Status de Tramitacao"),
row0, form_actions(label="Pesquisar"))
) )

12
sapl/materia/views.py

@ -56,7 +56,8 @@ from sapl.settings import MAX_DOC_UPLOAD_SIZE, MEDIA_ROOT
from sapl.utils import (autor_label, autor_modal, gerar_hash_arquivo, get_base_url, from sapl.utils import (autor_label, autor_modal, gerar_hash_arquivo, get_base_url,
get_client_ip, get_mime_type_from_file_extension, lista_anexados, get_client_ip, get_mime_type_from_file_extension, lista_anexados,
mail_service_configured, montar_row_autor, SEPARADOR_HASH_PROPOSICAO, mail_service_configured, montar_row_autor, SEPARADOR_HASH_PROPOSICAO,
show_results_filter_set, YES_NO_CHOICES, get_tempfile_dir) show_results_filter_set, YES_NO_CHOICES, get_tempfile_dir,
google_recaptcha_configured)
from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm,
AnexadaEmLoteFilterSet, AdicionarVariasAutoriasFilterSet, AnexadaEmLoteFilterSet, AdicionarVariasAutoriasFilterSet,
@ -2090,6 +2091,10 @@ class AcompanhamentoMateriaView(CreateView):
messages.error(request, _('Serviço de Acompanhamento de ' messages.error(request, _('Serviço de Acompanhamento de '
'Matérias não foi configurado')) 'Matérias não foi configurado'))
return redirect('/') return redirect('/')
if not google_recaptcha_configured():
self.logger.warning(_('Google Recaptcha não configurado!'))
messages.error(request, _('Google Recaptcha não configurado!'))
return redirect(request.META.get('HTTP_REFERER', '/'))
pk = self.kwargs['pk'] pk = self.kwargs['pk']
materia = MateriaLegislativa.objects.get(id=pk) materia = MateriaLegislativa.objects.get(id=pk)
@ -2106,6 +2111,11 @@ class AcompanhamentoMateriaView(CreateView):
'Matérias não foi configurado')) 'Matérias não foi configurado'))
return redirect('/') return redirect('/')
if not google_recaptcha_configured():
self.logger.warning(_('Google Recaptcha não configurado!'))
messages.error(request, _('Google Recaptcha não configurado!'))
return redirect(request.META.get('HTTP_REFERER', '/'))
form = AcompanhamentoMateriaForm(request.POST) form = AcompanhamentoMateriaForm(request.POST)
pk = self.kwargs['pk'] pk = self.kwargs['pk']
materia = MateriaLegislativa.objects.get(id=pk) materia = MateriaLegislativa.objects.get(id=pk)

25
sapl/protocoloadm/forms.py

@ -2,12 +2,12 @@ import logging
import re import re
from crispy_forms.bootstrap import InlineRadios, Alert, FormActions from crispy_forms.bootstrap import InlineRadios, Alert, FormActions
from crispy_forms.layout import (Button, Column, Div, Fieldset, HTML, from crispy_forms.layout import (Button, Div, Fieldset, HTML,
Layout, Submit) Layout, Submit)
from django import forms from django import forms
from django.core.exceptions import (MultipleObjectsReturned, from django.core.exceptions import (MultipleObjectsReturned,
ObjectDoesNotExist, ValidationError) ObjectDoesNotExist, ValidationError)
from django.db import models, transaction from django.db import transaction
from django.db.models import Max from django.db.models import Max
from django.forms import ModelForm from django.forms import ModelForm
from django.utils import timezone from django.utils import timezone
@ -20,14 +20,14 @@ from sapl.crispy_layout_mixin import (form_actions, SaplFormHelper,
from sapl.materia.models import (MateriaLegislativa, from sapl.materia.models import (MateriaLegislativa,
TipoMateriaLegislativa, TipoMateriaLegislativa,
UnidadeTramitacao) UnidadeTramitacao)
from sapl.protocoloadm.models import Protocolo
from sapl.utils import (AnoNumeroOrderingFilter, autor_label, autor_modal, from sapl.utils import (AnoNumeroOrderingFilter, autor_label, autor_modal,
choice_anos_com_documentoadministrativo, choice_anos_com_documentoadministrativo,
choice_anos_com_materias, choice_anos_com_materias,
choice_anos_com_protocolo, choice_force_optional, choice_anos_com_protocolo, choice_force_optional,
FileFieldCheckMixin, FilterOverridesMetaMixin, FileFieldCheckMixin, FilterOverridesMetaMixin,
lista_anexados, RangeWidgetOverride, RANGE_ANOS, lista_anexados, RANGE_ANOS,
validar_arquivo, YES_NO_CHOICES) validar_arquivo, YES_NO_CHOICES,
GoogleRecapthaMixin)
from .models import (Anexado, AcompanhamentoDocumento, from .models import (Anexado, AcompanhamentoDocumento,
DocumentoAcessorioAdministrativo, DocumentoAcessorioAdministrativo,
@ -48,7 +48,7 @@ NATUREZA_PROCESSO = [('0', 'Administrativo'),
EM_TRAMITACAO = [(0, 'Sim'), (1, 'Não')] EM_TRAMITACAO = [(0, 'Sim'), (1, 'Não')]
class AcompanhamentoDocumentoForm(ModelForm): class AcompanhamentoDocumentoForm(GoogleRecapthaMixin, ModelForm):
class Meta: class Meta:
model = AcompanhamentoDocumento model = AcompanhamentoDocumento
@ -56,17 +56,10 @@ class AcompanhamentoDocumentoForm(ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
row1 = to_row([('email', 12)]) kwargs['title_label'] = _('Acompanhamento de Documento por e-mail')
kwargs['action_label'] = _('Cadastrar')
self.helper = SaplFormHelper() super().__init__(*args, **kwargs)
self.helper.layout = Layout(
Fieldset(
_('Acompanhamento de Documento por e-mail'),
row1,
form_actions(label='Cadastrar')
)
)
super(AcompanhamentoDocumentoForm, self).__init__(*args, **kwargs)
class ProtocoloFilterSet(django_filters.FilterSet): class ProtocoloFilterSet(django_filters.FilterSet):

13
sapl/protocoloadm/views.py

@ -40,7 +40,8 @@ from sapl.protocoloadm.models import Protocolo, DocumentoAdministrativo
from sapl.relatorios.views import relatorio_doc_administrativos from sapl.relatorios.views import relatorio_doc_administrativos
from sapl.utils import (create_barcode, get_base_url, get_client_ip, from sapl.utils import (create_barcode, get_base_url, get_client_ip,
get_mime_type_from_file_extension, lista_anexados, get_mime_type_from_file_extension, lista_anexados,
show_results_filter_set, mail_service_configured, from_date_to_datetime_utc) show_results_filter_set, mail_service_configured, from_date_to_datetime_utc,
google_recaptcha_configured)
from .forms import (AcompanhamentoDocumentoForm, AnexadoEmLoteFilterSet, AnexadoForm, from .forms import (AcompanhamentoDocumentoForm, AnexadoEmLoteFilterSet, AnexadoForm,
AnularProtocoloAdmForm, compara_tramitacoes_doc, AnularProtocoloAdmForm, compara_tramitacoes_doc,
@ -200,6 +201,11 @@ class AcompanhamentoDocumentoView(CreateView):
'Documentos não foi configurado')) 'Documentos não foi configurado'))
return redirect('/') return redirect('/')
if not google_recaptcha_configured():
self.logger.warning(_('Google Recaptcha não configurado!'))
messages.error(request, _('Google Recaptcha não configurado!'))
return redirect(request.META.get('HTTP_REFERER', '/'))
pk = self.kwargs['pk'] pk = self.kwargs['pk']
documento = DocumentoAdministrativo.objects.get(id=pk) documento = DocumentoAdministrativo.objects.get(id=pk)
@ -214,6 +220,11 @@ class AcompanhamentoDocumentoView(CreateView):
'Documentos não foi configurado')) 'Documentos não foi configurado'))
return redirect('/') return redirect('/')
if not google_recaptcha_configured():
self.logger.warning(_('Google Recaptcha não configurado!'))
messages.error(request, _('Google Recaptcha não configurado!'))
return redirect(request.META.get('HTTP_REFERER', '/'))
form = AcompanhamentoDocumentoForm(request.POST) form = AcompanhamentoDocumentoForm(request.POST)
pk = self.kwargs['pk'] pk = self.kwargs['pk']
documento = DocumentoAdministrativo.objects.get(id=pk) documento = DocumentoAdministrativo.objects.get(id=pk)

2
sapl/settings.py

@ -195,6 +195,8 @@ TEMPLATES = [
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
'sapl.context_processors.parliament_info', 'sapl.context_processors.parliament_info',
'sapl.context_processors.mail_service_configured', 'sapl.context_processors.mail_service_configured',
'sapl.context_processors.google_recaptcha_configured',
], ],
'debug': DEBUG 'debug': DEBUG
}, },

19
sapl/templates/base/layouts.yaml

@ -11,17 +11,17 @@ CasaLegislativa:
- informacao_geral - informacao_geral
AppConfig: AppConfig:
{% trans 'Configurações Gerais' %}: {% trans 'Configurações Gerais' %}:
- esfera_federacao - esfera_federacao
- documentos_administrativos - documentos_administrativos
#{% trans 'Módulo Parlamentares' %}: #{% trans 'Módulo Parlamentares' %}:
#{% trans 'Módulo Mesa Diretora' %}: #{% trans 'Módulo Mesa Diretora' %}:
#{% trans 'Módulo Comissões' %}: #{% trans 'Módulo Comissões' %}:
#{% trans 'Módulo Bancadas Parlamentares' %}: #{% trans 'Módulo Bancadas Parlamentares' %}:
# {% trans 'Módulo Normas Jurídicas' %}: # {% trans 'Módulo Normas Jurídicas' %}:
@ -38,23 +38,26 @@ AppConfig:
- texto_articulado_proposicao texto_articulado_materia texto_articulado_norma - texto_articulado_proposicao texto_articulado_materia texto_articulado_norma
#{% trans 'Módulo Sessão Plenária' %}: #{% trans 'Módulo Sessão Plenária' %}:
#{% trans 'Módulo LexML' %}: #{% trans 'Módulo LexML' %}:
#{% trans 'Módulo Administrativo' %}: #{% trans 'Módulo Administrativo' %}:
{% trans 'Estatísticas de acesso' %}: {% trans 'Estatísticas de acesso' %}:
- estatisticas_acesso_normas - estatisticas_acesso_normas
{% trans 'Assinaturas' %}: {% trans 'Assinaturas' %}:
- assinatura_ata - assinatura_ata
{% trans 'Módulo Painel' %}: {% trans 'Módulo Painel' %}:
- cronometro_discurso cronometro_aparte - cronometro_discurso cronometro_aparte
- cronometro_ordem cronometro_consideracoes - cronometro_ordem cronometro_consideracoes
- mostrar_brasao_painel - mostrar_brasao_painel
{% trans 'Segurança' %}:
- google_recaptcha_site_key google_recaptcha_secret_key
TipoAutor: TipoAutor:
{% trans 'Tipo Autor' %}: {% trans 'Tipo Autor' %}:
- descricao - descricao

11
sapl/templates/base/login.html

@ -1,12 +1,12 @@
{% extends "crud/detail.html" %} {% extends "crud/detail.html" %}
{% load i18n %} {% load i18n %}
{% block base_content %} {% block base_content %}
<!-- O bloco comentado é para ser implementado após as autorizacoes --> <!-- O bloco comentado é para ser implementado após as autorizacoes -->
<!-- {% if next %} <!-- {% if next %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
<p>Você não tem acesso a esta página. Se quiser continuar, faça o Login.</p> <p>Você não tem acesso a esta página. Se quiser continuar, faça o Login.</p>
{% else %} {% else %}
<p>Por favor, faça o Login para acessar esta página.</p> <p>Por favor, faça o Login para acessar esta página.</p>
{% endif %} {% endif %}
@ -41,7 +41,12 @@
</tr> </tr>
</table> </table>
</p> </p>
<h5><a href="{% url 'sapl.base:recuperar_senha_email' %}"><center>Esqueceu sua senha?</center></a></h6> {% if google_recaptcha_configured %}
<h5><a href="{% url 'sapl.base:recuperar_senha_email' %}"><center>Esqueceu sua senha?</center></a></h6>
{% else %}
<br>
{% endif %}
<p class="bs-component"> <p class="bs-component">
<center> <center>
<input class="btn btn-lg btn-success btn-block" type="submit" value="login" /> <input class="btn btn-lg btn-success btn-block" type="submit" value="login" />

4
sapl/templates/base/recuperar_senha_email_form.html

@ -1 +1,5 @@
{% extends "crud/form.html" %} {% extends "crud/form.html" %}
{% block head_content %}{{block.super}}
<script src='https://www.google.com/recaptcha/api.js'></script>
{% endblock %}

8
sapl/templates/materia/acompanhamento_materia.html

@ -1,7 +1,13 @@
{% extends "crud/detail.html" %} {% extends "crud/detail.html" %}
{% load i18n %} {% load i18n %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
{% block actions %} {% endblock %}
{% block head_content %}{{block.super}}
<script src='https://www.google.com/recaptcha/api.js'></script>
{% endblock %}
{% block actions %}{% endblock %}
{% block detail_content %} {% block detail_content %}
<h1>Acompanhamento de Matéria</h1> <h1>Acompanhamento de Matéria</h1>

2
sapl/templates/materia/materialegislativa_detail.html

@ -3,7 +3,7 @@
{% block sub_actions %} {% block sub_actions %}
{{ block.super }} {{ block.super }}
{% if object.em_tramitacao and mail_service_configured %} {% if object.em_tramitacao and mail_service_configured and google_recaptcha_configured %}
<div class="actions btn-group btn-group-sm" role="group"> <div class="actions btn-group btn-group-sm" role="group">
<a href="{% url 'sapl.materia:acompanhar_materia' object.id %}" class="btn btn-outline-primary">Acompanhar Matéria</a> <a href="{% url 'sapl.materia:acompanhar_materia' object.id %}" class="btn btn-outline-primary">Acompanhar Matéria</a>
</div> </div>

2
sapl/templates/materia/materialegislativa_filter.html

@ -156,7 +156,7 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<p></p> <p></p>
{% if m.em_tramitacao and mail_service_configured %} {% if m.em_tramitacao and mail_service_configured and google_recaptcha_configured %}
<a href="{% url 'sapl.materia:acompanhar_materia' m.id %}">Acompanhar Matéria</a> <a href="{% url 'sapl.materia:acompanhar_materia' m.id %}">Acompanhar Matéria</a>
{% endif %} {% endif %}
{% endif %} {% endif %}

8
sapl/templates/protocoloadm/acompanhamento_documento.html

@ -1,7 +1,13 @@
{% extends "crud/detail.html" %} {% extends "crud/detail.html" %}
{% load i18n %} {% load i18n %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
{% block actions %} {% endblock %}
{% block head_content %}{{block.super}}
<script src='https://www.google.com/recaptcha/api.js'></script>
{% endblock %}
{% block actions %}{% endblock %}
{% block detail_content %} {% block detail_content %}
<h1>Acompanhamento de Documento</h1> <h1>Acompanhamento de Documento</h1>

2
sapl/templates/protocoloadm/documentoadministrativo_filter.html

@ -63,7 +63,7 @@
{% if d.texto_integral %} {% if d.texto_integral %}
<strong><a href="{{ d.texto_integral.url }}">Texto Integral</a></strong></br> <strong><a href="{{ d.texto_integral.url }}">Texto Integral</a></strong></br>
{% endif %} {% endif %}
{% if d.tramitacao and mail_service_configured %} {% if d.tramitacao and mail_service_configured and google_recaptcha_configured %}
<a href="{% url 'sapl.protocoloadm:acompanhar_documento' d.id %}">Acompanhar Documento</a> <a href="{% url 'sapl.protocoloadm:acompanhar_documento' d.id %}">Acompanhar Documento</a>
{% endif %} {% endif %}
</td> </td>

146
sapl/utils.py

@ -1,23 +1,18 @@
from itertools import groupby from functools import wraps
import django_filters
import hashlib import hashlib
from itertools import groupby
import logging import logging
import magic from operator import itemgetter
import os import os
import re
import unicodedata
import platform import platform
import re
import requests
import tempfile import tempfile
from crispy_forms.layout import Button, HTML
from easy_thumbnails import source_generators
from floppyforms import ClearableFileInput
from functools import wraps
from operator import itemgetter
from reversion_compare.admin import CompareVersionAdmin
from unicodedata import normalize as unicodedata_normalize from unicodedata import normalize as unicodedata_normalize
from unipath.path import Path import unicodedata
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Button, HTML, Fieldset, Div
from django import forms from django import forms
from django.apps import apps from django.apps import apps
from django.conf import settings from django.conf import settings
@ -35,6 +30,12 @@ from django.forms.widgets import SplitDateTimeWidget
from django.utils import six, timezone from django.utils import six, timezone
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
import django_filters
from easy_thumbnails import source_generators
from floppyforms import ClearableFileInput
import magic
from reversion_compare.admin import CompareVersionAdmin
from unipath.path import Path
from sapl.crispy_layout_mixin import (form_actions, SaplFormHelper, from sapl.crispy_layout_mixin import (form_actions, SaplFormHelper,
SaplFormLayout, to_row) SaplFormLayout, to_row)
@ -60,11 +61,11 @@ def num_materias_por_tipo(qs, attr_tipo='tipo'):
def sort_function(m): return m.tipo def sort_function(m): return m.tipo
else: else:
def sort_function(m): return m.materia.tipo def sort_function(m): return m.materia.tipo
# select_related eh importante por questoes de desempenho, pois caso # select_related eh importante por questoes de desempenho, pois caso
# contrario ele realizara uma consulta ao banco para cada iteracao, # contrario ele realizara uma consulta ao banco para cada iteracao,
# na key do groupby (uma alternativa é só usar tipo_id, na chave). # na key do groupby (uma alternativa é só usar tipo_id, na chave).
qs2 = qs.select_related(attr_tipo).order_by(attr_tipo+'_id') qs2 = qs.select_related(attr_tipo).order_by(attr_tipo + '_id')
for key, values in groupby(qs2, key=sort_function): for key, values in groupby(qs2, key=sort_function):
# poderia usar qtdes[key] = len(list(values)) aqui, mas # poderia usar qtdes[key] = len(list(values)) aqui, mas
@ -76,16 +77,16 @@ def num_materias_por_tipo(qs, attr_tipo='tipo'):
def validar_arquivo(arquivo, nome_campo): def validar_arquivo(arquivo, nome_campo):
if len(arquivo.name) > 200: if len(arquivo.name) > 200:
raise ValidationError( raise ValidationError(
"Certifique-se de que o nome do arquivo no " \ "Certifique-se de que o nome do arquivo no "
"campo '" + nome_campo + "' tenha no máximo 200 caracteres " \ "campo '" + nome_campo + "' tenha no máximo 200 caracteres "
"(ele possui {})".format(len(arquivo.name)) "(ele possui {})".format(len(arquivo.name))
) )
if arquivo.size > MAX_DOC_UPLOAD_SIZE: if arquivo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError( raise ValidationError(
"O arquivo " + nome_campo + " deve ser menor que " \ "O arquivo " + nome_campo + " deve ser menor que "
"{0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb".format( "{0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb".format(
(MAX_DOC_UPLOAD_SIZE/1024)/1024, (MAX_DOC_UPLOAD_SIZE / 1024) / 1024,
(arquivo.size/1024)/1024 (arquivo.size / 1024) / 1024
) )
) )
@ -96,18 +97,18 @@ def pil_image(source, exif_orientation=False, **options):
def dont_break_out(value, max_part=50): def dont_break_out(value, max_part=50):
_safe = value.split() _safe = value.split()
def chunkstring(string): def chunkstring(string):
return re.findall('.{%d}' % max_part, string) return re.findall('.{%d}' % max_part, string)
def __map(a): def __map(a):
if len(a) <= max_part: if len(a) <= max_part:
return a return a
return '<br>' + '<br>'.join(chunkstring(a)) return '<br>' + '<br>'.join(chunkstring(a))
_safe = map(__map, _safe) _safe = map(__map, _safe)
_safe = ' '.join(_safe) _safe = ' '.join(_safe)
_safe = mark_safe(_safe) _safe = mark_safe(_safe)
return value return value
@ -178,7 +179,9 @@ def montar_row_autor(name):
return autor_row return autor_row
#TODO: Esta função é utilizada? # TODO: Esta função é utilizada?
def montar_helper_autor(self): def montar_helper_autor(self):
autor_row = montar_row_autor('nome') autor_row = montar_row_autor('nome')
self.helper = SaplFormHelper() self.helper = SaplFormHelper()
@ -1008,14 +1011,21 @@ def mail_service_configured(request=None):
return settings.EMAIL_RUNNING return settings.EMAIL_RUNNING
def google_recaptcha_configured():
from sapl.base.models import AppConfig
return not AppConfig.attr('google_recaptcha_site_key') == ''
def lista_anexados(principal, isMateriaLegislativa=True): def lista_anexados(principal, isMateriaLegislativa=True):
anexados_total = [] anexados_total = []
if isMateriaLegislativa: #MateriaLegislativa if isMateriaLegislativa: # MateriaLegislativa
from sapl.materia.models import Anexada from sapl.materia.models import Anexada
anexados_iterator = Anexada.objects.filter(materia_principal=principal) anexados_iterator = Anexada.objects.filter(materia_principal=principal)
else: #DocAdm else: # DocAdm
from sapl.protocoloadm.models import Anexado from sapl.protocoloadm.models import Anexado
anexados_iterator = Anexado.objects.filter(documento_principal=principal) anexados_iterator = Anexado.objects.filter(
documento_principal=principal)
anexadas_temp = list(anexados_iterator) anexadas_temp = list(anexados_iterator)
@ -1024,12 +1034,14 @@ def lista_anexados(principal, isMateriaLegislativa=True):
if isMateriaLegislativa: if isMateriaLegislativa:
if anx.materia_anexada not in anexados_total: if anx.materia_anexada not in anexados_total:
anexados_total.append(anx.materia_anexada) anexados_total.append(anx.materia_anexada)
anexados_anexado = Anexada.objects.filter(materia_principal=anx.materia_anexada) anexados_anexado = Anexada.objects.filter(
materia_principal=anx.materia_anexada)
anexadas_temp.extend(anexados_anexado) anexadas_temp.extend(anexados_anexado)
else: else:
if anx.documento_anexado not in anexados_total: if anx.documento_anexado not in anexados_total:
anexados_total.append(anx.documento_anexado) anexados_total.append(anx.documento_anexado)
anexados_anexado = Anexado.objects.filter(documento_principal=anx.documento_anexado) anexados_anexado = Anexado.objects.filter(
documento_principal=anx.documento_anexado)
anexadas_temp.extend(anexados_anexado) anexadas_temp.extend(anexados_anexado)
if principal in anexados_total: if principal in anexados_total:
anexados_total.remove(principal) anexados_total.remove(principal)
@ -1058,10 +1070,84 @@ class OverwriteStorage(FileSystemStorage):
Muda o comportamento padrão do Django e o faz sobrescrever arquivos de Muda o comportamento padrão do Django e o faz sobrescrever arquivos de
mesmo nome que foram carregados pelo usuário ao invés de renomeá-los. mesmo nome que foram carregados pelo usuário ao invés de renomeá-los.
''' '''
def get_available_name(self, name, max_length=None): def get_available_name(self, name, max_length=None):
if self.exists(name): if self.exists(name):
os.remove(os.path.join(settings.MEDIA_ROOT, name)) os.remove(os.path.join(settings.MEDIA_ROOT, name))
return name return name
def get_tempfile_dir(): def get_tempfile_dir():
return '/tmp' if platform.system() == 'Darwin' else tempfile.gettempdir() return '/tmp' if platform.system() == 'Darwin' else tempfile.gettempdir()
class GoogleRecapthaMixin:
logger = logging.getLogger(__name__)
def __init__(self, *args, **kwargs):
from sapl.base.models import AppConfig
title_label = kwargs.pop('title_label')
action_label = kwargs.pop('action_label')
row1 = to_row(
[
(Div(
css_class="g-recaptcha float-right", # if not settings.DEBUG else '',
data_sitekey=AppConfig.attr('google_recaptcha_site_key')
), 5),
('email', 7),
]
)
self.helper = FormHelper()
self.helper.layout = SaplFormLayout(
Fieldset(
title_label,
row1
),
actions=form_actions(label=action_label)
)
super().__init__(*args, **kwargs)
def clean(self):
super().clean()
cd = self.cleaned_data
recaptcha = self.data.get('g-recaptcha-response', '')
if not recaptcha:
raise ValidationError(
_('Verificação do reCAPTCHA não efetuada.'))
from sapl.base.models import AppConfig
url = ('https://www.google.com/recaptcha/api/siteverify?'
'secret=%s'
'&response=%s' % (AppConfig.attr('google_recaptcha_secret_key'),
recaptcha))
try:
r = requests.post(url)
if r.ok:
jdata = r.json()
else:
raise ValidationError(
_('Ocorreu um erro na validação do reCAPTCHA.'))
except Exception as e:
logging.error(e)
raise ValidationError(
_('Ocorreu um erro na validação do reCAPTCHA.'))
if jdata['success']:
return cd
else:
raise ValidationError(
_('Ocorreu um erro na validação do reCAPTCHA.'))
return cd

Loading…
Cancel
Save