Browse Source

Impl múltiplos autores e refatora admin de usuários (#3371)

* add model OperadorAutor

* refatora autor e impl form e view para operadores

* refatora AutorForm para seleção de usuários

* remove OperadorAutorCrud

* restrige usuários na lista de operadores

* ajusta qs p/ lista de usuários em autor

* add listview com pesquisa para autores

* remove pesquisa independente de autores

* ajustes no filtro de autores e display de operadores

* refatora restrições de usuário em proposição

* corrige exclusão de vinculo de usuário votante

* impl edição de votante e autor para usuário

* corrige run python em migration

* organia imports em sapl/base/forms.py

* add restrição de user ativo na lista de user de autor

* corrige erro apontado por test_str_sanity

* ajusta teste de criação de proposição

* ajusta erro apontado em rules/test_rules
pull/3378/head
Leandro Roberto Silva 4 years ago
committed by GitHub
parent
commit
abc3e3f21c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      sapl/api/views.py
  2. 426
      sapl/base/forms.py
  3. 62
      sapl/base/migrations/0046_auto_20210314_1532.py
  4. 19
      sapl/base/migrations/0047_auto_20210315_1522.py
  5. 45
      sapl/base/models.py
  6. 4
      sapl/base/urls.py
  7. 279
      sapl/base/views.py
  8. 400
      sapl/materia/tests/test_materia.py
  9. 38
      sapl/materia/views.py
  10. 9
      sapl/parlamentares/forms.py
  11. 43
      sapl/parlamentares/migrations/0035_auto_20210315_1522.py
  12. 4
      sapl/parlamentares/models.py
  13. 90
      sapl/parlamentares/views.py
  14. 6
      sapl/rules/__init__.py
  15. 10
      sapl/rules/apps.py
  16. 9
      sapl/rules/map_rules.py
  17. 14
      sapl/templates/base/autor_detail.html
  18. 51
      sapl/templates/base/autor_filter.html
  19. 46
      sapl/templates/base/autor_form.html
  20. 7
      sapl/templates/base/layouts.yaml
  21. 34
      sapl/templates/bootstrap4/layout/checkboxselectmultiple.html
  22. 6
      sapl/templates/materia/proposicao_detail.html
  23. 9
      sapl/templates/menu_tabelas_auxiliares.yaml
  24. 27
      sapl/utils.py

12
sapl/api/views.py

@ -524,7 +524,17 @@ class _ProposicaoViewSet:
q = Q(data_recebimento__isnull=False, object_id__isnull=False) q = Q(data_recebimento__isnull=False, object_id__isnull=False)
if not self.request.user.is_anonymous: if not self.request.user.is_anonymous:
q |= Q(autor__user=self.request.user)
autor_do_usuario_logado = self.request.user.autor_set.first()
# se usuário logado é operador de algum autor
if autor_do_usuario_logado:
q = Q(autor=autor_do_usuario_logado)
# se é operador de protocolo, ve qualquer coisa enviada
if self.request.user.has_perm('protocoloadm.list_protocolo'):
q = Q(data_envio__isnull=False) | Q(
data_devolucao__isnull=False)
qs = qs.filter(q) qs = qs.filter(q)
return qs return qs

426
sapl/base/forms.py

@ -1,7 +1,6 @@
import logging import logging
import os import os
from crispy_forms.bootstrap import FieldWithButtons, InlineRadios, StrictButton, FormActions from crispy_forms.bootstrap import FieldWithButtons, InlineRadios, StrictButton, FormActions
from crispy_forms.layout import HTML, Button, Div, Field, Fieldset, Layout, Row, Submit from crispy_forms.layout import HTML, Button, Div, Field, Fieldset, Layout, Row, Submit
from django import forms from django import forms
@ -15,11 +14,12 @@ from django.db import models, transaction
from django.db.models import Q from django.db.models import Q
from django.forms import Form, ModelForm from django.forms import Form, ModelForm
from django.utils import timezone from django.utils import timezone
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
from sapl.audiencia.models import AudienciaPublica from sapl.audiencia.models import AudienciaPublica
from sapl.base.models import Autor, TipoAutor from sapl.base.models import Autor, TipoAutor, OperadorAutor
from sapl.comissoes.models import Reuniao from sapl.comissoes.models import Reuniao
from sapl.crispy_layout_mixin import (form_actions, to_column, to_row, from sapl.crispy_layout_mixin import (form_actions, to_column, to_row,
SaplFormHelper, SaplFormLayout) SaplFormHelper, SaplFormLayout)
@ -27,17 +27,18 @@ from sapl.materia.models import (DocumentoAcessorio, MateriaEmTramitacao,
MateriaLegislativa, UnidadeTramitacao, MateriaLegislativa, UnidadeTramitacao,
StatusTramitacao) StatusTramitacao)
from sapl.norma.models import NormaJuridica from sapl.norma.models import NormaJuridica
from sapl.parlamentares.models import Partido, SessaoLegislativa from sapl.parlamentares.models import Partido, SessaoLegislativa,\
Parlamentar, Votante
from sapl.protocoloadm.models import DocumentoAdministrativo from sapl.protocoloadm.models import DocumentoAdministrativo
from sapl.rules import SAPL_GROUP_AUTOR, SAPL_GROUP_VOTANTE
from sapl.sessao.models import SessaoPlenaria from sapl.sessao.models import SessaoPlenaria
from sapl.settings import MAX_IMAGE_UPLOAD_SIZE from sapl.settings import MAX_IMAGE_UPLOAD_SIZE
from sapl.utils import (autor_label, autor_modal, ChoiceWithoutValidationField, from sapl.utils import (autor_label, autor_modal, ChoiceWithoutValidationField,
choice_anos_com_normas, choice_anos_com_materias, choice_anos_com_normas, choice_anos_com_materias,
FilterOverridesMetaMixin, FileFieldCheckMixin, FilterOverridesMetaMixin, FileFieldCheckMixin,
AnoNumeroOrderingFilter, ImageThumbnailFileInput, ImageThumbnailFileInput, qs_override_django_filter,
models_with_gr_for_model, qs_override_django_filter, RANGE_ANOS, YES_NO_CHOICES,
RangeWidgetOverride, RANGE_ANOS, YES_NO_CHOICES, GoogleRecapthaMixin, parlamentares_ativos)
GoogleRecapthaMixin)
from .models import AppConfig, CasaLegislativa from .models import AppConfig, CasaLegislativa
@ -85,6 +86,18 @@ class UserAdminForm(ModelForm):
max_length=40, max_length=40,
widget=forms.TextInput(attrs={'readonly': 'readonly'})) widget=forms.TextInput(attrs={'readonly': 'readonly'}))
parlamentar = forms.ModelChoiceField(
label=_('Este usuário é um Parlamentar Votante?'),
queryset=Parlamentar.objects.all(),
required=False,
help_text='Se o usuário que está sendo cadastrado (ou em edição) é um usuário para que um parlamentar possa votar, você pode selecionar o parlamentar nas opções acima.')
autor = forms.ModelChoiceField(
label=_('Este usuário registrará proposições para um Autor?'),
queryset=Autor.objects.all(),
required=False,
help_text='Se o usuário que está sendo cadastrado (ou em edição) é um usuário para cadastro de proposições, você pode selecionar para que autor ele registrará proposições.')
class Meta: class Meta:
model = get_user_model() model = get_user_model()
fields = [ fields = [
@ -97,8 +110,11 @@ class UserAdminForm(ModelForm):
'new_password1', 'new_password1',
'new_password2', 'new_password2',
'groups',
'parlamentar',
'autor',
'groups',
'user_permissions', 'user_permissions',
] ]
@ -111,15 +127,18 @@ class UserAdminForm(ModelForm):
self.granular = kwargs.pop('granular', None) self.granular = kwargs.pop('granular', None)
self.instance = kwargs.get('instance', None) self.instance = kwargs.get('instance', None)
row_pwd = to_row( row_pwd = [
[ ('username', 4),
('username', 4), ('email', 6),
('email', 6), ('is_active', 2),
('is_active', 2), ('first_name', 6),
('first_name', 6), ('last_name', 6),
('last_name', 6), ('new_password1', 3 if self.instance and self.instance.pk else 6),
('new_password1', 3 if self.instance and self.instance.pk else 6), ('new_password2', 3 if self.instance and self.instance.pk else 6),
('new_password2', 3 if self.instance and self.instance.pk else 6), ]
if self.instance and self.instance.pk:
row_pwd += [
( (
FieldWithButtons( FieldWithButtons(
'token', 'token',
@ -129,13 +148,18 @@ class UserAdminForm(ModelForm):
css_class="btn-outline-primary"), css_class="btn-outline-primary"),
css_class='' if self.instance and self.instance.pk else 'd-none'), css_class='' if self.instance and self.instance.pk else 'd-none'),
6 6
), )
]
('groups', 12), row_pwd += [
] + ([('user_permissions', 12)] if not self.granular is None else []) ('parlamentar', 6),
('autor', 6),
('groups', 12),
) ] + ([('user_permissions', 12)] if not self.granular is None else [])
row_pwd = to_row(row_pwd)
self.helper = SaplFormHelper() self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(row_pwd) self.helper.layout = SaplFormLayout(row_pwd)
@ -143,19 +167,33 @@ class UserAdminForm(ModelForm):
self.fields['groups'].widget = forms.CheckboxSelectMultiple() self.fields['groups'].widget = forms.CheckboxSelectMultiple()
self.fields['parlamentar'].choices = [('', '---------')] + [
(p.id, p) for p in parlamentares_ativos(timezone.now())
]
if not self.instance.pk: if not self.instance.pk:
self.fields['groups'].choices = [ self.fields['groups'].choices = [
(g.id, g) for g in Group.objects.exclude(name__in=['Autor', 'Votante']).order_by('name') (g.id, g) for g in Group.objects.exclude(
name__in=['Autor', 'Votante']
).order_by('name')
] ]
else: else:
operadorautor = self.instance.operadorautor_set.first()
votante = self.instance.votante_set.first()
self.fields['token'].initial = self.instance.auth_token.key self.fields['token'].initial = self.instance.auth_token.key
self.fields['autor'].initial = operadorautor.autor if operadorautor else None
self.fields['parlamentar'].initial = votante.parlamentar if votante else None
self.fields['groups'].choices = [ self.fields['groups'].choices = [
(g.id, g) for g in self.instance.groups.exclude(name__in=['Autor', 'Votante']).order_by('name') (g.id, g) for g in self.instance.groups.exclude(
name__in=['Autor', 'Votante']
).order_by('name')
] + [ ] + [
(g.id, g) for g in Group.objects.exclude( (g.id, g) for g in Group.objects.exclude(
user=self.instance).exclude(name__in=['Autor', 'Votante']).order_by('name') user=self.instance).exclude(
name__in=['Autor', 'Votante']
).order_by('name')
] ]
self.fields[ self.fields[
@ -178,35 +216,61 @@ class UserAdminForm(ModelForm):
'codename') 'codename')
] ]
# self.fields['user_permissions'].queryset = self.fields[
# 'user_permissions'].queryset.all().order_by('name')
def save(self, commit=True): def save(self, commit=True):
if self.cleaned_data['new_password1']: if self.cleaned_data['new_password1']:
self.instance.set_password(self.cleaned_data['new_password1']) self.instance.set_password(self.cleaned_data['new_password1'])
votante = None
permissions = None permissions = None
votante = None
operadorautor = None
if self.instance.id: if self.instance.id:
inst_old = get_user_model().objects.get(pk=self.instance.pk) inst_old = get_user_model().objects.get(pk=self.instance.pk)
votante = inst_old.groups.filter(name='Votante').first()
autor = inst_old.groups.filter(name='Autor').first()
if self.granular is None: if self.granular is None:
permissions = list(inst_old.user_permissions.all()) permissions = list(inst_old.user_permissions.all())
inst_new = super().save(commit) votante = inst_old.votante_set.first()
operadorautor = inst_old.operadorautor_set.first()
if votante: inst = super().save(commit)
inst_new.groups.add(votante)
if autor:
inst_new.groups.add(autor)
if permissions: if permissions:
inst_new.user_permissions.add(*permissions) inst.user_permissions.add(*permissions)
g_autor = Group.objects.get(name=SAPL_GROUP_AUTOR)
g_votante = Group.objects.get(name=SAPL_GROUP_VOTANTE)
return inst_new if not self.cleaned_data['autor']:
inst.groups.remove(g_autor)
if operadorautor:
operadorautor.delete()
else:
inst.groups.add(g_autor)
if operadorautor:
if operadorautor.autor != self.cleaned_data['autor']:
operadorautor.autor = self.cleaned_data['autor']
operadorautor.save()
else:
operadorautor = OperadorAutor()
operadorautor.user = inst
operadorautor.autor = self.cleaned_data['autor']
operadorautor.save()
if not self.cleaned_data['parlamentar']:
inst.groups.remove(g_votante)
if votante:
votante.delete()
else:
inst.groups.add(g_votante)
if votante:
if votante.parlamentar != self.cleaned_data['parlamentar']:
votante.parlamentar = self.cleaned_data['parlamentar']
votante.save()
else:
votante = Votante()
votante.user = inst
votante.parlamentar = self.cleaned_data['parlamentar']
votante.save()
return inst
def clean(self): def clean(self):
data = super().clean() data = super().clean()
@ -226,6 +290,29 @@ class UserAdminForm(ModelForm):
password_validation.validate_password( password_validation.validate_password(
new_password2, self.instance) new_password2, self.instance)
parlamentar = data.get('parlamentar', None)
if parlamentar and parlamentar.votante_set.exists() and \
parlamentar.votante_set.first().user != self.instance:
raise forms.ValidationError(
mark_safe(
'O Parlamentar <strong>{}</strong> '
'já está associado a outro usuário: <strong>{}</strong>.<br>'
'Para realizar nova associação, você precisa '
'primeiro cancelar esta já existente.'.format(
parlamentar,
parlamentar.votante_set.first().user
))
)
autor = data.get('autor', None)
if parlamentar and autor:
if autor.autor_related != parlamentar:
raise forms.ValidationError(
'Um usuário não deve ser Votante de um parlamentar, e operador de um Autor que possui um parlamentar diferente, ou mesmo outro tipo de Autor.'
)
"""
if 'email' in data and data['email']: if 'email' in data and data['email']:
duplicidade = get_user_model().objects.filter(email=data['email']) duplicidade = get_user_model().objects.filter(email=data['email'])
if self.instance.id: if self.instance.id:
@ -236,7 +323,7 @@ class UserAdminForm(ModelForm):
"Email já cadastrado para: {}".format( "Email já cadastrado para: {}".format(
', '.join(map(lambda x: str(x), duplicidade.all())), ', '.join(map(lambda x: str(x), duplicidade.all())),
) )
) )"""
return data return data
@ -445,11 +532,6 @@ class AutorForm(ModelForm):
required=False, required=False,
label=_('Confirmar Email')) label=_('Confirmar Email'))
username = forms.CharField(label=get_user_model()._meta.get_field(
get_user_model().USERNAME_FIELD).verbose_name.capitalize(),
required=False,
max_length=50)
q = forms.CharField( q = forms.CharField(
max_length=120, required=False, max_length=120, required=False,
label='Pesquise o nome do Autor com o ' label='Pesquise o nome do Autor com o '
@ -459,10 +541,14 @@ class AutorForm(ModelForm):
required=False, required=False,
widget=forms.RadioSelect()) widget=forms.RadioSelect())
action_user = forms.ChoiceField( operadores = forms.ModelMultipleChoiceField(
label=_('Usuário com acesso ao Sistema para este Autor'), queryset=get_user_model().objects.all(),
choices=ACTION_CREATE_USERS_AUTOR_CHOICE, widget=forms.CheckboxSelectMultiple(),
widget=forms.RadioSelect()) label=_('Usuários do SAPL ligados ao autor acima selecionado'),
required=False,
help_text=_(
'Para ser listado aqui, o usuário não pode estar em nenhum outro autor e deve estar marcado como ativo.')
)
class Meta: class Meta:
model = Autor model = Autor
@ -471,8 +557,8 @@ class AutorForm(ModelForm):
'cargo', 'cargo',
'autor_related', 'autor_related',
'q', 'q',
'action_user', 'operadores'
'username'] ]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -498,34 +584,41 @@ class AutorForm(ModelForm):
Field('autor_related'), Field('autor_related'),
css_class='radiogroup-autor-related hidden'), css_class='radiogroup-autor-related hidden'),
12))) 12)))
operadores_select = to_row(
row2 = Row(to_column((InlineRadios('action_user'), 8)), [
to_column((Div('username'), 4))) ('operadores', 12)
]
row3 = Row(to_column(('senha', 3)), )
to_column(('senha_confirma', 3)),
to_column(('email', 3)),
to_column(('confirma_email', 3)),
css_class='new_user_fields hidden')
row4 = Row(to_column((
Div(InlineRadios('status_user'),
css_class='radiogroup-status hidden'),
12))) if 'status_user' in self.Meta.fields else None
controle_acesso = [row2, row3]
if row4:
controle_acesso.append(row4)
controle_acesso = Fieldset(_('Controle de Acesso do Autor'),
*controle_acesso)
self.helper = SaplFormHelper() self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(autor_select, controle_acesso) self.helper.layout = SaplFormLayout(autor_select, *operadores_select)
super(AutorForm, self).__init__(*args, **kwargs) super(AutorForm, self).__init__(*args, **kwargs)
self.fields['action_user'].initial = 'N' self.fields['operadores'].choices = [
(
u.id,
u.username,
u
)
for u in get_user_model().objects.filter(
operadorautor_set__autor=self.instance
).order_by('-is_active',
get_user_model().USERNAME_FIELD
) if self.instance.id
] + [
(
u.id,
u.username,
u
)
for u in get_user_model().objects.filter(
operadorautor_set__isnull=True,
is_active=True
).order_by('-is_active',
get_user_model().USERNAME_FIELD
)
]
if self.instance.pk: if self.instance.pk:
if self.instance.autor_related: if self.instance.autor_related:
@ -537,35 +630,6 @@ class AutorForm(ModelForm):
self.fields['autor_related'].initial = self.instance.autor_related self.fields['autor_related'].initial = self.instance.autor_related
if self.instance.user:
self.fields['username'].initial = getattr(
self.instance.user,
get_user_model().USERNAME_FIELD)
self.fields['action_user'].initial = 'A'
self.fields['username'].label = "{} ({})".format(self.fields['username'].label,
getattr(self.instance.user,
get_user_model().USERNAME_FIELD))
if 'status_user' in self.Meta.fields:
self.fields['status_user'].initial = 'R'
self.fields['status_user'].label = "{} ({})".format(self.fields['status_user'].label,
getattr(self.instance.user,
get_user_model().USERNAME_FIELD))
self.fields['username'].widget.attrs.update({
'data': getattr(
self.instance.user,
get_user_model().USERNAME_FIELD)
if self.instance.user else ''})
if 'status_user' in self.Meta.fields:
self.fields['status_user'].widget.attrs.update({
'data': getattr(
self.instance.user,
get_user_model().USERNAME_FIELD)
if self.instance.user else ''})
def valida_igualdade(self, texto1, texto2, msg): def valida_igualdade(self, texto1, texto2, msg):
if texto1 != texto2: if texto1 != texto2:
self.logger.warning( self.logger.warning(
@ -580,76 +644,13 @@ class AutorForm(ModelForm):
if not self.is_valid(): if not self.is_valid():
return self.cleaned_data return self.cleaned_data
User = get_user_model()
cd = self.cleaned_data cd = self.cleaned_data
if 'action_user' not in cd or not cd['action_user']:
self.logger.warning(
'Não Informado se o Autor terá usuário '
'vinculado para acesso ao Sistema.'
)
raise ValidationError(_('Informe se o Autor terá usuário '
'vinculado para acesso ao Sistema.'))
if 'status_user' in self.Meta.fields:
if self.instance.pk and self.instance.user_id:
if getattr(
self.instance.user,
get_user_model().USERNAME_FIELD) != cd['username']:
if 'status_user' not in cd or not cd['status_user']:
self.logger.warning(
'Foi trocado ou removido o usuário deste Autor ({}), '
'mas não foi informado como se deve proceder '
'com o usuário que está sendo desvinculado? ({})'.format(
cd['username'], get_user_model().USERNAME_FIELD
)
)
raise ValidationError(
_('Foi trocado ou removido o usuário deste Autor, '
'mas não foi informado como se deve proceder '
'com o usuário que está sendo desvinculado?'))
qs_user = User.objects.all()
qs_autor = Autor.objects.all() qs_autor = Autor.objects.all()
if self.instance.pk: if self.instance.pk:
qs_autor = qs_autor.exclude(pk=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.warning(
'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.warning('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.warning(error_msg)
raise ValidationError(_(error_msg))
"""
'if' não é necessário por ser campo obrigatório e o framework
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']: if 'tipo' not in cd or not cd['tipo']:
self.logger.warning('Tipo do Autor não selecionado.') self.logger.warning('Tipo do Autor não selecionado.')
raise ValidationError( raise ValidationError(
@ -699,21 +700,8 @@ class AutorForm(ModelForm):
return self.cleaned_data return self.cleaned_data
@transaction.atomic @transaction.atomic
def save(self, commit=False): def save(self, commit=True):
autor = super(AutorForm, self).save(commit) autor = self.instance
user_old = autor.user if autor.user_id else None
u = None
param_username = {
get_user_model().USERNAME_FIELD: self.cleaned_data['username']}
if self.cleaned_data['action_user'] == 'A':
u = get_user_model().objects.get(**param_username)
if not u.is_active:
u.is_active = settings.DEBUG
u.save()
autor.user = u
if not autor.tipo.content_type: if not autor.tipo.content_type:
autor.content_type = None autor.content_type = None
@ -724,33 +712,7 @@ class AutorForm(ModelForm):
).objects.get(pk=self.cleaned_data['autor_related']) ).objects.get(pk=self.cleaned_data['autor_related'])
autor.nome = str(autor.autor_related) autor.nome = str(autor.autor_related)
autor.save() autor = super(AutorForm, self).save(commit)
# FIXME melhorar captura de grupo de Autor, levando em conta,
# no mínimo, a tradução.
grupo = Group.objects.filter(name='Autor')[0]
if self.cleaned_data['action_user'] != 'N':
autor.user.groups.add(grupo)
if user_old and user_old != autor.user:
user_old.groups.remove(grupo)
else:
if 'status_user' in self.Meta.fields:
if 'status_user' in self.cleaned_data and user_old:
if self.cleaned_data['status_user'] == 'X':
user_old.delete()
elif self.cleaned_data['status_user'] == 'D':
user_old.groups.remove(grupo)
user_old.is_active = False
user_old.save()
elif self.cleaned_data['status_user'] == 'R':
user_old.groups.remove(grupo)
elif user_old:
user_old.groups.remove(grupo)
elif user_old:
user_old.groups.remove(grupo)
return autor return autor
@ -776,26 +738,36 @@ class AutorFilterSet(django_filters.FilterSet):
form_actions(label='Pesquisar'))) form_actions(label='Pesquisar')))
class AutorFormForAdmin(AutorForm): class OperadorAutorForm(ModelForm):
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: class Meta:
model = Autor model = OperadorAutor
fields = ['tipo', fields = ['user', ]
'nome',
'cargo', def __init__(self, *args, **kwargs):
'autor_related',
'q', row = to_row([('user', 12)])
'action_user',
'username', self.helper = SaplFormHelper()
'status_user'] self.helper.layout = SaplFormLayout(
Fieldset(_('Operador'), row))
super(OperadorAutorForm, self).__init__(*args, **kwargs)
self.fields['user'].choices = [
(
u.id,
'{} - {} - {}'.format(
u.get_full_name(),
getattr(u, u.USERNAME_FIELD),
u.email
)
)
for u in get_user_model().objects.all().order_by(
get_user_model().USERNAME_FIELD
)
]
self.fields['user'].widget = forms.RadioSelect()
class RelatorioDocumentosAcessoriosFilterSet(django_filters.FilterSet): class RelatorioDocumentosAcessoriosFilterSet(django_filters.FilterSet):

62
sapl/base/migrations/0046_auto_20210314_1532.py

@ -0,0 +1,62 @@
# Generated by Django 2.2.13 on 2021-03-14 18:32
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
def adjust_user_oto_m2m(apps, schema_editor):
Autor = apps.get_model('base', 'Autor')
autores = Autor.objects.all()
for a in autores:
if a.user:
a.operadores.add(a.user)
def adjust_remove_grupo_parlamentar(apps, schema_editor):
Group = apps.get_model('auth', 'Group')
Group.objects.filter(name='Parlamentar').delete()
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('base', '0045_auto_20210301_1537'),
]
operations = [
migrations.CreateModel(
name='OperadorAutor',
fields=[
('id', models.AutoField(auto_created=True,
primary_key=True, serialize=False, verbose_name='ID')),
('autor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='operadorautor_set', to='base.Autor', verbose_name='Autor')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='operadorautor_set', to=settings.AUTH_USER_MODEL, verbose_name='Operador do Autor')),
],
options={
'verbose_name': 'Operador do Autor',
'verbose_name_plural': 'Operadores do Autor',
'unique_together': {('user', 'autor')},
},
),
migrations.AddField(
model_name='autor',
name='operadores',
field=models.ManyToManyField(
related_name='autor_set', through='base.OperadorAutor', to=settings.AUTH_USER_MODEL),
),
migrations.RunPython(adjust_user_oto_m2m),
migrations.RunPython(adjust_remove_grupo_parlamentar),
migrations.RemoveField(
model_name='autor',
name='user',
),
]

19
sapl/base/migrations/0047_auto_20210315_1522.py

@ -0,0 +1,19 @@
# Generated by Django 2.2.13 on 2021-03-15 18:22
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0046_auto_20210314_1532'),
]
operations = [
migrations.AlterField(
model_name='autor',
name='operadores',
field=models.ManyToManyField(related_name='autor_set', through='base.OperadorAutor', to=settings.AUTH_USER_MODEL, verbose_name='Operadores'),
),
]

45
sapl/base/models.py

@ -1,6 +1,7 @@
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.db.models.deletion import CASCADE
from django.db.models.signals import post_migrate from django.db.models.signals import post_migrate
from django.db.utils import DEFAULT_DB_ALIAS from django.db.utils import DEFAULT_DB_ALIAS
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -9,6 +10,7 @@ import reversion
from sapl.utils import (LISTA_DE_UFS, YES_NO_CHOICES, from sapl.utils import (LISTA_DE_UFS, YES_NO_CHOICES,
get_settings_auth_user_model, models_with_gr_for_model) get_settings_auth_user_model, models_with_gr_for_model)
DOC_ADM_OSTENSIVO = 'O' DOC_ADM_OSTENSIVO = 'O'
DOC_ADM_RESTRITIVO = 'R' DOC_ADM_RESTRITIVO = 'R'
@ -256,10 +258,14 @@ class TipoAutor(models.Model):
@reversion.register() @reversion.register()
class Autor(models.Model): class Autor(models.Model):
user = models.OneToOneField( operadores = models.ManyToManyField(
get_settings_auth_user_model(), get_settings_auth_user_model(),
on_delete=models.SET_NULL, through='OperadorAutor',
null=True) through_fields=('autor', 'user'),
symmetrical=False,
related_name='autor_set',
verbose_name='Operadores')
tipo = models.ForeignKey( tipo = models.ForeignKey(
TipoAutor, TipoAutor,
verbose_name=_('Tipo do Autor'), verbose_name=_('Tipo do Autor'),
@ -298,11 +304,40 @@ class Autor(models.Model):
return '{} - {}'.format(self.nome, self.cargo) return '{} - {}'.format(self.nome, self.cargo)
else: else:
return str(self.nome) return str(self.nome)
if self.user:
return str(self.user.username)
return '?' return '?'
class OperadorAutor(models.Model):
user = models.ForeignKey(
get_settings_auth_user_model(),
verbose_name=_('Operador do Autor'),
related_name='operadorautor_set',
on_delete=CASCADE)
autor = models.ForeignKey(
Autor,
related_name='operadorautor_set',
verbose_name=_('Autor'),
on_delete=CASCADE)
@property
def user_name(self):
return '%s - %s' % (
self.autor,
self.user)
class Meta:
verbose_name = _('Operador do Autor')
verbose_name_plural = _('Operadores do Autor')
unique_together = (
('user', 'autor', ),)
def __str__(self):
return self.user_name
class AuditLog(models.Model): class AuditLog(models.Model):
operation = ('C', 'D', 'U') operation = ('C', 'D', 'U')

4
sapl/base/urls.py

@ -7,7 +7,7 @@ from django.contrib.auth.decorators import permission_required
from django.views.generic.base import RedirectView, TemplateView from django.views.generic.base import RedirectView, TemplateView
from sapl.base.views import (AutorCrud, ConfirmarEmailView, TipoAutorCrud, get_estatistica, from sapl.base.views import (AutorCrud, ConfirmarEmailView, TipoAutorCrud, get_estatistica,
PesquisarAutorView, RecuperarSenhaEmailView, RecuperarSenhaFinalizadoView, RecuperarSenhaEmailView, RecuperarSenhaFinalizadoView,
RecuperarSenhaConfirmaView, RecuperarSenhaCompletoView, RelatorioMateriaAnoAssuntoView, RecuperarSenhaConfirmaView, RecuperarSenhaCompletoView, RelatorioMateriaAnoAssuntoView,
IndexView, UserCrud) IndexView, UserCrud)
from sapl.settings import MEDIA_URL, LOGOUT_REDIRECT_URL from sapl.settings import MEDIA_URL, LOGOUT_REDIRECT_URL
@ -59,8 +59,6 @@ urlpatterns = [
url(r'^sistema/autor/tipo/', include(TipoAutorCrud.get_urls())), url(r'^sistema/autor/tipo/', include(TipoAutorCrud.get_urls())),
url(r'^sistema/autor/', include(AutorCrud.get_urls())), url(r'^sistema/autor/', include(AutorCrud.get_urls())),
url(r'^sistema/autor/pesquisar-autor/',
PesquisarAutorView.as_view(), name='pesquisar_autor'),
url(r'^sistema/ajuda/(?P<topic>\w+)$', url(r'^sistema/ajuda/(?P<topic>\w+)$',
HelpTopicView.as_view(), name='help_topic'), HelpTopicView.as_view(), name='help_topic'),

279
sapl/base/views.py

@ -8,16 +8,17 @@ import os
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
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.db import connection from django.db import connection
from django.db.models import Count, Q, ProtectedError, Max, F from django.db.models import Count, Q, Max, F
from django.forms.utils import ErrorList
from django.http import Http404, HttpResponseRedirect, JsonResponse from django.http import Http404, HttpResponseRedirect, JsonResponse
from django.shortcuts import render, redirect from django.shortcuts import 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.urls import reverse, reverse_lazy
@ -25,22 +26,21 @@ 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 ( from django.views.generic import (FormView, ListView)
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.query import SearchQuerySet from haystack.query import SearchQuerySet
from haystack.views import SearchView from haystack.views import SearchView
from rest_framework.authtoken.models import Token
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.forms import (AutorForm, AutorFormForAdmin, TipoAutorForm, AutorFilterSet, RecuperarSenhaForm, from sapl.base.forms import (AutorForm, TipoAutorForm, AutorFilterSet, RecuperarSenhaForm,
NovaSenhaForm, UserAdminForm) NovaSenhaForm, UserAdminForm,
from sapl.base.models import Autor, TipoAutor OperadorAutorForm)
from sapl.base.models import Autor, TipoAutor, OperadorAutor
from sapl.comissoes.models import Comissao, Reuniao from sapl.comissoes.models import Comissao, Reuniao
from sapl.crud.base import CrudAux, make_pagination, Crud,\ from sapl.crud.base import CrudAux, make_pagination, Crud,\
ListWithSearchForm ListWithSearchForm, MasterDetailCrud
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,
MateriaAssunto) MateriaAssunto)
@ -61,7 +61,8 @@ from sapl.sessao.models import (
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, sapl_as_sapn) google_recaptcha_configured, sapl_as_sapn,
groups_remove_user, groups_add_user)
from .forms import (AlterarSenhaForm, CasaLegislativaForm, ConfiguracoesAppForm, RelatorioAtasFilterSet, from .forms import (AlterarSenhaForm, CasaLegislativaForm, ConfiguracoesAppForm, RelatorioAtasFilterSet,
RelatorioAudienciaFilterSet, RelatorioDataFimPrazoTramitacaoFilterSet, RelatorioAudienciaFilterSet, RelatorioDataFimPrazoTramitacaoFilterSet,
@ -193,203 +194,133 @@ class AutorCrud(CrudAux):
help_topic = 'autor' help_topic = 'autor'
class BaseMixin(CrudAux.BaseMixin): class BaseMixin(CrudAux.BaseMixin):
list_field_names = ['tipo', 'nome', 'user'] list_field_names = ['nome', 'tipo', 'operadores']
class DeleteView(CrudAux.DeleteView): def send_mail_operadores(self):
def delete(self, *args, **kwargs):
self.object = self.get_object()
if self.object.user:
# FIXME melhorar captura de grupo de Autor, levando em conta
# trad
grupo = Group.objects.filter(name='Autor')[0]
self.object.user.groups.remove(grupo)
return CrudAux.DeleteView.delete(self, *args, **kwargs)
class UpdateView(CrudAux.UpdateView):
logger = logging.getLogger(__name__)
layout_key = None
form_class = AutorForm
def form_valid(self, form):
# devido a implement do form o form_valid do Crud deve ser pulado
return super(CrudAux.UpdateView, self).form_valid(form)
def post(self, request, *args, **kwargs):
if request.user.is_superuser:
self.form_class = AutorFormForAdmin
return CrudAux.UpdateView.post(self, request, *args, **kwargs)
def get(self, request, *args, **kwargs):
if request.user.is_superuser:
self.form_class = AutorFormForAdmin
return CrudAux.UpdateView.get(self, request, *args, **kwargs)
def get_success_url(self):
username = self.request.user.username
pk_autor = self.object.id
url_reverse = reverse('sapl.base:autor_detail',
kwargs={'pk': pk_autor})
if not mail_service_configured():
self.logger.warning(_('Registro de Autor sem envio de email. '
'Servidor de email não configurado.'))
return url_reverse
try:
self.logger.debug('user={}. Enviando email na edição '
'de Autores.'.format(username))
kwargs = {}
user = self.object.user
if not user:
return url_reverse
kwargs['token'] = default_token_generator.make_token(user)
kwargs['uidb64'] = urlsafe_base64_encode(force_bytes(user.pk))
assunto = "SAPL - Confirmação de Conta"
full_url = self.request.get_raw_uri()
url_base = full_url[:full_url.find('sistema') - 1]
mensagem = (
"Este e-mail foi utilizado para fazer cadastro no " +
"SAPL com o perfil de Autor. Agora você pode " +
"criar/editar/enviar Proposições.\n" +
"Seu nome de usuário é: " +
self.request.POST['username'] + "\n"
"Caso você não tenha feito este cadastro, por favor " +
"ignore esta mensagem. Caso tenha, clique " +
"no link abaixo\n" + url_base +
reverse('sapl.base:confirmar_email', kwargs=kwargs))
remetente = settings.EMAIL_SEND_USER
destinatario = [user.email]
send_mail(assunto, mensagem, remetente, destinatario,
fail_silently=False)
except Exception as e:
self.logger.error('user={}. Erro no envio de email na edição de'
' Autores. {}'.format(username, str(e)))
return url_reverse
class CreateView(CrudAux.CreateView):
logger = logging.getLogger(__name__)
form_class = AutorForm
layout_key = None
def post(self, request, *args, **kwargs):
if request.user.is_superuser:
self.form_class = AutorFormForAdmin
return CrudAux.CreateView.post(self, request, *args, **kwargs)
def get(self, request, *args, **kwargs):
if request.user.is_superuser:
self.form_class = AutorFormForAdmin
return CrudAux.CreateView.get(self, request, *args, **kwargs)
def get_success_url(self):
username = self.request.user.username username = self.request.user.username
pk_autor = self.object.id
url_reverse = reverse('sapl.base:autor_detail',
kwargs={'pk': pk_autor})
if not mail_service_configured(): if not mail_service_configured():
self.logger.warning(_('Registro de Autor sem envio de email. ' self.logger.warning(_('Registro de Autor sem envio de email. '
'Servidor de email não configurado.')) 'Servidor de email não configurado.'))
return url_reverse return
try: try:
self.logger.debug('user=' + username + self.logger.debug('user=' + username +
'. Enviando email na criação de Autores.') '. Enviando email na criação de Autores.')
kwargs = {} kwargs = {}
user = self.object.user
for user in self.object.operadores.all():
if not user:
return url_reverse if not user.email:
self.logger.warning(
kwargs['token'] = default_token_generator.make_token(user) _('Registro de Autor sem envio de email. '
kwargs['uidb64'] = urlsafe_base64_encode(force_bytes(user.pk)) 'Usuário sem um email cadastrado.'))
assunto = "SAPL - Confirmação de Conta" continue
full_url = self.request.get_raw_uri()
url_base = full_url[:full_url.find('sistema') - 1] kwargs['token'] = default_token_generator.make_token(user)
kwargs['uidb64'] = urlsafe_base64_encode(
mensagem = ( force_bytes(user.pk))
"Este e-mail foi utilizado para fazer cadastro no " + assunto = "SAPL - Confirmação de Conta"
"SAPL com o perfil de Autor. Agora você pode " + full_url = self.request.get_raw_uri()
"criar/editar/enviar Proposições.\n" + url_base = full_url[:full_url.find('sistema') - 1]
"Seu nome de usuário é: " +
self.request.POST['username'] + "\n" mensagem = (
"Caso você não tenha feito este cadastro, por favor " + "Este e-mail foi utilizado para fazer cadastro no " +
"ignore esta mensagem. Caso tenha, clique " + "SAPL com o perfil de Autor. Agora você pode " +
"no link abaixo\n" + url_base + "criar/editar/enviar Proposições.\n" +
reverse('sapl.base:confirmar_email', kwargs=kwargs)) "Seu nome de usuário é: " +
remetente = settings.EMAIL_SEND_USER self.request.POST['username'] + "\n"
destinatario = [user.email] "Caso você não tenha feito este cadastro, por favor " +
send_mail(assunto, mensagem, remetente, destinatario, "ignore esta mensagem. Caso tenha, clique " +
fail_silently=False) "no link abaixo\n" + url_base +
reverse('sapl.base:confirmar_email', kwargs=kwargs))
remetente = settings.EMAIL_SEND_USER
destinatario = [user.email]
send_mail(assunto, mensagem, remetente, destinatario,
fail_silently=False)
except Exception as e: except Exception as e:
print( print(
_('Erro no envio de email na criação de Autores.')) _('Erro no envio de email na criação de Autores.'))
self.logger.error( self.logger.error(
'user=' + username + '. Erro no envio de email na criação de Autores. ' + str(e)) 'user=' + username + '. Erro no envio de email na criação de Autores. ' + str(e))
return url_reverse class DeleteView(CrudAux.DeleteView):
class PesquisarAutorView(FilterView): def delete(self, *args, **kwargs):
model = Autor self.object = self.get_object()
filterset_class = AutorFilterSet
paginate_by = 10
def get_filterset_kwargs(self, filterset_class): grupo = Group.objects.filter(name='Autor')[0]
super().get_filterset_kwargs(filterset_class) lista_operadores = list(self.object.operadores.all())
kwargs = {'data': self.request.GET or None} response = CrudAux.DeleteView.delete(self, *args, **kwargs)
qs = self.get_queryset().order_by('nome').distinct() if not Autor.objects.filter(pk=kwargs['pk']).exists():
for u in lista_operadores:
u.groups.remove(grupo)
kwargs.update({ return response
'queryset': qs,
})
return kwargs
def get_context_data(self, **kwargs): class DetailView(CrudAux.DetailView):
context = super().get_context_data(**kwargs)
paginator = context['paginator'] def hook_operadores(self, obj):
page_obj = context['page_obj'] r = '<ul>{}</ul>'.format(
''.join(
[
'<li>{} - <i>({})</i> - '
'<small>{}</small>'
'</li>'.format(u.first_name, u, u.email)
for u in obj.operadores.all()
]
)
context['page_range'] = make_pagination( )
page_obj.number, paginator.num_pages) return 'Operadores', r
context['NO_ENTRIES_MSG'] = 'Nenhum Autor encontrado!' class UpdateView(CrudAux.UpdateView):
logger = logging.getLogger(__name__)
layout_key = None
form_class = AutorForm
context['title'] = _('Autores') def get_success_url(self):
self.send_mail_operadores()
return super().get_success_url()
return context class CreateView(CrudAux.CreateView):
logger = logging.getLogger(__name__)
form_class = AutorForm
layout_key = None
def get(self, request, *args, **kwargs): def get_success_url(self):
super().get(request) self.send_mail_operadores()
return super().get_success_url()
data = self.filterset.data class ListView(CrudAux.ListView):
url = '' form_search_class = ListWithSearchForm
if data:
url = "&" + str(self.request.META['QUERY_STRING'])
if url.startswith("&page"):
ponto_comeco = url.find('nome=') - 1
url = url[ponto_comeco:]
context = self.get_context_data(filter=self.filterset, def hook_operadores(self, *args, **kwargs):
object_list=self.object_list, r = '<ul>{}</ul>'.format(
filter_url=url, ''.join(
numero_res=len(self.object_list)) [
'<li>{} - <i>({})</i></li>'.format(u.first_name, u)
for u in args[0].operadores.all()
]
)
context['show_results'] = show_results_filter_set( )
self.request.GET.copy()) return r, ''
return self.render_to_response(context) def get_queryset(self):
qs = self.model.objects.all()
q_param = self.request.GET.get('q', '')
if q_param:
q = Q(nome__icontains=q_param)
q |= Q(cargo__icontains=q_param)
q |= Q(tipo__descricao__icontains=q_param)
q |= Q(operadores__username__icontains=q_param)
q |= Q(operadores__email__icontains=q_param)
qs = qs.filter(q)
return qs.distinct('nome', 'id').order_by('nome', 'id')
class RelatoriosListView(TemplateView): class RelatoriosListView(TemplateView):

400
sapl/materia/tests/test_materia.py

@ -1,22 +1,23 @@
from datetime import date from datetime import date
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.files.uploadedfile import SimpleUploadedFile from django.core.files.uploadedfile import SimpleUploadedFile
from django.urls import reverse
from django.db.models import Max from django.db.models import Max
from django.urls import reverse
from model_bakery import baker from model_bakery import baker
import pytest import pytest
from sapl.base.models import Autor, TipoAutor, AppConfig from sapl.base.models import Autor, TipoAutor, AppConfig
from sapl.comissoes.models import Comissao, TipoComissao from sapl.comissoes.models import Comissao, TipoComissao
from sapl.materia.forms import (TramitacaoForm, compara_tramitacoes_mat,
TramitacaoUpdateForm)
from sapl.materia.models import (Anexada, Autoria, DespachoInicial, from sapl.materia.models import (Anexada, Autoria, DespachoInicial,
DocumentoAcessorio, MateriaLegislativa, DocumentoAcessorio, MateriaLegislativa,
Numeracao, Proposicao, RegimeTramitacao, Numeracao, Proposicao, RegimeTramitacao,
StatusTramitacao, TipoDocumento, StatusTramitacao, TipoDocumento,
TipoMateriaLegislativa, TipoProposicao, TipoMateriaLegislativa, TipoProposicao,
Tramitacao, UnidadeTramitacao) Tramitacao, UnidadeTramitacao)
from sapl.materia.forms import (TramitacaoForm, compara_tramitacoes_mat,
TramitacaoUpdateForm)
from sapl.norma.models import (LegislacaoCitada, NormaJuridica, from sapl.norma.models import (LegislacaoCitada, NormaJuridica,
TipoNormaJuridica) TipoNormaJuridica)
from sapl.parlamentares.models import Legislatura from sapl.parlamentares.models import Legislatura
@ -25,57 +26,57 @@ from sapl.utils import models_with_gr_for_model, lista_anexados
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_lista_materias_anexadas(): def test_lista_materias_anexadas():
tipo_materia = baker.make( tipo_materia = baker.make(
TipoMateriaLegislativa, TipoMateriaLegislativa,
descricao="Tipo_Teste" descricao="Tipo_Teste"
) )
regime_tramitacao = baker.make( regime_tramitacao = baker.make(
RegimeTramitacao, RegimeTramitacao,
descricao="Regime_Teste" descricao="Regime_Teste"
) )
materia_principal = baker.make( materia_principal = baker.make(
MateriaLegislativa, MateriaLegislativa,
numero=20, numero=20,
ano=2018, ano=2018,
data_apresentacao="2018-01-04", data_apresentacao="2018-01-04",
regime_tramitacao=regime_tramitacao, regime_tramitacao=regime_tramitacao,
tipo=tipo_materia tipo=tipo_materia
) )
materia_anexada = baker.make( materia_anexada = baker.make(
MateriaLegislativa, MateriaLegislativa,
numero=21, numero=21,
ano=2019, ano=2019,
data_apresentacao="2019-05-04", data_apresentacao="2019-05-04",
regime_tramitacao=regime_tramitacao, regime_tramitacao=regime_tramitacao,
tipo=tipo_materia tipo=tipo_materia
) )
materia_anexada_anexada = baker.make( materia_anexada_anexada = baker.make(
MateriaLegislativa, MateriaLegislativa,
numero=22, numero=22,
ano=2020, ano=2020,
data_apresentacao="2020-01-05", data_apresentacao="2020-01-05",
regime_tramitacao=regime_tramitacao, regime_tramitacao=regime_tramitacao,
tipo=tipo_materia tipo=tipo_materia
) )
baker.make( baker.make(
Anexada, Anexada,
materia_principal=materia_principal, materia_principal=materia_principal,
materia_anexada=materia_anexada, materia_anexada=materia_anexada,
data_anexacao="2019-05-11" data_anexacao="2019-05-11"
) )
baker.make( baker.make(
Anexada, Anexada,
materia_principal=materia_anexada, materia_principal=materia_anexada,
materia_anexada=materia_anexada_anexada, materia_anexada=materia_anexada_anexada,
data_anexacao="2020-11-05" data_anexacao="2020-11-05"
) )
lista = lista_anexados(materia_principal) lista = lista_anexados(materia_principal)
assert len(lista) == 2 assert len(lista) == 2
assert lista[0] == materia_anexada assert lista[0] == materia_anexada
assert lista[1] == materia_anexada_anexada assert lista[1] == materia_anexada_anexada
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
@ -123,6 +124,7 @@ def test_lista_materias_anexadas_ciclo():
assert len(lista) == 1 assert len(lista) == 1
assert lista[0] == materia_anexada assert lista[0] == materia_anexada
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def make_unidade_tramitacao(descricao): def make_unidade_tramitacao(descricao):
# Cria uma comissão para ser a unidade de tramitação # Cria uma comissão para ser a unidade de tramitação
@ -551,9 +553,9 @@ def test_proposicao_submit(admin_client):
autor = baker.make( autor = baker.make(
Autor, Autor,
user=user,
tipo=tipo_autor, tipo=tipo_autor,
nome='Autor Teste') nome='Autor Teste')
autor.operadores.add(user)
file_content = 'file_content' file_content = 'file_content'
texto = SimpleUploadedFile("file.txt", file_content.encode('UTF-8')) texto = SimpleUploadedFile("file.txt", file_content.encode('UTF-8'))
@ -597,9 +599,9 @@ def test_form_errors_proposicao(admin_client):
autor = baker.make( autor = baker.make(
Autor, Autor,
user=user,
tipo=tipo_autor, tipo=tipo_autor,
nome='Autor Teste') nome='Autor Teste')
autor.operadores.add(user)
file_content = 'file_content' file_content = 'file_content'
texto = SimpleUploadedFile("file.txt", file_content.encode('UTF-8')) texto = SimpleUploadedFile("file.txt", file_content.encode('UTF-8'))
@ -695,42 +697,41 @@ def test_tramitacoes_materias_anexadas(admin_client):
config = baker.make(AppConfig, tramitacao_materia=True) config = baker.make(AppConfig, tramitacao_materia=True)
tipo_materia = baker.make( tipo_materia = baker.make(
TipoMateriaLegislativa, TipoMateriaLegislativa,
descricao="Tipo_Teste" descricao="Tipo_Teste"
) )
materia_principal = baker.make( materia_principal = baker.make(
MateriaLegislativa, MateriaLegislativa,
ano=2018, ano=2018,
data_apresentacao="2018-01-04", data_apresentacao="2018-01-04",
tipo=tipo_materia tipo=tipo_materia
) )
materia_anexada = baker.make( materia_anexada = baker.make(
MateriaLegislativa, MateriaLegislativa,
ano=2019, ano=2019,
data_apresentacao="2019-05-04", data_apresentacao="2019-05-04",
tipo=tipo_materia tipo=tipo_materia
) )
materia_anexada_anexada = baker.make( materia_anexada_anexada = baker.make(
MateriaLegislativa, MateriaLegislativa,
ano=2020, ano=2020,
data_apresentacao="2020-01-05", data_apresentacao="2020-01-05",
tipo=tipo_materia tipo=tipo_materia
) )
baker.make( baker.make(
Anexada, Anexada,
materia_principal=materia_principal, materia_principal=materia_principal,
materia_anexada=materia_anexada, materia_anexada=materia_anexada,
data_anexacao="2019-05-11" data_anexacao="2019-05-11"
) )
baker.make( baker.make(
Anexada, Anexada,
materia_principal=materia_anexada, materia_principal=materia_anexada,
materia_anexada=materia_anexada_anexada, materia_anexada=materia_anexada_anexada,
data_anexacao="2020-11-05" data_anexacao="2020-11-05"
) )
unidade_tramitacao_local_1 = make_unidade_tramitacao(descricao="Teste 1") unidade_tramitacao_local_1 = make_unidade_tramitacao(descricao="Teste 1")
unidade_tramitacao_destino_1 = make_unidade_tramitacao(descricao="Teste 2") unidade_tramitacao_destino_1 = make_unidade_tramitacao(descricao="Teste 2")
unidade_tramitacao_destino_2 = make_unidade_tramitacao(descricao="Teste 3") unidade_tramitacao_destino_2 = make_unidade_tramitacao(descricao="Teste 3")
@ -741,131 +742,146 @@ def test_tramitacoes_materias_anexadas(admin_client):
# Teste criação de Tramitacao # Teste criação de Tramitacao
form = TramitacaoForm(data={}) form = TramitacaoForm(data={})
form.data = {'data_tramitacao':date(2019, 5, 6), form.data = {'data_tramitacao': date(2019, 5, 6),
'unidade_tramitacao_local':unidade_tramitacao_local_1.pk, 'unidade_tramitacao_local': unidade_tramitacao_local_1.pk,
'unidade_tramitacao_destino':unidade_tramitacao_destino_1.pk, 'unidade_tramitacao_destino': unidade_tramitacao_destino_1.pk,
'status':status.pk, 'status': status.pk,
'urgente': False, 'urgente': False,
'texto': "Texto de teste"} 'texto': "Texto de teste"}
form.instance.materia_id=materia_principal.pk form.instance.materia_id = materia_principal.pk
assert form.is_valid() assert form.is_valid()
tramitacao_principal = form.save() tramitacao_principal = form.save()
tramitacao_anexada = materia_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first() tramitacao_anexada = materia_anexada.tramitacao_set.order_by(
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first() '-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by(
# Verifica se foram criadas as tramitações para as matérias anexadas e anexadas às anexadas '-data_tramitacao', '-id').first()
assert materia_principal.tramitacao_set.order_by('-data_tramitacao', '-id').first() == tramitacao_principal
assert tramitacao_principal.materia.em_tramitacao == (tramitacao_principal.status.indicador != "F") # Verifica se foram criadas as tramitações para as matérias anexadas e
# anexadas às anexadas
assert materia_principal.tramitacao_set.order_by(
'-data_tramitacao', '-id').first() == tramitacao_principal
assert tramitacao_principal.materia.em_tramitacao == (
tramitacao_principal.status.indicador != "F")
assert compara_tramitacoes_mat(tramitacao_principal, tramitacao_anexada) assert compara_tramitacoes_mat(tramitacao_principal, tramitacao_anexada)
assert MateriaLegislativa.objects.get(id=materia_anexada.pk).em_tramitacao \ assert MateriaLegislativa.objects.get(id=materia_anexada.pk).em_tramitacao \
== (tramitacao_anexada.status.indicador != "F") == (tramitacao_anexada.status.indicador != "F")
assert compara_tramitacoes_mat(tramitacao_anexada_anexada, tramitacao_principal) assert compara_tramitacoes_mat(
tramitacao_anexada_anexada, tramitacao_principal)
assert MateriaLegislativa.objects.get(id=materia_anexada_anexada.pk).em_tramitacao \ assert MateriaLegislativa.objects.get(id=materia_anexada_anexada.pk).em_tramitacao \
== (tramitacao_anexada_anexada.status.indicador != "F") == (tramitacao_anexada_anexada.status.indicador != "F")
# Teste Edição de Tramitacao # Teste Edição de Tramitacao
form = TramitacaoUpdateForm(data={}) form = TramitacaoUpdateForm(data={})
# Alterando unidade_tramitacao_destino # Alterando unidade_tramitacao_destino
form.data = {'data_tramitacao':tramitacao_principal.data_tramitacao, form.data = {'data_tramitacao': tramitacao_principal.data_tramitacao,
'unidade_tramitacao_local':tramitacao_principal.unidade_tramitacao_local.pk, 'unidade_tramitacao_local': tramitacao_principal.unidade_tramitacao_local.pk,
'unidade_tramitacao_destino':unidade_tramitacao_destino_2.pk, 'unidade_tramitacao_destino': unidade_tramitacao_destino_2.pk,
'status':tramitacao_principal.status.pk, 'status': tramitacao_principal.status.pk,
'urgente': tramitacao_principal.urgente, 'urgente': tramitacao_principal.urgente,
'texto': tramitacao_principal.texto} 'texto': tramitacao_principal.texto}
form.instance = tramitacao_principal form.instance = tramitacao_principal
assert form.is_valid() assert form.is_valid()
tramitacao_principal = form.save() tramitacao_principal = form.save()
tramitacao_anexada = materia_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first() tramitacao_anexada = materia_anexada.tramitacao_set.order_by(
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first() '-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by(
'-data_tramitacao', '-id').first()
assert tramitacao_principal.unidade_tramitacao_destino == unidade_tramitacao_destino_2 assert tramitacao_principal.unidade_tramitacao_destino == unidade_tramitacao_destino_2
assert tramitacao_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2 assert tramitacao_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2
assert tramitacao_anexada_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2 assert tramitacao_anexada_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2
# Teste Remoção de Tramitacao # Teste Remoção de Tramitacao
url = reverse('sapl.materia:tramitacao_delete', url = reverse('sapl.materia:tramitacao_delete',
kwargs={'pk': tramitacao_principal.pk}) kwargs={'pk': tramitacao_principal.pk})
response = admin_client.post(url, {'confirmar':'confirmar'} ,follow=True) response = admin_client.post(url, {'confirmar': 'confirmar'}, follow=True)
assert Tramitacao.objects.filter(id=tramitacao_principal.pk).count() == 0 assert Tramitacao.objects.filter(id=tramitacao_principal.pk).count() == 0
assert Tramitacao.objects.filter(id=tramitacao_anexada.pk).count() == 0 assert Tramitacao.objects.filter(id=tramitacao_anexada.pk).count() == 0
assert Tramitacao.objects.filter(id=tramitacao_anexada_anexada.pk).count() == 0 assert Tramitacao.objects.filter(
id=tramitacao_anexada_anexada.pk).count() == 0
# Testes para quando as tramitações das anexadas divergem # Testes para quando as tramitações das anexadas divergem
form = TramitacaoForm(data={}) form = TramitacaoForm(data={})
form.data = {'data_tramitacao':date(2019, 5, 6), form.data = {'data_tramitacao': date(2019, 5, 6),
'unidade_tramitacao_local':unidade_tramitacao_local_1.pk, 'unidade_tramitacao_local': unidade_tramitacao_local_1.pk,
'unidade_tramitacao_destino':unidade_tramitacao_destino_1.pk, 'unidade_tramitacao_destino': unidade_tramitacao_destino_1.pk,
'status':status.pk, 'status': status.pk,
'urgente': False, 'urgente': False,
'texto': "Texto de teste"} 'texto': "Texto de teste"}
form.instance.materia_id=materia_principal.pk form.instance.materia_id = materia_principal.pk
assert form.is_valid() assert form.is_valid()
tramitacao_principal = form.save() tramitacao_principal = form.save()
tramitacao_anexada = materia_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first() tramitacao_anexada = materia_anexada.tramitacao_set.order_by(
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first() '-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by(
'-data_tramitacao', '-id').first()
form = TramitacaoUpdateForm(data={}) form = TramitacaoUpdateForm(data={})
# Alterando unidade_tramitacao_destino # Alterando unidade_tramitacao_destino
form.data = {'data_tramitacao':tramitacao_anexada.data_tramitacao, form.data = {'data_tramitacao': tramitacao_anexada.data_tramitacao,
'unidade_tramitacao_local':tramitacao_anexada.unidade_tramitacao_local.pk, 'unidade_tramitacao_local': tramitacao_anexada.unidade_tramitacao_local.pk,
'unidade_tramitacao_destino':unidade_tramitacao_destino_2.pk, 'unidade_tramitacao_destino': unidade_tramitacao_destino_2.pk,
'status':tramitacao_anexada.status.pk, 'status': tramitacao_anexada.status.pk,
'urgente': tramitacao_anexada.urgente, 'urgente': tramitacao_anexada.urgente,
'texto': tramitacao_anexada.texto} 'texto': tramitacao_anexada.texto}
form.instance = tramitacao_anexada form.instance = tramitacao_anexada
assert form.is_valid() assert form.is_valid()
tramitacao_anexada = form.save() tramitacao_anexada = form.save()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first() tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by(
'-data_tramitacao', '-id').first()
assert tramitacao_principal.unidade_tramitacao_destino == unidade_tramitacao_destino_1 assert tramitacao_principal.unidade_tramitacao_destino == unidade_tramitacao_destino_1
assert tramitacao_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2 assert tramitacao_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2
assert tramitacao_anexada_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2 assert tramitacao_anexada_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2
# Editando a tramitação principal, as tramitações anexadas não devem ser editadas # Editando a tramitação principal, as tramitações anexadas não devem ser
# editadas
form = TramitacaoUpdateForm(data={}) form = TramitacaoUpdateForm(data={})
# Alterando o texto # Alterando o texto
form.data = {'data_tramitacao':tramitacao_principal.data_tramitacao, form.data = {'data_tramitacao': tramitacao_principal.data_tramitacao,
'unidade_tramitacao_local':tramitacao_principal.unidade_tramitacao_local.pk, 'unidade_tramitacao_local': tramitacao_principal.unidade_tramitacao_local.pk,
'unidade_tramitacao_destino':tramitacao_principal.unidade_tramitacao_destino.pk, 'unidade_tramitacao_destino': tramitacao_principal.unidade_tramitacao_destino.pk,
'status':tramitacao_principal.status.pk, 'status': tramitacao_principal.status.pk,
'urgente': tramitacao_principal.urgente, 'urgente': tramitacao_principal.urgente,
'texto': "Testando a alteração"} 'texto': "Testando a alteração"}
form.instance = tramitacao_principal form.instance = tramitacao_principal
assert form.is_valid() assert form.is_valid()
tramitacao_principal = form.save() tramitacao_principal = form.save()
tramitacao_anexada = materia_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first() tramitacao_anexada = materia_anexada.tramitacao_set.order_by(
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first() '-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by(
'-data_tramitacao', '-id').first()
assert tramitacao_principal.texto == "Testando a alteração" assert tramitacao_principal.texto == "Testando a alteração"
assert not tramitacao_anexada.texto == "Testando a alteração" assert not tramitacao_anexada.texto == "Testando a alteração"
assert not tramitacao_anexada_anexada.texto == "Testando a alteração" assert not tramitacao_anexada_anexada.texto == "Testando a alteração"
# Removendo a tramitação pricipal, as tramitações anexadas não devem ser removidas, pois divergiram # Removendo a tramitação pricipal, as tramitações anexadas não devem ser
# removidas, pois divergiram
url = reverse('sapl.materia:tramitacao_delete', url = reverse('sapl.materia:tramitacao_delete',
kwargs={'pk': tramitacao_principal.pk}) kwargs={'pk': tramitacao_principal.pk})
response = admin_client.post(url, {'confirmar':'confirmar'} ,follow=True) response = admin_client.post(url, {'confirmar': 'confirmar'}, follow=True)
assert Tramitacao.objects.filter(id=tramitacao_principal.pk).count() == 0 assert Tramitacao.objects.filter(id=tramitacao_principal.pk).count() == 0
assert Tramitacao.objects.filter(id=tramitacao_anexada.pk).count() == 1 assert Tramitacao.objects.filter(id=tramitacao_anexada.pk).count() == 1
assert Tramitacao.objects.filter(id=tramitacao_anexada_anexada.pk).count() == 1 assert Tramitacao.objects.filter(
id=tramitacao_anexada_anexada.pk).count() == 1
# Removendo a tramitação anexada, a tramitação anexada à anexada deve ser removida # Removendo a tramitação anexada, a tramitação anexada à anexada deve ser
# removida
url = reverse('sapl.materia:tramitacao_delete', url = reverse('sapl.materia:tramitacao_delete',
kwargs={'pk': tramitacao_anexada.pk}) kwargs={'pk': tramitacao_anexada.pk})
response = admin_client.post(url, {'confirmar':'confirmar'} ,follow=True) response = admin_client.post(url, {'confirmar': 'confirmar'}, follow=True)
assert Tramitacao.objects.filter(id=tramitacao_anexada.pk).count() == 0 assert Tramitacao.objects.filter(id=tramitacao_anexada.pk).count() == 0
assert Tramitacao.objects.filter(id=tramitacao_anexada_anexada.pk).count() == 0 assert Tramitacao.objects.filter(
id=tramitacao_anexada_anexada.pk).count() == 0
# Agora testando para caso não seja desejado tramitar as matérias anexadas # Agora testando para caso não seja desejado tramitar as matérias anexadas
# junto com as matérias principais # junto com as matérias principais
@ -877,62 +893,71 @@ def test_tramitacoes_materias_anexadas(admin_client):
# Teste criação de Tramitacao # Teste criação de Tramitacao
form = TramitacaoForm(data={}) form = TramitacaoForm(data={})
form.data = {'data_tramitacao':date(2019, 5, 6), form.data = {'data_tramitacao': date(2019, 5, 6),
'unidade_tramitacao_local':unidade_tramitacao_local_1.pk, 'unidade_tramitacao_local': unidade_tramitacao_local_1.pk,
'unidade_tramitacao_destino':unidade_tramitacao_destino_1.pk, 'unidade_tramitacao_destino': unidade_tramitacao_destino_1.pk,
'status':status.pk, 'status': status.pk,
'urgente': False, 'urgente': False,
'texto': "Texto de teste"} 'texto': "Texto de teste"}
form.instance.materia_id=materia_principal.pk form.instance.materia_id = materia_principal.pk
assert form.is_valid() assert form.is_valid()
tramitacao_principal = form.save() tramitacao_principal = form.save()
tramitacao_anexada = materia_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first() tramitacao_anexada = materia_anexada.tramitacao_set.order_by(
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first() '-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by(
'-data_tramitacao', '-id').first()
# Deve ser criada tramitação apenas para a matéria principal # Deve ser criada tramitação apenas para a matéria principal
assert materia_principal.tramitacao_set.order_by('-data_tramitacao', '-id').first() == tramitacao_principal assert materia_principal.tramitacao_set.order_by(
'-data_tramitacao', '-id').first() == tramitacao_principal
assert not tramitacao_anexada assert not tramitacao_anexada
assert not tramitacao_anexada_anexada assert not tramitacao_anexada_anexada
# Criação de uma tramitação igual para a anexada à principal para testar a
# Criação de uma tramitação igual para a anexada à principal para testar a edição # edição
form = TramitacaoForm(data={}) form = TramitacaoForm(data={})
form.data = {'data_tramitacao':date(2019, 5, 6), form.data = {'data_tramitacao': date(2019, 5, 6),
'unidade_tramitacao_local':unidade_tramitacao_local_1.pk, 'unidade_tramitacao_local': unidade_tramitacao_local_1.pk,
'unidade_tramitacao_destino':unidade_tramitacao_destino_1.pk, 'unidade_tramitacao_destino': unidade_tramitacao_destino_1.pk,
'status':status.pk, 'status': status.pk,
'urgente': False, 'urgente': False,
'texto': "Texto de teste"} 'texto': "Texto de teste"}
form.instance.materia_id=materia_anexada.pk form.instance.materia_id = materia_anexada.pk
assert form.is_valid() assert form.is_valid()
tramitacao_anexada = form.save() tramitacao_anexada = form.save()
tramitacao_principal = materia_principal.tramitacao_set.order_by('-data_tramitacao', '-id').first() tramitacao_principal = materia_principal.tramitacao_set.order_by(
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first() '-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by(
assert materia_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first() == tramitacao_anexada '-data_tramitacao', '-id').first()
assert materia_principal.tramitacao_set.order_by('-data_tramitacao', '-id').all().count() == 1
assert materia_anexada.tramitacao_set.order_by(
'-data_tramitacao', '-id').first() == tramitacao_anexada
assert materia_principal.tramitacao_set.order_by(
'-data_tramitacao', '-id').all().count() == 1
assert compara_tramitacoes_mat(tramitacao_principal, tramitacao_anexada) assert compara_tramitacoes_mat(tramitacao_principal, tramitacao_anexada)
assert not tramitacao_anexada_anexada assert not tramitacao_anexada_anexada
# Teste Edição de Tramitacao # Teste Edição de Tramitacao
form = TramitacaoUpdateForm(data={}) form = TramitacaoUpdateForm(data={})
# Alterando unidade_tramitacao_destino # Alterando unidade_tramitacao_destino
form.data = {'data_tramitacao':tramitacao_principal.data_tramitacao, form.data = {'data_tramitacao': tramitacao_principal.data_tramitacao,
'unidade_tramitacao_local':tramitacao_principal.unidade_tramitacao_local.pk, 'unidade_tramitacao_local': tramitacao_principal.unidade_tramitacao_local.pk,
'unidade_tramitacao_destino':unidade_tramitacao_destino_2.pk, 'unidade_tramitacao_destino': unidade_tramitacao_destino_2.pk,
'status':tramitacao_principal.status.pk, 'status': tramitacao_principal.status.pk,
'urgente': tramitacao_principal.urgente, 'urgente': tramitacao_principal.urgente,
'texto': tramitacao_principal.texto} 'texto': tramitacao_principal.texto}
form.instance = tramitacao_principal form.instance = tramitacao_principal
assert form.is_valid() assert form.is_valid()
tramitacao_principal = form.save() tramitacao_principal = form.save()
tramitacao_anexada = materia_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first() tramitacao_anexada = materia_anexada.tramitacao_set.order_by(
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first() '-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by(
'-data_tramitacao', '-id').first()
assert tramitacao_principal.unidade_tramitacao_destino == unidade_tramitacao_destino_2 assert tramitacao_principal.unidade_tramitacao_destino == unidade_tramitacao_destino_2
assert tramitacao_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_1 assert tramitacao_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_1
@ -941,18 +966,20 @@ def test_tramitacoes_materias_anexadas(admin_client):
# Alterando a tramitação anexada para testar a remoção de tramitações # Alterando a tramitação anexada para testar a remoção de tramitações
# Alterando unidade_tramitacao_destino # Alterando unidade_tramitacao_destino
form = TramitacaoUpdateForm(data={}) form = TramitacaoUpdateForm(data={})
form.data = {'data_tramitacao':tramitacao_principal.data_tramitacao, form.data = {'data_tramitacao': tramitacao_principal.data_tramitacao,
'unidade_tramitacao_local':tramitacao_principal.unidade_tramitacao_local.pk, 'unidade_tramitacao_local': tramitacao_principal.unidade_tramitacao_local.pk,
'unidade_tramitacao_destino':unidade_tramitacao_destino_2.pk, 'unidade_tramitacao_destino': unidade_tramitacao_destino_2.pk,
'status':tramitacao_principal.status.pk, 'status': tramitacao_principal.status.pk,
'urgente': tramitacao_principal.urgente, 'urgente': tramitacao_principal.urgente,
'texto': tramitacao_principal.texto} 'texto': tramitacao_principal.texto}
form.instance = tramitacao_anexada form.instance = tramitacao_anexada
assert form.is_valid() assert form.is_valid()
tramitacao_anexada = form.save() tramitacao_anexada = form.save()
tramitacao_principal = materia_principal.tramitacao_set.order_by('-data_tramitacao', '-id').first() tramitacao_principal = materia_principal.tramitacao_set.order_by(
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first() '-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by(
'-data_tramitacao', '-id').first()
assert tramitacao_principal.unidade_tramitacao_destino == unidade_tramitacao_destino_2 assert tramitacao_principal.unidade_tramitacao_destino == unidade_tramitacao_destino_2
assert tramitacao_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2 assert tramitacao_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2
@ -960,9 +987,10 @@ def test_tramitacoes_materias_anexadas(admin_client):
assert compara_tramitacoes_mat(tramitacao_principal, tramitacao_anexada) assert compara_tramitacoes_mat(tramitacao_principal, tramitacao_anexada)
# Testando a remoção # Testando a remoção
# Removendo a tramitação pricipal, as tramitações anexadas não devem ser removidas # Removendo a tramitação pricipal, as tramitações anexadas não devem ser
# removidas
url = reverse('sapl.materia:tramitacao_delete', url = reverse('sapl.materia:tramitacao_delete',
kwargs={'pk': tramitacao_principal.pk}) kwargs={'pk': tramitacao_principal.pk})
response = admin_client.post(url, {'confirmar':'confirmar'} ,follow=True) response = admin_client.post(url, {'confirmar': 'confirmar'}, follow=True)
assert Tramitacao.objects.filter(id=tramitacao_principal.pk).count() == 0 assert Tramitacao.objects.filter(id=tramitacao_principal.pk).count() == 0
assert Tramitacao.objects.filter(id=tramitacao_anexada.pk).count() == 1 assert Tramitacao.objects.filter(id=tramitacao_anexada.pk).count() == 1

38
sapl/materia/views.py

@ -1,14 +1,11 @@
from datetime import datetime
from datetime import datetime from datetime import datetime
from io import BytesIO from io import BytesIO
import itertools
import logging import logging
import os import os
from random import choice from random import choice
import shutil import shutil
from string import ascii_letters, digits from string import ascii_letters, digits
import tempfile
import time import time
import zipfile import zipfile
@ -24,7 +21,7 @@ from django.http import HttpResponse, JsonResponse
from django.http.response import Http404, HttpResponseRedirect from django.http.response import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.shortcuts import render from django.shortcuts import render
from django.template import loader, RequestContext from django.template import loader
from django.urls import reverse from django.urls import reverse
from django.utils import formats, timezone from django.utils import formats, timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -37,7 +34,7 @@ import weasyprint
import sapl import sapl
from sapl.base.email_utils import do_envia_email_confirmacao from sapl.base.email_utils import do_envia_email_confirmacao
from sapl.base.models import Autor, CasaLegislativa, AppConfig as BaseAppConfig from sapl.base.models import Autor, CasaLegislativa, AppConfig as BaseAppConfig
from sapl.comissoes.models import Comissao, Participacao, Composicao from sapl.comissoes.models import Participacao
from sapl.compilacao.models import STATUS_TA_IMMUTABLE_RESTRICT, STATUS_TA_PRIVATE from sapl.compilacao.models import STATUS_TA_IMMUTABLE_RESTRICT, STATUS_TA_PRIVATE
from sapl.compilacao.views import IntegracaoTaView from sapl.compilacao.views import IntegracaoTaView
from sapl.crispy_layout_mixin import form_actions, SaplFormHelper, SaplFormLayout from sapl.crispy_layout_mixin import form_actions, SaplFormHelper, SaplFormLayout
@ -55,7 +52,7 @@ 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, get_tempfile_dir,
google_recaptcha_configured) google_recaptcha_configured)
from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm,
@ -107,7 +104,10 @@ def proposicao_texto(request, pk):
if proposicao.texto_original: if proposicao.texto_original:
if (not proposicao.data_recebimento and if (not proposicao.data_recebimento and
proposicao.autor.user_id != request.user.id): not proposicao.autor.operadores.filter(
id=request.user.id
).exists()
):
logger.error("user=" + username + ". Usuário ({}) não tem permissão para acessar o texto original." logger.error("user=" + username + ". Usuário ({}) não tem permissão para acessar o texto original."
.format(request.user.id)) .format(request.user.id))
messages.error(request, _( messages.error(request, _(
@ -321,8 +321,9 @@ class ProposicaoTaView(IntegracaoTaView):
proposicao = get_object_or_404(self.model, pk=kwargs['pk']) proposicao = get_object_or_404(self.model, pk=kwargs['pk'])
if not proposicao.data_envio and\ if not proposicao.data_envio and \
request.user != proposicao.autor.user: not proposicao.autor.operadores.filter(
id=request.user.id).exists():
raise Http404() raise Http404()
return IntegracaoTaView.get(self, request, *args, **kwargs) return IntegracaoTaView.get(self, request, *args, **kwargs)
@ -660,7 +661,7 @@ class RetornarProposicao(UpdateView):
"user=" + username + ". Objeto Proposicao com id={} não encontrado.".format(kwargs['pk'])) "user=" + username + ". Objeto Proposicao com id={} não encontrado.".format(kwargs['pk']))
raise Http404() raise Http404()
if p.autor.user != request.user: if not p.autor.operadores.filter(id=request.user.id).exists():
self.logger.error( self.logger.error(
"user=" + username + ". Usuário ({}) sem acesso a esta opção.".format(request.user)) "user=" + username + ". Usuário ({}) sem acesso a esta opção.".format(request.user))
messages.error( messages.error(
@ -808,7 +809,7 @@ class UnidadeTramitacaoCrud(CrudAux):
class ProposicaoCrud(Crud): class ProposicaoCrud(Crud):
model = Proposicao model = Proposicao
help_topic = 'proposicao' help_topic = 'proposicao'
container_field = 'autor__user' container_field = 'autor__operadores'
class BaseMixin(Crud.BaseMixin): class BaseMixin(Crud.BaseMixin):
list_field_names = [ list_field_names = [
@ -877,7 +878,7 @@ class ProposicaoCrud(Crud):
p = Proposicao.objects.get(id=kwargs['pk']) p = Proposicao.objects.get(id=kwargs['pk'])
msg_error = '' msg_error = ''
if p and p.autor.user == user: if p and p.autor.operadores.filter(id=request.user.id).exists():
if action == 'send': if action == 'send':
if p.data_envio and p.data_recebimento: if p.data_envio and p.data_recebimento:
msg_error = _('Proposição já foi enviada e recebida.') msg_error = _('Proposição já foi enviada e recebida.')
@ -986,7 +987,7 @@ class ProposicaoCrud(Crud):
if not self.has_permission(): if not self.has_permission():
return self.handle_no_permission() return self.handle_no_permission()
if p.autor.user != request.user: if not p.autor.operadores.filter(id=request.user.id).exists():
if not p.data_envio and not p.data_devolucao: if not p.data_envio and not p.data_devolucao:
raise Http404() raise Http404()
@ -1187,7 +1188,7 @@ class ReciboProposicaoView(TemplateView):
return (Proposicao.objects.filter( return (Proposicao.objects.filter(
id=self.kwargs['pk'], id=self.kwargs['pk'],
autor__user_id=self.request.user.id).exists()) autor__operadores=self.request.user).exists())
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(ReciboProposicaoView, self).get_context_data( context = super(ReciboProposicaoView, self).get_context_data(
@ -1250,7 +1251,8 @@ class HistoricoProposicaoView(PermissionRequiredMixin, ListView):
return qs return qs
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(HistoricoProposicaoView, self).get_context_data(**kwargs) context = super(HistoricoProposicaoView,
self).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(
@ -1433,7 +1435,8 @@ class TramitacaoCrud(MasterDetailCrud):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
tramitacao = Tramitacao.objects.select_related('materia').get(id=self.kwargs['pk']) tramitacao = Tramitacao.objects.select_related(
'materia').get(id=self.kwargs['pk'])
materia = tramitacao.materia materia = tramitacao.materia
url = reverse('sapl.materia:tramitacao_list', url = reverse('sapl.materia:tramitacao_list',
kwargs={'pk': materia.id}) kwargs={'pk': materia.id})
@ -2053,7 +2056,8 @@ class MateriaLegislativaPesquisaView(FilterView):
"tipo",) "tipo",)
# retorna somente MateriaLegislativa sem MateriaAssunto se True # retorna somente MateriaLegislativa sem MateriaAssunto se True
materia_assunto_null = self.request.GET.get("materiaassunto_null", False) materia_assunto_null = self.request.GET.get(
"materiaassunto_null", False)
if materia_assunto_null: if materia_assunto_null:
qs = qs.filter(materiaassunto__isnull=True) qs = qs.filter(materiaassunto__isnull=True)

9
sapl/parlamentares/forms.py

@ -219,11 +219,12 @@ class ParlamentarForm(FileFieldCheckMixin, ModelForm):
def save(self, commit=True): def save(self, commit=True):
parlamentar = super().save() parlamentar = super().save()
autor = parlamentar.autor.first() autor = parlamentar.autor.first()
usuario = autor.user if autor else None
if autor and usuario: if autor:
usuario.is_active = parlamentar.ativo usuarios = autor.operadores.all()
usuario.save() for u in usuarios:
u.is_active = parlamentar.ativo
u.save()
return parlamentar return parlamentar

43
sapl/parlamentares/migrations/0035_auto_20210315_1522.py

@ -0,0 +1,43 @@
# Generated by Django 2.2.13 on 2021-03-15 18:22
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
def remove_grupo_votante_de_usuario(apps, schema_editor):
Group = apps.get_model('auth', 'Group')
User = apps.get_model('auth', 'User')
g, created = Group.objects.get_or_create(name='Votante')
# usuários do grupo votante sem vínculo com parlamentar
users_votantes = g.user_set.filter(votante_set__isnull=True)
for u in users_votantes:
u.groups.remove(g)
class Migration(migrations.Migration):
dependencies = [
('parlamentares', '0034_auto_20210303_1428'),
]
operations = [
migrations.AlterField(
model_name='votante',
name='parlamentar',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT,
related_name='votante_set', to='parlamentares.Parlamentar', verbose_name='Parlamentar'),
),
migrations.AlterField(
model_name='votante',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT,
related_name='votante_set', to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
migrations.RunPython(remove_grupo_votante_de_usuario),
]

4
sapl/parlamentares/models.py

@ -609,10 +609,10 @@ class FrenteParlamentar(models.Model):
class Votante(models.Model): class Votante(models.Model):
parlamentar = models.ForeignKey( parlamentar = models.ForeignKey(
Parlamentar, verbose_name=_('Parlamentar'), Parlamentar, verbose_name=_('Parlamentar'),
on_delete=models.PROTECT, related_name='parlamentar') on_delete=models.PROTECT, related_name='votante_set')
user = models.ForeignKey( user = models.ForeignKey(
get_settings_auth_user_model(), on_delete=models.PROTECT, get_settings_auth_user_model(), on_delete=models.PROTECT,
verbose_name=_('User'), related_name='user') verbose_name=_('User'), related_name='votante_set')
data = models.DateTimeField( data = models.DateTimeField(
verbose_name=_('Data'), auto_now_add=True, verbose_name=_('Data'), auto_now_add=True,
max_length=30, null=True, blank=True) max_length=30, null=True, blank=True)

90
sapl/parlamentares/views.py

@ -4,15 +4,16 @@ import logging
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
from django.urls import reverse, reverse_lazy
from django.db.models import F, Q from django.db.models import F, Q
from django.db.models.aggregates import Count from django.db.models.aggregates import Count
from django.http import JsonResponse from django.http import JsonResponse
from django.http.response import HttpResponseRedirect from django.http.response import HttpResponseRedirect
from django.shortcuts import render from django.shortcuts import render
from django.templatetags.static import static from django.templatetags.static import static
from django.urls import reverse, reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.utils.datastructures import MultiValueDictKeyError from django.utils.datastructures import MultiValueDictKeyError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -22,7 +23,6 @@ from django.views.generic.edit import UpdateView
from django_filters.views import FilterView from django_filters.views import FilterView
from image_cropping.utils import get_backend from image_cropping.utils import get_backend
from sapl.base.forms import SessaoLegislativaForm, PartidoForm from sapl.base.forms import SessaoLegislativaForm, PartidoForm
from sapl.base.models import Autor from sapl.base.models import Autor
from sapl.comissoes.models import Participacao from sapl.comissoes.models import Participacao
@ -31,13 +31,13 @@ from sapl.crud.base import (RP_CHANGE, RP_DETAIL, RP_LIST, Crud, CrudAux,
MasterDetailCrud, make_pagination) MasterDetailCrud, make_pagination)
from sapl.materia.models import Autoria, Proposicao, Relatoria from sapl.materia.models import Autoria, Proposicao, Relatoria
from sapl.parlamentares.apps import AppConfig from sapl.parlamentares.apps import AppConfig
from sapl.rules import SAPL_GROUP_VOTANTE
from sapl.utils import (parlamentares_ativos, show_results_filter_set) from sapl.utils import (parlamentares_ativos, show_results_filter_set)
from .forms import (ColigacaoFilterSet, FiliacaoForm, FrenteForm, LegislaturaForm, MandatoForm, from .forms import (ColigacaoFilterSet, FiliacaoForm, FrenteForm, LegislaturaForm, MandatoForm,
ParlamentarCreateForm, ParlamentarForm, VotanteForm, ParlamentarCreateForm, ParlamentarForm, VotanteForm,
ParlamentarFilterSet, PartidoFilterSet, VincularParlamentarForm, ParlamentarFilterSet, PartidoFilterSet, VincularParlamentarForm,
BlocoForm, FrenteParlamentarForm, BlocoMembroForm) BlocoForm, FrenteParlamentarForm, BlocoMembroForm)
from .models import (CargoMesa, Coligacao, ComposicaoColigacao, ComposicaoMesa, from .models import (CargoMesa, Coligacao, ComposicaoColigacao, ComposicaoMesa,
Dependente, Filiacao, Frente, Legislatura, Mandato, Dependente, Filiacao, Frente, Legislatura, Mandato,
NivelInstrucao, Parlamentar, Partido, SessaoLegislativa, NivelInstrucao, Parlamentar, Partido, SessaoLegislativa,
@ -53,7 +53,8 @@ NivelInstrucaoCrud = CrudAux.build(NivelInstrucao, 'nivel_instrucao')
TipoAfastamentoCrud = CrudAux.build(TipoAfastamento, 'tipo_afastamento') TipoAfastamentoCrud = CrudAux.build(TipoAfastamento, 'tipo_afastamento')
TipoMilitarCrud = CrudAux.build(SituacaoMilitar, 'tipo_situa_militar') TipoMilitarCrud = CrudAux.build(SituacaoMilitar, 'tipo_situa_militar')
DependenteCrud = MasterDetailCrud.build(Dependente, 'parlamentar', 'dependente') DependenteCrud = MasterDetailCrud.build(
Dependente, 'parlamentar', 'dependente')
class SessaoLegislativaCrud(CrudAux): class SessaoLegislativaCrud(CrudAux):
@ -101,6 +102,10 @@ class VotanteView(MasterDetailCrud):
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
obj = self.get_object() obj = self.get_object()
g = Group.objects.filter(name=SAPL_GROUP_VOTANTE)[0]
obj.user.groups.remove(g)
obj.delete() obj.delete()
return HttpResponseRedirect( return HttpResponseRedirect(
reverse('sapl.parlamentares:votante_list', reverse('sapl.parlamentares:votante_list',
@ -247,9 +252,9 @@ class PesquisarColigacaoView(FilterView):
'queryset': self.get_queryset().order_by('nome').distinct() 'queryset': self.get_queryset().order_by('nome').distinct()
}) })
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(PesquisarColigacaoView, self).get_context_data(**kwargs) context = super(PesquisarColigacaoView,
self).get_context_data(**kwargs)
paginator = context['paginator'] paginator = context['paginator']
page_obj = context['page_obj'] page_obj = context['page_obj']
@ -278,7 +283,8 @@ class PesquisarColigacaoView(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)
@ -296,7 +302,6 @@ class PesquisarPartidoView(FilterView):
'queryset': self.get_queryset().order_by('nome').distinct() 'queryset': self.get_queryset().order_by('nome').distinct()
}) })
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(PesquisarPartidoView, self).get_context_data(**kwargs) context = super(PesquisarPartidoView, self).get_context_data(**kwargs)
@ -326,7 +331,8 @@ class PesquisarPartidoView(FilterView):
object_list=self.object_list, object_list=self.object_list,
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)
@ -355,17 +361,17 @@ class ParticipacaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView):
comissoes = [] comissoes = []
for p in object_list: for p in object_list:
comissao = [ comissao = [
(p.composicao.comissao.nome, reverse( (p.composicao.comissao.nome, reverse(
'sapl.comissoes:comissao_detail', kwargs={ 'sapl.comissoes:comissao_detail', kwargs={
'pk': p.composicao.comissao.pk})), 'pk': p.composicao.comissao.pk})),
(p.cargo.nome, None), (p.cargo.nome, None),
(p.composicao.periodo.data_inicio.strftime( (p.composicao.periodo.data_inicio.strftime(
"%d/%m/%Y") + ' a ' + "%d/%m/%Y") + ' a ' +
p.composicao.periodo.data_fim.strftime("%d/%m/%Y"), p.composicao.periodo.data_fim.strftime("%d/%m/%Y"),
None) None)
] ]
comissoes.append(comissao) comissoes.append(comissao)
return comissoes return comissoes
def get_headers(self): def get_headers(self):
@ -417,11 +423,13 @@ class ColigacaoCrud(CrudAux):
def coligacao_legislatura(request): def coligacao_legislatura(request):
try: try:
coligacoes = Coligacao.objects.filter(legislatura=request.GET['legislatura']).order_by('nome') coligacoes = Coligacao.objects.filter(
legislatura=request.GET['legislatura']).order_by('nome')
except: except:
coligacoes = [] coligacoes = []
lista_coligacoes = [(coligacao.id, str(coligacao)) for coligacao in coligacoes] lista_coligacoes = [(coligacao.id, str(coligacao))
for coligacao in coligacoes]
return JsonResponse({'coligacoes': lista_coligacoes}) return JsonResponse({'coligacoes': lista_coligacoes})
@ -746,7 +754,8 @@ class ParlamentarCrud(Crud):
". Tentando obter id da legislatura.") ". Tentando obter id da legislatura.")
return int(self.request.GET['pk']) return int(self.request.GET['pk'])
except: except:
self.logger.warning("User=" + username + ". Legislatura não possui ID. Buscando em todas as entradas.") self.logger.warning(
"User=" + username + ". Legislatura não possui ID. Buscando em todas as entradas.")
legislaturas = Legislatura.objects.all() legislaturas = Legislatura.objects.all()
for l in legislaturas: for l in legislaturas:
if l.atual(): if l.atual():
@ -974,7 +983,8 @@ def altera_field_mesa(request):
# é alterado o campo de sessão ou feita alguma operação # é alterado o campo de sessão ou feita alguma operação
# de inclusão/remoção. # de inclusão/remoção.
if request.GET['sessao']: if request.GET['sessao']:
sessao_selecionada = SessaoLegislativa.objects.get(id=request.GET['sessao']) sessao_selecionada = SessaoLegislativa.objects.get(
id=request.GET['sessao'])
# Caso a mudança tenha sido no campo legislatura, a sessão # Caso a mudança tenha sido no campo legislatura, a sessão
# atual deve ser a primeira daquela legislatura # atual deve ser a primeira daquela legislatura
@ -1183,19 +1193,21 @@ def altera_field_mesa_public_view(request):
else: else:
legislatura = Legislatura.objects.order_by('-data_inicio').first() legislatura = Legislatura.objects.order_by('-data_inicio').first()
sessoes = legislatura.sessaolegislativa_set.filter(tipo='O').order_by('-data_inicio') sessoes = legislatura.sessaolegislativa_set.filter(
tipo='O').order_by('-data_inicio')
if not sessoes: if not sessoes:
return JsonResponse({'msg': ('Nenhuma sessão encontrada!', 0)}) return JsonResponse({'msg': ('Nenhuma sessão encontrada!', 0)})
# Verifica se já tem uma sessão selecionada. Ocorre quando é alterado o campo de sessão # Verifica se já tem uma sessão selecionada. Ocorre quando é alterado o
# campo de sessão
sessao_selecionada = request.GET.get('sessao') sessao_selecionada = request.GET.get('sessao')
if not sessao_selecionada: if not sessao_selecionada:
try: try:
year = timezone.now().year year = timezone.now().year
logger.info(f"user={username}. Tentando obter sessões com data_inicio.ano = {year}.") logger.info(
f"user={username}. Tentando obter sessões com data_inicio.ano = {year}.")
sessao_selecionada = sessoes.get(data_inicio__year=year).id sessao_selecionada = sessoes.get(data_inicio__year=year).id
except ObjectDoesNotExist: except ObjectDoesNotExist:
logger.error(f"user={username}. Sessões não encontradas com com data_inicio.ano = {year}. " logger.error(f"user={username}. Sessões não encontradas com com data_inicio.ano = {year}. "
@ -1205,9 +1217,12 @@ def altera_field_mesa_public_view(request):
# Atualiza os componentes da view após a mudança # Atualiza os componentes da view após a mudança
lista_sessoes = [(s.id, s.__str__()) for s in sessoes] lista_sessoes = [(s.id, s.__str__()) for s in sessoes]
composicao_mesa = ComposicaoMesa.objects.select_related('cargo', 'parlamentar').filter(sessao_legislativa=sessao_selecionada).order_by('cargo_id') composicao_mesa = ComposicaoMesa.objects.select_related('cargo', 'parlamentar').filter(
cargos_ocupados = list(composicao_mesa.values_list('cargo__id', 'cargo__descricao')) sessao_legislativa=sessao_selecionada).order_by('cargo_id')
parlamentares_ocupados = list(composicao_mesa.values_list('parlamentar__id', 'parlamentar__nome_parlamentar')) cargos_ocupados = list(composicao_mesa.values_list(
'cargo__id', 'cargo__descricao'))
parlamentares_ocupados = list(composicao_mesa.values_list(
'parlamentar__id', 'parlamentar__nome_parlamentar'))
lista_fotos = [] lista_fotos = []
lista_partidos = [] lista_partidos = []
@ -1215,7 +1230,8 @@ def altera_field_mesa_public_view(request):
sessao = SessaoLegislativa.objects.get(id=sessao_selecionada) sessao = SessaoLegislativa.objects.get(id=sessao_selecionada)
for p in parlamentares_ocupados: for p in parlamentares_ocupados:
parlamentar = Parlamentar.objects.get(id=p[0]) parlamentar = Parlamentar.objects.get(id=p[0])
lista_partidos.append(partido_parlamentar_sessao_legislativa(sessao, parlamentar)) lista_partidos.append(
partido_parlamentar_sessao_legislativa(sessao, parlamentar))
if parlamentar.fotografia: if parlamentar.fotografia:
try: try:
thumbnail_url = get_backend().get_thumbnail_url( thumbnail_url = get_backend().get_thumbnail_url(
@ -1230,7 +1246,8 @@ def altera_field_mesa_public_view(request):
lista_fotos.append(thumbnail_url) lista_fotos.append(thumbnail_url)
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
logger.error(F'erro processando arquivo: {parlamentar.fotografia.path}') logger.error(
F'erro processando arquivo: {parlamentar.fotografia.path}')
else: else:
lista_fotos.append(None) lista_fotos.append(None)
@ -1242,7 +1259,7 @@ def altera_field_mesa_public_view(request):
'lista_fotos': lista_fotos, 'lista_fotos': lista_fotos,
'sessao_selecionada': sessao_selecionada, 'sessao_selecionada': sessao_selecionada,
'msg': ('', 1) 'msg': ('', 1)
}) })
class VincularParlamentarView(PermissionRequiredMixin, FormView): class VincularParlamentarView(PermissionRequiredMixin, FormView):
@ -1262,7 +1279,8 @@ class VincularParlamentarView(PermissionRequiredMixin, FormView):
'data_fim_mandato': form.cleaned_data['legislatura'].data_fim 'data_fim_mandato': form.cleaned_data['legislatura'].data_fim
} }
data_expedicao_diploma = form.cleaned_data.get('data_expedicao_diploma') data_expedicao_diploma = form.cleaned_data.get(
'data_expedicao_diploma')
if data_expedicao_diploma: if data_expedicao_diploma:
kwargs.update({'data_expedicao_diploma': data_expedicao_diploma}) kwargs.update({'data_expedicao_diploma': data_expedicao_diploma})
@ -1337,6 +1355,6 @@ def get_sessoes_legislatura(request):
json_response = {'sessoes_legislativas': []} json_response = {'sessoes_legislativas': []}
for s in SessaoLegislativa.objects.filter(legislatura_id=legislatura_id): for s in SessaoLegislativa.objects.filter(legislatura_id=legislatura_id):
json_response['sessoes_legislativas'].append( (s.id, str(s)) ) json_response['sessoes_legislativas'].append((s.id, str(s)))
return JsonResponse(json_response) return JsonResponse(json_response)

6
sapl/rules/__init__.py

@ -27,7 +27,6 @@ SAPL_GROUP_SESSAO = _("Operador de Sessão Plenária")
SAPL_GROUP_PAINEL = _("Operador de Painel Eletrônico") SAPL_GROUP_PAINEL = _("Operador de Painel Eletrônico")
SAPL_GROUP_GERAL = _("Operador Geral") SAPL_GROUP_GERAL = _("Operador Geral")
SAPL_GROUP_AUTOR = _("Autor") SAPL_GROUP_AUTOR = _("Autor")
SAPL_GROUP_PARLAMENTAR = _("Parlamentar")
SAPL_GROUP_VOTANTE = _("Votante") SAPL_GROUP_VOTANTE = _("Votante")
# TODO - funcionalidade ainda não existe mas está aqui para efeito de anotação # TODO - funcionalidade ainda não existe mas está aqui para efeito de anotação
@ -48,8 +47,11 @@ SAPL_GROUPS = [
SAPL_GROUP_PAINEL, SAPL_GROUP_PAINEL,
SAPL_GROUP_GERAL, SAPL_GROUP_GERAL,
SAPL_GROUP_AUTOR, SAPL_GROUP_AUTOR,
SAPL_GROUP_PARLAMENTAR,
SAPL_GROUP_VOTANTE, SAPL_GROUP_VOTANTE,
SAPL_GROUP_LOGIN_SOCIAL, SAPL_GROUP_LOGIN_SOCIAL,
SAPL_GROUP_ANONYMOUS, SAPL_GROUP_ANONYMOUS,
] ]
SAPL_GROUPS_DELETE = [
]

10
sapl/rules/apps.py

@ -167,6 +167,7 @@ def get_rules():
print(group_name, e) print(group_name, e)
def groups_add_user(self, user, groups_name): def groups_add_user(self, user, groups_name):
if not isinstance(groups_name, list): if not isinstance(groups_name, list):
groups_name = [groups_name, ] groups_name = [groups_name, ]
for group_name in groups_name: for group_name in groups_name:
@ -211,7 +212,8 @@ def get_rules():
def update_groups(self): def update_groups(self):
print('') print('')
print("\033[93m\033[1m{}\033[0m".format(_('Atualizando grupos do SAPL:'))) print("\033[93m\033[1m{}\033[0m".format(
_('Atualizando grupos do SAPL:')))
for rules_group in self.rules_patterns: for rules_group in self.rules_patterns:
group_name = rules_group['group'] group_name = rules_group['group']
rules_list = rules_group['rules'] rules_list = rules_group['rules']
@ -242,5 +244,7 @@ def revision_pre_delete_signal(sender, **kwargs):
models.signals.post_migrate.connect(receiver=update_groups) models.signals.post_migrate.connect(receiver=update_groups)
models.signals.post_migrate.connect(receiver=create_proxy_permissions, dispatch_uid="django.contrib.auth.management.create_permissions") models.signals.post_migrate.connect(
models.signals.pre_delete.connect(receiver=revision_pre_delete_signal, dispatch_uid="pre_delete_signal") receiver=create_proxy_permissions, dispatch_uid="django.contrib.auth.management.create_permissions")
models.signals.pre_delete.connect(
receiver=revision_pre_delete_signal, dispatch_uid="pre_delete_signal")

9
sapl/rules/map_rules.py

@ -41,7 +41,7 @@ from sapl.rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL, RP_LIST,
SAPL_GROUP_AUTOR, SAPL_GROUP_COMISSOES, SAPL_GROUP_AUTOR, SAPL_GROUP_COMISSOES,
SAPL_GROUP_GERAL, SAPL_GROUP_LOGIN_SOCIAL, SAPL_GROUP_GERAL, SAPL_GROUP_LOGIN_SOCIAL,
SAPL_GROUP_MATERIA, SAPL_GROUP_NORMA, SAPL_GROUP_MATERIA, SAPL_GROUP_NORMA,
SAPL_GROUP_PAINEL, SAPL_GROUP_PARLAMENTAR, SAPL_GROUP_PAINEL,
SAPL_GROUP_PROTOCOLO, SAPL_GROUP_SESSAO, SAPL_GROUP_PROTOCOLO, SAPL_GROUP_SESSAO,
SAPL_GROUP_VOTANTE) SAPL_GROUP_VOTANTE)
from sapl.sessao import models as sessao from sapl.sessao import models as sessao
@ -211,11 +211,6 @@ rules_group_autor = {
] ]
} }
rules_group_parlamentar = {
'group': SAPL_GROUP_PARLAMENTAR,
'rules': []
}
rules_group_votante = { rules_group_votante = {
'group': SAPL_GROUP_VOTANTE, 'group': SAPL_GROUP_VOTANTE,
'rules': [ 'rules': [
@ -235,6 +230,7 @@ rules_group_geral = {
[RP_ADD], __perms_publicas__), [RP_ADD], __perms_publicas__),
(base.TipoAutor, __base__, __perms_publicas__), (base.TipoAutor, __base__, __perms_publicas__),
(base.Autor, __base__, __perms_publicas__), (base.Autor, __base__, __perms_publicas__),
(base.OperadorAutor, __base__, __perms_publicas__),
(base.AuditLog, __base__, set()), (base.AuditLog, __base__, set()),
(protocoloadm.StatusTramitacaoAdministrativo, __base__, set()), (protocoloadm.StatusTramitacaoAdministrativo, __base__, set()),
@ -370,7 +366,6 @@ rules_patterns = [
rules_group_painel, rules_group_painel,
rules_group_geral, rules_group_geral,
rules_group_autor, rules_group_autor,
rules_group_parlamentar,
rules_group_votante, rules_group_votante,
rules_group_anonymous, # anotação para validação do teste de rules rules_group_anonymous, # anotação para validação do teste de rules

14
sapl/templates/base/autor_detail.html

@ -1,14 +0,0 @@
{% extends "crud/detail.html" %}
{% load i18n %}
{% load crispy_forms_tags staticfiles %}
{% block sub_actions %}
<div class="actions btn-group btn-group-sm" role="group">
<a href="{% url 'sapl.base:pesquisar_autor' %}" class="btn btn-outline-primary">
{% blocktrans with verbose_name=view.verbose_name %} Pesquisar {{ verbose_name }} {% endblocktrans %}
</a>
<a href="{{ view.create_url }}" class="btn btn-outline-primary">
{% blocktrans with verbose_name=view.verbose_name %} Adicionar {{ verbose_name }} {% endblocktrans %}
</a>
</div>
{% endblock sub_actions %}

51
sapl/templates/base/autor_filter.html

@ -1,51 +0,0 @@
{% extends "crud/list.html" %}
{% load i18n %}
{% load crispy_forms_tags staticfiles %}
{% block base_content %}
{% if not show_results %}
{% crispy filter.form %}
{% else %}
<div class="actions btn-group float-right btn-group-sm" role="group">
<a href="{% url 'sapl.base:pesquisar_autor' %}" class="btn btn-outline-primary">{% trans 'Fazer nova pesquisa' %}</a>
{% if not request.user.is_anonymous %}
<a href="{% url 'sapl.base:autor_create' %}"class="btn btn-outline-primary">Cadastrar Autor</a>
{% endif %}
</div>
<br>
{% if numero_res > 0 %}
{% if numero_res == 1 %}
<p>Foi encontrado {{ numero_res }} resultado</p>
{% else %}
<p>Foram encontrados {{ numero_res }} resultados</p>
{% endif %}
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Tipo do Autor</th>
<th>Nome do Autor</th>
<th>Usuário</th>
</tr>
</thead>
<tbody>
{% for autor in page_obj %}
<tr>
<td>{{ autor.tipo }}</td>
<td>
<a href="{% url 'sapl.base:autor_detail' autor.pk %}">
{% if autor.nome %} {{ autor.nome }} {% else %} - {% endif %}
</a>
</td>
<td>{% if autor.user %} {{ autor.user }} {% else %} - {% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<font size="4"><p align="center">{{ NO_ENTRIES_MSG }}</p></font>
{% endif %}
{% endif %}
<br/>
{% include 'paginacao.html'%}
<br /><br /><br />
{% endblock base_content %}

46
sapl/templates/base/autor_form.html

@ -87,58 +87,12 @@ $(document).ready(function(){
update_search(pk); update_search(pk);
}); });
$('input[name=action_user]').change(function(event) {
if (!this.checked)
return;
$('#div_id_username input').prop('readonly', '');
if (event.target.value == 'N') {
$('.new_user_fields').addClass('hidden');
if ($('input[name=username]').attr('data') != '')
$('.radiogroup-status').removeClass('hidden');
if (flag_create) {
$('#div_id_username').addClass('hidden');
}
else {
$('#div_id_username input').prop('readonly', 'readonly');
}
}
else {
$('.radiogroup-status').addClass('hidden');
$('#div_id_username').removeClass('hidden');
$('.new_user_fields').addClass('hidden');
}
if (!flag_create) {
var username = $('input[name=username]');
if (username.length == 1) {
if ((event.target.value == 'A' && username.attr('data') != '' && username[0].value != username.attr('data'))
|| (event.target.value == 'C' && username.attr('data') != ''))
$('.radiogroup-status').removeClass('hidden');
}
}
});
$('input[name=username]').keyup(function(event) {
if (!flag_create)
if (this.getAttribute('data') != '' && this.value != this.getAttribute('data'))
$('.radiogroup-status').removeClass('hidden');
else
$('.radiogroup-status').addClass('hidden');
});
$('input[name=action_user]:checked').trigger('change');
if (flag_create) if (flag_create)
$('input[name=autor_related]').closest('.radio').remove(); $('input[name=autor_related]').closest('.radio').remove();
var pk = $('#id_tipo').val(); var pk = $('#id_tipo').val();
if (pk) if (pk)
update_search(pk, $('#id_q').val().length > 0) update_search(pk, $('#id_q').val().length > 0)
}); });
</script> </script>
{% endblock %} {% endblock %}

7
sapl/templates/base/layouts.yaml

@ -13,7 +13,7 @@ CasaLegislativa:
UserDetail: UserDetail:
{% trans 'Usuário' %}: {% trans 'Usuário' %}:
- usuario username:3 is_active:2 - usuario username:3 is_active:2
- auth_token - auth_token votante_set__parlamentar|m2m_urlize_for_detail operadorautor_set__autor|m2m_urlize_for_detail
- groups - groups
- user_permissions - user_permissions
@ -72,7 +72,12 @@ Autor:
{% trans 'Autor' %}: {% trans 'Autor' %}:
- tipo:3 nome - tipo:3 nome
- cargo - cargo
- operadores
AutorCreate: AutorCreate:
{% trans 'Cadastro de Usuários Autores' %}: {% trans 'Cadastro de Usuários Autores' %}:
- tipo:3 search_autor - tipo:3 search_autor
OperadorAutor:
{% trans 'Operador de Autor' %}:
- user

34
sapl/templates/bootstrap4/layout/checkboxselectmultiple.html

@ -0,0 +1,34 @@
{% load crispy_forms_filters common_tags %}
{% load l10n %}
<div class="{% if inline_class %}form-check{% endif %}{% if field_class %} {{ field_class }}{% endif %}"{% if flat_attrs %} {{ flat_attrs|safe }}{% endif %}>
{% include 'bootstrap4/layout/field_errors_block.html' %}
{% for choice in field.field.choices %}
{% if choice.2 %}
<div class="form-check">
{% if choice.2|meta_model_value:'label' == 'auth.User' %}
<label class="form-check-label d-flex align-items-center py-2" id="id_{{ field.id_for_label }}_{{ forloop.counter }}" for="id_{{ field.html_name }}_{{ forloop.counter }}">
<input type="checkbox" class="form-check-input"{% if choice.0 in field.value or choice.0|stringformat:"s" in field.value or choice.0|stringformat:"s" == field.value|default_if_none:""|stringformat:"s" %} checked="checked"{% endif %} name="{{ field.html_name }}" id="id_{{ field.html_name }}_{{ forloop.counter }}" value="{{ choice.0|unlocalize }}" {{ field.field.widget.attrs|flatatt }}>
<div class="d-inline-block">
{{choice.2.get_full_name}}
<i>({{choice.2}})</i><br>
<small>{{choice.2.email}}</small>
</div>
</label>
{% endif %}
</div>
{% else %}
{% if not inline_class %}<div class="form-check">{% endif %}
<label id="id_{{ field.id_for_label }}_{{ forloop.counter }}" class="form-check-{% if inline_class %}{{ inline_class }}{% else %}label{% endif %}" for="id_{{ field.html_name }}_{{ forloop.counter }}">
<input type="checkbox" class="form-check-input"{% if choice.0 in field.value or choice.0|stringformat:"s" in field.value or choice.0|stringformat:"s" == field.value|default_if_none:""|stringformat:"s" %} checked="checked"{% endif %} name="{{ field.html_name }}" id="id_{{ field.html_name }}_{{ forloop.counter }}" value="{{ choice.0|unlocalize }}" {{ field.field.widget.attrs|flatatt }}>
{{ choice.1|unlocalize }}
</label>
{% if not inline_class %}</div>{% endif %}
{% endif %}
{% endfor %}
{% include 'bootstrap4/layout/help_text.html' %}
</div>

6
sapl/templates/materia/proposicao_detail.html

@ -3,7 +3,7 @@
{% load tz %} {% load tz %}
{% block sub_actions %} {% block sub_actions %}
{{block.super}} {{block.super}}
{% if user == object.autor.user %} {% if user in object.autor.operadores.all %}
<div class="actions btn-group btn-group-sm {%block sub_actions_pull%}{% endblock%}" role="group"> <div class="actions btn-group btn-group-sm {%block sub_actions_pull%}{% endblock%}" role="group">
{% if object.texto_articulado.exists %} {% if object.texto_articulado.exists %}
<a class="btn btn-success" href="{% url 'sapl.materia:proposicao_ta' object.pk%}">{% trans "Texto Eletrônico" %}</a> <a class="btn btn-success" href="{% url 'sapl.materia:proposicao_ta' object.pk%}">{% trans "Texto Eletrônico" %}</a>
@ -15,7 +15,7 @@
{% endif %} {% endif %}
{% endblock sub_actions%} {% endblock sub_actions%}
{% block editions %} {% block editions %}
{% if user == object.autor.user %} {% if user in object.autor.operadores.all %}
{% if object.data_envio %} {% if object.data_envio %}
{% block editions_actions_return %} {% block editions_actions_return %}
<div class="actions btn-group" role="group"> <div class="actions btn-group" role="group">
@ -39,7 +39,7 @@
{% endif %} {% endif %}
{% endblock editions %} {% endblock editions %}
{% block detail_content %} {% block detail_content %}
{% if user == object.autor.user %} {% if user in object.autor.operadores.all %}
<h2 class="legend">{% model_verbose_name 'sapl.materia.models.Proposicao' %}</h2> <h2 class="legend">{% model_verbose_name 'sapl.materia.models.Proposicao' %}</h2>
<div class="row"> <div class="row">
<div class="col-sm-3"> <div class="col-sm-3">

9
sapl/templates/menu_tabelas_auxiliares.yaml

@ -8,15 +8,12 @@
- title: {% trans 'Configurações da Aplicação' %} - title: {% trans 'Configurações da Aplicação' %}
url: sapl.base:appconfig_list url: sapl.base:appconfig_list
css_class: btn btn-link css_class: btn btn-link
- title: {% trans 'Pesquisar Autor' %}
url: sapl.base:pesquisar_autor
css_class: btn btn-link
- title: {% trans 'Adicionar Autor' %}
url: sapl.base:autor_create
css_class: btn btn-link
- title: {% trans 'Tipo de Autor' %} - title: {% trans 'Tipo de Autor' %}
url: sapl.base:tipoautor_list url: sapl.base:tipoautor_list
css_class: btn btn-link css_class: btn btn-link
- title: {% trans 'Autores' %}
url: sapl.base:autor_list
css_class: btn btn-link
{% if not sapl_as_sapn%} {% if not sapl_as_sapn%}
- title: {% trans 'Módulo Parlamentares' %} - title: {% trans 'Módulo Parlamentares' %}
css_class: head_title css_class: head_title

27
sapl/utils.py

@ -28,7 +28,6 @@ from django.db import models
from django.db.models import Q from django.db.models import Q
from django.forms import BaseForm from django.forms import BaseForm
from django.forms.widgets import SplitDateTimeWidget from django.forms.widgets import SplitDateTimeWidget
from django.urls.base import clear_url_caches
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 _
@ -51,6 +50,32 @@ from sapl.settings import MAX_DOC_UPLOAD_SIZE
SEPARADOR_HASH_PROPOSICAO = 'K' SEPARADOR_HASH_PROPOSICAO = 'K'
def groups_remove_user(user, groups_name):
from django.contrib.auth.models import Group
if not isinstance(groups_name, list):
groups_name = [groups_name, ]
for group_name in groups_name:
if not group_name or not user.groups.filter(
name=group_name).exists():
continue
g = Group.objects.get_or_create(name=group_name)[0]
user.groups.remove(g)
def groups_add_user(user, groups_name):
from django.contrib.auth.models import Group
if not isinstance(groups_name, list):
groups_name = [groups_name, ]
for group_name in groups_name:
if not group_name or user.groups.filter(
name=group_name).exists():
continue
g = Group.objects.get_or_create(name=group_name)[0]
user.groups.add(g)
def num_materias_por_tipo(qs, attr_tipo='tipo'): def num_materias_por_tipo(qs, attr_tipo='tipo'):
""" """
:argument um QuerySet em MateriaLegislativa :argument um QuerySet em MateriaLegislativa

Loading…
Cancel
Save