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. 86
      sapl/base/forms.py
  2. 23
      sapl/base/migrations/0043_auto_20210203_1442.py
  3. 20
      sapl/base/models.py
  4. 208
      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. 3
      sapl/templates/base/layouts.yaml
  12. 5
      sapl/templates/base/login.html
  13. 4
      sapl/templates/base/recuperar_senha_email_form.html
  14. 6
      sapl/templates/materia/acompanhamento_materia.html
  15. 2
      sapl/templates/materia/materialegislativa_detail.html
  16. 2
      sapl/templates/materia/materialegislativa_filter.html
  17. 6
      sapl/templates/protocoloadm/acompanhamento_documento.html
  18. 2
      sapl/templates/protocoloadm/documentoadministrativo_filter.html
  19. 124
      sapl/utils.py

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

20
sapl/base/models.py

@ -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,
) )

208
sapl/base/views.py

@ -1,10 +1,10 @@
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
@ -14,49 +14,54 @@ from django.contrib.auth.views import (PasswordResetView,PasswordResetConfirmVie
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,7 +378,8 @@ 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)
@ -471,11 +496,13 @@ class RelatorioPresencaSessaoView(RelatorioMixin, FilterView):
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,14 +524,16 @@ 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 = []
@ -517,29 +546,35 @@ class RelatorioPresencaSessaoView(RelatorioMixin, FilterView):
_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()
@ -852,7 +896,8 @@ class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView):
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
@ -1110,16 +1155,17 @@ class RelatorioNormasVigenciaView(RelatorioMixin, FilterView):
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 ''
@ -1189,7 +1238,8 @@ class EstatisticasAcessoNormas(TemplateView):
# 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
@ -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,6 +1372,7 @@ 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))
@ -1327,6 +1380,7 @@ def is_ciclo_unique(ciclo, ciclos_set):
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
@ -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(
@ -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(
@ -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(
'-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) 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()
] ]
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,
@ -1964,7 +2027,8 @@ class DeleteUsuarioView(PermissionRequiredMixin, DeleteView):
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:
@ -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,7 +2367,8 @@ 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)
@ -2309,7 +2378,6 @@ def pesquisa_textual(request):
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

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

3
sapl/templates/base/layouts.yaml

@ -55,6 +55,9 @@ AppConfig:
- 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

5
sapl/templates/base/login.html

@ -41,7 +41,12 @@
</tr> </tr>
</table> </table>
</p> </p>
{% if google_recaptcha_configured %}
<h5><a href="{% url 'sapl.base:recuperar_senha_email' %}"><center>Esqueceu sua senha?</center></a></h6> <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 %}

6
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 head_content %}{{block.super}}
<script src='https://www.google.com/recaptcha/api.js'></script>
{% endblock %}
{% block actions %}{% 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 %}

6
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 head_content %}{{block.super}}
<script src='https://www.google.com/recaptcha/api.js'></script>
{% endblock %}
{% block actions %}{% 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>

124
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)
@ -76,13 +77,13 @@ 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
@ -179,6 +180,8 @@ 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,6 +1011,12 @@ 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
@ -1015,7 +1024,8 @@ def lista_anexados(principal, isMateriaLegislativa=True):
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