diff --git a/sapl/api/views.py b/sapl/api/views.py index a5ebaabf5..6d506e6bf 100644 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -182,63 +182,63 @@ class SaplApiViewSetConstrutor(): SaplApiViewSetConstrutor.build_class() """ -1. Constroi uma rest_framework.viewsets.ModelViewSet para +1. Constroi uma rest_framework.viewsets.ModelViewSet para todos os models de todas as apps do sapl 2. Define DjangoFilterBackend como ferramenta de filtro dos campos 3. Define Serializer como a seguir: 3.1 - Define um Serializer genérico para cada módel 3.2 - Recupera Serializer customizado em sapl.api.serializers - 3.3 - Para todo model é opcional a existência de + 3.3 - Para todo model é opcional a existência de sapl.api.serializers.{model}Serializer. Caso não seja definido um Serializer customizado, utiliza-se o trivial 4. Define um FilterSet como a seguir: 4.1 - Define um FilterSet genérico para cada módel 4.2 - Recupera FilterSet customizado em sapl.api.forms - 4.3 - Para todo model é opcional a existência de + 4.3 - Para todo model é opcional a existência de sapl.api.forms.{model}FilterSet. Caso não seja definido um FilterSet customizado, utiliza-se o trivial - 4.4 - todos os campos que aceitam lookup 'exact' + 4.4 - todos os campos que aceitam lookup 'exact' podem ser filtrados por default - + 5. SaplApiViewSetConstrutor não cria padrões e/ou exige conhecimento alem dos - exigidos pela DRF. - + exigidos pela DRF. + 6. As rotas são criadas seguindo nome da app e nome do model http://localhost:9000/api/{applabel}/{model_name}/ e seguem as variações definidas em: https://www.django-rest-framework.org/api-guide/routers/#defaultrouter - + 7. Todas as viewsets construídas por SaplApiViewSetConstrutor e suas rotas (paginate list, detail, edit, create, delete) bem como testes em ambiente de desenvolvimento podem ser conferidas em: - http://localhost:9000/api/ + http://localhost:9000/api/ desde que settings.DEBUG=True **SaplApiViewSetConstrutor._built_sets** é um dict de dicts de models conforme: { ... - + 'audiencia': { 'tipoaudienciapublica': TipoAudienciaPublicaViewSet, 'audienciapublica': AudienciaPublicaViewSet, 'anexoaudienciapublica': AnexoAudienciaPublicaViewSet - + ... - + }, - + ... - + 'base': { 'casalegislativa': CasaLegislativaViewSet, 'appconfig': AppConfigViewSet, - + ... - + } - + ... - + } """ @@ -315,17 +315,17 @@ class customize(object): @customize(Autor) class _AutorViewSet: """ - Neste exemplo de customização do que foi criado em - SaplApiViewSetConstrutor além do ofertado por + Neste exemplo de customização do que foi criado em + SaplApiViewSetConstrutor além do ofertado por rest_framework.viewsets.ModelViewSet, dentre outras customizações possíveis, foi adicionado as rotas referentes aos relacionamentos genéricos * padrão de ModelViewSet /api/base/autor/ POST - create - /api/base/autor/ GET - list - /api/base/autor/{pk}/ GET - detail - /api/base/autor/{pk}/ PUT - update - /api/base/autor/{pk}/ PATCH - partial_update + /api/base/autor/ GET - list + /api/base/autor/{pk}/ GET - detail + /api/base/autor/{pk}/ PUT - update + /api/base/autor/{pk}/ PATCH - partial_update /api/base/autor/{pk}/ DELETE - destroy * rotas desta classe local criadas pelo método build: @@ -336,7 +336,7 @@ class _AutorViewSet: /api/base/autor/bloco devolve apenas autores que são blocos parlamentares /api/base/autor/bancada - devolve apenas autores que são bancadas parlamentares + devolve apenas autores que são bancadas parlamentares /api/base/autor/frente devolve apenas autores que são Frene parlamentares /api/base/autor/orgao @@ -485,7 +485,7 @@ class _ProposicaoViewSet: * Permissões: * Usuário Dono: - * Pode listar todas suas Proposições + * Pode listar todas suas Proposições * Usuário Conectado ou Anônimo: * Pode listar todas as Proposições incorporadas @@ -496,7 +496,7 @@ class _ProposicaoViewSet: * Permissões: * Usuário Dono: - * Pode recuperar qualquer de suas Proposições + * Pode recuperar qualquer de suas Proposições * Usuário Conectado ou Anônimo: * Pode recuperar qualquer das proposições incorporadas @@ -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 @@ -586,7 +596,7 @@ class _DocumentoAdministrativoViewSet: """ Diante da lógica implementada na manutenção de documentos administrativos: - - Se o comportamento é doc adm ostensivo, deve passar pelo + - Se o comportamento é doc adm ostensivo, deve passar pelo teste de permissões sem avaliá-las - se o comportamento é doc adm restritivo, deve passar pelo teste de permissões avaliando-as @@ -599,7 +609,7 @@ class _DocumentoAdministrativoViewSet: """ mesmo tendo passado pelo teste de permissões, deve ser filtrado, pelo campo restrito. Sendo este igual a True, disponibilizar apenas - a um usuário conectado. Apenas isso, sem critérios outros de permissão, + a um usuário conectado. Apenas isso, sem critérios outros de permissão, conforme implementado em DocumentoAdministrativoCrud """ qs = super().get_queryset() diff --git a/sapl/base/forms.py b/sapl/base/forms.py index e56dd7a3b..1dd54e3a7 100644 --- a/sapl/base/forms.py +++ b/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,15 +127,18 @@ class UserAdminForm(ModelForm): self.granular = kwargs.pop('granular', None) self.instance = kwargs.get('instance', None) - row_pwd = to_row( - [ - ('username', 4), - ('email', 6), - ('is_active', 2), - ('first_name', 6), - ('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), + row_pwd = [ + ('username', 4), + ('email', 6), + ('is_active', 2), + ('first_name', 6), + ('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 - ), + ) + ] - ('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.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) - 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): 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 {} ' + 'já está associado a outro usuário: {}.
' + '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 já - mostrar a mensagem de obrigatório junto ao campo. mas foi colocado - ainda assim para renderizar um message.danger no topo do form. - """ if 'tipo' not in cd or not cd['tipo']: self.logger.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): diff --git a/sapl/base/migrations/0046_auto_20210314_1532.py b/sapl/base/migrations/0046_auto_20210314_1532.py new file mode 100644 index 000000000..d5f558db3 --- /dev/null +++ b/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', + ), + + ] diff --git a/sapl/base/migrations/0047_auto_20210315_1522.py b/sapl/base/migrations/0047_auto_20210315_1522.py new file mode 100644 index 000000000..23a576231 --- /dev/null +++ b/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'), + ), + ] diff --git a/sapl/base/models.py b/sapl/base/models.py index ae5db11a8..fb2a3bd66 100644 --- a/sapl/base/models.py +++ b/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') diff --git a/sapl/base/urls.py b/sapl/base/urls.py index 992b0d05b..b85c55a44 100644 --- a/sapl/base/urls.py +++ b/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\w+)$', HelpTopicView.as_view(), name='help_topic'), diff --git a/sapl/base/views.py b/sapl/base/views.py index 3bb3d47fe..447509882 100644 --- a/sapl/base/views.py +++ b/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,203 +194,133 @@ 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 - - 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) + + 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)) + 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: print( _('Erro no envio de email na criação de Autores.')) 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 = ''.format( + ''.join( + [ + '
  • {} - ({}) - ' + '{}' + '
  • '.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 = ''.format( + ''.join( + [ + '
  • {} - ({})
  • '.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): diff --git a/sapl/materia/tests/test_materia.py b/sapl/materia/tests/test_materia.py index 99e372149..fab1742e0 100644 --- a/sapl/materia/tests/test_materia.py +++ b/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 @@ -25,57 +26,57 @@ from sapl.utils import models_with_gr_for_model, lista_anexados @pytest.mark.django_db(transaction=False) def test_lista_materias_anexadas(): - tipo_materia = baker.make( - TipoMateriaLegislativa, - descricao="Tipo_Teste" - ) - regime_tramitacao = baker.make( - RegimeTramitacao, - descricao="Regime_Teste" - ) - materia_principal = baker.make( - MateriaLegislativa, - numero=20, - ano=2018, - data_apresentacao="2018-01-04", - regime_tramitacao=regime_tramitacao, - tipo=tipo_materia - ) - materia_anexada = baker.make( - MateriaLegislativa, - numero=21, - ano=2019, - data_apresentacao="2019-05-04", - regime_tramitacao=regime_tramitacao, - tipo=tipo_materia - ) - materia_anexada_anexada = baker.make( - MateriaLegislativa, - numero=22, - ano=2020, - data_apresentacao="2020-01-05", - regime_tramitacao=regime_tramitacao, - tipo=tipo_materia - ) - - baker.make( - Anexada, - materia_principal=materia_principal, - materia_anexada=materia_anexada, - data_anexacao="2019-05-11" - ) - baker.make( - Anexada, - materia_principal=materia_anexada, - materia_anexada=materia_anexada_anexada, - data_anexacao="2020-11-05" - ) - - lista = lista_anexados(materia_principal) - - assert len(lista) == 2 - assert lista[0] == materia_anexada - assert lista[1] == materia_anexada_anexada + tipo_materia = baker.make( + TipoMateriaLegislativa, + descricao="Tipo_Teste" + ) + regime_tramitacao = baker.make( + RegimeTramitacao, + descricao="Regime_Teste" + ) + materia_principal = baker.make( + MateriaLegislativa, + numero=20, + ano=2018, + data_apresentacao="2018-01-04", + regime_tramitacao=regime_tramitacao, + tipo=tipo_materia + ) + materia_anexada = baker.make( + MateriaLegislativa, + numero=21, + ano=2019, + data_apresentacao="2019-05-04", + regime_tramitacao=regime_tramitacao, + tipo=tipo_materia + ) + materia_anexada_anexada = baker.make( + MateriaLegislativa, + numero=22, + ano=2020, + data_apresentacao="2020-01-05", + regime_tramitacao=regime_tramitacao, + tipo=tipo_materia + ) + + baker.make( + Anexada, + materia_principal=materia_principal, + materia_anexada=materia_anexada, + data_anexacao="2019-05-11" + ) + baker.make( + Anexada, + materia_principal=materia_anexada, + materia_anexada=materia_anexada_anexada, + data_anexacao="2020-11-05" + ) + + lista = lista_anexados(materia_principal) + + assert len(lista) == 2 + assert lista[0] == materia_anexada + assert lista[1] == materia_anexada_anexada @pytest.mark.django_db(transaction=False) @@ -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 @@ -526,7 +528,7 @@ def test_form_errors_tramitacao(admin_client): ['Este campo é obrigatório.']) assert (response.context_data['form'].errors[ 'unidade_tramitacao_destino'] == ['Este campo é obrigatório.']) - + @pytest.mark.django_db(transaction=False) def test_form_errors_relatoria(admin_client): @@ -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')) @@ -695,42 +697,41 @@ def test_tramitacoes_materias_anexadas(admin_client): config = baker.make(AppConfig, tramitacao_materia=True) tipo_materia = baker.make( - TipoMateriaLegislativa, - descricao="Tipo_Teste" + TipoMateriaLegislativa, + descricao="Tipo_Teste" ) materia_principal = baker.make( - MateriaLegislativa, - ano=2018, - data_apresentacao="2018-01-04", - tipo=tipo_materia + MateriaLegislativa, + ano=2018, + data_apresentacao="2018-01-04", + tipo=tipo_materia ) materia_anexada = baker.make( - MateriaLegislativa, - ano=2019, - data_apresentacao="2019-05-04", - tipo=tipo_materia + MateriaLegislativa, + ano=2019, + data_apresentacao="2019-05-04", + tipo=tipo_materia ) materia_anexada_anexada = baker.make( - MateriaLegislativa, - ano=2020, - data_apresentacao="2020-01-05", - tipo=tipo_materia + MateriaLegislativa, + ano=2020, + data_apresentacao="2020-01-05", + tipo=tipo_materia ) baker.make( - Anexada, - materia_principal=materia_principal, - materia_anexada=materia_anexada, - data_anexacao="2019-05-11" + Anexada, + materia_principal=materia_principal, + materia_anexada=materia_anexada, + data_anexacao="2019-05-11" ) baker.make( - Anexada, - materia_principal=materia_anexada, - materia_anexada=materia_anexada_anexada, - data_anexacao="2020-11-05" + Anexada, + materia_principal=materia_anexada, + materia_anexada=materia_anexada_anexada, + 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") @@ -741,131 +742,146 @@ def test_tramitacoes_materias_anexadas(admin_client): # Teste criação de Tramitacao form = TramitacaoForm(data={}) - form.data = {'data_tramitacao':date(2019, 5, 6), - 'unidade_tramitacao_local':unidade_tramitacao_local_1.pk, - 'unidade_tramitacao_destino':unidade_tramitacao_destino_1.pk, - 'status':status.pk, - 'urgente': False, - 'texto': "Texto de teste"} - form.instance.materia_id=materia_principal.pk + form.data = {'data_tramitacao': date(2019, 5, 6), + 'unidade_tramitacao_local': unidade_tramitacao_local_1.pk, + 'unidade_tramitacao_destino': unidade_tramitacao_destino_1.pk, + 'status': status.pk, + 'urgente': False, + 'texto': "Texto de teste"} + form.instance.materia_id = materia_principal.pk 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) + == (tramitacao_anexada.status.indicador != "F") + 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") - + == (tramitacao_anexada_anexada.status.indicador != "F") # Teste Edição de Tramitacao form = TramitacaoUpdateForm(data={}) # Alterando unidade_tramitacao_destino - form.data = {'data_tramitacao':tramitacao_principal.data_tramitacao, - 'unidade_tramitacao_local':tramitacao_principal.unidade_tramitacao_local.pk, - 'unidade_tramitacao_destino':unidade_tramitacao_destino_2.pk, - 'status':tramitacao_principal.status.pk, - 'urgente': tramitacao_principal.urgente, - 'texto': tramitacao_principal.texto} + form.data = {'data_tramitacao': tramitacao_principal.data_tramitacao, + 'unidade_tramitacao_local': tramitacao_principal.unidade_tramitacao_local.pk, + 'unidade_tramitacao_destino': unidade_tramitacao_destino_2.pk, + 'status': tramitacao_principal.status.pk, + 'urgente': tramitacao_principal.urgente, + 'texto': tramitacao_principal.texto} form.instance = tramitacao_principal 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) + 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={}) - form.data = {'data_tramitacao':date(2019, 5, 6), - 'unidade_tramitacao_local':unidade_tramitacao_local_1.pk, - 'unidade_tramitacao_destino':unidade_tramitacao_destino_1.pk, - 'status':status.pk, - 'urgente': False, - 'texto': "Texto de teste"} - form.instance.materia_id=materia_principal.pk + form.data = {'data_tramitacao': date(2019, 5, 6), + 'unidade_tramitacao_local': unidade_tramitacao_local_1.pk, + 'unidade_tramitacao_destino': unidade_tramitacao_destino_1.pk, + 'status': status.pk, + 'urgente': False, + 'texto': "Texto de teste"} + form.instance.materia_id = materia_principal.pk 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 - form.data = {'data_tramitacao':tramitacao_anexada.data_tramitacao, - 'unidade_tramitacao_local':tramitacao_anexada.unidade_tramitacao_local.pk, - 'unidade_tramitacao_destino':unidade_tramitacao_destino_2.pk, - 'status':tramitacao_anexada.status.pk, - 'urgente': tramitacao_anexada.urgente, - 'texto': tramitacao_anexada.texto} + form.data = {'data_tramitacao': tramitacao_anexada.data_tramitacao, + 'unidade_tramitacao_local': tramitacao_anexada.unidade_tramitacao_local.pk, + 'unidade_tramitacao_destino': unidade_tramitacao_destino_2.pk, + 'status': tramitacao_anexada.status.pk, + 'urgente': tramitacao_anexada.urgente, + 'texto': tramitacao_anexada.texto} form.instance = tramitacao_anexada 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, - 'unidade_tramitacao_local':tramitacao_principal.unidade_tramitacao_local.pk, - 'unidade_tramitacao_destino':tramitacao_principal.unidade_tramitacao_destino.pk, - 'status':tramitacao_principal.status.pk, - 'urgente': tramitacao_principal.urgente, - 'texto': "Testando a alteração"} + form.data = {'data_tramitacao': tramitacao_principal.data_tramitacao, + 'unidade_tramitacao_local': tramitacao_principal.unidade_tramitacao_local.pk, + 'unidade_tramitacao_destino': tramitacao_principal.unidade_tramitacao_destino.pk, + 'status': tramitacao_principal.status.pk, + 'urgente': tramitacao_principal.urgente, + 'texto': "Testando a alteração"} form.instance = tramitacao_principal 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 - url = reverse('sapl.materia:tramitacao_delete', - kwargs={'pk': tramitacao_principal.pk}) - response = admin_client.post(url, {'confirmar':'confirmar'} ,follow=True) + # 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 - - # 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_anexada.pk).count() == 1 + + # 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 @@ -877,62 +893,71 @@ def test_tramitacoes_materias_anexadas(admin_client): # Teste criação de Tramitacao form = TramitacaoForm(data={}) - form.data = {'data_tramitacao':date(2019, 5, 6), - 'unidade_tramitacao_local':unidade_tramitacao_local_1.pk, - 'unidade_tramitacao_destino':unidade_tramitacao_destino_1.pk, - 'status':status.pk, - 'urgente': False, - 'texto': "Texto de teste"} - form.instance.materia_id=materia_principal.pk + form.data = {'data_tramitacao': date(2019, 5, 6), + 'unidade_tramitacao_local': unidade_tramitacao_local_1.pk, + 'unidade_tramitacao_destino': unidade_tramitacao_destino_1.pk, + 'status': status.pk, + 'urgente': False, + 'texto': "Texto de teste"} + form.instance.materia_id = materia_principal.pk 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, - 'unidade_tramitacao_destino':unidade_tramitacao_destino_1.pk, - 'status':status.pk, - 'urgente': False, - 'texto': "Texto de teste"} - form.instance.materia_id=materia_anexada.pk + form.data = {'data_tramitacao': date(2019, 5, 6), + 'unidade_tramitacao_local': unidade_tramitacao_local_1.pk, + 'unidade_tramitacao_destino': unidade_tramitacao_destino_1.pk, + 'status': status.pk, + 'urgente': False, + 'texto': "Texto de teste"} + form.instance.materia_id = materia_anexada.pk 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 # Teste Edição de Tramitacao form = TramitacaoUpdateForm(data={}) # Alterando unidade_tramitacao_destino - form.data = {'data_tramitacao':tramitacao_principal.data_tramitacao, - 'unidade_tramitacao_local':tramitacao_principal.unidade_tramitacao_local.pk, - 'unidade_tramitacao_destino':unidade_tramitacao_destino_2.pk, - 'status':tramitacao_principal.status.pk, - 'urgente': tramitacao_principal.urgente, - 'texto': tramitacao_principal.texto} + form.data = {'data_tramitacao': tramitacao_principal.data_tramitacao, + 'unidade_tramitacao_local': tramitacao_principal.unidade_tramitacao_local.pk, + 'unidade_tramitacao_destino': unidade_tramitacao_destino_2.pk, + 'status': tramitacao_principal.status.pk, + 'urgente': tramitacao_principal.urgente, + 'texto': tramitacao_principal.texto} form.instance = tramitacao_principal 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 @@ -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 unidade_tramitacao_destino form = TramitacaoUpdateForm(data={}) - form.data = {'data_tramitacao':tramitacao_principal.data_tramitacao, - 'unidade_tramitacao_local':tramitacao_principal.unidade_tramitacao_local.pk, - 'unidade_tramitacao_destino':unidade_tramitacao_destino_2.pk, - 'status':tramitacao_principal.status.pk, - 'urgente': tramitacao_principal.urgente, - 'texto': tramitacao_principal.texto} + form.data = {'data_tramitacao': tramitacao_principal.data_tramitacao, + 'unidade_tramitacao_local': tramitacao_principal.unidade_tramitacao_local.pk, + 'unidade_tramitacao_destino': unidade_tramitacao_destino_2.pk, + 'status': tramitacao_principal.status.pk, + 'urgente': tramitacao_principal.urgente, + 'texto': tramitacao_principal.texto} form.instance = tramitacao_anexada 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,9 +987,10 @@ 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 - url = reverse('sapl.materia:tramitacao_delete', - kwargs={'pk': tramitacao_principal.pk}) - response = admin_client.post(url, {'confirmar':'confirmar'} ,follow=True) + # 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) assert Tramitacao.objects.filter(id=tramitacao_principal.pk).count() == 0 - assert Tramitacao.objects.filter(id=tramitacao_anexada.pk).count() == 1 \ No newline at end of file + assert Tramitacao.objects.filter(id=tramitacao_anexada.pk).count() == 1 diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 0d2c0aced..df0274f5b 100644 --- a/sapl/materia/views.py +++ b/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, _( @@ -321,8 +321,9 @@ class ProposicaoTaView(IntegracaoTaView): proposicao = get_object_or_404(self.model, pk=kwargs['pk']) - if not proposicao.data_envio and\ - request.user != proposicao.autor.user: + if not proposicao.data_envio and \ + 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) diff --git a/sapl/parlamentares/forms.py b/sapl/parlamentares/forms.py index e93a80dc8..31e216184 100755 --- a/sapl/parlamentares/forms.py +++ b/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 diff --git a/sapl/parlamentares/migrations/0035_auto_20210315_1522.py b/sapl/parlamentares/migrations/0035_auto_20210315_1522.py new file mode 100644 index 000000000..398011606 --- /dev/null +++ b/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), + + + ] diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index e0d55ebf6..423481b56 100644 --- a/sapl/parlamentares/models.py +++ b/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) diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index b8b2755e9..9d2f585e8 100644 --- a/sapl/parlamentares/views.py +++ b/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) @@ -355,17 +361,17 @@ class ParticipacaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView): comissoes = [] for p in object_list: - comissao = [ - (p.composicao.comissao.nome, reverse( - 'sapl.comissoes:comissao_detail', kwargs={ - 'pk': p.composicao.comissao.pk})), - (p.cargo.nome, None), - (p.composicao.periodo.data_inicio.strftime( - "%d/%m/%Y") + ' a ' + - p.composicao.periodo.data_fim.strftime("%d/%m/%Y"), - None) - ] - comissoes.append(comissao) + comissao = [ + (p.composicao.comissao.nome, reverse( + 'sapl.comissoes:comissao_detail', kwargs={ + 'pk': p.composicao.comissao.pk})), + (p.cargo.nome, None), + (p.composicao.periodo.data_inicio.strftime( + "%d/%m/%Y") + ' a ' + + p.composicao.periodo.data_fim.strftime("%d/%m/%Y"), + None) + ] + comissoes.append(comissao) return comissoes def get_headers(self): @@ -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,8 +983,9 @@ 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 else: @@ -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) @@ -1242,7 +1259,7 @@ def altera_field_mesa_public_view(request): 'lista_fotos': lista_fotos, 'sessao_selecionada': sessao_selecionada, 'msg': ('', 1) - }) + }) class VincularParlamentarView(PermissionRequiredMixin, FormView): @@ -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}) @@ -1337,6 +1355,6 @@ def get_sessoes_legislatura(request): json_response = {'sessoes_legislativas': []} 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) diff --git a/sapl/rules/__init__.py b/sapl/rules/__init__.py index bc4d27653..780e64311 100644 --- a/sapl/rules/__init__.py +++ b/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 = [ + +] diff --git a/sapl/rules/apps.py b/sapl/rules/apps.py index 18ad112a6..6a3c45f4b 100644 --- a/sapl/rules/apps.py +++ b/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") diff --git a/sapl/rules/map_rules.py b/sapl/rules/map_rules.py index cfc138ca7..e4aa467dc 100644 --- a/sapl/rules/map_rules.py +++ b/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()), @@ -261,7 +257,7 @@ rules_group_geral = { (materia.StatusTramitacao, __base__, __perms_publicas__), (materia.UnidadeTramitacao, __base__, __perms_publicas__), (materia.ConfigEtiquetaMateriaLegislativa, __base__, set()), - + (norma.AssuntoNorma, __base__, __perms_publicas__), (norma.TipoNormaJuridica, __base__, __perms_publicas__), @@ -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 diff --git a/sapl/templates/base/autor_detail.html b/sapl/templates/base/autor_detail.html deleted file mode 100644 index c9938e372..000000000 --- a/sapl/templates/base/autor_detail.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "crud/detail.html" %} -{% load i18n %} -{% load crispy_forms_tags staticfiles %} - -{% block sub_actions %} -
    - - {% blocktrans with verbose_name=view.verbose_name %} Pesquisar {{ verbose_name }} {% endblocktrans %} - - - {% blocktrans with verbose_name=view.verbose_name %} Adicionar {{ verbose_name }} {% endblocktrans %} - -
    -{% endblock sub_actions %} diff --git a/sapl/templates/base/autor_filter.html b/sapl/templates/base/autor_filter.html deleted file mode 100644 index 5e1a05542..000000000 --- a/sapl/templates/base/autor_filter.html +++ /dev/null @@ -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 %} -
    - {% trans 'Fazer nova pesquisa' %} - {% if not request.user.is_anonymous %} - Cadastrar Autor - {% endif %} -
    -
    - {% if numero_res > 0 %} - {% if numero_res == 1 %} -

    Foi encontrado {{ numero_res }} resultado

    - {% else %} -

    Foram encontrados {{ numero_res }} resultados

    - {% endif %} - - - - - - - - - - {% for autor in page_obj %} - - - - - - {% endfor %} - -
    Tipo do AutorNome do AutorUsuário
    {{ autor.tipo }} - - {% if autor.nome %} {{ autor.nome }} {% else %} - {% endif %} - - {% if autor.user %} {{ autor.user }} {% else %} - {% endif %}
    - {% else %} -

    {{ NO_ENTRIES_MSG }}

    - {% endif %} - {% endif %} -
    - {% include 'paginacao.html'%} -


    -{% endblock base_content %} diff --git a/sapl/templates/base/autor_form.html b/sapl/templates/base/autor_form.html index 49007257d..492cb6da7 100644 --- a/sapl/templates/base/autor_form.html +++ b/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) - }); - - - {% endblock %} diff --git a/sapl/templates/base/layouts.yaml b/sapl/templates/base/layouts.yaml index 6d4b69c9f..658eb8e17 100644 --- a/sapl/templates/base/layouts.yaml +++ b/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 diff --git a/sapl/templates/bootstrap4/layout/checkboxselectmultiple.html b/sapl/templates/bootstrap4/layout/checkboxselectmultiple.html new file mode 100644 index 000000000..592734fb4 --- /dev/null +++ b/sapl/templates/bootstrap4/layout/checkboxselectmultiple.html @@ -0,0 +1,34 @@ +{% load crispy_forms_filters common_tags %} +{% load l10n %} + +
    + {% include 'bootstrap4/layout/field_errors_block.html' %} + + {% for choice in field.field.choices %} + {% if choice.2 %} +
    + {% if choice.2|meta_model_value:'label' == 'auth.User' %} + + {% endif %} + +
    + {% else %} + {% if not inline_class %}
    {% endif %} + + {% if not inline_class %}
    {% endif %} + {% endif %} + {% endfor %} + + {% include 'bootstrap4/layout/help_text.html' %} +
    diff --git a/sapl/templates/materia/proposicao_detail.html b/sapl/templates/materia/proposicao_detail.html index 0f4418d03..40c0e806b 100644 --- a/sapl/templates/materia/proposicao_detail.html +++ b/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 %}
    {% if object.texto_articulado.exists %} {% trans "Texto Eletrônico" %} @@ -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 %}
    @@ -39,7 +39,7 @@ {% endif %} {% endblock editions %} {% block detail_content %} - {% if user == object.autor.user %} + {% if user in object.autor.operadores.all %}

    {% model_verbose_name 'sapl.materia.models.Proposicao' %}

    diff --git a/sapl/templates/menu_tabelas_auxiliares.yaml b/sapl/templates/menu_tabelas_auxiliares.yaml index 6c159be40..7241509cb 100644 --- a/sapl/templates/menu_tabelas_auxiliares.yaml +++ b/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 diff --git a/sapl/utils.py b/sapl/utils.py index a588d8a3c..1acd472ae 100644 --- a/sapl/utils.py +++ b/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