import logging
import os
from crispy_forms.bootstrap import FieldWithButtons, InlineRadios, StrictButton, FormActions
from crispy_forms.layout import HTML, Button, Div, Field, Fieldset, Layout, Row, Submit
from django import forms
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import (AuthenticationForm, PasswordResetForm,
SetPasswordForm)
from django.contrib.auth.models import Group, User
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.db import models, transaction
from django.db.models import Q
from django.forms import Form, ModelForm, widgets
from django.utils import timezone
from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
import django_filters
from sapl.audiencia.models import AudienciaPublica
from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import Reuniao, Comissao
from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column,
to_row, SaplFormHelper)
from sapl.materia.models import (MateriaLegislativa, UnidadeTramitacao, StatusTramitacao,
DocumentoAcessorio, TipoMateriaLegislativa, MateriaEmTramitacao)
from sapl.norma.models import (NormaJuridica, NormaEstatisticas)
from sapl.protocoloadm.models import DocumentoAdministrativo
from sapl.parlamentares.models import SessaoLegislativa, Partido, HistoricoPartido
from sapl.sessao.models import SessaoPlenaria
from sapl.settings import MAX_IMAGE_UPLOAD_SIZE
from sapl.utils import (autor_label, autor_modal, ChoiceWithoutValidationField,
choice_anos_com_normas, choice_anos_com_materias,
FilterOverridesMetaMixin, FileFieldCheckMixin,
intervalos_tem_intersecao,
ImageThumbnailFileInput, models_with_gr_for_model,
qs_override_django_filter, RangeWidgetOverride,
RANGE_ANOS, YES_NO_CHOICES, AnoNumeroOrderingFilter)
from .models import AppConfig, CasaLegislativa, AutorUser
from operator import xor
ACTION_CREATE_USERS_AUTOR_CHOICE = [
('A', _('Associar um usuário existente')),
('N', _('Autor sem Usuário de Acesso ao Sapl')),
]
STATUS_USER_CHOICE = [
('R', _('Apenas retirar Perfil de Autor do Usuário que está sendo'
' desvinculado')),
('D', _('Retirar Perfil de Autor e desativar Usuário que está sendo'
' desvinculado')),
('X', _('Excluir Usuário')),
]
def get_roles():
roles = [(g.id, g.name) for g in Group.objects.all().order_by('name')
if g.name != 'Votante']
return roles
class UsuarioCreateForm(ModelForm):
logger = logging.getLogger(__name__)
firstname = forms.CharField(
required=True,
label="Nome",
max_length=30
)
lastname = forms.CharField(
required=True,
label="Sobrenome",
max_length=30
)
password1 = forms.CharField(
required=True,
widget=forms.PasswordInput,
label='Senha',
min_length=6,
max_length=128
)
password2 = forms.CharField(
required=True,
widget=forms.PasswordInput,
label='Confirmar senha',
min_length=6,
max_length=128
)
user_active = forms.ChoiceField(
required=True,
choices=YES_NO_CHOICES,
label="Usuário ativo?",
initial='True'
)
roles = forms.MultipleChoiceField(
required=True,
widget=forms.CheckboxSelectMultiple(),
choices=get_roles
)
class Meta:
model = get_user_model()
fields = [
get_user_model().USERNAME_FIELD, 'firstname', 'lastname',
'password1', 'password2', 'user_active', 'roles'
] + (['email']
if get_user_model().USERNAME_FIELD != 'email' else [])
def clean(self):
super().clean()
if not self.is_valid():
return self.cleaned_data
data = self.cleaned_data
if data['password1'] != data['password2']:
self.logger.error('Erro de validação. Senhas informadas ({}, {}) são diferentes.'.format(
data['password1'], data['password2']))
raise ValidationError('Senhas informadas são diferentes')
return data
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
row0 = to_row([('username', 12)])
row1 = to_row([('firstname', 6),
('lastname', 6)])
row2 = to_row([('email', 6),
('user_active', 6)])
row3 = to_row(
[('password1', 6),
('password2', 6)])
self.helper = SaplFormHelper()
self.helper.layout = Layout(
row0,
row1,
row3,
row2,
'roles',
form_actions(label='Confirmar'))
class UsuarioFilterSet(django_filters.FilterSet):
username = django_filters.CharFilter(
label=_('Nome de Usuário'),
lookup_expr='icontains')
class Meta:
model = User
fields = ['username']
def __init__(self, *args, **kwargs):
super(UsuarioFilterSet, self).__init__(*args, **kwargs)
row0 = to_row([('username', 12)])
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisa de Usuário'),
row0,
form_actions(label='Pesquisar'))
)
class UsuarioEditForm(ModelForm):
logger = logging.getLogger(__name__)
# ROLES = [(g.id, g.name) for g in Group.objects.all().order_by('name')]
ROLES = []
token = forms.CharField(
required=False,
label="Token",
max_length=40,
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
first_name = forms.CharField(
required=False,
label="Nome",
max_length=30)
last_name = forms.CharField(
required=False,
label="Sobrenome",
max_length=30)
password1 = forms.CharField(
required=False,
widget=forms.PasswordInput,
label='Senha')
password2 = forms.CharField(
required=False, widget=forms.PasswordInput,
label='Confirmar senha')
user_active = forms.ChoiceField(
choices=YES_NO_CHOICES,
required=True,
label="Usuário ativo?",
initial='True')
roles = forms.MultipleChoiceField(
required=True,
widget=forms.CheckboxSelectMultiple(),
choices=get_roles)
class Meta:
model = get_user_model()
fields = [
get_user_model().USERNAME_FIELD,
"token",
"first_name",
"last_name",
'password1',
'password2',
'user_active',
'roles']
if get_user_model().USERNAME_FIELD != 'email':
fields.extend(['email'])
def __init__(self, *args, **kwargs):
super(UsuarioEditForm, self).__init__(*args, **kwargs)
rows = to_row((
('first_name', 6),
('last_name', 6),
('email', 6),
('user_active', 6),
('password1', 6),
('password2', 6),
('roles', 12)))
self.helper = SaplFormHelper()
self.helper.layout = Layout(
'username',
FieldWithButtons('token', StrictButton('Renovar', id="renovar-token", css_class="btn-outline-primary")),
rows,
form_actions(
more=[
HTML("Cancelar")],
label='Salvar Alterações'))
def clean(self):
super().clean()
if not self.is_valid():
return self.cleaned_data
data = self.cleaned_data
if data['password1'] and data['password1'] != data['password2']:
self.logger.error("Erro de validação. Senhas informadas ({}, {}) são diferentes."
.format(data['password1'], data['password2']))
raise ValidationError('Senhas informadas são diferentes')
return data
class SessaoLegislativaForm(FileFieldCheckMixin, ModelForm):
logger = logging.getLogger(__name__)
class Meta:
model = SessaoLegislativa
exclude = []
def clean(self):
cleaned_data = super(SessaoLegislativaForm, self).clean()
if not self.is_valid():
return cleaned_data
flag_edit = True
data_inicio = cleaned_data['data_inicio']
data_fim = cleaned_data['data_fim']
legislatura = cleaned_data['legislatura']
numero = cleaned_data['numero']
data_inicio_leg = legislatura.data_inicio
data_fim_leg = legislatura.data_fim
pk = self.initial['id'] if self.initial else None
# Queries para verificar se existem Sessões Legislativas no período selecionado no form
# Caso onde a data_inicio e data_fim são iguais a de alguma sessão já
# criada
primeiro_caso = Q(data_inicio=data_inicio, data_fim=data_fim)
# Caso onde a data_inicio está entre o início e o fim de uma Sessão já
# existente
segundo_caso = Q(data_inicio__lt=data_inicio,
data_fim__range=(data_inicio, data_fim))
# Caso onde a data_fim está entre o início e o fim de uma Sessão já
# existente
terceiro_caso = Q(data_inicio__range=(
data_inicio, data_fim), data_fim__gt=data_fim)
sessoes_existentes = SessaoLegislativa.objects.filter(primeiro_caso | segundo_caso | terceiro_caso).\
exclude(pk=pk)
if sessoes_existentes:
raise ValidationError('Já existe registrado uma Sessão Legislativa que coincide com a data '
'inserida, favor verificar as Sessões existentes antes de criar uma '
'nova Sessão Legislativa')
#sessoes_legislativas = SessaoLegislativa.objects.filter(legislatura=legislatura).exclude(pk=pk)
# if sessoes_legislativas:
# numeracoes = [n.numero for n in sessoes_legislativas]
# numeracoes = sorted(numeracoes)
# ult = max(numeracoes)
#
# else:
# ult = SessaoLegislativa.objects.latest('data_fim')
# flag_edit = ult.id != pk
# ult = ult.numero
ult = 0
if numero <= ult and flag_edit:
self.logger.error('O número da SessaoLegislativa ({}) é menor ou igual '
'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 '
'que o de Sessões Legislativas passadas')
if data_inicio < data_inicio_leg or \
data_inicio > data_fim_leg:
self.logger.error('A data de início ({}) da SessaoLegislativa está compreendida '
'fora da data início ({}) e fim ({}) da Legislatura '
'selecionada'.format(data_inicio, data_inicio_leg, data_fim_leg))
raise ValidationError('A data de início da Sessão Legislativa deve estar compreendida '
'entre a data início e fim da Legislatura selecionada')
if data_fim > data_fim_leg or \
data_fim < data_inicio_leg:
self.logger.error('A data de fim ({}) da SessaoLegislativa está compreendida '
'fora da data início ({}) e fim ({}) da Legislatura '
'selecionada.'.format(data_fim, data_inicio_leg, data_fim_leg))
raise ValidationError('A data de fim da Sessão Legislativa deve estar compreendida '
'entre a data início e fim da Legislatura selecionada')
if data_inicio > data_fim:
self.logger.error(
'Data início ({}) superior à data fim ({}).'.format(data_inicio, data_fim))
raise ValidationError(
'Data início não pode ser superior à data fim')
data_inicio_intervalo = cleaned_data['data_inicio_intervalo']
data_fim_intervalo = cleaned_data['data_fim_intervalo']
if data_inicio_intervalo and data_fim_intervalo and \
data_inicio_intervalo > data_fim_intervalo:
self.logger.error('Data início de intervalo ({}) superior à '
'data fim de intervalo ({}).'.format(data_inicio_intervalo, data_fim_intervalo))
raise ValidationError('Data início de intervalo não pode ser '
'superior à data fim de intervalo')
if data_inicio_intervalo:
if data_inicio_intervalo < data_inicio or \
data_inicio_intervalo < data_inicio_leg or \
data_inicio_intervalo > data_fim or \
data_inicio_intervalo > data_fim_leg:
self.logger.error('A data de início do intervalo ({}) não está compreendida entre '
'as datas de início ({}) e fim ({}) tanto da Legislatura quanto da '
'própria Sessão Legislativa ({} e {}).'
.format(data_inicio_intervalo, data_inicio_leg, data_fim_leg, data_inicio, data_fim))
raise ValidationError('A data de início do intervalo deve estar compreendida entre '
'as datas de início e fim tanto da Legislatura quanto da '
'própria Sessão Legislativa')
if data_fim_intervalo:
if data_fim_intervalo > data_fim or \
data_fim_intervalo > data_fim_leg or \
data_fim_intervalo < data_inicio or \
data_fim_intervalo < data_inicio_leg:
self.logger.error('A data de fim do intervalo ({}) não está compreendida entre '
'as datas de início ({}) e fim ({}) tanto da Legislatura quanto da '
'própria Sessão Legislativa ({} e {}).'
.format(data_fim_intervalo, data_inicio_leg, data_fim_leg, data_inicio, data_fim))
raise ValidationError('A data de fim do intervalo deve estar compreendida entre '
'as datas de início e fim tanto da Legislatura quanto da '
'própria Sessão Legislativa')
return cleaned_data
class TipoAutorForm(ModelForm):
class Meta:
model = TipoAutor
fields = ['descricao']
def __init__(self, *args, **kwargs):
super(TipoAutorForm, self).__init__(*args, **kwargs)
def clean(self):
super(TipoAutorForm, self).clean()
if not self.is_valid():
return self.cleaned_data
cd = self.cleaned_data
lista = ['comissão',
'comis',
'parlamentar',
'bancada',
'bloco',
'comissao',
'vereador',
'órgão',
'orgao',
'deputado',
'senador',
'vereadora',
'frente']
for l in lista:
if l in cd['descricao'].lower():
raise ValidationError(_('A descrição colocada não pode ser usada '
'por ser equivalente a um tipo já existente'))
class AutorForm(ModelForm):
logger = logging.getLogger(__name__)
senha = forms.CharField(
max_length=20,
label=_('Senha'),
required=False,
widget=forms.PasswordInput())
senha_confirma = forms.CharField(
max_length=20,
label=_('Confirmar Senha'),
required=False,
widget=forms.PasswordInput())
email = forms.EmailField(
required=False,
label=_('Email'))
confirma_email = forms.EmailField(
required=False,
label=_('Confirmar Email'))
q = forms.CharField(
max_length=50, required=False,
label='Pesquise o nome do Autor com o '
'tipo Selecionado e marque o escolhido.')
autor_related = ChoiceWithoutValidationField(label='',
required=False,
widget=forms.RadioSelect())
class Meta:
model = Autor
fields = ['tipo',
'nome',
'cargo',
'autor_related',
'q',
]
def __init__(self, *args, **kwargs):
autor_related = Div(
FieldWithButtons(
Field('q',
placeholder=_('Pesquisar por possíveis autores para '
'o Tipo de Autor selecionado.')),
StrictButton(
_('Filtrar'), css_class='btn-outline-primary btn-filtrar-autor',
type='button')),
css_class='hidden',
data_action='create',
data_application='AutorSearch',
data_field='autor_related')
autor_select = Row(to_column(('tipo', 3)),
Div(to_column(('nome', 7)),
to_column(('cargo', 5)),
css_class="div_nome_cargo row col"),
to_column((autor_related, 9)),
to_column((Div(
Field('autor_related'),
css_class='radiogroup-autor-related hidden'),
12)))
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(autor_select)
super().__init__(*args, **kwargs)
if self.instance.pk:
if self.instance.autor_related:
self.fields['autor_related'].choices = [
(self.instance.autor_related.pk,
self.instance.autor_related)]
self.fields['q'].initial = ''
self.fields['autor_related'].initial = self.instance.autor_related
def valida_igualdade(self, texto1, texto2, msg):
if texto1 != texto2:
self.logger.error(
'Textos diferentes. ("{}" e "{}")'.format(texto1, texto2))
raise ValidationError(msg)
return True
def clean(self):
super().clean()
if not self.is_valid():
return self.cleaned_data
cd = self.cleaned_data
qs_autor = Autor.objects.all()
if self.instance.pk:
qs_autor = qs_autor.exclude(pk=self.instance.pk)
if self.instance.user:
qs_user = qs_user.exclude(pk=self.instance.user.pk)
if cd['action_user'] == 'A':
param_username = {get_user_model().USERNAME_FIELD: cd['username']}
if not User.objects.filter(**param_username).exists():
self.logger.error(
'Não existe usuário com username "%s". ' % cd['username'])
raise ValidationError(
_('Não existe usuário com username "%s". '
'Para utilizar esse username você deve selecionar '
'"Criar novo Usuário".') % cd['username'])
if cd['action_user'] != 'N':
if 'username' not in cd or not cd['username']:
self.logger.error('Username não informado.')
raise ValidationError(_('O username deve ser informado.'))
param_username = {
'user__' + get_user_model().USERNAME_FIELD: cd['username']}
autor_vinculado = qs_autor.filter(**param_username)
if autor_vinculado.exists():
nome = autor_vinculado[0].nome
error_msg = 'Já existe um autor para este ' \
'usuário ({}): {}'.format(cd['username'], nome)
self.logger.error(error_msg)
raise ValidationError(_(error_msg))
"""
'if' não é necessário por ser campo obrigatório e o framework já
mostrar a mensagem de obrigatório junto ao campo. mas foi colocado
ainda assim para renderizar um message.danger no topo do form.
"""
if 'tipo' not in cd or not cd['tipo']:
self.logger.error('Tipo do Autor não selecionado.')
raise ValidationError(
_('O Tipo do Autor deve ser selecionado.'))
tipo = cd['tipo']
if not tipo.content_type:
if 'nome' not in cd or not cd['nome']:
self.logger.error('Nome do Autor não informado.')
raise ValidationError(
_('O Nome do Autor deve ser informado.'))
elif qs_autor.filter(nome=cd['nome']).exists():
raise ValidationError("Autor '%s' já existente!" % cd['nome'])
else:
if 'autor_related' not in cd or not cd['autor_related']:
self.logger.error('Registro de %s não escolhido para ser '
'vinculado ao cadastro de Autor' % tipo.descricao)
raise ValidationError(
_('Um registro de %s deve ser escolhido para ser '
'vinculado ao cadastro de Autor') % tipo.descricao)
if not tipo.content_type.model_class().objects.filter(
pk=cd['autor_related']).exists():
self.logger.error('O Registro definido (%s-%s) não está na base '
'de %s.' % (cd['autor_related'], cd['q'], tipo.descricao))
raise ValidationError(
_('O Registro definido (%s-%s) não está na base de %s.'
) % (cd['autor_related'], cd['q'], tipo.descricao))
qs_autor_selected = qs_autor.filter(
object_id=cd['autor_related'],
content_type_id=cd['tipo'].content_type_id)
if qs_autor_selected.exists():
autor = qs_autor_selected.first()
self.logger.error('Já existe um autor Cadastrado para '
'%s' % autor.autor_related)
raise ValidationError(
_('Já existe um autor Cadastrado para %s'
) % autor.autor_related)
return self.cleaned_data
@transaction.atomic
def save(self, commit=False):
autor = super().save(commit)
if not autor.tipo.content_type:
autor.content_type = None
autor.object_id = None
autor.autor_related = None
else:
autor.autor_related = autor.tipo.content_type.model_class(
).objects.get(pk=self.cleaned_data['autor_related'])
autor.nome = str(autor.autor_related)
autor.save()
return autor
class AutorFormForAdmin(AutorForm):
status_user = forms.ChoiceField(
label=_('Bloqueio do Usuário Existente'),
choices=STATUS_USER_CHOICE,
widget=forms.RadioSelect(),
required=False,
help_text=_('Se vc está trocando ou removendo o usuário deste Autor, '
'como o Sistema deve proceder com o usuário que está sendo'
' desvinculado?'))
class Meta:
model = Autor
fields = ['tipo',
'nome',
'cargo',
'autor_related',
'q',
]
class RelatorioDocumentosAcessoriosFilterSet(django_filters.FilterSet):
@property
def qs(self):
parent = super(RelatorioDocumentosAcessoriosFilterSet, self).qs
return parent.distinct().order_by('-data')
class Meta(FilterOverridesMetaMixin):
model = DocumentoAcessorio
fields = ['tipo', 'materia__tipo', 'data']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.filters['tipo'].label = 'Tipo de Documento'
self.filters['materia__tipo'].label = 'Tipo de Matéria do Documento'
self.filters['data'].label = 'Período (Data Inicial - Data Final)'
self.form.fields['tipo'].required = True
row0 = to_row([('tipo', 6),
('materia__tipo', 6)])
row1 = to_row([('data', 12)])
buttons = FormActions(
*[
HTML('''