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()
"""
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()

14
sapl/base/forms.py

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

6
sapl/base/models.py

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

2
sapl/base/views.py

@ -317,6 +317,8 @@ class AutorCrud(CrudAux):
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')

38
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)

9
sapl/parlamentares/forms.py

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

6
sapl/templates/materia/proposicao_detail.html

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

Loading…
Cancel
Save