Browse Source

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

pull/3371/head
Leandro Roberto 5 years ago
parent
commit
ad9e72f177
  1. 70
      sapl/api/views.py
  2. 14
      sapl/base/forms.py
  3. 6
      sapl/base/models.py
  4. 2
      sapl/base/views.py
  5. 38
      sapl/materia/views.py
  6. 9
      sapl/parlamentares/forms.py
  7. 6
      sapl/templates/materia/proposicao_detail.html

70
sapl/api/views.py

@ -182,63 +182,63 @@ class SaplApiViewSetConstrutor():
SaplApiViewSetConstrutor.build_class() 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 todos os models de todas as apps do sapl
2. Define DjangoFilterBackend como ferramenta de filtro dos campos 2. Define DjangoFilterBackend como ferramenta de filtro dos campos
3. Define Serializer como a seguir: 3. Define Serializer como a seguir:
3.1 - Define um Serializer genérico para cada módel 3.1 - Define um Serializer genérico para cada módel
3.2 - Recupera Serializer customizado em sapl.api.serializers 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. sapl.api.serializers.{model}Serializer.
Caso não seja definido um Serializer customizado, utiliza-se o trivial Caso não seja definido um Serializer customizado, utiliza-se o trivial
4. Define um FilterSet como a seguir: 4. Define um FilterSet como a seguir:
4.1 - Define um FilterSet genérico para cada módel 4.1 - Define um FilterSet genérico para cada módel
4.2 - Recupera FilterSet customizado em sapl.api.forms 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. sapl.api.forms.{model}FilterSet.
Caso não seja definido um FilterSet customizado, utiliza-se o trivial 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 podem ser filtrados por default
5. SaplApiViewSetConstrutor não cria padrões e/ou exige conhecimento alem dos 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 6. As rotas são criadas seguindo nome da app e nome do model
http://localhost:9000/api/{applabel}/{model_name}/ http://localhost:9000/api/{applabel}/{model_name}/
e seguem as variações definidas em: e seguem as variações definidas em:
https://www.django-rest-framework.org/api-guide/routers/#defaultrouter https://www.django-rest-framework.org/api-guide/routers/#defaultrouter
7. Todas as viewsets construídas por SaplApiViewSetConstrutor e suas rotas 7. Todas as viewsets construídas por SaplApiViewSetConstrutor e suas rotas
(paginate list, detail, edit, create, delete) (paginate list, detail, edit, create, delete)
bem como testes em ambiente de desenvolvimento podem ser conferidas em: bem como testes em ambiente de desenvolvimento podem ser conferidas em:
http://localhost:9000/api/ http://localhost:9000/api/
desde que settings.DEBUG=True desde que settings.DEBUG=True
**SaplApiViewSetConstrutor._built_sets** é um dict de dicts de models conforme: **SaplApiViewSetConstrutor._built_sets** é um dict de dicts de models conforme:
{ {
... ...
'audiencia': { 'audiencia': {
'tipoaudienciapublica': TipoAudienciaPublicaViewSet, 'tipoaudienciapublica': TipoAudienciaPublicaViewSet,
'audienciapublica': AudienciaPublicaViewSet, 'audienciapublica': AudienciaPublicaViewSet,
'anexoaudienciapublica': AnexoAudienciaPublicaViewSet 'anexoaudienciapublica': AnexoAudienciaPublicaViewSet
... ...
}, },
... ...
'base': { 'base': {
'casalegislativa': CasaLegislativaViewSet, 'casalegislativa': CasaLegislativaViewSet,
'appconfig': AppConfigViewSet, 'appconfig': AppConfigViewSet,
... ...
} }
... ...
} }
""" """
@ -315,17 +315,17 @@ class customize(object):
@customize(Autor) @customize(Autor)
class _AutorViewSet: class _AutorViewSet:
""" """
Neste exemplo de customização do que foi criado em Neste exemplo de customização do que foi criado em
SaplApiViewSetConstrutor além do ofertado por SaplApiViewSetConstrutor além do ofertado por
rest_framework.viewsets.ModelViewSet, dentre outras customizações rest_framework.viewsets.ModelViewSet, dentre outras customizações
possíveis, foi adicionado as rotas referentes aos relacionamentos genéricos possíveis, foi adicionado as rotas referentes aos relacionamentos genéricos
* padrão de ModelViewSet * padrão de ModelViewSet
/api/base/autor/ POST - create /api/base/autor/ POST - create
/api/base/autor/ GET - list /api/base/autor/ GET - list
/api/base/autor/{pk}/ GET - detail /api/base/autor/{pk}/ GET - detail
/api/base/autor/{pk}/ PUT - update /api/base/autor/{pk}/ PUT - update
/api/base/autor/{pk}/ PATCH - partial_update /api/base/autor/{pk}/ PATCH - partial_update
/api/base/autor/{pk}/ DELETE - destroy /api/base/autor/{pk}/ DELETE - destroy
* rotas desta classe local criadas pelo método build: * rotas desta classe local criadas pelo método build:
@ -336,7 +336,7 @@ class _AutorViewSet:
/api/base/autor/bloco /api/base/autor/bloco
devolve apenas autores que são blocos parlamentares devolve apenas autores que são blocos parlamentares
/api/base/autor/bancada /api/base/autor/bancada
devolve apenas autores que são bancadas parlamentares devolve apenas autores que são bancadas parlamentares
/api/base/autor/frente /api/base/autor/frente
devolve apenas autores que são Frene parlamentares devolve apenas autores que são Frene parlamentares
/api/base/autor/orgao /api/base/autor/orgao
@ -485,7 +485,7 @@ class _ProposicaoViewSet:
* Permissões: * Permissões:
* Usuário Dono: * Usuário Dono:
* Pode listar todas suas Proposições * Pode listar todas suas Proposições
* Usuário Conectado ou Anônimo: * Usuário Conectado ou Anônimo:
* Pode listar todas as Proposições incorporadas * Pode listar todas as Proposições incorporadas
@ -496,7 +496,7 @@ class _ProposicaoViewSet:
* Permissões: * Permissões:
* Usuário Dono: * Usuário Dono:
* Pode recuperar qualquer de suas Proposições * Pode recuperar qualquer de suas Proposições
* Usuário Conectado ou Anônimo: * Usuário Conectado ou Anônimo:
* Pode recuperar qualquer das proposições incorporadas * Pode recuperar qualquer das proposições incorporadas
@ -524,7 +524,17 @@ class _ProposicaoViewSet:
q = Q(data_recebimento__isnull=False, object_id__isnull=False) q = Q(data_recebimento__isnull=False, object_id__isnull=False)
if not self.request.user.is_anonymous: if not self.request.user.is_anonymous:
q |= Q(autor__user=self.request.user)
autor_do_usuario_logado = self.request.user.autor_set.first()
# se usuário logado é operador de algum autor
if autor_do_usuario_logado:
q = Q(autor=autor_do_usuario_logado)
# se é operador de protocolo, ve qualquer coisa enviada
if self.request.user.has_perm('protocoloadm.list_protocolo'):
q = Q(data_envio__isnull=False) | Q(
data_devolucao__isnull=False)
qs = qs.filter(q) qs = qs.filter(q)
return qs return qs
@ -586,7 +596,7 @@ class _DocumentoAdministrativoViewSet:
""" """
Diante da lógica implementada na manutenção de documentos Diante da lógica implementada na manutenção de documentos
administrativos: 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 teste de permissões sem avaliá-las
- se o comportamento é doc adm restritivo, deve passar pelo - se o comportamento é doc adm restritivo, deve passar pelo
teste de permissões avaliando-as teste de permissões avaliando-as
@ -599,7 +609,7 @@ class _DocumentoAdministrativoViewSet:
""" """
mesmo tendo passado pelo teste de permissões, deve ser filtrado, mesmo tendo passado pelo teste de permissões, deve ser filtrado,
pelo campo restrito. Sendo este igual a True, disponibilizar apenas 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 conforme implementado em DocumentoAdministrativoCrud
""" """
qs = super().get_queryset() qs = super().get_queryset()

