From 926659c0f06823a046ce1db865caf2c3bec5d90e Mon Sep 17 00:00:00 2001 From: LeandroRoberto Date: Fri, 28 Oct 2016 13:43:10 -0200 Subject: [PATCH 1/3] =?UTF-8?q?Add=20v=C3=ADnculo=20com=20assunto=20de=20n?= =?UTF-8?q?orma=20na=20edi=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/norma/forms.py | 28 +++---------- .../migrations/0020_auto_20161028_1335.py | 39 +++++++++++++++++++ .../migrations/0021_auto_20161028_1335.py | 24 ++++++++++++ sapl/norma/models.py | 20 +--------- sapl/norma/urls.py | 5 +-- sapl/norma/views.py | 21 +--------- sapl/rules/map_rules.py | 1 - sapl/templates/norma/layouts.yaml | 1 + sapl/templates/norma/subnav.yaml | 2 - 9 files changed, 75 insertions(+), 66 deletions(-) create mode 100644 sapl/norma/migrations/0020_auto_20161028_1335.py create mode 100644 sapl/norma/migrations/0021_auto_20161028_1335.py diff --git a/sapl/norma/forms.py b/sapl/norma/forms.py index a767c37fa..a2410f9ce 100644 --- a/sapl/norma/forms.py +++ b/sapl/norma/forms.py @@ -4,7 +4,7 @@ from crispy_forms.helper import FormHelper from crispy_forms.layout import Fieldset, Layout from django import forms from django.core.exceptions import ObjectDoesNotExist, ValidationError -from django.forms import ModelForm +from django.forms import ModelForm, widgets from django.utils.translation import ugettext_lazy as _ from sapl.crispy_layout_mixin import form_actions, to_row @@ -12,7 +12,7 @@ from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.settings import MAX_DOC_UPLOAD_SIZE from sapl.utils import RANGE_ANOS -from .models import AssuntoNorma, AssuntoNormaRelationship, NormaJuridica +from .models import AssuntoNorma, NormaJuridica def get_esferas(): @@ -30,26 +30,6 @@ ORDENACAO_CHOICES = [('', '---------'), ('data,tipo,ano,numero', _('Data/Tipo/Ano/Número'))] -class AssuntoNormaRelationshipForm(ModelForm): - - class Meta: - model = AssuntoNormaRelationship - fields = ['assunto'] - - def save(self, commit=False): - norma_assunto = super(AssuntoNormaRelationshipForm, self).save(commit) - try: - AssuntoNormaRelationship.objects.get( - norma=norma_assunto.norma, - assunto=norma_assunto.assunto) - except ObjectDoesNotExist: - norma_assunto.save() - else: - raise forms.ValidationError( - "Esse Assunto já está anexado nesta Norma Jurídica.") - return norma_assunto - - # TODO termos, pesquisa textual, assunto(M2M) class NormaJuridicaPesquisaForm(ModelForm): @@ -177,7 +157,9 @@ class NormaJuridicaForm(ModelForm): 'ementa', 'indexacao', 'observacao', - 'texto_integral'] + 'texto_integral', + 'assuntos'] + widgets = {'assuntos': widgets.CheckboxSelectMultiple} def clean(self): cleaned_data = self.cleaned_data diff --git a/sapl/norma/migrations/0020_auto_20161028_1335.py b/sapl/norma/migrations/0020_auto_20161028_1335.py new file mode 100644 index 000000000..4391c8340 --- /dev/null +++ b/sapl/norma/migrations/0020_auto_20161028_1335.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-10-28 13:35 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('norma', '0019_auto_20161028_0232'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='assuntonormarelationship', + unique_together=set([]), + ), + migrations.RemoveField( + model_name='assuntonormarelationship', + name='assunto', + ), + migrations.RemoveField( + model_name='assuntonormarelationship', + name='norma', + ), + migrations.RemoveField( + model_name='normajuridica', + name='assuntos', + ), + migrations.AddField( + model_name='normajuridica', + name='assuntos', + field=models.TextField(blank=True, null=True), + ), + migrations.DeleteModel( + name='AssuntoNormaRelationship', + ), + ] diff --git a/sapl/norma/migrations/0021_auto_20161028_1335.py b/sapl/norma/migrations/0021_auto_20161028_1335.py new file mode 100644 index 000000000..bbadc60d2 --- /dev/null +++ b/sapl/norma/migrations/0021_auto_20161028_1335.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-10-28 13:35 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('norma', '0020_auto_20161028_1335'), + ] + + operations = [ + migrations.RemoveField( + model_name='normajuridica', + name='assuntos', + ), + migrations.AddField( + model_name='normajuridica', + name='assuntos', + field=models.ManyToManyField(blank=True, to='norma.AssuntoNorma', verbose_name='Assuntos'), + ), + ] diff --git a/sapl/norma/models.py b/sapl/norma/models.py index a9fa72780..debbdadc9 100644 --- a/sapl/norma/models.py +++ b/sapl/norma/models.py @@ -97,9 +97,8 @@ class NormaJuridica(models.Model): choices=YES_NO_CHOICES) # XXX was a CharField (attention on migrate) assuntos = models.ManyToManyField( - AssuntoNorma, - verbose_name=_('Assuntos'), - through='AssuntoNormaRelationship') + AssuntoNorma, blank=True, + verbose_name=_('Assuntos')) data_vigencia = models.DateField(blank=True, null=True) timestamp = models.DateTimeField() @@ -135,21 +134,6 @@ class NormaJuridica(models.Model): update_fields=update_fields) -class AssuntoNormaRelationship(models.Model): - assunto = models.ForeignKey(AssuntoNorma, verbose_name=_('Assunto')) - norma = models.ForeignKey(NormaJuridica, verbose_name=_('Norma')) - - class Meta: - unique_together = ( - ('assunto', 'norma'), - ) - verbose_name = _('Assunto') - verbose_name_plural = _('Assuntos') - - def __str__(self): - return self.assunto.assunto - - class LegislacaoCitada(models.Model): materia = models.ForeignKey(MateriaLegislativa) norma = models.ForeignKey(NormaJuridica) diff --git a/sapl/norma/urls.py b/sapl/norma/urls.py index f911c917f..a8d8c59d7 100644 --- a/sapl/norma/urls.py +++ b/sapl/norma/urls.py @@ -1,6 +1,6 @@ from django.conf.urls import include, url -from sapl.norma.views import (AssuntoNormaCrud, AssuntoNormaRelationshipCrud, +from sapl.norma.views import (AssuntoNormaCrud, NormaCrud, NormaPesquisaView, NormaTaView, PesquisaNormaListView, TipoNormaCrud) @@ -10,8 +10,7 @@ app_name = AppConfig.name urlpatterns = [ - url(r'^norma/', include(NormaCrud.get_urls() + - AssuntoNormaRelationshipCrud.get_urls())), + url(r'^norma/', include(NormaCrud.get_urls())), # Integração com Compilação url(r'^norma/(?P[0-9]+)/ta$', NormaTaView.as_view(), name='norma_ta'), diff --git a/sapl/norma/views.py b/sapl/norma/views.py index 283c43bb8..b76d6b517 100644 --- a/sapl/norma/views.py +++ b/sapl/norma/views.py @@ -11,8 +11,8 @@ from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux, MasterDetailCrud, make_pagination) from sapl.norma.forms import NormaJuridicaForm -from .forms import AssuntoNormaRelationshipForm, NormaJuridicaPesquisaForm -from .models import (AssuntoNorma, AssuntoNormaRelationship, NormaJuridica, +from .forms import NormaJuridicaPesquisaForm +from .models import (AssuntoNorma, NormaJuridica, TipoNormaJuridica) # LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '') @@ -41,23 +41,6 @@ class NormaTaView(IntegracaoTaView): return self.get_redirect_deactivated() -class AssuntoNormaRelationshipCrud(MasterDetailCrud): - model = AssuntoNormaRelationship - parent_field = 'norma' - help_path = '' - public = [RP_LIST, RP_DETAIL] - - class BaseMixin(MasterDetailCrud.BaseMixin): - list_field_names = ['assunto'] - ordering = 'assunto' - - class CreateView(MasterDetailCrud.CreateView): - form_class = AssuntoNormaRelationshipForm - - class UpdateView(MasterDetailCrud.UpdateView): - form_class = AssuntoNormaRelationshipForm - - class NormaCrud(Crud): model = NormaJuridica help_path = 'norma_juridica' diff --git a/sapl/rules/map_rules.py b/sapl/rules/map_rules.py index 8f9441e62..ab3ceb846 100644 --- a/sapl/rules/map_rules.py +++ b/sapl/rules/map_rules.py @@ -86,7 +86,6 @@ rules_group_norma = { 'group': SAPL_GROUP_NORMA, 'rules': [ (norma.NormaJuridica, __base__), - (norma.AssuntoNormaRelationship, __base__), (norma.VinculoNormaJuridica, __base__), # Publicacao está com permissão apenas para norma e não para matéria diff --git a/sapl/templates/norma/layouts.yaml b/sapl/templates/norma/layouts.yaml index f52d65102..99e14640c 100644 --- a/sapl/templates/norma/layouts.yaml +++ b/sapl/templates/norma/layouts.yaml @@ -34,6 +34,7 @@ NormaJuridicaCreate: - ementa - indexacao - observacao + - assuntos LegislacaoCitada: {% trans 'Legislação Citada' %}: diff --git a/sapl/templates/norma/subnav.yaml b/sapl/templates/norma/subnav.yaml index 465585e41..be3cf3c0a 100644 --- a/sapl/templates/norma/subnav.yaml +++ b/sapl/templates/norma/subnav.yaml @@ -2,8 +2,6 @@ - title: {% trans 'Início' %} url: normajuridica_detail -- title: {% trans 'Assuntos' %} - url: assuntonormarelationship_list # Opção adicionada para chamar o TextoArticulado da norma. # para integração foram necessárias apenas criar a url norma_ta em urls.py From 20ffd0458c0aafb2d6a0f753cb6fc882289f4e73 Mon Sep 17 00:00:00 2001 From: LeandroRoberto Date: Mon, 31 Oct 2016 13:59:51 -0200 Subject: [PATCH 2/3] Add test q valida permissoes usadas em decorators --- sapl/rules/tests/test_rules.py | 28 ++++ scripts/lista_permissions_in_decorators.py | 142 +++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 scripts/lista_permissions_in_decorators.py diff --git a/sapl/rules/tests/test_rules.py b/sapl/rules/tests/test_rules.py index e03bf85d0..be145e3ea 100644 --- a/sapl/rules/tests/test_rules.py +++ b/sapl/rules/tests/test_rules.py @@ -9,6 +9,8 @@ import pytest from sapl.rules import SAPL_GROUPS from sapl.rules.map_rules import rules_patterns from sapl.test_urls import create_perms_post_migrate +from scripts.lista_permissions_in_decorators import \ + lista_permissions_in_decorators from scripts.lista_urls import lista_urls @@ -152,3 +154,29 @@ def test_permission_required_of_views_exists(url_item): assert p, _('Permissão (%s) na view (%s) não existe.') % ( codename, view) + + +_lista_permissions_in_decorators = lista_permissions_in_decorators() + + +@pytest.mark.django_db(transaction=False) +@pytest.mark.parametrize('permission', _lista_permissions_in_decorators) +def test_permission_required_of_decorators(permission): + """ + testa se, nos decorators permission_required com ou sem method_decorator + as permissões fixas escritas manualmente realmente exitem em Permission + """ + + for app in sapl_appconfs: + # readequa permissões dos models adicionando + # list e detail permissions + create_perms_post_migrate(app) + + codename = permission[0].split('.') + p = Permission.objects.filter( + content_type__app_label=codename[0], + codename=codename[1]).exists() + + assert p, _('Permissão (%s) na view (%s) não existe.') % ( + permission[0], + permission[1]) diff --git a/scripts/lista_permissions_in_decorators.py b/scripts/lista_permissions_in_decorators.py new file mode 100644 index 000000000..8138cd0f7 --- /dev/null +++ b/scripts/lista_permissions_in_decorators.py @@ -0,0 +1,142 @@ +import ast +import inspect +import os + + +if __name__ == '__main__': + + import django + + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sapl.settings") + django.setup() + +if True: + from scripts.lista_urls import lista_urls + + +def get_decorators(cls): + target = cls + decorators = {} + + def visit_FunctionDef(node): + decorators[node.name] = [] + for n in node.decorator_list: + name = '' + if isinstance(n, ast.Call): + name = n.func.attr if isinstance( + n.func, ast.Attribute) else n.func.id + else: + name = n.attr if isinstance(n, ast.Attribute) else n.id + + decorators[node.name].append(name) + + node_iter = ast.NodeVisitor() + node_iter.visit_FunctionDef = visit_FunctionDef + node_iter.visit(ast.parse(inspect.getsource(target))) + return decorators + + +def get_permission_requireds(cls): + target = cls + decorators = [] + + def get_permission_required(arg): + + for perm in arg.args: + + if isinstance(perm, ast.Str): + decorators.append(getattr(perm, perm._fields[0])) + continue + + if isinstance(perm, (ast.Tuple, ast.List)): + if 'elts' not in perm._fields: + continue + + for elt in perm.elts: + + if isinstance(elt, ast.Str): + decorators.append(getattr(elt, elt._fields[0])) + + def get_method_decorator(n): + for arg in n.args: + + if not isinstance(arg, ast.Call): + continue + + """ + Espera-se que: + - o decorator seja uma função + - esta função tenha o meta atributo 'id' + - id = 'permission_required' + - esta função tenha argumento args + """ + if ('func' not in arg._fields or + 'id' not in arg.func._fields or + arg.func.id != 'permission_required' or + 'args' not in arg._fields): + continue + + get_permission_required(arg) + + def visit_FunctionDef(node): + for n in node.decorator_list: + if not isinstance(n, ast.Call): + continue + + """ + Espera-se que: + - o decorator seja uma função + - esta função tenha o meta atributo 'id' + - id = 'method_decorator' + - esta função tenha argumento args + """ + if ('func' not in n._fields or + 'id' not in n.func._fields or + n.func.id != 'method_decorator' or + 'args' not in n._fields): + get_permission_required(n) + else: + get_method_decorator(n) + + node_iter = ast.NodeVisitor() + node_iter.visit_FunctionDef = visit_FunctionDef + node_iter.visit(ast.parse(inspect.getsource(target))) + return decorators + + +class ListaPermissionInDecorators(): + decorators = [] + + def lista_permissions_in_decorators(self): + urls = lista_urls() + + for url_item in urls: + key, url, var, app_name = url_item + if hasattr(key, 'view_class'): + view = key.view_class + elif hasattr(key, 'cls'): + view = key.cls + else: + view = key + + if not view.__module__.startswith('sapl.'): + continue + + try: + decorators = list(map(lambda x: (x, view), + get_permission_requireds(view) + )) + self.decorators += decorators + except: + pass + return self.decorators + + def __call__(self): + return self.lista_permissions_in_decorators() + + +lista_permissions_in_decorators = ListaPermissionInDecorators() + +if __name__ == '__main__': + _lista_permissions_in_decorators = lista_permissions_in_decorators() + print(_lista_permissions_in_decorators) From d5891f62d082aa668a2480120c73c5a7589af6eb Mon Sep 17 00:00:00 2001 From: LeandroRoberto Date: Thu, 3 Nov 2016 16:53:21 -0200 Subject: [PATCH 3/3] Corrige method save do form NormaJuridicaForm --- sapl/norma/forms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sapl/norma/forms.py b/sapl/norma/forms.py index 77e9604a4..4fd8c8579 100644 --- a/sapl/norma/forms.py +++ b/sapl/norma/forms.py @@ -190,8 +190,8 @@ class NormaJuridicaForm(ModelForm): return texto_integral def save(self, commit=False): - norma = super(NormaJuridicaForm, self).save(commit) + norma = self.instance norma.timestamp = datetime.now() norma.materia = self.cleaned_data['materia'] - norma.save() + norma = super(NormaJuridicaForm, self).save(commit=True) return norma