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. 408
      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. 237
      sapl/base/views.py
  8. 118
      sapl/materia/tests/test_materia.py
  9. 36
      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. 64
      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)
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)
return qs

408
sapl/base/forms.py

@ -1,7 +1,6 @@
import logging
import os
from crispy_forms.bootstrap import FieldWithButtons, InlineRadios, StrictButton, FormActions
from crispy_forms.layout import HTML, Button, Div, Field, Fieldset, Layout, Row, Submit
from django import forms
@ -15,11 +14,12 @@ from django.db import models, transaction
from django.db.models import Q
from django.forms import Form, ModelForm
from django.utils import timezone
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
import django_filters
from sapl.audiencia.models import AudienciaPublica
from sapl.base.models import Autor, TipoAutor
from sapl.base.models import Autor, TipoAutor, OperadorAutor
from sapl.comissoes.models import Reuniao
from sapl.crispy_layout_mixin import (form_actions, to_column, to_row,
SaplFormHelper, SaplFormLayout)
@ -27,17 +27,18 @@ from sapl.materia.models import (DocumentoAcessorio, MateriaEmTramitacao,
MateriaLegislativa, UnidadeTramitacao,
StatusTramitacao)
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.rules import SAPL_GROUP_AUTOR, SAPL_GROUP_VOTANTE
from sapl.sessao.models import SessaoPlenaria
from sapl.settings import MAX_IMAGE_UPLOAD_SIZE
from sapl.utils import (autor_label, autor_modal, ChoiceWithoutValidationField,
choice_anos_com_normas, choice_anos_com_materias,
FilterOverridesMetaMixin, FileFieldCheckMixin,
AnoNumeroOrderingFilter, ImageThumbnailFileInput,
models_with_gr_for_model, qs_override_django_filter,
RangeWidgetOverride, RANGE_ANOS, YES_NO_CHOICES,
GoogleRecapthaMixin)
ImageThumbnailFileInput, qs_override_django_filter,
RANGE_ANOS, YES_NO_CHOICES,
GoogleRecapthaMixin, parlamentares_ativos)
from .models import AppConfig, CasaLegislativa
@ -85,6 +86,18 @@ class UserAdminForm(ModelForm):
max_length=40,
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:
model = get_user_model()
fields = [
@ -97,8 +110,11 @@ class UserAdminForm(ModelForm):
'new_password1',
'new_password2',
'groups',
'parlamentar',
'autor',
'groups',
'user_permissions',
]
@ -111,8 +127,7 @@ class UserAdminForm(ModelForm):
self.granular = kwargs.pop('granular', None)
self.instance = kwargs.get('instance', None)
row_pwd = to_row(
[
row_pwd = [
('username', 4),
('email', 6),
('is_active', 2),
@ -120,6 +135,10 @@ class UserAdminForm(ModelForm):
('last_name', 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),
]
if self.instance and self.instance.pk:
row_pwd += [
(
FieldWithButtons(
'token',
@ -129,13 +148,18 @@ class UserAdminForm(ModelForm):
css_class="btn-outline-primary"),
css_class='' if self.instance and self.instance.pk else 'd-none'),
6
),
)
]
row_pwd += [
('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.layout = SaplFormLayout(row_pwd)
@ -143,19 +167,33 @@ class UserAdminForm(ModelForm):
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:
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:
operadorautor = self.instance.operadorautor_set.first()
votante = self.instance.votante_set.first()
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 = [
(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(
user=self.instance).exclude(name__in=['Autor', 'Votante']).order_by('name')
user=self.instance).exclude(
name__in=['Autor', 'Votante']
).order_by('name')
]
self.fields[
@ -178,35 +216,61 @@ class UserAdminForm(ModelForm):
'codename')
]
# self.fields['user_permissions'].queryset = self.fields[
# 'user_permissions'].queryset.all().order_by('name')
def save(self, commit=True):
if self.cleaned_data['new_password1']:
self.instance.set_password(self.cleaned_data['new_password1'])
votante = None
permissions = None
votante = None
operadorautor = None
if self.instance.id:
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:
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_new.groups.add(votante)
if autor:
inst_new.groups.add(autor)
inst = super().save(commit)
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)
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_new
return inst
def clean(self):
data = super().clean()
@ -226,6 +290,29 @@ class UserAdminForm(ModelForm):
password_validation.validate_password(
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']:
duplicidade = get_user_model().objects.filter(email=data['email'])
if self.instance.id:
@ -236,7 +323,7 @@ class UserAdminForm(ModelForm):
"Email já cadastrado para: {}".format(
', '.join(map(lambda x: str(x), duplicidade.all())),
)
)
)"""
return data
@ -445,11 +532,6 @@ class AutorForm(ModelForm):
required=False,
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(
max_length=120, required=False,
label='Pesquise o nome do Autor com o '
@ -459,10 +541,14 @@ class AutorForm(ModelForm):
required=False,
widget=forms.RadioSelect())
action_user = forms.ChoiceField(
label=_('Usuário com acesso ao Sistema para este Autor'),
choices=ACTION_CREATE_USERS_AUTOR_CHOICE,
widget=forms.RadioSelect())
operadores = forms.ModelMultipleChoiceField(
queryset=get_user_model().objects.all(),
widget=forms.CheckboxSelectMultiple(),
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:
model = Autor
@ -471,8 +557,8 @@ class AutorForm(ModelForm):
'cargo',
'autor_related',
'q',
'action_user',
'username']
'operadores'
]
def __init__(self, *args, **kwargs):
@ -498,34 +584,41 @@ class AutorForm(ModelForm):
Field('autor_related'),
css_class='radiogroup-autor-related hidden'),
12)))
row2 = Row(to_column((InlineRadios('action_user'), 8)),
to_column((Div('username'), 4)))
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)
operadores_select = to_row(
[
('operadores', 12)
]
)
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)
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.autor_related:
@ -537,35 +630,6 @@ class AutorForm(ModelForm):
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):
if texto1 != texto2:
self.logger.warning(
@ -580,76 +644,13 @@ class AutorForm(ModelForm):
if not self.is_valid():
return self.cleaned_data
User = get_user_model()
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()
if self.instance.pk:
qs_autor = qs_autor.exclude(pk=self.instance.pk)
if self.instance.user:
qs_user = qs_user.exclude(pk=self.instance.user.pk)
if cd['action_user'] == 'A':
param_username = {get_user_model().USERNAME_FIELD: cd['username']}
if not User.objects.filter(**param_username).exists():
self.logger.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']:
self.logger.warning('Tipo do Autor não selecionado.')
raise ValidationError(
@ -699,21 +700,8 @@ class AutorForm(ModelForm):
return self.cleaned_data
@transaction.atomic
def save(self, commit=False):
autor = super(AutorForm, self).save(commit)
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
def save(self, commit=True):
autor = self.instance
if not autor.tipo.content_type:
autor.content_type = None
@ -724,33 +712,7 @@ class AutorForm(ModelForm):
).objects.get(pk=self.cleaned_data['autor_related'])
autor.nome = str(autor.autor_related)
autor.save()
# 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)
autor = super(AutorForm, self).save(commit)
return autor
@ -776,26 +738,36 @@ class AutorFilterSet(django_filters.FilterSet):
form_actions(label='Pesquisar')))
class AutorFormForAdmin(AutorForm):
status_user = forms.ChoiceField(
label=_('Bloqueio do Usuário Existente'),
choices=STATUS_USER_CHOICE,
widget=forms.RadioSelect(),
required=False,
help_text=_('Se vc está trocando ou removendo o usuário deste Autor, '
'como o Sistema deve proceder com o usuário que está sendo'
' desvinculado?'))
class OperadorAutorForm(ModelForm):
class Meta:
model = Autor
fields = ['tipo',
'nome',
'cargo',
'autor_related',
'q',
'action_user',
'username',
'status_user']
model = OperadorAutor
fields = ['user', ]
def __init__(self, *args, **kwargs):
row = to_row([('user', 12)])
self.helper = SaplFormHelper()
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):

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.models import ContentType
from django.db import models
from django.db.models.deletion import CASCADE
from django.db.models.signals import post_migrate
from django.db.utils import DEFAULT_DB_ALIAS
from django.utils.translation import ugettext_lazy as _
@ -9,6 +10,7 @@ import reversion
from sapl.utils import (LISTA_DE_UFS, YES_NO_CHOICES,
get_settings_auth_user_model, models_with_gr_for_model)
DOC_ADM_OSTENSIVO = 'O'
DOC_ADM_RESTRITIVO = 'R'
@ -256,10 +258,14 @@ class TipoAutor(models.Model):
@reversion.register()
class Autor(models.Model):
user = models.OneToOneField(
operadores = models.ManyToManyField(
get_settings_auth_user_model(),
on_delete=models.SET_NULL,
null=True)
through='OperadorAutor',
through_fields=('autor', 'user'),
symmetrical=False,
related_name='autor_set',
verbose_name='Operadores')
tipo = models.ForeignKey(
TipoAutor,
verbose_name=_('Tipo do Autor'),
@ -298,11 +304,40 @@ class Autor(models.Model):
return '{} - {}'.format(self.nome, self.cargo)
else:
return str(self.nome)
if self.user:
return str(self.user.username)
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):
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 sapl.base.views import (AutorCrud, ConfirmarEmailView, TipoAutorCrud, get_estatistica,
PesquisarAutorView, RecuperarSenhaEmailView, RecuperarSenhaFinalizadoView,
RecuperarSenhaEmailView, RecuperarSenhaFinalizadoView,
RecuperarSenhaConfirmaView, RecuperarSenhaCompletoView, RelatorioMateriaAnoAssuntoView,
IndexView, UserCrud)
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/', include(AutorCrud.get_urls())),
url(r'^sistema/autor/pesquisar-autor/',
PesquisarAutorView.as_view(), name='pesquisar_autor'),
url(r'^sistema/ajuda/(?P<topic>\w+)$',
HelpTopicView.as_view(), name='help_topic'),

237
sapl/base/views.py

@ -8,16 +8,17 @@ import os
from django.contrib import messages
from django.contrib.auth import get_user_model
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.views import (PasswordResetView, PasswordResetConfirmView, PasswordResetCompleteView,
PasswordResetDoneView)
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied, ValidationError
from django.core.mail import send_mail
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.shortcuts import render, redirect
from django.shortcuts import redirect
from django.template import TemplateDoesNotExist
from django.template.loader import get_template
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.http import urlsafe_base64_decode, urlsafe_base64_encode
from django.utils.translation import ugettext_lazy as _
from django.views.generic import (
CreateView, DetailView, DeleteView, FormView, ListView, UpdateView)
from django.views.generic import (FormView, ListView)
from django.views.generic.base import RedirectView, TemplateView
from django_filters.views import FilterView
from haystack.query import SearchQuerySet
from haystack.views import SearchView
from rest_framework.authtoken.models import Token
from sapl import settings
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica
from sapl.base.forms import (AutorForm, AutorFormForAdmin, TipoAutorForm, AutorFilterSet, RecuperarSenhaForm,
NovaSenhaForm, UserAdminForm)
from sapl.base.models import Autor, TipoAutor
from sapl.base.forms import (AutorForm, TipoAutorForm, AutorFilterSet, RecuperarSenhaForm,
NovaSenhaForm, UserAdminForm,
OperadorAutorForm)
from sapl.base.models import Autor, TipoAutor, OperadorAutor
from sapl.comissoes.models import Comissao, Reuniao
from sapl.crud.base import CrudAux, make_pagination, Crud,\
ListWithSearchForm
ListWithSearchForm, MasterDetailCrud
from sapl.materia.models import (Anexada, Autoria, DocumentoAcessorio, MateriaEmTramitacao, MateriaLegislativa,
Proposicao, StatusTramitacao, TipoDocumento, TipoMateriaLegislativa, UnidadeTramitacao,
MateriaAssunto)
@ -61,7 +61,8 @@ from sapl.sessao.models import (
from sapl.settings import EMAIL_SEND_USER
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,
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,
RelatorioAudienciaFilterSet, RelatorioDataFimPrazoTramitacaoFilterSet,
@ -193,124 +194,33 @@ class AutorCrud(CrudAux):
help_topic = 'autor'
class BaseMixin(CrudAux.BaseMixin):
list_field_names = ['tipo', 'nome', 'user']
list_field_names = ['nome', 'tipo', 'operadores']
class DeleteView(CrudAux.DeleteView):
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):
def send_mail_operadores(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
return
try:
self.logger.debug('user=' + username +
'. Enviando email na criação de Autores.')
kwargs = {}
user = self.object.user
if not user:
return url_reverse
for user in self.object.operadores.all():
if not user.email:
self.logger.warning(
_('Registro de Autor sem envio de email. '
'Usuário sem um email cadastrado.'))
continue
kwargs['token'] = default_token_generator.make_token(user)
kwargs['uidb64'] = urlsafe_base64_encode(force_bytes(user.pk))
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]
@ -335,61 +245,82 @@ class AutorCrud(CrudAux):
self.logger.error(
'user=' + username + '. Erro no envio de email na criação de Autores. ' + str(e))
return url_reverse
class DeleteView(CrudAux.DeleteView):
class PesquisarAutorView(FilterView):
model = Autor
filterset_class = AutorFilterSet
paginate_by = 10
def delete(self, *args, **kwargs):
self.object = self.get_object()
def get_filterset_kwargs(self, filterset_class):
super().get_filterset_kwargs(filterset_class)
grupo = Group.objects.filter(name='Autor')[0]
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({
'queryset': qs,
})
return kwargs
return response
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
class DetailView(CrudAux.DetailView):
paginator = context['paginator']
page_obj = context['page_obj']
def hook_operadores(self, 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):
super().get(request)
def get_success_url(self):
self.send_mail_operadores()
return super().get_success_url()
data = self.filterset.data
url = ''
if data:
url = "&" + str(self.request.META['QUERY_STRING'])
if url.startswith("&page"):
ponto_comeco = url.find('nome=') - 1
url = url[ponto_comeco:]
class ListView(CrudAux.ListView):
form_search_class = ListWithSearchForm
context = self.get_context_data(filter=self.filterset,
object_list=self.object_list,
filter_url=url,
numero_res=len(self.object_list))
def hook_operadores(self, *args, **kwargs):
r = '<ul>{}</ul>'.format(
''.join(
[
'<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):

118
sapl/materia/tests/test_materia.py

@ -1,22 +1,23 @@
from datetime import date
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.files.uploadedfile import SimpleUploadedFile
from django.urls import reverse
from django.db.models import Max
from django.urls import reverse
from model_bakery import baker
import pytest
from sapl.base.models import Autor, TipoAutor, AppConfig
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,
DocumentoAcessorio, MateriaLegislativa,
Numeracao, Proposicao, RegimeTramitacao,
StatusTramitacao, TipoDocumento,
TipoMateriaLegislativa, TipoProposicao,
Tramitacao, UnidadeTramitacao)
from sapl.materia.forms import (TramitacaoForm, compara_tramitacoes_mat,
TramitacaoUpdateForm)
from sapl.norma.models import (LegislacaoCitada, NormaJuridica,
TipoNormaJuridica)
from sapl.parlamentares.models import Legislatura
@ -123,6 +124,7 @@ def test_lista_materias_anexadas_ciclo():
assert len(lista) == 1
assert lista[0] == materia_anexada
@pytest.mark.django_db(transaction=False)
def make_unidade_tramitacao(descricao):
# 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,
user=user,
tipo=tipo_autor,
nome='Autor Teste')
autor.operadores.add(user)
file_content = 'file_content'
texto = SimpleUploadedFile("file.txt", file_content.encode('UTF-8'))
@ -597,9 +599,9 @@ def test_form_errors_proposicao(admin_client):
autor = baker.make(
Autor,
user=user,
tipo=tipo_autor,
nome='Autor Teste')
autor.operadores.add(user)
file_content = 'file_content'
texto = SimpleUploadedFile("file.txt", file_content.encode('UTF-8'))
@ -730,7 +732,6 @@ def test_tramitacoes_materias_anexadas(admin_client):
data_anexacao="2020-11-05"
)
unidade_tramitacao_local_1 = make_unidade_tramitacao(descricao="Teste 1")
unidade_tramitacao_destino_1 = make_unidade_tramitacao(descricao="Teste 2")
unidade_tramitacao_destino_2 = make_unidade_tramitacao(descricao="Teste 3")
@ -752,20 +753,25 @@ def test_tramitacoes_materias_anexadas(admin_client):
assert form.is_valid()
tramitacao_principal = form.save()
tramitacao_anexada = materia_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first()
# 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")
tramitacao_anexada = materia_anexada.tramitacao_set.order_by(
'-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by(
'-data_tramitacao', '-id').first()
# 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 MateriaLegislativa.objects.get(id=materia_anexada.pk).em_tramitacao \
== (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 \
== (tramitacao_anexada_anexada.status.indicador != "F")
# Teste Edição de Tramitacao
form = TramitacaoUpdateForm(data={})
# Alterando unidade_tramitacao_destino
@ -779,22 +785,23 @@ def test_tramitacoes_materias_anexadas(admin_client):
assert form.is_valid()
tramitacao_principal = form.save()
tramitacao_anexada = materia_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first()
tramitacao_anexada = materia_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_2
assert tramitacao_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2
assert tramitacao_anexada_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2
# Teste Remoção de Tramitacao
url = reverse('sapl.materia:tramitacao_delete',
kwargs={'pk': tramitacao_principal.pk})
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_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
form = TramitacaoForm(data={})
@ -809,8 +816,10 @@ def test_tramitacoes_materias_anexadas(admin_client):
assert form.is_valid()
tramitacao_principal = form.save()
tramitacao_anexada = materia_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first()
tramitacao_anexada = materia_anexada.tramitacao_set.order_by(
'-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by(
'-data_tramitacao', '-id').first()
form = TramitacaoUpdateForm(data={})
# Alterando unidade_tramitacao_destino
@ -825,13 +834,15 @@ def test_tramitacoes_materias_anexadas(admin_client):
assert form.is_valid()
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_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={})
# Alterando o texto
form.data = {'data_tramitacao': tramitacao_principal.data_tramitacao,
@ -844,28 +855,33 @@ def test_tramitacoes_materias_anexadas(admin_client):
assert form.is_valid()
tramitacao_principal = form.save()
tramitacao_anexada = materia_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first()
tramitacao_anexada = materia_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.texto == "Testando a alteração"
assert not tramitacao_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',
kwargs={'pk': tramitacao_principal.pk})
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_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',
kwargs={'pk': tramitacao_anexada.pk})
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_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
# junto com as matérias principais
@ -888,16 +904,19 @@ def test_tramitacoes_materias_anexadas(admin_client):
assert form.is_valid()
tramitacao_principal = form.save()
tramitacao_anexada = materia_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first()
tramitacao_anexada = materia_anexada.tramitacao_set.order_by(
'-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
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_anexada
# Criação de uma tramitação igual para a anexada à principal para testar a edição
# Criação de uma tramitação igual para a anexada à principal para testar a
# edição
form = TramitacaoForm(data={})
form.data = {'data_tramitacao': date(2019, 5, 6),
'unidade_tramitacao_local': unidade_tramitacao_local_1.pk,
@ -910,11 +929,15 @@ def test_tramitacoes_materias_anexadas(admin_client):
assert form.is_valid()
tramitacao_anexada = form.save()
tramitacao_principal = materia_principal.tramitacao_set.order_by('-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first()
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
tramitacao_principal = materia_principal.tramitacao_set.order_by(
'-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by(
'-data_tramitacao', '-id').first()
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 not tramitacao_anexada_anexada
@ -931,8 +954,10 @@ def test_tramitacoes_materias_anexadas(admin_client):
assert form.is_valid()
tramitacao_principal = form.save()
tramitacao_anexada = materia_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first()
tramitacao_anexada = materia_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_2
assert tramitacao_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_1
@ -951,8 +976,10 @@ def test_tramitacoes_materias_anexadas(admin_client):
assert form.is_valid()
tramitacao_anexada = form.save()
tramitacao_principal = materia_principal.tramitacao_set.order_by('-data_tramitacao', '-id').first()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.order_by('-data_tramitacao', '-id').first()
tramitacao_principal = materia_principal.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_2
assert tramitacao_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2
@ -960,7 +987,8 @@ def test_tramitacoes_materias_anexadas(admin_client):
assert compara_tramitacoes_mat(tramitacao_principal, tramitacao_anexada)
# 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',
kwargs={'pk': tramitacao_principal.pk})
response = admin_client.post(url, {'confirmar': 'confirmar'}, follow=True)

36
sapl/materia/views.py

@ -1,14 +1,11 @@
from datetime import datetime
from datetime import datetime
from io import BytesIO
import itertools
import logging
import os
from random import choice
import shutil
from string import ascii_letters, digits
import tempfile
import time
import zipfile
@ -24,7 +21,7 @@ from django.http import HttpResponse, JsonResponse
from django.http.response import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.shortcuts import render
from django.template import loader, RequestContext
from django.template import loader
from django.urls import reverse
from django.utils import formats, timezone
from django.utils.translation import ugettext_lazy as _
@ -37,7 +34,7 @@ import weasyprint
import sapl
from sapl.base.email_utils import do_envia_email_confirmacao
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.views import IntegracaoTaView
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,
get_client_ip, get_mime_type_from_file_extension, lista_anexados,
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)
from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm,
@ -107,7 +104,10 @@ def proposicao_texto(request, pk):
if proposicao.texto_original:
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."
.format(request.user.id))
messages.error(request, _(
@ -322,7 +322,8 @@ class ProposicaoTaView(IntegracaoTaView):
proposicao = get_object_or_404(self.model, pk=kwargs['pk'])
if not proposicao.data_envio and \
request.user != proposicao.autor.user:
not proposicao.autor.operadores.filter(
id=request.user.id).exists():
raise Http404()
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']))
raise Http404()
if p.autor.user != request.user:
if not p.autor.operadores.filter(id=request.user.id).exists():
self.logger.error(
"user=" + username + ". Usuário ({}) sem acesso a esta opção.".format(request.user))
messages.error(
@ -808,7 +809,7 @@ class UnidadeTramitacaoCrud(CrudAux):
class ProposicaoCrud(Crud):
model = Proposicao
help_topic = 'proposicao'
container_field = 'autor__user'
container_field = 'autor__operadores'
class BaseMixin(Crud.BaseMixin):
list_field_names = [
@ -877,7 +878,7 @@ class ProposicaoCrud(Crud):
p = Proposicao.objects.get(id=kwargs['pk'])
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 p.data_envio and p.data_recebimento:
msg_error = _('Proposição já foi enviada e recebida.')
@ -986,7 +987,7 @@ class ProposicaoCrud(Crud):
if not self.has_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:
raise Http404()
@ -1187,7 +1188,7 @@ class ReciboProposicaoView(TemplateView):
return (Proposicao.objects.filter(
id=self.kwargs['pk'],
autor__user_id=self.request.user.id).exists())
autor__operadores=self.request.user).exists())
def get_context_data(self, **kwargs):
context = super(ReciboProposicaoView, self).get_context_data(
@ -1250,7 +1251,8 @@ class HistoricoProposicaoView(PermissionRequiredMixin, ListView):
return qs
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']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
@ -1433,7 +1435,8 @@ class TramitacaoCrud(MasterDetailCrud):
logger = logging.getLogger(__name__)
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
url = reverse('sapl.materia:tramitacao_list',
kwargs={'pk': materia.id})
@ -2053,7 +2056,8 @@ class MateriaLegislativaPesquisaView(FilterView):
"tipo",)
# 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:
qs = qs.filter(materiaassunto__isnull=True)

9
sapl/parlamentares/forms.py

@ -219,11 +219,12 @@ class ParlamentarForm(FileFieldCheckMixin, ModelForm):
def save(self, commit=True):
parlamentar = super().save()
autor = parlamentar.autor.first()
usuario = autor.user if autor else None
if autor and usuario:
usuario.is_active = parlamentar.ativo
usuario.save()
if autor:
usuarios = autor.operadores.all()
for u in usuarios:
u.is_active = parlamentar.ativo
u.save()
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):
parlamentar = models.ForeignKey(
Parlamentar, verbose_name=_('Parlamentar'),
on_delete=models.PROTECT, related_name='parlamentar')
on_delete=models.PROTECT, related_name='votante_set')
user = models.ForeignKey(
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(
verbose_name=_('Data'), auto_now_add=True,
max_length=30, null=True, blank=True)

64
sapl/parlamentares/views.py

@ -4,15 +4,16 @@ import logging
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType
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.aggregates import Count
from django.http import JsonResponse
from django.http.response import HttpResponseRedirect
from django.shortcuts import render
from django.templatetags.static import static
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.utils.datastructures import MultiValueDictKeyError
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 image_cropping.utils import get_backend
from sapl.base.forms import SessaoLegislativaForm, PartidoForm
from sapl.base.models import Autor
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)
from sapl.materia.models import Autoria, Proposicao, Relatoria
from sapl.parlamentares.apps import AppConfig
from sapl.rules import SAPL_GROUP_VOTANTE
from sapl.utils import (parlamentares_ativos, show_results_filter_set)
from .forms import (ColigacaoFilterSet, FiliacaoForm, FrenteForm, LegislaturaForm, MandatoForm,
ParlamentarCreateForm, ParlamentarForm, VotanteForm,
ParlamentarFilterSet, PartidoFilterSet, VincularParlamentarForm,
BlocoForm, FrenteParlamentarForm, BlocoMembroForm)
from .models import (CargoMesa, Coligacao, ComposicaoColigacao, ComposicaoMesa,
Dependente, Filiacao, Frente, Legislatura, Mandato,
NivelInstrucao, Parlamentar, Partido, SessaoLegislativa,
@ -53,7 +53,8 @@ NivelInstrucaoCrud = CrudAux.build(NivelInstrucao, 'nivel_instrucao')
TipoAfastamentoCrud = CrudAux.build(TipoAfastamento, 'tipo_afastamento')
TipoMilitarCrud = CrudAux.build(SituacaoMilitar, 'tipo_situa_militar')
DependenteCrud = MasterDetailCrud.build(Dependente, 'parlamentar', 'dependente')
DependenteCrud = MasterDetailCrud.build(
Dependente, 'parlamentar', 'dependente')
class SessaoLegislativaCrud(CrudAux):
@ -101,6 +102,10 @@ class VotanteView(MasterDetailCrud):
def delete(self, *args, **kwargs):
obj = self.get_object()
g = Group.objects.filter(name=SAPL_GROUP_VOTANTE)[0]
obj.user.groups.remove(g)
obj.delete()
return HttpResponseRedirect(
reverse('sapl.parlamentares:votante_list',
@ -247,9 +252,9 @@ class PesquisarColigacaoView(FilterView):
'queryset': self.get_queryset().order_by('nome').distinct()
})
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']
page_obj = context['page_obj']
@ -278,7 +283,8 @@ class PesquisarColigacaoView(FilterView):
filter_url=url,
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)
@ -296,7 +302,6 @@ class PesquisarPartidoView(FilterView):
'queryset': self.get_queryset().order_by('nome').distinct()
})
def get_context_data(self, **kwargs):
context = super(PesquisarPartidoView, self).get_context_data(**kwargs)
@ -326,7 +331,8 @@ class PesquisarPartidoView(FilterView):
object_list=self.object_list,
filter_url=url,
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)
@ -417,11 +423,13 @@ class ColigacaoCrud(CrudAux):
def coligacao_legislatura(request):
try:
coligacoes = Coligacao.objects.filter(legislatura=request.GET['legislatura']).order_by('nome')
coligacoes = Coligacao.objects.filter(
legislatura=request.GET['legislatura']).order_by('nome')
except:
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})
@ -746,7 +754,8 @@ class ParlamentarCrud(Crud):
". Tentando obter id da legislatura.")
return int(self.request.GET['pk'])
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()
for l in legislaturas:
if l.atual():
@ -974,7 +983,8 @@ def altera_field_mesa(request):
# é alterado o campo de sessão ou feita alguma operação
# de inclusão/remoção.
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
# atual deve ser a primeira daquela legislatura
@ -1183,19 +1193,21 @@ def altera_field_mesa_public_view(request):
else:
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:
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')
if not sessao_selecionada:
try:
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
except ObjectDoesNotExist:
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
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')
cargos_ocupados = list(composicao_mesa.values_list('cargo__id', 'cargo__descricao'))
parlamentares_ocupados = list(composicao_mesa.values_list('parlamentar__id', 'parlamentar__nome_parlamentar'))
composicao_mesa = ComposicaoMesa.objects.select_related('cargo', 'parlamentar').filter(
sessao_legislativa=sessao_selecionada).order_by('cargo_id')
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_partidos = []
@ -1215,7 +1230,8 @@ def altera_field_mesa_public_view(request):
sessao = SessaoLegislativa.objects.get(id=sessao_selecionada)
for p in parlamentares_ocupados:
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:
try:
thumbnail_url = get_backend().get_thumbnail_url(
@ -1230,7 +1246,8 @@ def altera_field_mesa_public_view(request):
lista_fotos.append(thumbnail_url)
except Exception as e:
logger.error(e)
logger.error(F'erro processando arquivo: {parlamentar.fotografia.path}')
logger.error(
F'erro processando arquivo: {parlamentar.fotografia.path}')
else:
lista_fotos.append(None)
@ -1262,7 +1279,8 @@ class VincularParlamentarView(PermissionRequiredMixin, FormView):
'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:
kwargs.update({'data_expedicao_diploma': data_expedicao_diploma})

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_GERAL = _("Operador Geral")
SAPL_GROUP_AUTOR = _("Autor")
SAPL_GROUP_PARLAMENTAR = _("Parlamentar")
SAPL_GROUP_VOTANTE = _("Votante")
# 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_GERAL,
SAPL_GROUP_AUTOR,
SAPL_GROUP_PARLAMENTAR,
SAPL_GROUP_VOTANTE,
SAPL_GROUP_LOGIN_SOCIAL,
SAPL_GROUP_ANONYMOUS,
]
SAPL_GROUPS_DELETE = [
]

10
sapl/rules/apps.py

@ -167,6 +167,7 @@ def get_rules():
print(group_name, e)
def groups_add_user(self, user, groups_name):
if not isinstance(groups_name, list):
groups_name = [groups_name, ]
for group_name in groups_name:
@ -211,7 +212,8 @@ def get_rules():
def update_groups(self):
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:
group_name = rules_group['group']
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=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")
models.signals.post_migrate.connect(
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_GERAL, SAPL_GROUP_LOGIN_SOCIAL,
SAPL_GROUP_MATERIA, SAPL_GROUP_NORMA,
SAPL_GROUP_PAINEL, SAPL_GROUP_PARLAMENTAR,
SAPL_GROUP_PAINEL,
SAPL_GROUP_PROTOCOLO, SAPL_GROUP_SESSAO,
SAPL_GROUP_VOTANTE)
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 = {
'group': SAPL_GROUP_VOTANTE,
'rules': [
@ -235,6 +230,7 @@ rules_group_geral = {
[RP_ADD], __perms_publicas__),
(base.TipoAutor, __base__, __perms_publicas__),
(base.Autor, __base__, __perms_publicas__),
(base.OperadorAutor, __base__, __perms_publicas__),
(base.AuditLog, __base__, set()),
(protocoloadm.StatusTramitacaoAdministrativo, __base__, set()),
@ -370,7 +366,6 @@ rules_patterns = [
rules_group_painel,
rules_group_geral,
rules_group_autor,
rules_group_parlamentar,
rules_group_votante,
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);
});
$('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)
$('input[name=autor_related]').closest('.radio').remove();
var pk = $('#id_tipo').val();
if (pk)
update_search(pk, $('#id_q').val().length > 0)
});
</script>
{% endblock %}

7
sapl/templates/base/layouts.yaml

@ -13,7 +13,7 @@ CasaLegislativa:
UserDetail:
{% trans 'Usuário' %}:
- 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
- user_permissions
@ -72,7 +72,12 @@ Autor:
{% trans 'Autor' %}:
- tipo:3 nome
- cargo
- operadores
AutorCreate:
{% trans 'Cadastro de Usuários Autores' %}:
- 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 %}
{% block sub_actions %}
{{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">
{% if object.texto_articulado.exists %}
<a class="btn btn-success" href="{% url 'sapl.materia:proposicao_ta' object.pk%}">{% trans "Texto Eletrônico" %}</a>
@ -15,7 +15,7 @@
{% endif %}
{% endblock sub_actions%}
{% block editions %}
{% if user == object.autor.user %}
{% if user in object.autor.operadores.all %}
{% if object.data_envio %}
{% block editions_actions_return %}
<div class="actions btn-group" role="group">
@ -39,7 +39,7 @@
{% endif %}
{% endblock editions %}
{% 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>
<div class="row">
<div class="col-sm-3">

9
sapl/templates/menu_tabelas_auxiliares.yaml

@ -8,15 +8,12 @@
- title: {% trans 'Configurações da Aplicação' %}
url: sapl.base:appconfig_list
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' %}
url: sapl.base:tipoautor_list
css_class: btn btn-link
- title: {% trans 'Autores' %}
url: sapl.base:autor_list
css_class: btn btn-link
{% if not sapl_as_sapn%}
- title: {% trans 'Módulo Parlamentares' %}
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.forms import BaseForm
from django.forms.widgets import SplitDateTimeWidget
from django.urls.base import clear_url_caches
from django.utils import six, timezone
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
@ -51,6 +50,32 @@ from sapl.settings import MAX_DOC_UPLOAD_SIZE
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'):
"""
:argument um QuerySet em MateriaLegislativa

Loading…
Cancel
Save