14
sapl/base/forms.py

@ -523,11 +523,10 @@ class AutorForm(ModelForm):
u u
) )
for u in get_user_model().objects.filter( for u in get_user_model().objects.filter(
is_active=True,
operadorautor_set__autor=self.instance operadorautor_set__autor=self.instance
).order_by( ).order_by('-is_active',
get_user_model().USERNAME_FIELD get_user_model().USERNAME_FIELD
) if self.instance.id ) if self.instance.id
] + [ ] + [
( (
u.id, u.id,
@ -535,11 +534,10 @@ class AutorForm(ModelForm):
u u
) )
for u in get_user_model().objects.filter( for u in get_user_model().objects.filter(
is_active=True,
operadorautor_set__isnull=True operadorautor_set__isnull=True
).order_by( ).order_by('-is_active',
get_user_model().USERNAME_FIELD get_user_model().USERNAME_FIELD
) )
] ]
if self.instance.pk: if self.instance.pk:

6
sapl/base/models.py

@ -304,8 +304,10 @@ class Autor(models.Model):
return '{} - {}'.format(self.nome, self.cargo) return '{} - {}'.format(self.nome, self.cargo)
else: else:
return str(self.nome) return str(self.nome)
if self.user:
return str(self.user.username) if self.operadores.exists():
return str(self.operadores.first())
return '?' return '?'

2
sapl/base/views.py

@ -317,6 +317,8 @@ class AutorCrud(CrudAux):
q = Q(nome__icontains=q_param) q = Q(nome__icontains=q_param)
q |= Q(cargo__icontains=q_param) q |= Q(cargo__icontains=q_param)
q |= Q(tipo__descricao__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) qs = qs.filter(q)
return qs.distinct('nome', 'id').order_by('nome', 'id') return qs.distinct('nome', 'id').order_by('nome', 'id')

38
sapl/materia/views.py

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

9
sapl/parlamentares/forms.py

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

6
sapl/templates/materia/proposicao_detail.html

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

Loading…
Cancel
Save