diff --git a/requirements/requirements.txt b/requirements/requirements.txt
index 9ddd7a255..4387cb37d 100644
--- a/requirements/requirements.txt
+++ b/requirements/requirements.txt
@@ -8,7 +8,7 @@ django-compressor==2.0
django-crispy-forms==1.6.0
django-extensions==1.6.7
django-extra-views==0.8.0
-django-filter==0.13.0
+django-filter==0.15.3
django-floppyforms==1.6.2
django-model-utils==2.5
django-sass-processor==0.4.6
diff --git a/sapl/api/forms.py b/sapl/api/forms.py
index 87e2c2990..c220c01b2 100644
--- a/sapl/api/forms.py
+++ b/sapl/api/forms.py
@@ -1,6 +1,7 @@
from django.db.models import Q
from django_filters.filters import MethodFilter, ModelChoiceFilter
from rest_framework.filters import FilterSet
+
from sapl.base.models import Autor, TipoAutor
from sapl.utils import generic_relations_for_model
@@ -21,7 +22,6 @@ class SaplGenericRelationSearchFilterSet(FilterSet):
order_by = []
for gr in generic_relations_for_model(self._meta.model):
- model = gr[0]
sgr = gr[1]
for item in sgr:
if item.related_model != self._meta.model:
diff --git a/sapl/api/serializers.py b/sapl/api/serializers.py
index 0791ab22f..bd3fb7def 100644
--- a/sapl/api/serializers.py
+++ b/sapl/api/serializers.py
@@ -1,4 +1,5 @@
from rest_framework import serializers
+
from sapl.base.models import Autor
from sapl.materia.models import MateriaLegislativa
diff --git a/sapl/api/urls.py b/sapl/api/urls.py
index 0d197efc9..195c7f84a 100644
--- a/sapl/api/urls.py
+++ b/sapl/api/urls.py
@@ -1,9 +1,9 @@
from django.conf import settings
-from django.conf.urls import url, include
+from django.conf.urls import include, url
from rest_framework.routers import DefaultRouter
-from sapl.api.views import MateriaLegislativaViewSet, AutorListView,\
- ModelChoiceView
+from sapl.api.views import (AutorListView, MateriaLegislativaViewSet,
+ ModelChoiceView)
from .apps import AppConfig
diff --git a/sapl/api/views.py b/sapl/api/views.py
index d0d1a875d..41d327bff 100644
--- a/sapl/api/views.py
+++ b/sapl/api/views.py
@@ -5,13 +5,14 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework.filters import DjangoFilterBackend
from rest_framework.generics import ListAPIView
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
-from rest_framework.permissions import IsAuthenticated, AllowAny
+from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import GenericViewSet
from sapl.api.forms import AutorChoiceFilterSet
-from sapl.api.serializers import ChoiceSerializer, AutorSerializer,\
- AutorChoiceSerializer, ModelChoiceSerializer, MateriaLegislativaSerializer
-
+from sapl.api.serializers import (AutorChoiceSerializer, AutorSerializer,
+ ChoiceSerializer,
+ MateriaLegislativaSerializer,
+ ModelChoiceSerializer)
from sapl.base.models import Autor, TipoAutor
from sapl.materia.models import MateriaLegislativa
from sapl.utils import SaplGenericRelation, sapl_logger
diff --git a/sapl/base/apps.py b/sapl/base/apps.py
index 0ca7a90cd..c3af888b7 100644
--- a/sapl/base/apps.py
+++ b/sapl/base/apps.py
@@ -1,115 +1,9 @@
-from builtins import LookupError
-from django.apps import apps
-from django.contrib.auth.management import _get_all_permissions
-from django.core import exceptions
-from django.db import router
-from django.db.models.signals import pre_migrate, post_migrate
-from django.db.utils import DEFAULT_DB_ALIAS
-from django.utils.translation import string_concat
-from django.utils.translation import ugettext_lazy as _
import django
-
-
-def create_proxy_permissions(
- app_config, verbosity=2, interactive=True,
- using=DEFAULT_DB_ALIAS, **kwargs):
- if not app_config.models_module:
- return
-
- # print(app_config)
-
- try:
- Permission = apps.get_model('auth', 'Permission')
- except LookupError:
- return
-
- if not router.allow_migrate_model(using, Permission):
- return
-
- from django.contrib.contenttypes.models import ContentType
-
- permission_name_max_length = Permission._meta.get_field('name').max_length
-
- # This will hold the permissions we're looking for as
- # (content_type, (codename, name))
- searched_perms = list()
- # The codenames and ctypes that should exist.
- ctypes = set()
- for klass in list(app_config.get_models()):
- opts = klass._meta
- permissions = (
- ("list_" + opts.model_name,
- string_concat(
- _('Visualizaçao da lista de'), ' ',
- opts.verbose_name_plural)),
- ("detail_" + opts.model_name,
- string_concat(
- _('Visualização dos detalhes de'), ' ',
- opts.verbose_name_plural)),
- )
- opts.permissions = tuple(
- set(list(permissions) + list(opts.permissions)))
-
- if opts.proxy:
- # Force looking up the content types in the current database
- # before creating foreign keys to them.
- app_label, model = opts.app_label, opts.model_name
-
- try:
- ctype = ContentType.objects.db_manager(
- using).get_by_natural_key(app_label, model)
- except:
- ctype = ContentType.objects.db_manager(
- using).create(app_label=app_label, model=model)
- else:
- ctype = ContentType.objects.db_manager(using).get_for_model(klass)
-
- ctypes.add(ctype)
- for perm in _get_all_permissions(klass._meta, ctype):
- searched_perms.append((ctype, perm))
-
- # Find all the Permissions that have a content_type for a model we're
- # looking for. We don't need to check for codenames since we already have
- # a list of the ones we're going to create.
- all_perms = set(Permission.objects.using(using).filter(
- content_type__in=ctypes,
- ).values_list(
- "content_type", "codename"
- ))
-
- perms = [
- Permission(codename=codename, name=name, content_type=ct)
- for ct, (codename, name) in searched_perms
- if (ct.pk, codename) not in all_perms
- ]
- # Validate the permissions before bulk_creation to avoid cryptic database
- # error when the name is longer than 255 characters
- for perm in perms:
- if len(perm.name) > permission_name_max_length:
- raise exceptions.ValidationError(
- 'The permission name %s of %s.%s '
- 'is longer than %s characters' % (
- perm.name,
- perm.content_type.app_label,
- perm.content_type.model,
- permission_name_max_length,
- )
- )
- Permission.objects.using(using).bulk_create(perms)
- if verbosity >= 2:
- for perm in perms:
- print("Adding permission '%s'" % perm)
+from django.utils.translation import ugettext_lazy as _
class AppConfig(django.apps.AppConfig):
name = 'sapl.base'
label = 'base'
verbose_name = _('Dados Básicos')
-
- def ready(self):
- #pre_migrate.connect(run_sql_organizers, self)
-
- post_migrate.connect(
- receiver=create_proxy_permissions,
- dispatch_uid="django.contrib.auth.management.create_permissions")
diff --git a/sapl/base/forms.py b/sapl/base/forms.py
index 46357ee00..5b4d9f734 100644
--- a/sapl/base/forms.py
+++ b/sapl/base/forms.py
@@ -1,22 +1,17 @@
+import django_filters
from crispy_forms.bootstrap import FieldWithButtons, InlineRadios, StrictButton
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Button, Div, Field, Fieldset, Layout, Row
-from crispy_forms.templatetags.crispy_forms_field import css_class
-
from django import forms
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import Group
from django.contrib.auth.password_validation import validate_password
-from django.contrib.contenttypes.fields import GenericRel
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models, transaction
from django.forms import ModelForm
-from django.utils.translation import ugettext_lazy as _, string_concat
-
-import django_filters
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import string_concat
@@ -26,11 +21,9 @@ from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column,
from sapl.materia.models import MateriaLegislativa
from sapl.sessao.models import SessaoPlenaria
from sapl.settings import MAX_IMAGE_UPLOAD_SIZE
-from sapl.utils import (RANGE_ANOS, ImageThumbnailFileInput,
- RangeWidgetOverride, autor_label, autor_modal,
-
- SaplGenericRelation, models_with_gr_for_model,
- ChoiceWithoutValidationField)
+from sapl.utils import (RANGE_ANOS, ChoiceWithoutValidationField,
+ ImageThumbnailFileInput, RangeWidgetOverride,
+ autor_label, autor_modal, models_with_gr_for_model)
from .models import AppConfig, CasaLegislativa
diff --git a/sapl/base/legacy.yaml b/sapl/base/legacy.yaml
new file mode 100644
index 000000000..a54c4ae06
--- /dev/null
+++ b/sapl/base/legacy.yaml
@@ -0,0 +1,8 @@
+TipoAutor:
+ descricao: des_tipo_autor
+
+Autor:
+ nome: nom_autor
+ cargo: des_cargo
+ tipo: tip_autor
+ username: col_username
diff --git a/sapl/base/models.py b/sapl/base/models.py
index ad7b7a5aa..ddf48628c 100644
--- a/sapl/base/models.py
+++ b/sapl/base/models.py
@@ -1,18 +1,10 @@
-from builtins import LookupError
-
-from django.apps import apps
-from django.contrib.auth.management import _get_all_permissions
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
-from django.core import exceptions
-from django.db import models, router
-from django.db.utils import DEFAULT_DB_ALIAS
-from django.utils.translation import string_concat
+from django.db import models
from django.utils.translation import ugettext_lazy as _
from sapl.utils import UF, YES_NO_CHOICES, get_settings_auth_user_model
-
TIPO_DOCUMENTO_ADMINISTRATIVO = (('O', _('Ostensivo')),
('R', _('Restritivo')))
diff --git a/sapl/base/templatetags/common_tags.py b/sapl/base/templatetags/common_tags.py
index 60cb80f62..b4f4bb23d 100644
--- a/sapl/base/templatetags/common_tags.py
+++ b/sapl/base/templatetags/common_tags.py
@@ -3,7 +3,6 @@ from django import template
from sapl.base.models import AppConfig
from sapl.parlamentares.models import Filiacao
-from sapl.utils import permissoes_adm
register = template.Library()
@@ -87,26 +86,6 @@ def get_delete_perm(value, arg):
return perm.__contains__(nome_app + can_delete)
-@register.filter
-def get_doc_adm_template_perms(user):
- app_config = AppConfig.objects.last()
-
- if app_config:
- if app_config.documentos_administrativos == 'O':
- return True
-
- return user.has_perms(permissoes_adm())
-
-
-@register.filter
-def ver_menu_sistema_perm(value):
- u = value
- if u.groups.filter(name='Operador Geral').exists() or u.is_superuser:
- return True
- else:
- return False
-
-
@register.filter
def ultima_filiacao(value):
parlamentar = value
@@ -120,11 +99,6 @@ def ultima_filiacao(value):
return None
-@register.filter
-def get_config_not_exists(user):
- return not AppConfig.objects.exists()
-
-
@register.filter
def get_config_attr(attribute):
return AppConfig.attr(attribute)
diff --git a/sapl/base/templatetags/menus.py b/sapl/base/templatetags/menus.py
index 1ce1d4ea5..e5b6b80d7 100644
--- a/sapl/base/templatetags/menus.py
+++ b/sapl/base/templatetags/menus.py
@@ -1,11 +1,10 @@
+import yaml
from django import template
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
-import yaml
from sapl.utils import sapl_logger
-
register = template.Library()
diff --git a/sapl/base/views.py b/sapl/base/views.py
index 1edcb58f4..4de2da9fc 100644
--- a/sapl/base/views.py
+++ b/sapl/base/views.py
@@ -13,8 +13,8 @@ from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
from django.utils.translation import ugettext_lazy as _
from django.views.generic.base import TemplateView
from django_filters.views import FilterView
-from sapl.base.forms import AutorForm, TipoAutorForm, AutorFormForAdmin
+from sapl.base.forms import AutorForm, AutorFormForAdmin, TipoAutorForm
from sapl.base.models import Autor, TipoAutor
from sapl.crud.base import CrudAux
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
diff --git a/sapl/compilacao/forms.py b/sapl/compilacao/forms.py
index b5b7cc09c..1559b97ef 100644
--- a/sapl/compilacao/forms.py
+++ b/sapl/compilacao/forms.py
@@ -42,10 +42,18 @@ class TipoTaForm(ModelForm):
label=TipoTextoArticulado._meta.get_field(
'descricao').verbose_name)
- participacao_social = forms.NullBooleanField(
+ participacao_social = forms.ChoiceField(
label=TipoTextoArticulado._meta.get_field(
'participacao_social').verbose_name,
- widget=forms.Select(choices=YES_NO_CHOICES),
+ choices=YES_NO_CHOICES,
+ widget=forms.RadioSelect(),
+ required=True)
+
+ publicacao_func = forms.ChoiceField(
+ label=TipoTextoArticulado._meta.get_field(
+ 'publicacao_func').verbose_name,
+ choices=YES_NO_CHOICES,
+ widget=forms.RadioSelect(),
required=True)
class Meta:
@@ -54,21 +62,27 @@ class TipoTaForm(ModelForm):
'descricao',
'content_type',
'participacao_social',
+ 'publicacao_func'
]
def __init__(self, *args, **kwargs):
row1 = to_row([
- ('sigla', 2),
- ('descricao', 4),
- ('content_type', 3),
- ('participacao_social', 3),
+ ('sigla', 3),
+ ('descricao', 5),
+ ('content_type', 4),
+ ])
+ row2 = to_row([
+ (InlineRadios('participacao_social'), 3),
+ (InlineRadios('publicacao_func'), 3),
])
self.helper = FormHelper()
self.helper.layout = SaplFormLayout(
Fieldset(_('Identificação Básica'),
- row1, css_class="col-md-12"))
+ row1, css_class="col-md-12"),
+ Fieldset(_('Funcionalidades'),
+ row2, css_class="col-md-12"))
super(TipoTaForm, self).__init__(*args, **kwargs)
diff --git a/sapl/compilacao/migrations/0059_auto_20161027_1323.py b/sapl/compilacao/migrations/0059_auto_20161027_1323.py
new file mode 100644
index 000000000..fdfe8de37
--- /dev/null
+++ b/sapl/compilacao/migrations/0059_auto_20161027_1323.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-10-27 13:23
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('compilacao', '0058_auto_20161008_0143'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='dispositivo',
+ options={'ordering': ['ta', 'ordem'], 'permissions': (('change_dispositivo_edicao_dinamica', 'Permissão de edição de dispositivos originais via editor dinâmico.'), ('change_dispositivo_edicao_avancada', 'Permissão de edição de dispositivos originais via formulários de edição avançada.'), ('change_dispositivo_registros_compilacao', 'Permissão de registro de compilação via editor dinâmico.'), ('change_dispositivo_notificacoes', 'Permissão de acesso às notificações de pendências.')), 'verbose_name': 'Dispositivo', 'verbose_name_plural': 'Dispositivos'},
+ ),
+ ]
diff --git a/sapl/compilacao/migrations/0060_auto_20161101_0913.py b/sapl/compilacao/migrations/0060_auto_20161101_0913.py
new file mode 100644
index 000000000..851746ec0
--- /dev/null
+++ b/sapl/compilacao/migrations/0060_auto_20161101_0913.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-11-01 09:13
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('compilacao', '0059_auto_20161027_1323'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='dispositivo',
+ options={'ordering': ['ta', 'ordem'], 'permissions': (('change_dispositivo_edicao_dinamica', 'Permissão de edição de dispositivos originais via editor dinâmico.'), ('change_dispositivo_edicao_avancada', 'Permissão de edição de dispositivos originais via formulários de edição avançada.'), ('change_dispositivo_registros_compilacao', 'Permissão de registro de compilação via editor dinâmico.'), ('view_dispositivo_notificacoes', 'Permissão de acesso às notificações de pendências.')), 'verbose_name': 'Dispositivo', 'verbose_name_plural': 'Dispositivos'},
+ ),
+ ]
diff --git a/sapl/compilacao/migrations/0061_auto_20161101_1025.py b/sapl/compilacao/migrations/0061_auto_20161101_1025.py
new file mode 100644
index 000000000..d74bdc8da
--- /dev/null
+++ b/sapl/compilacao/migrations/0061_auto_20161101_1025.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-11-01 10:25
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('compilacao', '0060_auto_20161101_0913'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='dispositivo',
+ name='ta',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dispositivos_set', to='compilacao.TextoArticulado', verbose_name='Texto Articulado'),
+ ),
+ ]
diff --git a/sapl/compilacao/migrations/0062_auto_20161101_1221.py b/sapl/compilacao/migrations/0062_auto_20161101_1221.py
new file mode 100644
index 000000000..63e703843
--- /dev/null
+++ b/sapl/compilacao/migrations/0062_auto_20161101_1221.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-11-01 12:21
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('compilacao', '0061_auto_20161101_1025'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='dispositivo',
+ options={'ordering': ['ta', 'ordem'], 'permissions': (('change_dispositivo_edicao_dinamica', 'Permissão de edição de dispositivos originais via editor dinâmico.'), ('change_dispositivo_edicao_avancada', 'Permissão de edição de dispositivos originais via formulários de edição avançada.'), ('change_dispositivo_registros_compilacao', 'Permissão de registro de compilação via editor dinâmico.'), ('view_dispositivo_notificacoes', 'Permissão de acesso às notificações de pendências.'), ('change_dispositivo_de_vigencia_global', 'Permissão alteração global do dispositivo de vigência')), 'verbose_name': 'Dispositivo', 'verbose_name_plural': 'Dispositivos'},
+ ),
+ ]
diff --git a/sapl/compilacao/migrations/0063_tipotextoarticulado_publicacao_func.py b/sapl/compilacao/migrations/0063_tipotextoarticulado_publicacao_func.py
new file mode 100644
index 000000000..53dac62fc
--- /dev/null
+++ b/sapl/compilacao/migrations/0063_tipotextoarticulado_publicacao_func.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-11-03 11:06
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('compilacao', '0062_auto_20161101_1221'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='tipotextoarticulado',
+ name='publicacao_func',
+ field=models.NullBooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=True, verbose_name='Histórico de Publicação'),
+ ),
+ ]
diff --git a/sapl/compilacao/migrations/0064_auto_20161104_1420.py b/sapl/compilacao/migrations/0064_auto_20161104_1420.py
new file mode 100644
index 000000000..508bc17ac
--- /dev/null
+++ b/sapl/compilacao/migrations/0064_auto_20161104_1420.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-11-04 14:20
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('compilacao', '0063_tipotextoarticulado_publicacao_func'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='textoarticulado',
+ options={'ordering': ['-data', '-numero'], 'permissions': (('view_restricted_textoarticulado', 'Pode ver qualquer Texto Articulado'),), 'verbose_name': 'Texto Articulado', 'verbose_name_plural': 'Textos Articulados'},
+ ),
+ migrations.AddField(
+ model_name='textoarticulado',
+ name='editable_only_by_owners',
+ field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=True, verbose_name='Editável apenas pelos donos do Texto Articulado'),
+ ),
+ migrations.AddField(
+ model_name='textoarticulado',
+ name='editing_locked',
+ field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=True, verbose_name='Texto Articulado em Edição'),
+ ),
+ migrations.AddField(
+ model_name='textoarticulado',
+ name='owners',
+ field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Donos do Texto Articulado'),
+ ),
+ migrations.AddField(
+ model_name='textoarticulado',
+ name='visibilidade',
+ field=models.IntegerField(choices=[(99, 'Privado'), (79, 'Restrito'), (89, 'Em Edição'), (0, 'Público')], default=99, verbose_name='Visibilidade'),
+ ),
+ migrations.AlterField(
+ model_name='tipotextoarticulado',
+ name='publicacao_func',
+ field=models.NullBooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Histórico de Publicação'),
+ ),
+ ]
diff --git a/sapl/compilacao/migrations/0065_auto_20161107_1024.py b/sapl/compilacao/migrations/0065_auto_20161107_1024.py
new file mode 100644
index 000000000..5da908346
--- /dev/null
+++ b/sapl/compilacao/migrations/0065_auto_20161107_1024.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-11-07 10:24
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('compilacao', '0064_auto_20161104_1420'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='textoarticulado',
+ old_name='visibilidade',
+ new_name='privacidade'
+ ),
+ ]
diff --git a/sapl/compilacao/migrations/0066_auto_20161107_1028.py b/sapl/compilacao/migrations/0066_auto_20161107_1028.py
new file mode 100644
index 000000000..a7c3ca92c
--- /dev/null
+++ b/sapl/compilacao/migrations/0066_auto_20161107_1028.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-11-07 10:28
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('compilacao', '0065_auto_20161107_1024'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='textoarticulado',
+ name='privacidade',
+ field=models.IntegerField(choices=[(99, 'Privado'), (79, 'Restrito'), (89, 'Em Edição'), (0, 'Público')], default=99, verbose_name='Privacidade'),
+ ),
+ ]
diff --git a/sapl/compilacao/migrations/0067_auto_20161107_1351.py b/sapl/compilacao/migrations/0067_auto_20161107_1351.py
new file mode 100644
index 000000000..54fcc16dc
--- /dev/null
+++ b/sapl/compilacao/migrations/0067_auto_20161107_1351.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-11-07 13:51
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('compilacao', '0066_auto_20161107_1028'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='textoarticulado',
+ options={'ordering': ['-data', '-numero'], 'permissions': (('view_restricted_textoarticulado', 'Pode ver qualquer Texto Articulado'), ('lock_unlock_textoarticulado', 'Pode bloquear/desbloquear edição de Texto Articulado')), 'verbose_name': 'Texto Articulado', 'verbose_name_plural': 'Textos Articulados'},
+ ),
+ ]
diff --git a/sapl/compilacao/migrations/0068_auto_20161107_1546.py b/sapl/compilacao/migrations/0068_auto_20161107_1546.py
new file mode 100644
index 000000000..96c5f726b
--- /dev/null
+++ b/sapl/compilacao/migrations/0068_auto_20161107_1546.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-11-07 15:46
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('compilacao', '0067_auto_20161107_1351'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='dispositivo',
+ options={'ordering': ['ta', 'ordem'], 'permissions': (('change_dispositivo_edicao_dinamica', 'Permissão de edição de dispositivos originais via editor dinâmico.'), ('change_your_dispositivo_edicao_dinamica', 'Permissão de edição de dispositivos originais via editor dinâmico desde que seja dono.'), ('change_dispositivo_edicao_avancada', 'Permissão de edição de dispositivos originais via formulários de edição avançada.'), ('change_dispositivo_registros_compilacao', 'Permissão de registro de compilação via editor dinâmico.'), ('view_dispositivo_notificacoes', 'Permissão de acesso às notificações de pendências.'), ('change_dispositivo_de_vigencia_global', 'Permissão alteração global do dispositivo de vigência')), 'verbose_name': 'Dispositivo', 'verbose_name_plural': 'Dispositivos'},
+ ),
+ ]
diff --git a/sapl/compilacao/migrations/0069_auto_20161107_1932.py b/sapl/compilacao/migrations/0069_auto_20161107_1932.py
new file mode 100644
index 000000000..743167a56
--- /dev/null
+++ b/sapl/compilacao/migrations/0069_auto_20161107_1932.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-11-07 19:32
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('compilacao', '0068_auto_20161107_1546'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='textoarticulado',
+ name='privacidade',
+ field=models.IntegerField(choices=[(99, 'Privado'), (79, 'Imotável Restrito'), (69, 'Imutável Público'), (89, 'Em Edição'), (0, 'Público')], default=99, verbose_name='Privacidade'),
+ ),
+ ]
diff --git a/sapl/compilacao/models.py b/sapl/compilacao/models.py
index da2708cfe..6752a82c1 100644
--- a/sapl/compilacao/models.py
+++ b/sapl/compilacao/models.py
@@ -1,8 +1,10 @@
+from django.contrib import messages
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import F, Q
from django.db.models.aggregates import Max
+from django.http.response import Http404
from django.template import defaultfilters
from django.utils.translation import ugettext_lazy as _
@@ -81,6 +83,12 @@ class TipoTextoArticulado(models.Model):
choices=YES_NO_CHOICES,
verbose_name=_('Participação Social'))
+ publicacao_func = models.NullBooleanField(
+ default=False,
+ blank=True, null=True,
+ choices=YES_NO_CHOICES,
+ verbose_name=_('Histórico de Publicação'))
+
class Meta:
verbose_name = _('Tipo de Texto Articulado')
verbose_name_plural = _('Tipos de Texto Articulados')
@@ -95,6 +103,25 @@ PARTICIPACAO_SOCIAL_CHOICES = [
(False, _('Não'))]
+STATUS_TA_PRIVATE = 99 # Só os donos podem ver
+STATUS_TA_EDITION = 89
+STATUS_TA_IMMUTABLE_RESTRICT = 79
+
+STATUS_TA_IMMUTABLE_PUBLIC = 69
+
+STATUS_TA_PUBLIC = 0
+
+PRIVACIDADE_STATUS = (
+ (STATUS_TA_PRIVATE, _('Privado')), # só dono ve e edita
+ # só quem tem permissão para ver
+ (STATUS_TA_IMMUTABLE_RESTRICT, _('Imotável Restrito')),
+ # só quem tem permissão para ver
+ (STATUS_TA_IMMUTABLE_PUBLIC, _('Imutável Público')),
+ (STATUS_TA_EDITION, _('Em Edição')), # só quem tem permissão para editar
+ (STATUS_TA_PUBLIC, _('Público')), # visualização pública
+)
+
+
class TextoArticulado(TimestampedMixin):
data = models.DateField(blank=True, null=True, verbose_name=_('Data'))
ementa = models.TextField(verbose_name=_('Ementa'))
@@ -118,10 +145,35 @@ class TextoArticulado(TimestampedMixin):
blank=True, null=True, default=None)
content_object = GenericForeignKey('content_type', 'object_id')
+ owners = models.ManyToManyField(
+ get_settings_auth_user_model(),
+ blank=True, verbose_name=_('Donos do Texto Articulado'))
+
+ editable_only_by_owners = models.BooleanField(
+ choices=YES_NO_CHOICES,
+ default=True,
+ verbose_name=_('Editável apenas pelos donos do Texto Articulado'))
+
+ editing_locked = models.BooleanField(
+ choices=YES_NO_CHOICES,
+ default=True,
+ verbose_name=_('Texto Articulado em Edição'))
+
+ privacidade = models.IntegerField(
+ _('Privacidade'),
+ choices=PRIVACIDADE_STATUS,
+ default=STATUS_TA_PRIVATE)
+
class Meta:
verbose_name = _('Texto Articulado')
verbose_name_plural = _('Textos Articulados')
ordering = ['-data', '-numero']
+ permissions = (
+ ('view_restricted_textoarticulado',
+ _('Pode ver qualquer Texto Articulado')),
+ ('lock_unlock_textoarticulado',
+ _('Pode bloquear/desbloquear edição de Texto Articulado')),
+ )
def __str__(self):
if self.content_object:
@@ -132,6 +184,102 @@ class TextoArticulado(TimestampedMixin):
'numero': self.numero,
'data': defaultfilters.date(self.data, "d \d\e F \d\e Y")}
+ def hash(self):
+ from django.core import serializers
+ import hashlib
+ data = serializers.serialize(
+ "xml", Dispositivo.objects.filter(
+ Q(ta_id=self.id) | Q(ta_publicado_id=self.id)))
+ md5 = hashlib.md5()
+ md5.update(data.encode('utf-8'))
+ return md5.hexdigest()
+
+ def can_use_dynamic_editing(self, user):
+ return not self.editing_locked and\
+ (not self.editable_only_by_owners and
+ user.has_perm(
+ 'compilacao.change_dispositivo_edicao_dinamica') or
+ self.editable_only_by_owners and user in self.owners.all() and
+ user.has_perm(
+ 'compilacao.change_your_dispositivo_edicao_dinamica'))
+
+ def has_view_permission(self, request):
+ if self.privacidade in (STATUS_TA_IMMUTABLE_PUBLIC, STATUS_TA_PUBLIC):
+ return True
+
+ if request.user in self.owners.all():
+ return True
+
+ if self.privacidade == STATUS_TA_IMMUTABLE_RESTRICT and\
+ request.user.has_perm(
+ 'compilacao.view_restricted_textoarticulado'):
+ return True
+
+ elif self.privacidade == STATUS_TA_EDITION:
+ if request.user.has_perm(
+ 'compilacao.change_dispositivo_edicao_dinamica'):
+ return True
+ else:
+ messages.error(request, _(
+ 'Este Texto Articulado está em edição.'))
+
+ elif self.privacidade == STATUS_TA_PRIVATE:
+ if request.user in self.owners.all():
+ return True
+ else:
+ raise Http404()
+
+ return False
+
+ def has_edit_permission(self, request):
+
+ if self.privacidade == STATUS_TA_PRIVATE:
+ if request.user not in self.owners.all():
+ raise Http404()
+
+ if not self.can_use_dynamic_editing(request.user):
+ messages.error(request, _(
+ 'Usuário sem permissão para edição.'))
+ return False
+ else:
+ return True
+
+ if self.privacidade == STATUS_TA_IMMUTABLE_RESTRICT:
+ messages.error(request, _(
+ 'A edição deste Texto Articulado está bloqueada. '
+ 'Este documento é imutável e de acesso é restrito.'))
+ return False
+
+ if self.privacidade == STATUS_TA_IMMUTABLE_PUBLIC:
+ messages.error(request, _(
+ 'A edição deste Texto Articulado está bloqueada. '
+ 'Este documento é imutável.'))
+ return False
+
+ if self.editing_locked and\
+ self.privacidade in (STATUS_TA_PUBLIC, STATUS_TA_EDITION) and\
+ not request.user.has_perm(
+ 'compilacao.lock_unlock_textoarticulado'):
+ messages.error(request, _(
+ 'A edição deste Texto Articulado está bloqueada. '
+ 'É necessário acessar com usuário que possui '
+ 'permissão de desbloqueio.'))
+ return False
+
+ if not request.user.has_perm(
+ 'compilacao.change_dispositivo_edicao_dinamica'):
+ messages.error(request, _(
+ 'Usuário sem permissão para edição.'))
+ return False
+
+ if self.editable_only_by_owners and\
+ request.user not in self.owners.all():
+ messages.error(request, _(
+ 'Apenas usuários donos do Texto Articulado podem editá-lo.'))
+ return False
+
+ return True
+
def reagrupar_ordem_de_dispositivos(self):
dpts = Dispositivo.objects.filter(ta=self)
@@ -635,7 +783,7 @@ class Dispositivo(BaseModel, TimestampedMixin):
ta = models.ForeignKey(
TextoArticulado,
- on_delete=models.PROTECT,
+ on_delete=models.CASCADE,
related_name='dispositivos_set',
verbose_name=_('Texto Articulado'))
ta_publicado = models.ForeignKey(
@@ -693,6 +841,23 @@ class Dispositivo(BaseModel, TimestampedMixin):
'ta_publicado',
'publicacao',),
)
+ permissions = (
+ ('change_dispositivo_edicao_dinamica', _(
+ 'Permissão de edição de dispositivos originais '
+ 'via editor dinâmico.')),
+ ('change_your_dispositivo_edicao_dinamica', _(
+ 'Permissão de edição de dispositivos originais '
+ 'via editor dinâmico desde que seja dono.')),
+ ('change_dispositivo_edicao_avancada', _(
+ 'Permissão de edição de dispositivos originais '
+ 'via formulários de edição avançada.')),
+ ('change_dispositivo_registros_compilacao', _(
+ 'Permissão de registro de compilação via editor dinâmico.')),
+ ('view_dispositivo_notificacoes', _(
+ 'Permissão de acesso às notificações de pendências.')),
+ ('change_dispositivo_de_vigencia_global', _(
+ 'Permissão alteração global do dispositivo de vigência')),
+ )
def __str__(self):
return '%(rotulo)s' % {
@@ -1248,7 +1413,7 @@ class Dispositivo(BaseModel, TimestampedMixin):
disps[0].get_numero_completo())
# dispositivo.transform_in_next()
else:
- dispositivo.set_numero_completo([1, 0, 0, 0, 0, 0, ])
+ dispositivo.set_numero_completo([0, 0, 0, 0, 0, 0, ])
else:
if ';' in tipo_base.rotulo_prefixo_texto:
diff --git a/sapl/compilacao/templatetags/compilacao_filters.py b/sapl/compilacao/templatetags/compilacao_filters.py
index de8b70530..f3965c43d 100644
--- a/sapl/compilacao/templatetags/compilacao_filters.py
+++ b/sapl/compilacao/templatetags/compilacao_filters.py
@@ -211,7 +211,7 @@ def heranca(request, d, ignore_ultimo=0, ignore_primeiro=0):
ta_id = str(d.ta_id)
d_pk = str(d.pk)
if ta_id not in ta_dpts_parents or d_pk not in ta_dpts_parents[ta_id]:
- print('recarregando estrutura temporaria de heranças')
+ #print('recarregando estrutura temporaria de heranças')
dpts_parents = {}
ta_dpts_parents[ta_id] = dpts_parents
update_dispositivos_parents(dpts_parents, ta_id)
@@ -295,3 +295,7 @@ def urldetail_content_type(obj):
@register.filter
def list(obj):
return [obj, ]
+
+@register.filter
+def can_use_dynamic_editing(texto_articulado, user):
+ return texto_articulado.can_use_dynamic_editing(user)
diff --git a/sapl/compilacao/views.py b/sapl/compilacao/views.py
index 9b7f79d95..3b25eb045 100644
--- a/sapl/compilacao/views.py
+++ b/sapl/compilacao/views.py
@@ -1,16 +1,16 @@
-import logging
-import sys
from collections import OrderedDict
from datetime import datetime, timedelta
+import logging
+import sys
from braces.views import FormMessagesMixin
from django import forms
from django.conf import settings
from django.contrib import messages
-from django.contrib.auth.decorators import login_required
+from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.contenttypes.models import ContentType
from django.core.signing import Signer
-from django.core.urlresolvers import reverse_lazy
+from django.core.urlresolvers import reverse_lazy, reverse
from django.db import connection, transaction
from django.db.models import Q
from django.db.utils import IntegrityError
@@ -18,10 +18,9 @@ from django.http.response import (HttpResponse, HttpResponseRedirect,
JsonResponse)
from django.shortcuts import get_object_or_404, redirect
from django.utils.dateparse import parse_date
-from django.utils.decorators import method_decorator
from django.utils.encoding import force_text
-from django.utils.translation import ugettext_lazy as _
from django.utils.translation import string_concat
+from django.utils.translation import ugettext_lazy as _
from django.views.generic.base import TemplateView
from django.views.generic.detail import DetailView
from django.views.generic.edit import (CreateView, DeleteView, FormView,
@@ -43,12 +42,14 @@ from sapl.compilacao.models import (Dispositivo, Nota,
Publicacao, TextoArticulado,
TipoDispositivo, TipoNota, TipoPublicacao,
TipoTextoArticulado, TipoVide,
- VeiculoPublicacao, Vide)
+ VeiculoPublicacao, Vide, STATUS_TA_EDITION,
+ STATUS_TA_PRIVATE, STATUS_TA_PUBLIC)
from sapl.compilacao.utils import (DISPOSITIVO_SELECT_RELATED,
DISPOSITIVO_SELECT_RELATED_EDIT)
from sapl.crud.base import Crud, CrudListView, make_pagination
from sapl.settings import BASE_DIR
+
TipoNotaCrud = Crud.build(TipoNota, 'tipo_nota')
TipoVideCrud = Crud.build(TipoVide, 'tipo_vide')
TipoPublicacaoCrud = Crud.build(TipoPublicacao, 'tipo_publicacao')
@@ -87,12 +88,31 @@ def choice_models_in_extenal_views():
return result
+def choice_model_type_foreignkey_in_extenal_views(id_tipo_ta=None):
+ yield None, '-------------'
+
+ if not id_tipo_ta:
+ return
+
+ tipo_ta = TipoTextoArticulado.objects.get(pk=id_tipo_ta)
+
+ integrations_view_names = get_integrations_view_names()
+ for item in integrations_view_names:
+ if hasattr(item, 'model_type_foreignkey'):
+ if (tipo_ta.content_type.model == item.model.__name__.lower() and
+ tipo_ta.content_type.app_label ==
+ item.model._meta.app_label):
+ for i in item.model_type_foreignkey.objects.all():
+ yield i.pk, i
+
+
class IntegracaoTaView(TemplateView):
def get_redirect_deactivated(self):
messages.error(
self.request,
- _('O modulo de Textos Articulados está desativado.'))
+ _('O modulo de Textos Articulados para %s está desativado.'
+ ) % self.model._meta.verbose_name_plural)
return redirect('/')
def get(self, request, *args, **kwargs):
@@ -100,6 +120,16 @@ class IntegracaoTaView(TemplateView):
try:
if settings.DEBUG or not TipoDispositivo.objects.exists():
self.import_pattern()
+
+ if hasattr(self, 'map_funcs'):
+ tipo_ta = TipoTextoArticulado.objects.get(
+ content_type=ContentType.objects.get_for_model(
+ self.model))
+
+ for key, value in self.map_funcs.items():
+ setattr(tipo_ta, key, value)
+ tipo_ta.save()
+
except Exception as e:
logger.error(
string_concat(
@@ -108,6 +138,26 @@ class IntegracaoTaView(TemplateView):
str(e)))
return self.get_redirect_deactivated()
+ assert hasattr(self, 'map_fields'), _(
+ """
+ O mapa dos campos não foi definido. Ele deve seguir a estrutura
+ de chaves abaixo:
+
+ map_fields = {
+ 'data': 'data',
+ 'ementa': 'ementa',
+ 'observacao': 'observacao',
+ 'numero': 'numero',
+ 'ano': 'ano',
+ }
+
+ Caso o model de integração não possua um dos campos,
+ implemente, ou passe `None` para as chaves que são fixas.
+ """)
+
+ map_fields = self.map_fields
+ ta_values = getattr(self, 'ta_values', {})
+
item = get_object_or_404(self.model, pk=kwargs['pk'])
related_object_type = ContentType.objects.get_for_model(item)
@@ -118,81 +168,71 @@ class IntegracaoTaView(TemplateView):
tipo_ta = TipoTextoArticulado.objects.filter(
content_type=related_object_type)
- if not ta.exists():
+ ta_exists = bool(ta.exists())
+ if not ta_exists:
ta = TextoArticulado()
tipo_ta = TipoTextoArticulado.objects.filter(
content_type=related_object_type)[:1]
if tipo_ta.exists():
ta.tipo_ta = tipo_ta[0]
ta.content_object = item
- else:
- ta = ta[0]
- if hasattr(item, 'ementa') and item.ementa:
- ta.ementa = item.ementa
- else:
- ta.ementa = _('Integração com %s sem ementa.') % item
+ ta.privacidade = ta_values.get('privacidade', STATUS_TA_EDITION)
- if hasattr(item, 'observacao') and item.observacao:
- ta.observacao = item.observacao
- else:
- ta.observacao = _('Integração com %s sem observacao.') % item
+ ta.editing_locked = ta_values.get('editing_locked', False)
+ ta.editable_only_by_owners = ta_values.get(
+ 'editable_only_by_owners', False)
- if hasattr(item, 'numero') and item.numero:
- ta.numero = item.numero
else:
- ta.numero = int('%s%s%s' % (
- int(datetime.now().year),
- int(datetime.now().month),
- int(datetime.now().day)))
-
- if hasattr(item, 'ano') and item.ano:
- ta.ano = item.ano
- else:
- ta.ano = datetime.now().year
+ ta = ta[0]
- if hasattr(item, 'data_apresentacao'):
- ta.data = item.data_apresentacao
- elif hasattr(item, 'data'):
- ta.data = item.data
- else:
- ta.data = datetime.now()
+ if not ta.data:
+ ta.data = getattr(item, map_fields['data']
+ if map_fields['data'] else 'xxx',
+ datetime.now())
+ if not ta.data:
+ ta.data = datetime.now()
- ta.save()
+ ta.ementa = getattr(
+ item, map_fields['ementa']
+ if map_fields['ementa'] else 'xxx', _(
+ 'Integração com %s sem ementa.') % item)
- return redirect(to=reverse_lazy('sapl.compilacao:ta_text',
- kwargs={'ta_id': ta.pk}))
+ ta.observacao = getattr(
+ item, map_fields['observacao']
+ if map_fields['observacao'] else 'xxx', '')
- """msg = messages.error if not request.user.is_anonymous(
- ) else messages.info
+ ta.numero = getattr(
+ item, map_fields['numero']
+ if map_fields['numero'] else 'xxx', int('%s%s%s' % (
+ int(datetime.now().year),
+ int(datetime.now().month),
+ int(datetime.now().day))))
- msg(request,
- _('A funcionalidade de Textos Articulados está desativada.'))
+ ta.ano = getattr(item, map_fields['ano']
+ if map_fields['ano'] else 'xxx', datetime.now().year)
- if not request.user.is_anonymous():
- msg(
- request,
- _('Para ativá-la, os Tipos de Textos devem ser criados.'))
+ ta.save()
- msg(request,
- _('Sua tela foi redirecionada para a tela de '
- 'cadastro de Textos Articulados.'))
+ if not ta_exists:
+ if ta.editable_only_by_owners and\
+ not self.request.user.is_anonymous():
+ ta.owners.add(self.request.user)
- return redirect(to=reverse_lazy('sapl.compilacao:tipo_ta_list',
- kwargs={}))
+ if not Dispositivo.objects.filter(ta_id=ta.pk).exists() and\
+ ta.can_use_dynamic_editing(self.request.user):
+ return redirect(to=reverse_lazy('sapl.compilacao:ta_text_edit',
+ kwargs={'ta_id': ta.pk}))
else:
-
- return redirect(to=reverse_lazy(
- '%s:%s_detail' % (
- item._meta.app_config.name, item._meta.model_name),
- kwargs={'pk': item.pk}))"""
+ return redirect(to=reverse_lazy('sapl.compilacao:ta_text',
+ kwargs={'ta_id': ta.pk}))
def import_pattern(self):
from unipath import Path
compilacao_app = Path(__file__).ancestor(1)
- print(compilacao_app)
+ # print(compilacao_app)
with open(compilacao_app + '/compilacao_data_tables.sql', 'r') as f:
lines = f.readlines()
lines = [line.rstrip('\n') for line in lines]
@@ -251,7 +291,20 @@ class IntegracaoTaView(TemplateView):
abstract = True
-class CompMixin:
+class CompMixin(PermissionRequiredMixin):
+ permission_required = []
+
+ def has_permission(self):
+ perms = self.get_permission_required()
+ # Torna a view pública se não possuir conteudo
+ # no atributo permission_required
+ return self.request.user.has_perms(perms) if len(perms) else True
+
+ @property
+ def ta(self):
+ ta = TextoArticulado.objects.get(
+ pk=self.kwargs.get('ta_id', self.kwargs.get('pk', 0)))
+ return ta
def get_context_data(self, **kwargs):
context = super(CompMixin, self).get_context_data(**kwargs)
@@ -271,6 +324,7 @@ class TipoTaListView(CompMixin, ListView):
model = TipoTextoArticulado
paginate_by = 10
verbose_name = model._meta.verbose_name
+ permission_required = 'compilacao.list_tipotextoarticulado'
@property
def title(self):
@@ -287,6 +341,7 @@ class TipoTaCreateView(CompMixin, FormMessagesMixin, CreateView):
template_name = "crud/form.html"
form_valid_message = _('Registro criado com sucesso!')
form_invalid_message = _('O registro não foi criado.')
+ permission_required = 'compilacao.add_tipotextoarticulado'
def get(self, request, *args, **kwargs):
self.object = None
@@ -308,12 +363,14 @@ class TipoTaCreateView(CompMixin, FormMessagesMixin, CreateView):
class TipoTaDetailView(CompMixin, DetailView):
model = TipoTextoArticulado
+ permission_required = 'compilacao.detail_tipotextoarticulado'
class TipoTaUpdateView(CompMixin, UpdateView):
model = TipoTextoArticulado
form_class = TipoTaForm
template_name = "crud/form.html"
+ permission_required = 'compilacao.change_tipotextoarticulado'
def get(self, request, *args, **kwargs):
self.object = self.get_object()
@@ -336,6 +393,7 @@ class TipoTaUpdateView(CompMixin, UpdateView):
class TipoTaDeleteView(CompMixin, DeleteView):
model = TipoTextoArticulado
template_name = "crud/confirm_delete.html"
+ permission_required = 'compilacao.delete_tipotextoarticulado'
@property
def detail_url(self):
@@ -350,6 +408,7 @@ class TaListView(CompMixin, ListView):
model = TextoArticulado
paginate_by = 10
verbose_name = model._meta.verbose_name
+ permission_required = 'compilacao.list_textoarticulado'
@property
def title(self):
@@ -367,10 +426,26 @@ class TaListView(CompMixin, ListView):
page_obj.number, paginator.num_pages)
return context
+ def get_queryset(self):
+ qs = ListView.get_queryset(self)
+
+ qs = qs.exclude(
+ ~Q(owners=self.request.user.id),
+ privacidade=STATUS_TA_PRIVATE)
+
+ return qs
+
class TaDetailView(CompMixin, DetailView):
model = TextoArticulado
+ def has_permission(self):
+ self.object = self.ta
+ if self.object.has_view_permission(self.request):
+ return CompMixin.has_permission(self)
+ else:
+ return False
+
@property
def title(self):
if self.get_object().content_object:
@@ -389,6 +464,7 @@ class TaCreateView(CompMixin, FormMessagesMixin, CreateView):
template_name = "crud/form.html"
form_valid_message = _('Registro criado com sucesso!')
form_invalid_message = _('O registro não foi criado.')
+ permission_required = 'compilacao.add_tipotextoarticulado'
def get_success_url(self):
return reverse_lazy('sapl.compilacao:ta_detail',
@@ -403,6 +479,7 @@ class TaUpdateView(CompMixin, UpdateView):
model = TextoArticulado
form_class = TaForm
template_name = "crud/form.html"
+ permission_required = 'compilacao.change_textoarticulado'
def get(self, request, *args, **kwargs):
self.object = self.get_object()
@@ -425,6 +502,7 @@ class TaUpdateView(CompMixin, UpdateView):
class TaDeleteView(CompMixin, DeleteView):
model = TextoArticulado
template_name = "crud/confirm_delete.html"
+ permission_required = 'compilacao.delete_textoarticulado'
@property
def detail_url(self):
@@ -464,14 +542,11 @@ class NotaMixin(DispositivoSuccessUrlMixin):
return initial
- @method_decorator(login_required)
- def dispatch(self, *args, **kwargs):
- return super(NotaMixin, self).dispatch(*args, **kwargs)
-
class NotasCreateView(NotaMixin, CreateView):
template_name = 'compilacao/ajax_form.html'
form_class = NotaForm
+ permission_required = 'compilacao.add_nota'
def get(self, request, *args, **kwargs):
flag_action, modelo_nota = self.get_modelo_nota(request)
@@ -506,6 +581,7 @@ class NotasEditView(NotaMixin, UpdateView):
model = Nota
template_name = 'compilacao/ajax_form.html'
form_class = NotaForm
+ permission_required = 'compilacao.change_nota'
def get(self, request, *args, **kwargs):
flag_action, modelo_nota = self.get_modelo_nota(request)
@@ -517,6 +593,8 @@ class NotasEditView(NotaMixin, UpdateView):
class NotasDeleteView(NotaMixin, TemplateView):
+ permission_required = 'compilacao.delete_nota'
+
def get(self, request, *args, **kwargs):
nt = Nota.objects.get(pk=self.kwargs['pk'])
nt.delete()
@@ -535,59 +613,28 @@ class VideMixin(DispositivoSuccessUrlMixin):
return initial
- @method_decorator(login_required)
- def dispatch(self, *args, **kwargs):
- return super(VideMixin, self).dispatch(*args, **kwargs)
-
-
-def choice_model_type_foreignkey_in_extenal_views(id_tipo_ta=None):
- yield None, '-------------'
-
- if not id_tipo_ta:
- return
-
- tipo_ta = TipoTextoArticulado.objects.get(pk=id_tipo_ta)
-
- integrations_view_names = get_integrations_view_names()
- for item in integrations_view_names:
- if hasattr(item, 'model_type_foreignkey'):
- if (tipo_ta.content_type.model == item.model.__name__.lower() and
- tipo_ta.content_type.app_label ==
- item.model._meta.app_label):
- for i in item.model_type_foreignkey.objects.all():
- yield i.pk, i
-
class VideCreateView(VideMixin, CreateView):
model = Vide
template_name = 'compilacao/ajax_form.html'
form_class = VideForm
+ permission_required = 'compilacao.add_vide'
def get(self, request, *args, **kwargs):
self.object = None
form = self.get_form()
return self.render_to_response(self.get_context_data(form=form))
- """
- def get_form_kwargs(self):
-
- kwargs = super(VideCreateView, self).get_form_kwargs()
-
- if 'choice_model_type_foreignkey_in_extenal_views' not in kwargs:
- kwargs.update({
- 'choice_model_type_foreignkey_in_extenal_views':
- choice_model_type_foreignkey_in_extenal_views
- })
-
- return kwargs"""
class VideEditView(VideMixin, UpdateView):
model = Vide
template_name = 'compilacao/ajax_form.html'
form_class = VideForm
+ permission_required = 'compilacao.change_vide'
class VideDeleteView(VideMixin, TemplateView):
+ permission_required = 'compilacao.delete_vide'
def get(self, request, *args, **kwargs):
vd = Vide.objects.get(pk=self.kwargs['pk'])
@@ -595,9 +642,26 @@ class VideDeleteView(VideMixin, TemplateView):
return HttpResponseRedirect(self.get_success_url())
-class PublicacaoListView(CompMixin, ListView):
+class PublicacaoMixin(CompMixin):
+
+ def dispatch(self, request, *args, **kwargs):
+ ta = self.ta
+ if not ta.tipo_ta.publicacao_func:
+ messages.error(request, _(
+ 'A funcionalidade de %s está desativada para %s.') % (
+ TipoTextoArticulado._meta.get_field(
+ 'publicacao_func').verbose_name,
+ ta.tipo_ta.descricao))
+ return redirect(reverse('sapl.compilacao:ta_text',
+ kwargs={'ta_id': self.kwargs['ta_id']}))
+
+ return PermissionRequiredMixin.dispatch(self, request, *args, **kwargs)
+
+
+class PublicacaoListView(PublicacaoMixin, ListView):
model = Publicacao
verbose_name = model._meta.verbose_name
+ permission_required = []
@property
def title(self):
@@ -605,11 +669,6 @@ class PublicacaoListView(CompMixin, ListView):
self.model._meta.verbose_name_plural,
self.ta))
- @property
- def ta(self):
- ta = TextoArticulado.objects.get(pk=self.kwargs['ta_id'])
- return ta
-
@property
def create_url(self):
return reverse_lazy(
@@ -626,12 +685,13 @@ class PublicacaoListView(CompMixin, ListView):
return context
-class PublicacaoCreateView(CompMixin, FormMessagesMixin, CreateView):
+class PublicacaoCreateView(PublicacaoMixin, FormMessagesMixin, CreateView):
model = Publicacao
form_class = PublicacaoForm
template_name = "crud/form.html"
form_valid_message = _('Registro criado com sucesso!')
form_invalid_message = _('O registro não foi criado.')
+ permission_required = 'compilacao.add_publicacao'
def get_success_url(self):
return reverse_lazy(
@@ -650,14 +710,16 @@ class PublicacaoCreateView(CompMixin, FormMessagesMixin, CreateView):
return {'ta': self.kwargs['ta_id']}
-class PublicacaoDetailView(CompMixin, DetailView):
+class PublicacaoDetailView(PublicacaoMixin, DetailView):
model = Publicacao
+ permission_required = 'compilacao.detail_publicacao'
-class PublicacaoUpdateView(CompMixin, UpdateView):
+class PublicacaoUpdateView(PublicacaoMixin, UpdateView):
model = Publicacao
form_class = PublicacaoForm
template_name = "crud/form.html"
+ permission_required = 'compilacao.change_publicacao'
def get(self, request, *args, **kwargs):
self.object = self.get_object()
@@ -678,9 +740,10 @@ class PublicacaoUpdateView(CompMixin, UpdateView):
return self.get_success_url()
-class PublicacaoDeleteView(CompMixin, DeleteView):
+class PublicacaoDeleteView(PublicacaoMixin, DeleteView):
model = Publicacao
template_name = "crud/confirm_delete.html"
+ permission_required = 'compilacao.delete_publicacao'
@property
def detail_url(self):
@@ -708,44 +771,14 @@ class TextView(CompMixin, ListView):
fim_vigencia = None
ta_vigencia = None
- def get(self, request, *args, **kwargs):
- ta = TextoArticulado.objects.get(pk=self.kwargs['ta_id'])
- self.object = ta
- if ta.content_object:
- item = ta.content_object
- self.object = item
- if hasattr(item, 'ementa') and item.ementa:
- ta.ementa = item.ementa
- else:
- ta.ementa = _('Integração com %s sem ementa.') % item
-
- if hasattr(item, 'observacao') and item.observacao:
- ta.observacao = item.observacao
- else:
- ta.observacao = _('Integração com %s sem observacao.') % item
-
- if hasattr(item, 'numero') and item.numero:
- ta.numero = item.numero
- else:
- ta.numero = int('%s%s%s' % (
- int(datetime.now().year),
- int(datetime.now().month),
- int(datetime.now().day)))
+ def has_permission(self):
+ self.object = self.ta
+ return self.object.has_view_permission(self.request)
- if hasattr(item, 'ano') and item.ano:
- ta.ano = item.ano
- else:
- ta.ano = datetime.now().year
-
- if hasattr(item, 'data_apresentacao'):
- ta.data = item.data_apresentacao
- elif hasattr(item, 'data'):
- ta.data = item.data
- else:
- ta.data = datetime.now()
- ta.save()
-
- return super(TextView, self).get(request, *args, **kwargs)
+ def get(self, request, *args, **kwargs):
+ if 'print' in request.GET:
+ self.template_name = 'compilacao/text_list__print_version.html'
+ return ListView.get(self, request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(TextView, self).get_context_data(**kwargs)
@@ -936,9 +969,45 @@ class DispositivoView(TextView):
return itens
-class TextEditView(TemplateView):
+class TextEditView(CompMixin, TemplateView):
template_name = 'compilacao/text_edit.html'
+ def has_permission(self):
+ self.object = self.ta
+ return self.object.has_edit_permission(self.request)
+
+ def get(self, request, *args, **kwargs):
+
+ if self.object.editing_locked:
+ if 'unlock' not in request.GET:
+ messages.error(
+ request, _(
+ 'A edição deste Texto Articulado está bloqueada.'))
+ return redirect(to=reverse_lazy(
+ 'sapl.compilacao:ta_text', kwargs={
+ 'ta_id': self.object.id}))
+ else:
+ # TODO - implementar logging de ação de usuário
+ self.object.editing_locked = False
+ self.object.privacidade = STATUS_TA_EDITION
+ self.object.save()
+ messages.success(request, _(
+ 'Texto Articulado desbloqueado com sucesso.'))
+ else:
+ if 'lock' in request.GET:
+ # TODO - implementar logging de ação de usuário
+ self.object.editing_locked = True
+ self.object.privacidade = STATUS_TA_PUBLIC
+ self.object.save()
+ messages.success(request, _(
+ 'Texto Articulado bloqueado com sucesso.'))
+
+ return redirect(to=reverse_lazy(
+ 'sapl.compilacao:ta_text', kwargs={
+ 'ta_id': self.object.id}))
+
+ return TemplateView.get(self, request, *args, **kwargs)
+
def get_context_data(self, **kwargs):
dispositivo_id = int(self.kwargs['dispositivo_id']) \
if 'dispositivo_id' in self.kwargs else 0
@@ -946,7 +1015,7 @@ class TextEditView(TemplateView):
if dispositivo_id:
self.object = Dispositivo.objects.get(pk=dispositivo_id)
- context = super(TextEditView, self).get_context_data(**kwargs)
+ context = super(TemplateView, self).get_context_data(**kwargs)
if not dispositivo_id:
ta = TextoArticulado.objects.get(pk=self.kwargs['ta_id'])
@@ -1721,6 +1790,12 @@ class ActionDispositivoCreateMixin(ActionsCommonsMixin):
for td in otds:
+ if td.dispositivo_de_alteracao:
+ if not self.request.user.has_perm(
+ 'compilacao.'
+ 'change_dispositivo_registros_compilacao'):
+ continue
+
if paradentro and not td.permitido_inserir_in(
tipb,
include_relative_autos=True,
@@ -2417,11 +2492,10 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin,
class DispositivoDinamicEditView(
- CompMixin, ActionsEditMixin, TextEditView, UpdateView):
+ ActionsEditMixin, TextEditView, UpdateView):
template_name = 'compilacao/text_edit_bloco.html'
model = Dispositivo
form_class = DispositivoEdicaoBasicaForm
- contador = -1
def get_initial(self):
initial = UpdateView.get_initial(self)
@@ -2615,7 +2689,6 @@ class DispositivoSearchFragmentFormView(ListView):
def get_queryset(self):
try:
-
n = 10
if 'max_results' in self.request.GET:
n = int(self.request.GET['max_results'])
@@ -2799,6 +2872,8 @@ class DispositivoEdicaoBasicaView(CompMixin, FormMessagesMixin, UpdateView):
form_invalid_message = _('Houve erro em registrar '
'as alterações no Dispositivo')
+ permission_required = 'compilacao.change_dispositivo_edicao_avancada'
+
@property
def cancel_url(self):
return reverse_lazy(
@@ -2871,6 +2946,8 @@ class DispositivoEdicaoVigenciaView(CompMixin, FormMessagesMixin, UpdateView):
form_invalid_message = _('Houve erro em registrar '
'as alterações no Dispositivo')
+ permission_required = 'compilacao.change_dispositivo_edicao_avancada'
+
@property
def cancel_url(self):
return reverse_lazy(
@@ -2894,6 +2971,9 @@ class DispositivoDefinidorVigenciaView(CompMixin, FormMessagesMixin, FormView):
form_invalid_message = _('Houve erro em registrar '
'as alterações no Dispositivo')
+ permission_required = ('compilacao.change_dispositivo_edicao_avancada',
+ 'compilacao.change_dispositivo_de_vigencia_global')
+
def get_form_kwargs(self):
kwargs = FormView.get_form_kwargs(self)
kwargs.update({
@@ -2951,6 +3031,8 @@ class DispositivoEdicaoAlteracaoView(CompMixin, FormMessagesMixin, UpdateView):
form_invalid_message = _('Houve erro em registrar '
'as alterações no Dispositivo')
+ permission_required = 'compilacao.change_dispositivo_registros_compilacao'
+
@property
def cancel_url(self):
return reverse_lazy(
@@ -2983,6 +3065,8 @@ class TextNotificacoesView(CompMixin, ListView, FormView):
template_name = 'compilacao/text_notificacoes.html'
form_class = TextNotificacoesForm
+ permission_required = 'compilacao.view_dispositivo_notificacoes'
+
def get(self, request, *args, **kwargs):
self.object = TextoArticulado.objects.get(pk=self.kwargs['ta_id'])
return super(TextNotificacoesView, self).get(request, *args, **kwargs)
diff --git a/sapl/crispy_layout_mixin.py b/sapl/crispy_layout_mixin.py
index 51b386d9c..028301502 100644
--- a/sapl/crispy_layout_mixin.py
+++ b/sapl/crispy_layout_mixin.py
@@ -91,7 +91,8 @@ def get_field_display(obj, fieldname):
else:
display = ''
elif 'ManyRelatedManager' in str(type(value))\
- or 'RelatedManager' in str(type(value)):
+ or 'RelatedManager' in str(type(value))\
+ or 'GenericRelatedObjectManager' in str(type(value)):
display = '
'
for v in value.all():
display += '%s ' % str(v)
diff --git a/sapl/crud/base.py b/sapl/crud/base.py
index 81450172d..cc84c7b4e 100644
--- a/sapl/crud/base.py
+++ b/sapl/crud/base.py
@@ -24,6 +24,8 @@ from django.views.generic.base import ContextMixin
from django.views.generic.list import MultipleObjectMixin
from sapl.crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display
+from sapl.rules.map_rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL,
+ RP_LIST)
from sapl.settings import BASE_DIR
from sapl.utils import normalize
@@ -33,10 +35,6 @@ logger = logging.getLogger(BASE_DIR.name)
ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \
'list', 'create', 'detail', 'update', 'delete'
-# RP - Radical das permissões para "..."
-RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE =\
- '.list_', '.detail_', '.add_', '.change_', '.delete_',
-
def _form_invalid_message(msg):
return '%s %s' % (_('Formulário inválido.'), msg)
@@ -1404,10 +1402,6 @@ class CrudBaseForListAndDetailExternalAppView(MasterDetailCrud):
class BaseMixin(Crud.PublicMixin, MasterDetailCrud.BaseMixin):
- @classmethod
- def url_name(cls, suffix):
- return '%s_parlamentar_%s' % (cls.model._meta.model_name, suffix)
-
def resolve_url(self, suffix, args=None):
obj = self.crud if hasattr(self, 'crud') else self
diff --git a/sapl/legacy/migration.py b/sapl/legacy/migration.py
index b2d6bb3da..aed78fd88 100644
--- a/sapl/legacy/migration.py
+++ b/sapl/legacy/migration.py
@@ -4,26 +4,31 @@ import pkg_resources
import yaml
from django.apps import apps
from django.apps.config import AppConfig
+from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.db import connections, models
-from django.db.models import CharField, TextField
+from django.db.models import CharField, TextField, ProtectedError
from django.db.models.base import ModelBase
from model_mommy import mommy
from model_mommy.mommy import foreign_key_required, make
-from sapl.base.models import ProblemaMigracao
+from sapl.base.models import Autor, ProblemaMigracao, TipoAutor
from sapl.comissoes.models import Composicao, Participacao
-from sapl.materia.models import StatusTramitacao, TipoProposicao, Tramitacao
-from sapl.norma.models import AssuntoNormaRelationship, NormaJuridica
+from sapl.materia.models import (Proposicao, StatusTramitacao, TipoDocumento,
+ TipoMateriaLegislativa, TipoProposicao,
+ Tramitacao)
+from sapl.norma.models import AssuntoNorma, NormaJuridica
from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import StatusTramitacaoAdministrativo
-from sapl.sessao.models import OrdemDia, SessaoPlenaria
+from sapl.sessao.models import ExpedienteMateria, OrdemDia, SessaoPlenaria
+from sapl.utils import normalize
# BASE ######################################################################
# apps to be migrated, in app dependency order (very important)
appconfs = [apps.get_app_config(n) for n in [
'parlamentares',
'comissoes',
+ 'base',
'materia',
'norma',
'sessao',
@@ -31,6 +36,7 @@ appconfs = [apps.get_app_config(n) for n in [
'protocoloadm', ]]
unique_constraints = []
+one_to_one_constraints = []
name_sets = [set(m.__name__ for m in ac.get_models()) for ac in appconfs]
@@ -118,6 +124,9 @@ def get_fk_related(field, value, label=None):
else:
value = None
else:
+ if field.model._meta.label == 'sessao.RegistroVotacao' and \
+ field.name == 'ordem':
+ return value
value = make_stub(field.related_model, value)
descricao = 'stub criado para entrada orfã!'
warn(msg + ' => ' + descricao)
@@ -155,18 +164,33 @@ def delete_constraints(model):
cursor = exec_sql("SELECT conname FROM pg_constraint WHERE conrelid = "
"(SELECT oid FROM pg_class WHERE relname LIKE "
"'%s') and contype = 'u';" % (table))
- result = cursor.fetchone()
+ result = ()
+ result = cursor.fetchall()
# se existir um resultado, unique constraint será deletado
- if result:
- warn('Excluindo unique constraint de nome %s' % result)
- args = model._meta.unique_together[0]
- args_list = list(args)
- unique_constraints.append([table, result[0], args_list, model])
+ for r in result:
+ if r[0].endswith('key'):
+ words_list = r[0].split('_')
+ one_to_one_constraints.append([table, r[0], words_list, model])
+ else:
+ args = None
+ args_list = []
+ if model._meta.unique_together:
+ args = model._meta.unique_together[0]
+ args_list = list(args)
+ unique_constraints.append([table, r[0], args_list, model])
+ warn('Excluindo unique constraint de nome %s' % r[0])
exec_sql("ALTER TABLE %s DROP CONSTRAINT %s;" %
- (table, result[0]))
+ (table, r[0]))
def recreate_constraints():
+ if one_to_one_constraints:
+ for constraint in one_to_one_constraints:
+ table, name, args, model = constraint
+ args_string = ''
+ args_string = "(" + "_".join(map(str, args[2:-1])) + ")"
+ exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" %
+ (table, name, args_string))
if unique_constraints:
for constraint in unique_constraints:
table, name, args, model = constraint
@@ -178,6 +202,7 @@ def recreate_constraints():
args_string += "(" + ', '.join(map(str, args)) + ")"
exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" %
(table, name, args_string))
+ one_to_one_constraints.clear()
unique_constraints.clear()
@@ -272,21 +297,58 @@ class DataMigrator:
self.data_mudada.setdefault('nome_campo', []).\
append(field.name)
if field_type == 'CharField' or field_type == 'TextField':
- if value is None:
+ if value is None or value == 'None':
value = ''
+ if field.model._meta.label == 'sessao.RegistroVotacao' and \
+ field.name == 'ordem' and \
+ not isinstance(value, OrdemDia):
+ try:
+ new_value = ExpedienteMateria.objects.get(pk=value)
+ setattr(new, 'expediente', new_value)
+ setattr(new, field.name, None)
+ continue
+ except ObjectDoesNotExist:
+ msg = 'FK [%s] não encontrada para valor %s ' \
+ '(em %s %s)' % (
+ field.name, value,
+ field.model.__name__, label or '---')
+ value = make_stub(field.related_model, value)
+ descricao = 'stub criado para entrada orfã!'
+ warn(msg + ' => ' + descricao)
+ save_relation(value, [field.name], msg, descricao,
+ eh_stub=True)
+ setattr(new, field.name, value)
+ elif field.model.__name__ == 'TipoAutor' and \
+ field.name == 'content_type':
+ try:
+ value = field.related_model.objects.get(
+ model=normalize(new.descricao.lower()).replace(' ',
+ ''))
+ except ObjectDoesNotExist:
+ value = None
setattr(new, field.name, value)
def migrate(self, obj=appconfs):
# warning: model/app migration order is of utmost importance
-
self.to_delete = []
ProblemaMigracao.objects.all().delete()
+ User.objects.all().delete()
+
info('Começando migração: %s...' % obj)
self._do_migrate(obj)
# exclude logically deleted in legacy base
info('Deletando models com ind_excluido...')
for obj in self.to_delete:
- obj.delete()
+ try:
+ obj.delete()
+ except ProtectedError:
+ msg = 'A entrada de PK %s da model %s não pode ser excluida' %\
+ (obj.pk, obj._meta.model_name)
+ descricao = 'Um ou mais objetos protegidos '
+ warn(msg + ' => ' + descricao)
+ save_relation(obj=obj, problema=msg,
+ descricao=descricao, eh_stub=False)
+
info('Deletando stubs desnecessários...')
while self.delete_stubs():
pass
@@ -299,7 +361,13 @@ class DataMigrator:
if model in self.field_renames)
self._do_migrate(models_to_migrate)
elif isinstance(obj, ModelBase):
- self.migrate_model(obj)
+ # A migração vai pular TipoProposicao e só vai migrar essa model
+ # antes de migrar Proposicao. Isso deve acontecer por causa da
+ # GenericRelation existente em TipoProposicao.
+ if not obj.__name__ == 'TipoProposicao':
+ if obj.__name__ == 'Proposicao':
+ self.migrate_model(TipoProposicao)
+ self.migrate_model(obj)
elif hasattr(obj, '__iter__'):
for item in obj:
self._do_migrate(item)
@@ -316,7 +384,11 @@ class DataMigrator:
# Clear all model entries
# They may have been created in a previous migration attempt
- model.objects.all().delete()
+ try:
+ model.objects.all().delete()
+ except ProtectedError:
+ Proposicao.objects.all().delete()
+ model.objects.all().delete()
delete_constraints(model)
# setup migration strategy for tables with or without a pk
@@ -416,13 +488,12 @@ def adjust_sessaoplenaria(new, old):
def adjust_tipoproposicao(new, old):
- if new.materia_ou_documento == 'M':
- field = TipoProposicao.tipo_materia.field
- value = get_fk_related(field=field, value=old.tip_mat_ou_doc)
- elif new.materia_ou_documento == 'D':
- field = TipoProposicao.tipo_documento.field
- value = get_fk_related(field=field, value=old.tip_mat_ou_doc)
- setattr(new, field.name, value)
+ if old.ind_mat_ou_doc == 'M':
+ new.tipo_conteudo_related = TipoMateriaLegislativa.objects.get(
+ pk=old.tip_mat_ou_doc)
+ elif old.ind_mat_ou_doc == 'D':
+ new.tipo_conteudo_related = TipoDocumento.objects.get(
+ pk=old.tip_mat_ou_doc)
def adjust_statustramitacao(new, old):
@@ -455,15 +526,24 @@ def adjust_normajuridica_antes_salvar(new, old):
def adjust_normajuridica_depois_salvar(new, old):
# Ajusta relação M2M
- lista_ids_assunto = old.cod_assunto.split(',')
- for id_assunto in lista_ids_assunto:
- relacao = AssuntoNormaRelationship()
- relacao.assunto_id = int(id_assunto)
- relacao.norma_id = new.pk
- relacao.save()
+ lista_pks_assunto = old.cod_assunto.split(',')
+ for pk_assunto in lista_pks_assunto:
+ new.assuntos.add(AssuntoNorma.objects.get(pk=pk_assunto))
+
+
+def adjust_autor(new, old):
+ new.autor_related = TipoAutor.objects.get(pk=old.tip_autor)
+ if old.col_username:
+ if not User.objects.filter(username=old.col_username).exists():
+ user = User(username=old.col_username, password=12345)
+ user.save()
+ new.user = user
+ else:
+ new.user = User.objects.filter(username=old.col_username)[0]
AJUSTE_ANTES_SALVAR = {
+ Autor: adjust_autor,
NormaJuridica: adjust_normajuridica_antes_salvar,
OrdemDia: adjust_ordemdia,
Parlamentar: adjust_parlamentar,
diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py
index ebb3ab215..aedb90532 100644
--- a/sapl/materia/forms.py
+++ b/sapl/materia/forms.py
@@ -1,12 +1,12 @@
-from datetime import datetime, date
+from datetime import date, datetime
import os
-from crispy_forms.bootstrap import Alert, InlineCheckboxes, FormActions,\
- InlineRadios
+from crispy_forms.bootstrap import (Alert, FormActions, InlineCheckboxes,
+ InlineRadios)
from crispy_forms.helper import FormHelper
-from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout, Row,\
- Field, Submit
+from crispy_forms.layout import (HTML, Button, Column, Field, Fieldset, Layout,
+ Submit)
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError
@@ -21,21 +21,25 @@ import django_filters
from sapl.base.models import Autor
from sapl.comissoes.models import Comissao
-from sapl.crispy_layout_mixin import form_actions, to_row, to_column,\
- SaplFormLayout
-from sapl.materia.models import TipoProposicao, RegimeTramitacao, TipoDocumento
+from sapl.compilacao.models import STATUS_TA_PRIVATE,\
+ STATUS_TA_IMMUTABLE_PUBLIC, TextoArticulado
+from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column,
+ to_row)
+from sapl.materia.models import TipoProposicao, MateriaLegislativa,\
+ RegimeTramitacao, TipoDocumento
from sapl.norma.models import (LegislacaoCitada, NormaJuridica,
TipoNormaJuridica)
from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import Protocolo
from sapl.settings import MAX_DOC_UPLOAD_SIZE
-from sapl.utils import (RANGE_ANOS, RangeWidgetOverride, autor_label,
- autor_modal, models_with_gr_for_model,
- ChoiceWithoutValidationField, YES_NO_CHOICES)
+from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES,
+ ChoiceWithoutValidationField,
+ MateriaPesquisaOrderingFilter, RangeWidgetOverride,
+ autor_label, autor_modal, models_with_gr_for_model)
import sapl
from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial,
- DocumentoAcessorio, MateriaLegislativa, Numeracao,
+ DocumentoAcessorio, Numeracao,
Proposicao, Relatoria, TipoMateriaLegislativa, Tramitacao,
UnidadeTramitacao)
@@ -65,6 +69,33 @@ class ReceberProposicaoForm(Form):
super(ReceberProposicaoForm, self).__init__(*args, **kwargs)
+class MateriaSimplificadaForm(ModelForm):
+
+ class Meta:
+ model = MateriaLegislativa
+ fields = ['tipo', 'numero', 'ano', 'data_apresentacao',
+ 'numero_protocolo', 'regime_tramitacao',
+ 'em_tramitacao', 'ementa', 'texto_original']
+
+ def __init__(self, *args, **kwargs):
+
+ row1 = to_row([('tipo', 6), ('numero', 3), ('ano', 3)])
+ row2 = to_row([('data_apresentacao', 6), ('numero_protocolo', 6)])
+ row3 = to_row([('regime_tramitacao', 6), ('em_tramitacao', 6)])
+ row4 = to_row([('ementa', 12)])
+ row5 = to_row([('texto_original', 12)])
+
+ self.helper = FormHelper()
+ self.helper.layout = Layout(
+ Fieldset(
+ _('Formulário Simplificado'),
+ row1, row2, row3, row4, row5,
+ form_actions(save_label='Salvar')
+ )
+ )
+ super(MateriaSimplificadaForm, self).__init__(*args, **kwargs)
+
+
class UnidadeTramitacaoForm(ModelForm):
class Meta:
@@ -84,66 +115,6 @@ class UnidadeTramitacaoForm(ModelForm):
return cleaned_data
-class ProposicaoOldForm(ModelForm):
-
- tipo_materia = forms.ModelChoiceField(
- label=_('Matéria Vinculada'),
- required=False,
- queryset=TipoMateriaLegislativa.objects.all(),
- empty_label='Selecione',
- )
-
- numero_materia = forms.CharField(
- label='Número', required=False)
-
- ano_materia = forms.CharField(
- label='Ano', required=False)
-
- def clean_texto_original(self):
- texto_original = self.cleaned_data.get('texto_original', False)
- if texto_original:
- if texto_original.size > MAX_DOC_UPLOAD_SIZE:
- raise ValidationError("Arquivo muito grande. ( > 5mb )")
- return texto_original
-
- def clean_data_envio(self):
- data_envio = self.cleaned_data.get('data_envio') or None
- if (not data_envio) and len(self.initial) > 1:
- data_envio = datetime.now()
- return data_envio
-
- def clean(self):
- cleaned_data = self.cleaned_data
- if 'tipo' in cleaned_data:
- if cleaned_data['tipo'].descricao == 'Parecer':
- if self.instance.materia:
- cleaned_data['materia'] = self.instance.materia
- else:
- try:
- materia = MateriaLegislativa.objects.get(
- tipo_id=cleaned_data['tipo_materia'],
- ano=cleaned_data['ano_materia'],
- numero=cleaned_data['numero_materia'])
- except ObjectDoesNotExist:
- msg = _('Matéria adicionada não existe!')
- raise ValidationError(msg)
- else:
- cleaned_data['materia'] = materia
- return cleaned_data
-
- def save(self, commit=False):
- proposicao = super(ProposicaoOldForm, self).save(commit)
- if 'materia' in self.cleaned_data:
- proposicao.materia = self.cleaned_data['materia']
- proposicao.save()
- return proposicao
-
- class Meta:
- model = Proposicao
- fields = ['tipo', 'data_envio', 'descricao', 'texto_original', 'autor']
- widgets = {'autor': forms.HiddenInput()}
-
-
class AcompanhamentoMateriaForm(ModelForm):
class Meta:
@@ -504,6 +475,8 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
label=u'Ano da Matéria',
choices=em_tramitacao)
+ o = MateriaPesquisaOrderingFilter()
+
class Meta:
model = MateriaLegislativa
fields = ['numero',
@@ -513,7 +486,7 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
'data_apresentacao',
'data_publicacao',
'autoria__autor__tipo',
- # 'autoria__autor__partido',
+ # FIXME 'autoria__autor__partido',
'relatoria__parlamentar_id',
'local_origem_externa',
'tramitacao__unidade_tramitacao_destino',
@@ -521,29 +494,6 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
'em_tramitacao',
]
- order_by = (
- ('', 'Selecione'),
- ('dataC', 'Data, Tipo, Ano, Numero - Ordem Crescente'),
- ('dataD', 'Data, Tipo, Ano, Numero - Ordem Decrescente'),
- ('tipoC', 'Tipo, Ano, Numero, Data - Ordem Crescente'),
- ('tipoD', 'Tipo, Ano, Numero, Data - Ordem Decrescente')
- )
-
- order_by_mapping = {
- '': [],
- 'dataC': ['data_apresentacao', 'tipo__sigla', 'ano', 'numero'],
- 'dataD': ['-data_apresentacao', '-tipo__sigla', '-ano', '-numero'],
- 'tipoC': ['tipo__sigla', 'ano', 'numero', 'data_apresentacao'],
- 'tipoD': ['-tipo__sigla', '-ano', '-numero', '-data_apresentacao'],
- }
-
- def get_order_by(self, order_value):
- if order_value in self.order_by_mapping:
- return self.order_by_mapping[order_value]
- else:
- return super(MateriaLegislativaFilterSet,
- self).get_order_by(order_value)
-
def __init__(self, *args, **kwargs):
super(MateriaLegislativaFilterSet, self).__init__(*args, **kwargs)
@@ -829,7 +779,8 @@ class TipoProposicaoForm(ModelForm):
content_type = cd['content_type']
- if 'tipo_conteudo_related' not in cd or not cd['tipo_conteudo_related']:
+ if 'tipo_conteudo_related' not in cd or not cd[
+ 'tipo_conteudo_related']:
raise ValidationError(
_('Seleção de Tipo não definida'))
@@ -951,11 +902,14 @@ class ProposicaoForm(forms.ModelForm):
if self.instance.materia_de_vinculo:
self.fields[
- 'tipo_materia'].initial = self.instance.materia_de_vinculo.tipo
+ 'tipo_materia'
+ ].initial = self.instance.materia_de_vinculo.tipo
self.fields[
- 'numero_materia'].initial = self.instance.materia_de_vinculo.numero
+ 'numero_materia'
+ ].initial = self.instance.materia_de_vinculo.numero
self.fields[
- 'ano_materia'].initial = self.instance.materia_de_vinculo.ano
+ 'ano_materia'
+ ].initial = self.instance.materia_de_vinculo.ano
def clean_texto_original(self):
texto_original = self.cleaned_data.get('texto_original', False)
@@ -1128,11 +1082,14 @@ class ConfirmarProposicaoForm(ProposicaoForm):
if self.instance.materia_de_vinculo:
self.fields[
- 'tipo_materia'].initial = self.instance.materia_de_vinculo.tipo
+ 'tipo_materia'
+ ].initial = self.instance.materia_de_vinculo.tipo
self.fields[
- 'numero_materia'].initial = self.instance.materia_de_vinculo.numero
+ 'numero_materia'
+ ].initial = self.instance.materia_de_vinculo.numero
self.fields[
- 'ano_materia'].initial = self.instance.materia_de_vinculo.ano
+ 'ano_materia'
+ ].initial = self.instance.materia_de_vinculo.ano
if self.proposicao_incorporacao_obrigatoria == 'C':
self.fields['gerar_protocolo'].initial = True
@@ -1148,8 +1105,8 @@ class ConfirmarProposicaoForm(ProposicaoForm):
raise ValidationError(
_('Regimente de Tramitação deve ser informado.'))
- elif self.instance.tipo.content_type.model_class() == TipoDocumento\
- and not cd['materia_de_vinculo']:
+ elif self.instance.tipo.content_type.model_class(
+ ) == TipoDocumento and not cd['materia_de_vinculo']:
raise ValidationError(
_('Documentos não podem ser incorporados sem definir '
@@ -1179,6 +1136,12 @@ class ConfirmarProposicaoForm(ProposicaoForm):
self.instance.data_envio = None
self.instance.save()
+ if self.instance.texto_articulado.exists():
+ ta = self.instance.texto_articulado.first()
+ ta.privacidade = STATUS_TA_PRIVATE
+ ta.editing_locked = False
+ ta.save()
+
self.instance.results = {
'messages': {
'success': [_('Devolução efetuada com sucesso.'), ]
@@ -1192,6 +1155,12 @@ class ConfirmarProposicaoForm(ProposicaoForm):
self.instance.data_devolucao = None
self.instance.data_recebimento = datetime.now()
+ if self.instance.texto_articulado.exists():
+ ta = self.instance.texto_articulado.first()
+ ta.privacidade = STATUS_TA_IMMUTABLE_PUBLIC
+ ta.editing_locked = True
+ ta.save()
+
self.instance.save()
"""
@@ -1216,7 +1185,8 @@ class ConfirmarProposicaoForm(ProposicaoForm):
proposicao = self.instance
conteudo_gerado = None
- if self.instance.tipo.content_type.model_class() == TipoMateriaLegislativa:
+ if self.instance.tipo.content_type.model_class(
+ ) == TipoMateriaLegislativa:
numero__max = MateriaLegislativa.objects.filter(
tipo=proposicao.tipo.tipo_conteudo_related,
ano=datetime.now().year).aggregate(Max('numero'))
@@ -1231,13 +1201,26 @@ class ConfirmarProposicaoForm(ProposicaoForm):
materia.data_apresentacao = datetime.now()
materia.em_tramitacao = True
materia.regime_tramitacao = cd['regime_tramitacao']
- materia.texto_original = File(
- proposicao.texto_original,
- os.path.basename(proposicao.texto_original.path))
- materia.texto_articulo = proposicao.texto_articulado
+
+ if proposicao.texto_original:
+ materia.texto_original = File(
+ proposicao.texto_original,
+ os.path.basename(proposicao.texto_original.path))
+
materia.save()
conteudo_gerado = materia
+ if proposicao.texto_articulado.exists():
+ ta = proposicao.texto_articulado.first()
+
+ ta.id = None
+ ta.content_object = materia
+ ta.save()
+
+ pass
+ # FIXME - gerar texto_articulado da materia com base na prop.
+ # materia.texto_articulo = proposicao.texto_articulado
+
self.instance.results['messages']['success'].append(_(
'Matéria Legislativa registrada com sucesso (%s)'
) % str(materia))
@@ -1349,7 +1332,8 @@ class ConfirmarProposicaoForm(ProposicaoForm):
protocolo.numero_paginas = cd['numero_de_paginas']
protocolo.anulado = False
- if self.instance.tipo.content_type.model_class() == TipoMateriaLegislativa:
+ if self.instance.tipo.content_type.model_class(
+ ) == TipoMateriaLegislativa:
protocolo.tipo_materia = proposicao.tipo.tipo_conteudo_related
elif self.instance.tipo.content_type.model_class() == TipoDocumento:
protocolo.tipo_documento = proposicao.tipo.tipo_conteudo_related
diff --git a/sapl/materia/legacy.yaml b/sapl/materia/legacy.yaml
index 899d1b72a..db2c8490f 100644
--- a/sapl/materia/legacy.yaml
+++ b/sapl/materia/legacy.yaml
@@ -37,6 +37,11 @@ MateriaLegislativa:
tipo: tip_id_basica
tipo_origem_externa: tip_origem_externa
+Autoria:
+ autor: cod_autor
+ materia: cod_materia
+ primeiro_autor: ind_primeiro_autor
+
AcompanhamentoMateria (AcompMateria):
email: end_email
hash: txt_hash
@@ -52,24 +57,6 @@ AssuntoMateria:
assunto: des_assunto
dispositivo: des_dispositivo
-TipoAutor:
- descricao: des_tipo_autor
-
-Autor:
- cargo: des_cargo
- comissao: cod_comissao
- nome: nom_autor
- parlamentar: cod_parlamentar
- partido: cod_partido
- tipo: tip_autor
- username: col_username
- user:
-
-Autoria:
- autor: cod_autor
- materia: cod_materia
- primeiro_autor: ind_primeiro_autor
-
DespachoInicial:
comissao: cod_comissao
materia: cod_materia
diff --git a/sapl/materia/models.py b/sapl/materia/models.py
index 32d8fb486..589f039ce 100644
--- a/sapl/materia/models.py
+++ b/sapl/materia/models.py
@@ -1,9 +1,5 @@
-import datetime
-import re
-
from django.contrib.auth.models import Group
-from django.contrib.contenttypes.fields import GenericForeignKey,\
- GenericRelation
+from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models.deletion import PROTECT
@@ -14,10 +10,9 @@ from sapl.base.models import Autor
from sapl.comissoes.models import Comissao
from sapl.compilacao.models import TextoArticulado
from sapl.parlamentares.models import Parlamentar
-from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES,
- restringe_tipos_de_arquivo_txt, SaplGenericRelation,
- SaplGenericForeignKey, texto_upload_path)
-
+from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, SaplGenericForeignKey,
+ SaplGenericRelation, restringe_tipos_de_arquivo_txt,
+ texto_upload_path)
EM_TRAMITACAO = [(1, 'Sim'),
(0, 'Não')]
diff --git a/sapl/materia/tests/test_materia.py b/sapl/materia/tests/test_materia.py
index d6031c442..a8aaaf2ec 100644
--- a/sapl/materia/tests/test_materia.py
+++ b/sapl/materia/tests/test_materia.py
@@ -1,9 +1,9 @@
+import pytest
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.urlresolvers import reverse
from model_mommy import mommy
-import pytest
from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import Comissao, TipoComissao
@@ -452,7 +452,7 @@ def test_proposicao_submit(admin_client):
*models_with_gr_for_model(TipoProposicao))
for pk, mct in enumerate(mcts):
- tipo_conteudo_related = mommy.make(mct, pk=pk)
+ tipo_conteudo_related = mommy.make(mct, pk=pk + 1)
response = admin_client.post(
reverse('sapl.materia:proposicao_create'),
@@ -475,7 +475,7 @@ def test_proposicao_submit(admin_client):
assert proposicao is not None
assert proposicao.descricao == 'Teste proposição'
assert proposicao.tipo.pk == 3
- assert proposicao.tipo.tipo_conteudo_related.pk == pk
+ assert proposicao.tipo.tipo_conteudo_related.pk == pk + 1
@pytest.mark.django_db(transaction=False)
diff --git a/sapl/materia/urls.py b/sapl/materia/urls.py
index c9e6a3270..9bfe31c5c 100644
--- a/sapl/materia/urls.py
+++ b/sapl/materia/urls.py
@@ -4,7 +4,8 @@ from sapl.materia.views import (AcompanhamentoConfirmarView,
AcompanhamentoExcluirView,
AcompanhamentoMateriaView, AnexadaCrud,
AutoriaCrud, ConfirmarProposicao,
- DespachoInicialCrud, DocumentoAcessorioCrud,
+ CriarProtocoloMateriaView, DespachoInicialCrud,
+ DocumentoAcessorioCrud,
DocumentoAcessorioEmLoteView,
LegislacaoCitadaCrud, MateriaLegislativaCrud,
MateriaLegislativaPesquisaView, MateriaTaView,
@@ -34,6 +35,10 @@ urlpatterns_materia = [
TramitacaoCrud.get_urls() +
RelatoriaCrud.get_urls() +
DocumentoAcessorioCrud.get_urls())),
+
+ url(r'^materia/(?P[0-9]+)/create_simplificado$',
+ CriarProtocoloMateriaView.as_view(),
+ name='materia_create_simplificado'),
url(r'^materia/recuperar-materia', recuperar_materia),
url(r'^materia/(?P[0-9]+)/ta$',
MateriaTaView.as_view(), name='materia_ta'),
diff --git a/sapl/materia/views.py b/sapl/materia/views.py
index 3fc9c8048..63d456a3e 100644
--- a/sapl/materia/views.py
+++ b/sapl/materia/views.py
@@ -5,48 +5,45 @@ from string import ascii_letters, digits
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML
from django.contrib import messages
+from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin
-from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist,\
- PermissionDenied
+from django.core.exceptions import ObjectDoesNotExist
from django.core.mail import send_mail
from django.core.urlresolvers import reverse
-from django.db.models import Q
from django.http import JsonResponse
-from django.http.response import HttpResponseRedirect, Http404
-from django.shortcuts import redirect
+from django.http.response import Http404, HttpResponseRedirect
+from django.shortcuts import redirect, get_object_or_404
from django.template import Context, loader
-from django.utils import dateformat, formats
-from django.utils.http import urlsafe_base64_decode
+from django.utils import formats
from django.utils.translation import ugettext_lazy as _
from django.views.generic import CreateView, ListView, TemplateView, UpdateView
from django.views.generic.base import RedirectView
from django.views.generic.edit import FormView
from django_filters.views import FilterView
-from sapl.base.models import Autor, CasaLegislativa, TipoAutor
+from sapl.base.models import Autor, CasaLegislativa
+from sapl.compilacao.models import STATUS_TA_PRIVATE, STATUS_TA_EDITION,\
+ STATUS_TA_IMMUTABLE_RESTRICT
+
from sapl.compilacao.views import IntegracaoTaView
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions
from sapl.crud.base import (ACTION_CREATE, ACTION_DELETE, ACTION_DETAIL,
ACTION_LIST, ACTION_UPDATE, RP_DETAIL, RP_LIST,
- Crud, CrudAux, CrudDetailView, MasterDetailCrud,
- make_pagination, PermissionRequiredForAppCrudMixin)
-from sapl.materia import apps
-from sapl.materia.forms import AnexadaForm, LegislacaoCitadaForm,\
- TipoProposicaoForm, ProposicaoForm, ConfirmarProposicaoForm
+ Crud, CrudAux, MasterDetailCrud,
+ PermissionRequiredForAppCrudMixin, make_pagination)
+from sapl.materia.forms import (AnexadaForm, ConfirmarProposicaoForm,
+ LegislacaoCitadaForm, ProposicaoForm,
+ TipoProposicaoForm)
from sapl.norma.models import LegislacaoCitada
+from sapl.protocoloadm.models import Protocolo
from sapl.utils import (TURNO_TRAMITACAO_CHOICES, YES_NO_CHOICES, autor_label,
autor_modal, gerar_hash_arquivo, get_base_url,
- montar_row_autor, permission_required_for_app,
- permissoes_autor, permissoes_materia,
- permissoes_protocoloadm, permission_required_for_app,
montar_row_autor)
import sapl
-
from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm,
- DocumentoAcessorioForm,
- MateriaLegislativaFilterSet,
- PrimeiraTramitacaoEmLoteFilterSet, ProposicaoOldForm,
+ DocumentoAcessorioForm, MateriaLegislativaFilterSet,
+ MateriaSimplificadaForm, PrimeiraTramitacaoEmLoteFilterSet,
ReceberProposicaoForm, TramitacaoEmLoteFilterSet,
filtra_tramitacao_destino,
filtra_tramitacao_destino_and_status,
@@ -74,9 +71,52 @@ TipoFimRelatoriaCrud = CrudAux.build(
TipoFimRelatoria, 'fim_relatoria')
+class CriarProtocoloMateriaView(CreateView):
+ template_name = "crud/form.html"
+ form_class = MateriaSimplificadaForm
+ form_valid_message = _('Matéria cadastrada com sucesso!')
+
+ def get_success_url(self, materia):
+ return reverse('sapl.materia:materialegislativa_detail', kwargs={
+ 'pk': materia.pk})
+
+ def get_context_data(self, **kwargs):
+ context = super(
+ CriarProtocoloMateriaView, self).get_context_data(**kwargs)
+
+ protocolo = Protocolo.objects.get(pk=self.kwargs['pk'])
+
+ context['form'].fields['tipo'].initial = protocolo.tipo_materia
+ context['form'].fields['numero'].initial = protocolo.numero
+ context['form'].fields['ano'].initial = protocolo.ano
+ context['form'].fields['data_apresentacao'].initial = protocolo.data
+ context['form'].fields['numero_protocolo'].initial = protocolo.numero
+ context['form'].fields['ementa'].initial = protocolo.observacao
+
+ return context
+
+ def form_valid(self, form):
+ materia = form.save()
+ return redirect(self.get_success_url(materia))
+
+
class MateriaTaView(IntegracaoTaView):
model = MateriaLegislativa
model_type_foreignkey = TipoMateriaLegislativa
+ map_fields = {
+ 'data': 'data_apresentacao',
+ 'ementa': 'ementa',
+ 'observacao': None,
+ 'numero': 'numero',
+ 'ano': 'ano',
+ }
+ map_funcs = {
+ 'publicacao_func': False,
+ }
+ ta_values = {
+ 'editable_only_by_owners': False,
+ 'editing_locked': False,
+ }
def get(self, request, *args, **kwargs):
"""
@@ -93,8 +133,20 @@ class MateriaTaView(IntegracaoTaView):
class ProposicaoTaView(IntegracaoTaView):
model = Proposicao
model_type_foreignkey = TipoProposicao
- # TODO implmentar o mapa de fields e utiliza-lo em IntegracaoTaView
- fields = {
+ map_fields = {
+ 'data': 'data_envio',
+ 'ementa': 'descricao',
+ 'observacao': None,
+ 'numero': 'numero_proposicao',
+ 'ano': 'ano',
+ }
+ map_funcs = {
+ 'publicacao_func': False
+ }
+ ta_values = {
+ 'editable_only_by_owners': True,
+ 'editing_locked': False,
+ 'privacidade': STATUS_TA_PRIVATE
}
def get(self, request, *args, **kwargs):
@@ -104,12 +156,19 @@ class ProposicaoTaView(IntegracaoTaView):
de usuário.
"""
if sapl.base.models.AppConfig.attr('texto_articulado_proposicao'):
+
+ proposicao = get_object_or_404(self.model, pk=kwargs['pk'])
+
+ if not proposicao.data_envio and\
+ request.user != proposicao.autor.user:
+ raise Http404()
+
return IntegracaoTaView.get(self, request, *args, **kwargs)
else:
return self.get_redirect_deactivated()
-@permission_required_for_app(app_label=apps.AppConfig.label)
+@permission_required('materia.detail_materialegislativa')
def recuperar_materia(request):
tipo = TipoMateriaLegislativa.objects.get(pk=request.GET['tipo'])
ano = request.GET.get('ano', '')
@@ -192,7 +251,7 @@ class ProposicaoDevolvida(PermissionRequiredMixin, ListView):
model = Proposicao
ordering = ['data_envio']
paginate_by = 10
- permission_required = permissoes_protocoloadm()
+ permission_required = ('materia.list_proposicao', )
def get_queryset(self):
return Proposicao.objects.filter(
@@ -216,7 +275,7 @@ class ProposicaoPendente(PermissionRequiredMixin, ListView):
model = Proposicao
ordering = ['data_envio', 'autor', 'tipo', 'descricao']
paginate_by = 10
- permission_required = permissoes_protocoloadm()
+ permission_required = ('materia.list_proposicao', )
def get_queryset(self):
return Proposicao.objects.filter(
@@ -241,7 +300,7 @@ class ProposicaoRecebida(PermissionRequiredMixin, ListView):
model = Proposicao
ordering = ['data_envio']
paginate_by = 10
- permission_required = permissoes_protocoloadm()
+ permission_required = ('materia.list_proposicao', )
def get_queryset(self):
return Proposicao.objects.filter(
@@ -274,10 +333,14 @@ class ReceberProposicao(PermissionRequiredForAppCrudMixin, FormView):
data_envio__isnull=False, data_recebimento__isnull=True)
for proposicao in proposicoes:
- # FIXME implementar hash para texto eletrônico
- hasher = gerar_hash_arquivo(
- proposicao.texto_original.path,
- str(proposicao.pk)) if proposicao.texto_original else None
+ if proposicao.texto_articulado.exists():
+ ta = proposicao.texto_articulado.first()
+ # FIXME hash para textos articulados
+ hasher = 'P' + ta.hash() + '/' + str(proposicao.id)
+ else:
+ hasher = gerar_hash_arquivo(
+ proposicao.texto_original.path,
+ str(proposicao.pk)) if proposicao.texto_original else None
if hasher == form.cleaned_data['cod_hash']:
return HttpResponseRedirect(
reverse('sapl.materia:proposicao-confirmar',
@@ -304,9 +367,6 @@ class ConfirmarProposicao(PermissionRequiredForAppCrudMixin, UpdateView):
form_class = ConfirmarProposicaoForm
def get_success_url(self):
- # FIXME redirecionamento trival,
- # ainda por implementar se será para protocolo ou para doc resultante
-
msgs = self.object.results['messages']
for key, value in msgs.items():
@@ -326,9 +386,15 @@ class ConfirmarProposicao(PermissionRequiredForAppCrudMixin, UpdateView):
data_recebimento__isnull=True)
self.object = None
# FIXME implementar hash para texto eletrônico
- hasher = gerar_hash_arquivo(
- proposicao.texto_original.path,
- str(proposicao.pk)) if proposicao.texto_original else None
+
+ if proposicao.texto_articulado.exists():
+ ta = proposicao.texto_articulado.first()
+ # FIXME hash para textos articulados
+ hasher = 'P' + ta.hash() + '/' + str(proposicao.id)
+ else:
+ hasher = gerar_hash_arquivo(
+ proposicao.texto_original.path,
+ str(proposicao.pk)) if proposicao.texto_original else None
if hasher == 'P%s/%s' % (self.kwargs['hash'], proposicao.pk):
self.object = proposicao
@@ -435,6 +501,13 @@ class ProposicaoCrud(Crud):
p.data_devolucao = None
p.data_envio = datetime.now()
p.save()
+
+ if p.texto_articulado.exists():
+ ta = p.texto_articulado.first()
+ ta.privacidade = STATUS_TA_IMMUTABLE_RESTRICT
+ ta.editing_locked = True
+ ta.save()
+
messages.success(request, _(
'Proposição enviada com sucesso.'))
@@ -447,6 +520,11 @@ class ProposicaoCrud(Crud):
else:
p.data_envio = None
p.save()
+ if p.texto_articulado.exists():
+ ta = p.texto_articulado.first()
+ ta.privacidade = STATUS_TA_PRIVATE
+ ta.editing_locked = False
+ ta.save()
messages.success(request, _(
'Proposição Retornada com sucesso.'))
@@ -508,9 +586,8 @@ class ProposicaoCrud(Crud):
messages.info(self.request,
_('Sempre que uma Proposição é inclusa ou '
'alterada e a opção "Texto Articulado " for '
- 'marcada, você será redirecionado para o '
- 'Texto Eletrônico. Use a opção "Editar Texto" '
- 'para construir seu texto.'))
+ 'marcada, você será redirecionado para a '
+ 'edição do Texto Eletrônico.'))
return reverse('sapl.materia:proposicao_ta',
kwargs={'pk': self.object.pk})
else:
@@ -564,7 +641,7 @@ class ProposicaoCrud(Crud):
class ReciboProposicaoView(TemplateView):
template_name = "materia/recibo_proposicao.html"
- permission_required = permissoes_autor()
+ permission_required = ('materia.detail_proposicao', )
def has_permission(self):
perms = self.get_permission_required()
@@ -579,10 +656,18 @@ class ReciboProposicaoView(TemplateView):
context = super(ReciboProposicaoView, self).get_context_data(
**kwargs)
proposicao = Proposicao.objects.get(pk=self.kwargs['pk'])
+
+ if proposicao.texto_original:
+ _hash = gerar_hash_arquivo(
+ proposicao.texto_original.path,
+ self.kwargs['pk'])
+ elif proposicao.texto_articulado.exists():
+ ta = proposicao.texto_articulado.first()
+ # FIXME hash para textos articulados
+ _hash = 'P' + ta.hash() + '/' + str(proposicao.id)
+
context.update({'proposicao': proposicao,
- 'hash': gerar_hash_arquivo(
- proposicao.texto_original.path,
- self.kwargs['pk'])})
+ 'hash': _hash})
return context
def get(self, request, *args, **kwargs):
@@ -592,7 +677,7 @@ class ReciboProposicaoView(TemplateView):
return TemplateView.get(self, request, *args, **kwargs)
if not proposicao.data_envio and not proposicao.data_devolucao:
- messages.error(request, _('Não é possível gerar recebo para uma '
+ messages.error(request, _('Não é possível gerar recibo para uma '
'Proposição ainda não enviada.'))
elif proposicao.data_devolucao:
messages.error(request, _('Não é possível gerar recibo.'))
@@ -916,7 +1001,7 @@ class MateriaLegislativaCrud(Crud):
class DocumentoAcessorioView(PermissionRequiredMixin, CreateView):
template_name = "materia/documento_acessorio.html"
form_class = DocumentoAcessorioForm
- permission_required = permissoes_materia()
+ permission_required = ('materia.add_documentoacessorio', )
def get(self, request, *args, **kwargs):
materia = MateriaLegislativa.objects.get(id=kwargs['pk'])
@@ -950,8 +1035,7 @@ class DocumentoAcessorioView(PermissionRequiredMixin, CreateView):
return reverse('sapl.materia:documento_acessorio', kwargs={'pk': pk})
-class AcompanhamentoConfirmarView(PermissionRequiredMixin, TemplateView):
- permission_required = permissoes_materia()
+class AcompanhamentoConfirmarView(TemplateView):
def get_redirect_url(self):
return reverse('sapl.sessao:list_pauta_sessao')
@@ -968,8 +1052,7 @@ class AcompanhamentoConfirmarView(PermissionRequiredMixin, TemplateView):
return HttpResponseRedirect(self.get_redirect_url())
-class AcompanhamentoExcluirView(PermissionRequiredMixin, TemplateView):
- permission_required = permissoes_materia()
+class AcompanhamentoExcluirView(TemplateView):
def get_redirect_url(self):
return reverse('sapl.sessao:list_pauta_sessao')
@@ -1045,9 +1128,8 @@ class MateriaLegislativaPesquisaView(FilterView):
return context
-class AcompanhamentoMateriaView(PermissionRequiredMixin, CreateView):
+class AcompanhamentoMateriaView(CreateView):
template_name = "materia/acompanhamento_materia.html"
- permission_required = permissoes_materia()
def get_random_chars(self):
s = ascii_letters + digits
@@ -1303,7 +1385,7 @@ def do_envia_email_tramitacao(request, materia):
class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
filterset_class = AcessorioEmLoteFilterSet
template_name = 'materia/em_lote/acessorio.html'
- permission_required = permissoes_materia()
+ permission_required = ('materia.add_documentoacessorio',)
def get_context_data(self, **kwargs):
context = super(DocumentoAcessorioEmLoteView,
@@ -1347,7 +1429,7 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
filterset_class = PrimeiraTramitacaoEmLoteFilterSet
template_name = 'materia/em_lote/tramitacao.html'
- permission_required = permissoes_materia()
+ permission_required = ('materia.add_tramitacao', )
def get_context_data(self, **kwargs):
context = super(PrimeiraTramitacaoEmLoteView,
diff --git a/sapl/norma/forms.py b/sapl/norma/forms.py
index c542f12b6..4fd8c8579 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 NormaJuridica
+from .models import AssuntoNorma, NormaJuridica
def get_esferas():
@@ -64,7 +64,7 @@ class NormaJuridicaPesquisaForm(ModelForm):
ano = forms.ModelChoiceField(
label='Ano',
required=False,
- queryset=NormaJuridica.objects.order_by('ano').values_list(
+ queryset=NormaJuridica.objects.order_by('-ano').values_list(
'ano', flat=True).distinct(),
empty_label='Selecione'
)
@@ -81,6 +81,13 @@ class NormaJuridicaPesquisaForm(ModelForm):
numero = forms.IntegerField(required=False)
+ assunto = forms.ModelChoiceField(
+ label='Assunto',
+ required=False,
+ queryset=AssuntoNorma.objects.all(),
+ empty_label='Selecione'
+ )
+
class Meta:
model = NormaJuridica
fields = ['tipo',
@@ -89,25 +96,20 @@ class NormaJuridicaPesquisaForm(ModelForm):
'periodo_inicial',
'periodo_final',
'publicacao_inicial',
- 'publicacao_final']
+ 'publicacao_final',
+ 'assunto']
def __init__(self, *args, **kwargs):
- row1 = to_row(
- [('tipo', 12)])
+ row1 = to_row([('tipo', 12)])
- row2 = to_row(
- [('numero', 6), ('ano', 6)])
+ row2 = to_row([('numero', 6), ('ano', 6)])
- row3 = to_row(
- [('periodo_inicial', 6), ('periodo_final', 6)])
+ row3 = to_row([('periodo_inicial', 6), ('periodo_final', 6)])
- row4 = to_row(
- [('publicacao_inicial', 6), ('publicacao_final', 6)])
+ row4 = to_row([('publicacao_inicial', 6), ('publicacao_final', 6)])
- row5 = to_row(
- [('em_vigencia', 6),
- ('ordenacao', 6)])
+ row5 = to_row([('em_vigencia', 4), ('ordenacao', 4), ('assunto', 4)])
self.helper = FormHelper()
self.helper.layout = Layout(
@@ -155,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
@@ -186,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
diff --git a/sapl/norma/migrations/0016_auto_20161027_1419.py b/sapl/norma/migrations/0016_auto_20161027_1419.py
new file mode 100644
index 000000000..b090e538a
--- /dev/null
+++ b/sapl/norma/migrations/0016_auto_20161027_1419.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-10-27 14:19
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('norma', '0015_auto_20160929_1635'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='assuntonormarelationship',
+ options={'verbose_name': 'Assunto', 'verbose_name_plural': 'Assuntos'},
+ ),
+ migrations.AlterUniqueTogether(
+ name='assuntonormarelationship',
+ unique_together=set([]),
+ ),
+ ]
diff --git a/sapl/norma/migrations/0017_auto_20161027_1432.py b/sapl/norma/migrations/0017_auto_20161027_1432.py
new file mode 100644
index 000000000..93208eb69
--- /dev/null
+++ b/sapl/norma/migrations/0017_auto_20161027_1432.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-10-27 14:32
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('norma', '0016_auto_20161027_1419'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='normajuridica',
+ name='assuntos',
+ field=models.ManyToManyField(through='norma.AssuntoNormaRelationship', to='norma.AssuntoNorma', verbose_name='Assuntos'),
+ ),
+ ]
diff --git a/sapl/norma/migrations/0018_auto_20161027_1434.py b/sapl/norma/migrations/0018_auto_20161027_1434.py
new file mode 100644
index 000000000..c70998631
--- /dev/null
+++ b/sapl/norma/migrations/0018_auto_20161027_1434.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-10-27 14:34
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('norma', '0017_auto_20161027_1432'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='assuntonormarelationship',
+ name='assunto',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='norma.AssuntoNorma', verbose_name='Assunto'),
+ ),
+ migrations.AlterField(
+ model_name='assuntonormarelationship',
+ name='norma',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='norma.NormaJuridica', verbose_name='Norma'),
+ ),
+ ]
diff --git a/sapl/norma/migrations/0019_auto_20161028_0232.py b/sapl/norma/migrations/0019_auto_20161028_0232.py
new file mode 100644
index 000000000..075607017
--- /dev/null
+++ b/sapl/norma/migrations/0019_auto_20161028_0232.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-10-28 02:32
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('norma', '0018_auto_20161027_1434'),
+ ]
+
+ operations = [
+ migrations.AlterUniqueTogether(
+ name='assuntonormarelationship',
+ unique_together=set([('assunto', 'norma')]),
+ ),
+ ]
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 f094df55f..debbdadc9 100644
--- a/sapl/norma/models.py
+++ b/sapl/norma/models.py
@@ -97,8 +97,8 @@ class NormaJuridica(models.Model):
choices=YES_NO_CHOICES)
# XXX was a CharField (attention on migrate)
assuntos = models.ManyToManyField(
- AssuntoNorma,
- through='AssuntoNormaRelationship')
+ AssuntoNorma, blank=True,
+ verbose_name=_('Assuntos'))
data_vigencia = models.DateField(blank=True, null=True)
timestamp = models.DateTimeField()
@@ -134,16 +134,6 @@ class NormaJuridica(models.Model):
update_fields=update_fields)
-class AssuntoNormaRelationship(models.Model):
- assunto = models.ForeignKey(AssuntoNorma)
- norma = models.ForeignKey(NormaJuridica)
-
- class Meta:
- unique_together = (
- ('assunto', 'norma'),
- )
-
-
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 a042e0236..a8d8c59d7 100644
--- a/sapl/norma/urls.py
+++ b/sapl/norma/urls.py
@@ -1,8 +1,8 @@
from django.conf.urls import include, url
-from sapl.norma.views import (AssuntoNormaCrud, NormaCrud, NormaPesquisaView,
- NormaTaView, PesquisaNormaListView,
- TipoNormaCrud)
+from sapl.norma.views import (AssuntoNormaCrud,
+ NormaCrud, NormaPesquisaView, NormaTaView,
+ PesquisaNormaListView, TipoNormaCrud)
from .apps import AppConfig
diff --git a/sapl/norma/views.py b/sapl/norma/views.py
index a8ca956ec..1cddc0ca9 100644
--- a/sapl/norma/views.py
+++ b/sapl/norma/views.py
@@ -7,11 +7,13 @@ from django.views.generic.base import RedirectView
from sapl.base.models import AppConfig
from sapl.compilacao.views import IntegracaoTaView
-from sapl.crud.base import RP_DETAIL, RP_LIST, Crud, CrudAux, make_pagination
+from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux,
+ MasterDetailCrud, make_pagination)
from sapl.norma.forms import NormaJuridicaForm
from .forms import NormaJuridicaPesquisaForm
-from .models import AssuntoNorma, NormaJuridica, TipoNormaJuridica
+from .models import (AssuntoNorma, NormaJuridica,
+ TipoNormaJuridica)
# LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '')
AssuntoNormaCrud = CrudAux.build(AssuntoNorma, 'assunto_norma_juridica',
@@ -26,6 +28,17 @@ TipoNormaCrud = CrudAux.build(
class NormaTaView(IntegracaoTaView):
model = NormaJuridica
model_type_foreignkey = TipoNormaJuridica
+ map_fields = {
+ 'data': 'data',
+ 'ementa': 'ementa',
+ 'observacao': 'observacao',
+ 'numero': 'numero',
+ 'ano': 'ano',
+ }
+
+ map_funcs = {
+ 'publicacao_func': True
+ }
def get(self, request, *args, **kwargs):
"""
@@ -116,6 +129,8 @@ class NormaPesquisaView(FormView):
kwargs['ordenacao'] = form.data['ordenacao']
if form.data['em_vigencia']:
kwargs['em_vigencia'] = form.data['em_vigencia']
+ if form.data['assunto']:
+ kwargs['assunto'] = form.data['assunto']
request.session['kwargs'] = kwargs
return redirect('sapl.norma:list_pesquisa_norma')
@@ -183,6 +198,9 @@ class PesquisaNormaListView(ListView):
if 'ano' in kwargs:
normas = normas.filter(ano=kwargs['ano'])
+ if 'assunto' in kwargs:
+ normas = normas.filter(assuntos=kwargs['assunto'])
+
return normas
def get_context_data(self, **kwargs):
diff --git a/sapl/painel/views.py b/sapl/painel/views.py
index 6da5c0b5a..1c37eb473 100644
--- a/sapl/painel/views.py
+++ b/sapl/painel/views.py
@@ -7,20 +7,22 @@ from django.shortcuts import render
from django.utils.translation import ugettext_lazy as _
from sapl.crud.base import Crud
+from sapl.painel.apps import AppConfig
from sapl.painel.models import Painel
from sapl.parlamentares.models import Filiacao
from sapl.sessao.models import (ExpedienteMateria, OrdemDia, PresencaOrdemDia,
RegistroVotacao, SessaoPlenaria,
SessaoPlenariaPresenca, VotoParlamentar)
-from sapl.utils import permissoes_painel
from .models import Cronometro
CronometroPainelCrud = Crud.build(Cronometro, '')
+# FIXME mudar lógica
+
def check_permission(user):
- return user.has_perms(permissoes_painel())
+ return user.has_module_perms(AppConfig.label)
@user_passes_test(check_permission)
diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py
index 4e937bd54..b461cdbee 100644
--- a/sapl/parlamentares/views.py
+++ b/sapl/parlamentares/views.py
@@ -54,6 +54,12 @@ class RelatoriaParlamentarCrud(CrudBaseForListAndDetailExternalAppView):
help_path = 'relatoria_parlamentar'
namespace = AppConfig.name
+ class BaseMixin(CrudBaseForListAndDetailExternalAppView.BaseMixin):
+
+ @classmethod
+ def url_name(cls, suffix):
+ return '%s_parlamentar_%s' % (cls.model._meta.model_name, suffix)
+
class ProposicaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView):
model = Proposicao
@@ -61,11 +67,31 @@ class ProposicaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView):
parent_field = 'autor__parlamentar_set'
namespace = AppConfig.name
+ class BaseMixin(CrudBaseForListAndDetailExternalAppView.BaseMixin):
+
+ @classmethod
+ def url_name(cls, suffix):
+ return '%s_parlamentar_%s' % (cls.model._meta.model_name, suffix)
+
class ListView(CrudBaseForListAndDetailExternalAppView.ListView):
def get_queryset(self):
return super().get_queryset().filter(
- data_envio__isnull=False)
+ data_envio__isnull=False,
+ data_recebimento__isnull=False)
+
+ class DetailView(CrudBaseForListAndDetailExternalAppView.DetailView):
+
+ @property
+ def extras_url(self):
+
+ if self.object.texto_articulado.exists():
+ ta = self.object.texto_articulado.first()
+ yield (str(reverse_lazy(
+ 'sapl.compilacao:ta_text',
+ kwargs={'ta_id': ta.pk})) + '?back_type=history',
+ 'btn-success',
+ _('Texto Eletrônico'))
class ParticipacaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView):
@@ -75,6 +101,12 @@ class ParticipacaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView):
list_field_names = ['composicao__comissao__nome', 'cargo__nome', (
'composicao__periodo__data_inicio', 'composicao__periodo__data_fim')]
+ class BaseMixin(CrudBaseForListAndDetailExternalAppView.BaseMixin):
+
+ @classmethod
+ def url_name(cls, suffix):
+ return '%s_parlamentar_%s' % (cls.model._meta.model_name, suffix)
+
class ListView(CrudBaseForListAndDetailExternalAppView.ListView):
ordering = ('-composicao__periodo')
diff --git a/sapl/protocoloadm/forms.py b/sapl/protocoloadm/forms.py
index 549cdc498..3e8a44ec5 100644
--- a/sapl/protocoloadm/forms.py
+++ b/sapl/protocoloadm/forms.py
@@ -13,14 +13,15 @@ from django.utils.translation import ugettext_lazy as _
from sapl.base.models import Autor
from sapl.crispy_layout_mixin import form_actions, to_row
from sapl.materia.models import UnidadeTramitacao
-from sapl.utils import (RANGE_ANOS, RangeWidgetOverride, autor_label,
- autor_modal)
+from sapl.utils import (RANGE_ANOS, AnoNumeroOrderingFilter,
+ RangeWidgetOverride, autor_label, autor_modal)
from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo,
Protocolo, TipoDocumentoAdministrativo,
TramitacaoAdministrativo)
TIPOS_PROTOCOLO = [('0', 'Enviado'), ('1', 'Recebido'), ('', 'Ambos')]
+TIPOS_PROTOCOLO_CREATE = [('0', 'Enviado'), ('1', 'Recebido')]
NATUREZA_PROCESSO = [('', 'Ambos'),
('0', 'Administrativo'),
@@ -68,6 +69,8 @@ class ProtocoloFilterSet(django_filters.FilterSet):
widget=forms.Select(
attrs={'class': 'selector'}))
+ o = AnoNumeroOrderingFilter()
+
class Meta:
model = Protocolo
fields = ['numero',
@@ -76,25 +79,6 @@ class ProtocoloFilterSet(django_filters.FilterSet):
'tipo_materia',
]
- order_by = (
- ('', 'Selecione'),
- ('CRE', 'Ordem Crescente'),
- ('DEC', 'Ordem Decrescente'),
- )
-
- order_by_mapping = {
- '': [],
- 'CRE': ['ano', 'numero'],
- 'DEC': ['-ano', '-numero'],
- }
-
- def get_order_by(self, order_value):
- if order_value in self.order_by_mapping:
- return self.order_by_mapping[order_value]
- else:
- return super(ProtocoloFilterSet,
- self).get_order_by(order_value)
-
def __init__(self, *args, **kwargs):
super(ProtocoloFilterSet, self).__init__(*args, **kwargs)
@@ -131,7 +115,7 @@ class ProtocoloFilterSet(django_filters.FilterSet):
self.form.helper = FormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
- Fieldset(_('Pesquisar Protocolo'),
+ Fieldset('',
row1, row2,
row3,
HTML(autor_label),
@@ -162,6 +146,8 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet):
interessado = django_filters.CharFilter(lookup_expr='icontains')
+ o = AnoNumeroOrderingFilter()
+
class Meta:
model = DocumentoAdministrativo
fields = ['tipo',
@@ -171,25 +157,6 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet):
'tramitacaoadministrativo__unidade_tramitacao_destino',
'tramitacaoadministrativo__status']
- order_by = (
- ('', 'Selecione'),
- ('CRE', 'Ordem Crescente'),
- ('DEC', 'Ordem Decrescente'),
- )
-
- order_by_mapping = {
- '': [],
- 'CRE': ['ano', 'numero'],
- 'DEC': ['-ano', '-numero'],
- }
-
- def get_order_by(self, order_value):
- if order_value in self.order_by_mapping:
- return self.order_by_mapping[order_value]
- else:
- return super(DocumentoAdministrativoFilterSet,
- self).get_order_by(order_value)
-
def __init__(self, *args, **kwargs):
super(DocumentoAdministrativoFilterSet, self).__init__(*args, **kwargs)
@@ -304,7 +271,7 @@ class ProtocoloDocumentForm(ModelForm):
tipo_protocolo = forms.ChoiceField(required=True,
label=_('Tipo de Protocolo'),
- choices=TIPOS_PROTOCOLO,)
+ choices=TIPOS_PROTOCOLO_CREATE,)
tipo_documento = forms.ModelChoiceField(
label=_('Tipo de Documento'),
@@ -313,7 +280,7 @@ class ProtocoloDocumentForm(ModelForm):
empty_label='Selecione',
)
- num_paginas = forms.CharField(label=_('Núm. Páginas'), required=True)
+ numero_paginas = forms.CharField(label=_('Núm. Páginas'), required=True)
assunto = forms.CharField(
widget=forms.Textarea, label='Assunto', required=True)
@@ -327,7 +294,7 @@ class ProtocoloDocumentForm(ModelForm):
model = Protocolo
fields = ['tipo_protocolo',
'tipo_documento',
- 'num_paginas',
+ 'numero_paginas',
'assunto',
'interessado',
'observacao',
@@ -339,7 +306,7 @@ class ProtocoloDocumentForm(ModelForm):
[(InlineRadios('tipo_protocolo'), 12)])
row2 = to_row(
[('tipo_documento', 6),
- ('num_paginas', 6)])
+ ('numero_paginas', 6)])
row3 = to_row(
[('assunto', 12)])
row4 = to_row(
@@ -364,11 +331,6 @@ class ProtocoloDocumentForm(ModelForm):
class ProtocoloMateriaForm(ModelForm):
-
- tipo_protocolo = forms.ChoiceField(required=True,
- label='Tipo de Protocolo',
- choices=TIPOS_PROTOCOLO,)
-
autor = forms.IntegerField(widget=forms.HiddenInput(), required=False)
def clean_autor(self):
@@ -383,8 +345,7 @@ class ProtocoloMateriaForm(ModelForm):
class Meta:
model = Protocolo
- fields = ['tipo_protocolo',
- 'tipo_materia',
+ fields = ['tipo_materia',
'numero_paginas',
'autor',
'observacao']
@@ -392,11 +353,9 @@ class ProtocoloMateriaForm(ModelForm):
def __init__(self, *args, **kwargs):
row1 = to_row(
- [(InlineRadios('tipo_protocolo'), 12)])
- row2 = to_row(
[('tipo_materia', 4),
('numero_paginas', 4)])
- row3 = to_row(
+ row2 = to_row(
[('autor', 0),
(Button('pesquisar',
'Pesquisar Autor',
@@ -404,25 +363,23 @@ class ProtocoloMateriaForm(ModelForm):
(Button('limpar',
'limpar Autor',
css_class='btn btn-primary btn-sm'), 10)])
- row4 = to_row(
+ row3 = to_row(
[('observacao', 12)])
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(_('Identificação da Matéria'),
row1,
+ HTML(autor_label),
+ HTML(autor_modal),
row2,
- HTML(autor_label),
- HTML(autor_modal),
row3,
- row4,
form_actions(save_label='Protocolar Matéria')
)
)
super(ProtocoloMateriaForm, self).__init__(
*args, **kwargs)
- self.fields['tipo_protocolo'].inline_class = True
class DocumentoAcessorioAdministrativoForm(ModelForm):
@@ -587,6 +544,10 @@ class DocumentoAdministrativoForm(ModelForm):
'texto_integral',
]
+ def save(self, commit=True):
+ documento = super(DocumentoAdministrativoForm, self).save(commit)
+ return documento
+
def __init__(self, *args, **kwargs):
row1 = to_row(
diff --git a/sapl/protocoloadm/migrations/0005_auto_20161027_1741.py b/sapl/protocoloadm/migrations/0005_auto_20161027_1741.py
new file mode 100644
index 000000000..891ff2a5e
--- /dev/null
+++ b/sapl/protocoloadm/migrations/0005_auto_20161027_1741.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-10-27 17:41
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('protocoloadm', '0004_auto_20161023_1444'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='protocolo',
+ options={'permissions': (('action_anular_protocolo', 'Permissão para Anular Protocolo'),), 'verbose_name': 'Protocolo', 'verbose_name_plural': 'Protocolos'},
+ ),
+ ]
diff --git a/sapl/protocoloadm/migrations/0006_auto_20161103_1721.py b/sapl/protocoloadm/migrations/0006_auto_20161103_1721.py
new file mode 100644
index 000000000..4100f4157
--- /dev/null
+++ b/sapl/protocoloadm/migrations/0006_auto_20161103_1721.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-11-03 17:21
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('protocoloadm', '0005_auto_20161027_1741'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='protocolo',
+ name='tipo_protocolo',
+ field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Tipo de Protocolo'),
+ ),
+ ]
diff --git a/sapl/protocoloadm/models.py b/sapl/protocoloadm/models.py
index 16fa8462d..68401a7d8 100644
--- a/sapl/protocoloadm/models.py
+++ b/sapl/protocoloadm/models.py
@@ -1,13 +1,9 @@
-from uuid import uuid4
-
from django.db import models
from django.utils.translation import ugettext_lazy as _
from model_utils import Choices
from sapl.base.models import Autor
-
-from sapl.materia.models import (TipoMateriaLegislativa,
- UnidadeTramitacao)
+from sapl.materia.models import TipoMateriaLegislativa, UnidadeTramitacao
from sapl.utils import RANGE_ANOS, YES_NO_CHOICES, texto_upload_path
@@ -123,7 +119,7 @@ class Protocolo(models.Model):
# TODO transformar campo timestamp em auto_now_add
timestamp = models.DateTimeField()
tipo_protocolo = models.PositiveIntegerField(
- verbose_name=_('Tipo de Protocolo'))
+ blank=True, null=True, verbose_name=_('Tipo de Protocolo'))
tipo_processo = models.PositiveIntegerField()
interessado = models.CharField(
max_length=60, blank=True, verbose_name=_('Interessado'))
@@ -153,6 +149,9 @@ class Protocolo(models.Model):
class Meta:
verbose_name = _('Protocolo')
verbose_name_plural = _('Protocolos')
+ permissions = (
+ ('action_anular_protocolo', _('Permissão para Anular Protocolo')),
+ )
class StatusTramitacaoAdministrativo(models.Model):
diff --git a/sapl/protocoloadm/tests/test_protocoloadm.py b/sapl/protocoloadm/tests/test_protocoloadm.py
index ea012b47f..66a2409f8 100644
--- a/sapl/protocoloadm/tests/test_protocoloadm.py
+++ b/sapl/protocoloadm/tests/test_protocoloadm.py
@@ -1,10 +1,10 @@
import datetime
+import pytest
from django.core.urlresolvers import reverse
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from model_mommy import mommy
-import pytest
from sapl.materia.models import UnidadeTramitacao
from sapl.protocoloadm.forms import AnularProcoloAdmForm
diff --git a/sapl/protocoloadm/urls.py b/sapl/protocoloadm/urls.py
index 7b2a1ac69..21c627b06 100644
--- a/sapl/protocoloadm/urls.py
+++ b/sapl/protocoloadm/urls.py
@@ -9,9 +9,8 @@ from sapl.protocoloadm.views import (AnularProtocoloAdmView,
DocumentoAcessorioAdministrativoView,
DocumentoAdministrativoCrud,
PesquisarDocumentoAdministrativoView,
- ProtocoloDocumentoCrud,
- ProtocoloDocumentoView, ProtocoloListView,
- ProtocoloMateriaCrud,
+ ProtocoloDocumentoView,
+ ProtocoloMateriaTemplateView,
ProtocoloMateriaView,
ProtocoloMostrarView,
ProtocoloPesquisaView,
@@ -42,31 +41,40 @@ urlpatterns_documento_administrativo = [
]
urlpatterns_protocolo = [
- url(r'^protocoloadm/protocolo-doc/',
- include(ProtocoloDocumentoCrud.get_urls())),
- url(r'^protocoloadm/protocolo-mat/',
- include(ProtocoloMateriaCrud.get_urls()), name='protocolomat'),
- url(r'^protocoloadm/protocolo$',
+
+ # url(r'^protocoloadm/protocolo-doc/',
+ # include(ProtocoloDocumentoCrud.get_urls())),
+ # url(r'^protocoloadm/protocolo-mat/',
+ # include(ProtocoloMateriaCrud.get_urls()), name='protocolomat'),
+ # url(r'^protocoloadm/protocolo-list$',
+ # ProtocoloListView.as_view(), name='protocolo_list'),
+
+ url(r'^protocoloadm/$',
ProtocoloPesquisaView.as_view(), name='protocolo'),
- url(r'^protocoloadm/protocolo-list$',
- ProtocoloListView.as_view(), name='protocolo_list'),
- url(r'^protocoloadm/anular-protocolo',
- AnularProtocoloAdmView.as_view(), name='anular_protocolo'),
+
url(r'^protocoloadm/protocolar-doc',
ProtocoloDocumentoView.as_view(), name='protocolar_doc'),
+
+
+ url(r'^protocoloadm/(?P\d+)/protocolo-mostrar$',
+ ProtocoloMostrarView.as_view(), name='protocolo_mostrar'),
+
+
+
+ url(r'^protocoloadm/(?P\d+)/continuar$',
+ ProtocoloMateriaTemplateView.as_view(), name='materia_continuar'),
+
+
+ url(r'^protocoloadm/anular-protocolo',
+ AnularProtocoloAdmView.as_view(), name='anular_protocolo'),
url(r'^protocoloadm/protocolar-mat',
ProtocoloMateriaView.as_view(), name='protocolar_mat'),
- # FIXME estas urls com pk e ano não fazem sentido
- # se vai buscar por pk não precisa de nenhuma outra informação
- # mas veja, apesar de chamar de pk aqui nas urls
- # usou-se dentro da view como paramentro para ano.
- url(r'^protocoloadm/(?P\d+)/(?P\d+)/protocolo-mostrar$',
- ProtocoloMostrarView.as_view(), name='protocolo_mostrar'),
- url(r'^protocoloadm/(?P\d+)/(?P\d+)/comprovante$',
+ url(r'^protocoloadm/(?P\d+)/comprovante$',
ComprovanteProtocoloView.as_view(), name='comprovante_protocolo'),
- url(r'^protocoloadm/(?P\d+)/(?P\d+)/criar-documento$',
+ url(r'^protocoloadm/(?P\d+)/criar-documento$',
CriarDocumentoProtocolo.as_view(), name='criar_documento'),
+
]
urlpatterns_sistema = [
diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py
index 8a52c1e38..e45c943b4 100644
--- a/sapl/protocoloadm/views.py
+++ b/sapl/protocoloadm/views.py
@@ -3,6 +3,7 @@ from datetime import date, datetime
from braces.views import FormValidMessageMixin
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
+from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from django.db.models import Max
from django.http import HttpResponseRedirect
@@ -12,12 +13,10 @@ from django.views.generic import CreateView, DetailView, FormView, ListView
from django.views.generic.base import TemplateView
from django_filters.views import FilterView
-from sapl.base.apps import AppConfig as AppsAppConfig
-from sapl.base.models import AppConfig
+import sapl
from sapl.crud.base import Crud, CrudAux, MasterDetailCrud, make_pagination
-from sapl.materia.models import TipoMateriaLegislativa
-from sapl.utils import (create_barcode, get_client_ip, permissoes_adm,
- permissoes_protocoloadm)
+from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
+from sapl.utils import create_barcode, get_client_ip
from .forms import (AnularProcoloAdmForm, DocumentoAcessorioAdministrativoForm,
DocumentoAdministrativoFilterSet,
@@ -32,9 +31,9 @@ TipoDocumentoAdministrativoCrud = CrudAux.build(
TipoDocumentoAdministrativo, '')
-ProtocoloDocumentoCrud = Crud.build(Protocolo, '')
+# ProtocoloDocumentoCrud = Crud.build(Protocolo, '')
# FIXME precisa de uma chave diferente para o layout
-ProtocoloMateriaCrud = Crud.build(Protocolo, '')
+# ProtocoloMateriaCrud = Crud.build(Protocolo, '')
DocumentoAcessorioAdministrativoCrud = Crud.build(
@@ -44,11 +43,11 @@ DocumentoAcessorioAdministrativoCrud = Crud.build(
class DocumentoAdministrativoMixin:
def has_permission(self):
- app_config = AppConfig.objects.last()
+ app_config = sapl.base.models.AppConfig.objects.last()
if app_config and app_config.documentos_administrativos == 'O':
return True
- return self.request.user.has_module_perms(AppsAppConfig.label)
+ return super().has_permission()
class DocumentoAdministrativoCrud(Crud):
@@ -60,10 +59,10 @@ class DocumentoAdministrativoCrud(Crud):
'numero_protocolo', 'assunto',
'interessado', 'tramitacao', 'texto_integral']
- class ListView(Crud.ListView, DocumentoAdministrativoMixin):
+ class ListView(DocumentoAdministrativoMixin, Crud.ListView):
pass
- class DetailView(Crud.DetailView, DocumentoAdministrativoMixin):
+ class DetailView(DocumentoAdministrativoMixin, Crud.DetailView):
pass
@@ -82,7 +81,7 @@ class ProtocoloPesquisaView(PermissionRequiredMixin, FilterView):
model = Protocolo
filterset_class = ProtocoloFilterSet
paginate_by = 10
- permission_required = permissoes_protocoloadm()
+ permission_required = ('protocoloadm.list_protocolo',)
def get_filterset_kwargs(self, filterset_class):
super(ProtocoloPesquisaView,
@@ -109,6 +108,8 @@ class ProtocoloPesquisaView(PermissionRequiredMixin, FilterView):
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
+ context['title'] = _('Pesquisa de Protocolos')
+
return context
def get(self, request, *args, **kwargs):
@@ -143,7 +144,7 @@ class ProtocoloListView(PermissionRequiredMixin, ListView):
context_object_name = 'protocolos'
model = Protocolo
paginate_by = 10
- permission_required = permissoes_protocoloadm()
+ permission_required = ('protocoloadm.list_protocolo',)
def get_queryset(self):
kwargs = self.request.session['kwargs']
@@ -166,7 +167,7 @@ class AnularProtocoloAdmView(PermissionRequiredMixin, CreateView):
template_name = 'protocoloadm/anular_protocoloadm.html'
form_class = AnularProcoloAdmForm
form_valid_message = _('Protocolo anulado com sucesso!')
- permission_required = permissoes_protocoloadm()
+ permission_required = ('protocoloadm.action_anular_protocolo', )
def get_success_url(self):
return reverse('sapl.protocoloadm:protocolo')
@@ -195,16 +196,18 @@ class ProtocoloDocumentoView(PermissionRequiredMixin,
template_name = "protocoloadm/protocolar_documento.html"
form_class = ProtocoloDocumentForm
form_valid_message = _('Protocolo cadastrado com sucesso!')
- permission_required = permissoes_protocoloadm()
+ permission_required = ('protocoloadm.add_protocolo', )
def get_success_url(self):
- return reverse('sapl.protocoloadm:protocolo')
+ return reverse('sapl.protocoloadm:protocolo_mostrar',
+ kwargs={'pk': self.object.id})
def form_valid(self, form):
f = form.save(commit=False)
try:
- numeracao = AppConfig.objects.last().sequencia_numeracao
+ numeracao = sapl.base.models.AppConfig.objects.last(
+ ).sequencia_numeracao
except AttributeError:
msg = _('É preciso definir a sequencia de ' +
'numeração na tabelas auxiliares!')
@@ -230,31 +233,28 @@ class ProtocoloDocumentoView(PermissionRequiredMixin,
f.assunto_ementa = self.request.POST['assunto']
f.save()
+ self.object = f
return redirect(self.get_success_url())
class CriarDocumentoProtocolo(PermissionRequiredMixin, CreateView):
template_name = "protocoloadm/criar_documento.html"
form_class = DocumentoAdministrativoForm
- permission_required = permissoes_protocoloadm()
+ permission_required = ('protocoloadm.add_documentoadministrativo',)
def get_initial(self):
- numero = self.kwargs['pk']
- ano = self.kwargs['ano']
- protocolo = Protocolo.objects.get(ano=ano, numero=numero)
+ protocolo = Protocolo.objects.get(pk=self.kwargs['pk'])
return self.criar_documento(protocolo)
def get_success_url(self):
return reverse('sapl.protocoloadm:protocolo_mostrar',
- kwargs={'pk': self.kwargs['pk'],
- 'ano': self.kwargs['ano']})
+ kwargs={'pk': self.kwargs['pk']})
def criar_documento(self, protocolo):
- numero = Protocolo.objects.filter(
- tipo_documento=protocolo.tipo_documento,
- ano=protocolo.ano,
- anulado=False).aggregate(Max('numero'))
+ numero_max = DocumentoAdministrativo.objects.filter(
+ tipo=protocolo.tipo_documento
+ ).aggregate(Max('numero'))['numero__max']
doc = {}
doc['tipo'] = protocolo.tipo_documento
@@ -263,24 +263,36 @@ class CriarDocumentoProtocolo(PermissionRequiredMixin, CreateView):
doc['numero_protocolo'] = protocolo.numero
doc['assunto'] = protocolo.assunto_ementa
doc['interessado'] = protocolo.interessado
- doc['numero'] = numero['numero__max']
- if doc['numero'] is None:
- doc['numero'] = 1
- else:
- doc['numero'] = doc['numero'] + 1
+ doc['numero'] = numero_max + 1 if numero_max else 1
return doc
class ProtocoloMostrarView(PermissionRequiredMixin, TemplateView):
template_name = "protocoloadm/protocolo_mostrar.html"
- permission_required = permissoes_protocoloadm()
+ permission_required = ('protocoloadm.detail_protocolo', )
def get_context_data(self, **kwargs):
context = super(ProtocoloMostrarView, self).get_context_data(**kwargs)
- numero = self.kwargs['pk']
- ano = self.kwargs['ano']
- protocolo = Protocolo.objects.get(ano=ano, numero=numero)
+ protocolo = Protocolo.objects.get(pk=self.kwargs['pk'])
+
+ if protocolo.tipo_materia:
+ try:
+ materia = MateriaLegislativa.objects.get(
+ numero_protocolo=protocolo.numero, ano=protocolo.ano)
+ except ObjectDoesNotExist:
+ context['materia'] = None
+ else:
+ context['materia'] = materia
+ elif protocolo.tipo_documento:
+ try:
+ documento = DocumentoAdministrativo.objects.get(
+ numero_protocolo=protocolo.numero, ano=protocolo.ano)
+ except ObjectDoesNotExist:
+ context['documento'] = None
+ else:
+ context['documento'] = documento
+
context['protocolo'] = protocolo
return context
@@ -288,16 +300,14 @@ class ProtocoloMostrarView(PermissionRequiredMixin, TemplateView):
class ComprovanteProtocoloView(PermissionRequiredMixin, TemplateView):
template_name = "protocoloadm/comprovante.html"
- permission_required = permissoes_protocoloadm()
+ permission_required = ('protocoloadm.detail_protocolo', )
def get_context_data(self, **kwargs):
context = super(ComprovanteProtocoloView, self).get_context_data(
**kwargs)
- numero = self.kwargs['pk']
- ano = self.kwargs['ano']
- protocolo = Protocolo.objects.get(ano=ano, numero=numero)
+ protocolo = Protocolo.objects.get(pk=self.kwargs['pk'])
# numero is string, padd with zeros left via .zfill()
- base64_data = create_barcode(numero.zfill(6))
+ base64_data = create_barcode(str(protocolo.numero).zfill(6))
barcode = 'data:image/png;base64,{0}'.format(base64_data)
autenticacao = _("** NULO **")
@@ -319,14 +329,16 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
template_name = "protocoloadm/protocolar_materia.html"
form_class = ProtocoloMateriaForm
form_valid_message = _('Matéria cadastrada com sucesso!')
- permission_required = permissoes_protocoloadm()
+ permission_required = ('protocoloadm.add_protocolo',)
- def get_success_url(self):
- return reverse('sapl.protocoloadm:protocolo')
+ def get_success_url(self, protocolo):
+ return reverse('sapl.protocoloadm:materia_continuar', kwargs={
+ 'pk': protocolo.pk})
def form_valid(self, form):
try:
- numeracao = AppConfig.objects.last().sequencia_numeracao
+ numeracao = sapl.base.models.AppConfig.objects.last(
+ ).sequencia_numeracao
except AttributeError:
msg = _('É preciso definir a sequencia de ' +
'numeração na tabelas auxiliares!')
@@ -349,7 +361,6 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
protocolo.data = datetime.now().strftime("%Y-%m-%d")
protocolo.hora = datetime.now().strftime("%H:%M")
protocolo.timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
- protocolo.tipo_protocolo = self.request.POST['tipo_protocolo']
protocolo.tipo_processo = '0' # TODO validar o significado
if form.cleaned_data['autor']:
protocolo.autor = form.cleaned_data['autor']
@@ -359,16 +370,29 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
protocolo.numero_paginas = self.request.POST['numero_paginas']
protocolo.observacao = self.request.POST['observacao']
protocolo.save()
- return redirect(self.get_success_url())
+ return redirect(self.get_success_url(protocolo))
-class PesquisarDocumentoAdministrativoView(PermissionRequiredMixin,
- FilterView,
- DocumentoAdministrativoMixin):
+class ProtocoloMateriaTemplateView(PermissionRequiredMixin, TemplateView):
+
+ template_name = "protocoloadm/MateriaTemplate.html"
+ permission_required = ('protocoloadm.detail_protocolo', )
+
+ def get_context_data(self, **kwargs):
+ context = super(ProtocoloMateriaTemplateView, self).get_context_data(
+ **kwargs)
+ protocolo = Protocolo.objects.get(pk=self.kwargs['pk'])
+ context.update({'protocolo': protocolo})
+ return context
+
+
+class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
+ PermissionRequiredMixin,
+ FilterView):
model = DocumentoAdministrativo
filterset_class = DocumentoAdministrativoFilterSet
paginate_by = 10
- permission_required = permissoes_adm()
+ permission_required = ('protocoloadm.list_documentoadministrativo', )
def get_filterset_kwargs(self, filterset_class):
super(PesquisarDocumentoAdministrativoView,
@@ -426,7 +450,7 @@ class PesquisarDocumentoAdministrativoView(PermissionRequiredMixin,
class DetailDocumentoAdministrativo(PermissionRequiredMixin, DetailView):
template_name = "protocoloadm/detail_doc_adm.html"
- permission_required = permissoes_adm()
+ permission_required = ('protocoloadm.detail_documentoadministrativo', )
def get(self, request, *args, **kwargs):
documento = DocumentoAdministrativo.objects.get(
@@ -468,7 +492,8 @@ class DetailDocumentoAdministrativo(PermissionRequiredMixin, DetailView):
class DocumentoAcessorioAdministrativoEditView(PermissionRequiredMixin,
FormView):
template_name = "protocoloadm/documento_acessorio_administrativo_edit.html"
- permission_required = permissoes_adm()
+ permission_required = (
+ 'protocoloadm.change_documentoacessorioadministrativo', )
def get(self, request, *args, **kwargs):
doc = DocumentoAdministrativo.objects.get(
@@ -516,7 +541,8 @@ class DocumentoAcessorioAdministrativoEditView(PermissionRequiredMixin,
class DocumentoAcessorioAdministrativoView(PermissionRequiredMixin, FormView):
template_name = "protocoloadm/documento_acessorio_administrativo.html"
- permission_required = permissoes_adm()
+ permission_required = (
+ 'protocoloadm.add_documentoacessorioadministrativo', )
def get(self, request, *args, **kwargs):
form = DocumentoAcessorioAdministrativoForm()
@@ -569,65 +595,13 @@ class TramitacaoAdmCrud(MasterDetailCrud):
class UpdateView(MasterDetailCrud.UpdateView):
form_class = TramitacaoAdmEditForm
- class ListView(MasterDetailCrud.ListView, DocumentoAdministrativoMixin):
+ class ListView(DocumentoAdministrativoMixin, MasterDetailCrud.ListView):
def get_queryset(self):
qs = super(MasterDetailCrud.ListView, self).get_queryset()
kwargs = {self.crud.parent_field: self.kwargs['pk']}
return qs.filter(**kwargs).order_by('-data_tramitacao', '-id')
- class DetailView(MasterDetailCrud.DetailView,
- DocumentoAdministrativoMixin):
+ class DetailView(DocumentoAdministrativoMixin,
+ MasterDetailCrud.DetailView):
pass
-
-
-"""
-def get_nome_autor(request):
- nome_autor = ''
- if request.method == 'GET':
- id = request.GET.get('id', '')
- try:
- autor = Autor.objects.get(pk=id)
- if autor.parlamentar:
- nome_autor = autor.parlamentar.nome_parlamentar
- elif autor.comissao:
- nome_autor = autor.comissao.nome
- except ObjectDoesNotExist:
- pass
- return HttpResponse("{\"nome\":\"" + nome_autor + "\"}",
- content_type="application/json; charset=utf-8")"""
-
-"""
-def pesquisa_autores(request):
- q = ''
- if request.method == 'GET':
- q = request.GET.get('q', '')
-
- autor = Autor.objects.filter(
- Q(nome__icontains=q) |
- Q(parlamentar__nome_parlamentar__icontains=q) |
- Q(comissao__nome__icontains=q)
- )
-
- autor = Autor.objects.filter(nome__icontains=q)
-
- autores = []
-
- for a in autor:
- nome = ''
- if a.nome:
- nome = a.nome
- elif a.parlamentar:
- nome = a.parlamentar.nome_parlamentar
- elif a.comissao:
- nome = a.comissao.nome
-
- autores.append((a.id, nome))
-
- autores = sorted(autores, key=lambda x: x[1])
-
- return HttpResponse(json.dumps(autores,
- sort_keys=True,
- ensure_ascii=False),
- content_type="application/json; charset=utf-8")
-"""
diff --git a/sapl/relatorios/views.py b/sapl/relatorios/views.py
index c574827a0..ac10778aa 100644
--- a/sapl/relatorios/views.py
+++ b/sapl/relatorios/views.py
@@ -19,7 +19,6 @@ from sapl.sessao.models import (ExpedienteMateria, ExpedienteSessao, Orador,
SessaoPlenariaPresenca, TipoExpediente)
from sapl.settings import STATIC_ROOT
from sapl.utils import UF
-import sapl
from .templates import (pdf_capa_processo_gerar,
pdf_documento_administrativo_gerar, pdf_espelho_gerar,
@@ -27,7 +26,6 @@ from .templates import (pdf_capa_processo_gerar,
pdf_ordem_dia_gerar, pdf_pauta_sessao_gerar,
pdf_protocolo_gerar, pdf_sessao_plenaria_gerar)
-
uf_dic = dict(UF)
diff --git a/sapl/rules/__init__.py b/sapl/rules/__init__.py
new file mode 100644
index 000000000..407b77ba1
--- /dev/null
+++ b/sapl/rules/__init__.py
@@ -0,0 +1,38 @@
+from django.utils.translation import ugettext_lazy as _
+
+default_app_config = 'sapl.rules.apps.AppConfig'
+
+
+SAPL_GROUP_ADMINISTRATIVO = _("Operador Administrativo")
+SAPL_GROUP_PROTOCOLO = _("Operador de Protocolo Administrativo")
+SAPL_GROUP_COMISSOES = _("Operador de Comissões")
+SAPL_GROUP_MATERIA = _("Operador de Matéria")
+SAPL_GROUP_NORMA = _("Operador de Norma Jurídica")
+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")
+
+# TODO - funcionalidade ainda não existe mas está aqui para efeito de anotação
+SAPL_GROUP_LOGIN_SOCIAL = _("Usuários com Login Social")
+
+# ANONYMOUS não é um grupo mas é uma variável usadas nas rules para anotar
+# explicitamente models que podem ter ação de usuários anônimos
+# como por exemplo AcompanhamentoMateria
+SAPL_GROUP_ANONYMOUS = ''
+
+SAPL_GROUPS = [
+ SAPL_GROUP_ADMINISTRATIVO,
+ SAPL_GROUP_PROTOCOLO,
+ SAPL_GROUP_COMISSOES,
+ SAPL_GROUP_MATERIA,
+ SAPL_GROUP_NORMA,
+ SAPL_GROUP_SESSAO,
+ SAPL_GROUP_PAINEL,
+ SAPL_GROUP_GERAL,
+ SAPL_GROUP_AUTOR,
+ SAPL_GROUP_PARLAMENTAR,
+ SAPL_GROUP_LOGIN_SOCIAL,
+ SAPL_GROUP_ANONYMOUS,
+]
diff --git a/sapl/rules/apps.py b/sapl/rules/apps.py
new file mode 100644
index 000000000..affce7bec
--- /dev/null
+++ b/sapl/rules/apps.py
@@ -0,0 +1,234 @@
+from builtins import LookupError
+
+import django
+from django.apps import apps
+from django.conf import settings
+from django.contrib.auth import get_user_model
+from django.contrib.auth.management import _get_all_permissions
+from django.core import exceptions
+from django.db import models, router
+from django.db.utils import DEFAULT_DB_ALIAS
+from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import string_concat
+
+from sapl.rules import (SAPL_GROUP_ADMINISTRATIVO, SAPL_GROUP_COMISSOES,
+ SAPL_GROUP_GERAL, SAPL_GROUP_MATERIA, SAPL_GROUP_NORMA,
+ SAPL_GROUP_PAINEL, SAPL_GROUP_PROTOCOLO,
+ SAPL_GROUP_SESSAO)
+
+
+class AppConfig(django.apps.AppConfig):
+ name = 'sapl.rules'
+ label = 'rules'
+ verbose_name = _('Regras de Acesso')
+
+
+def create_proxy_permissions(
+ app_config, verbosity=2, interactive=True,
+ using=DEFAULT_DB_ALIAS, **kwargs):
+ if not app_config.models_module:
+ return
+
+ # print(app_config)
+
+ try:
+ Permission = apps.get_model('auth', 'Permission')
+ except LookupError:
+ return
+
+ if not router.allow_migrate_model(using, Permission):
+ return
+
+ from django.contrib.contenttypes.models import ContentType
+
+ permission_name_max_length = Permission._meta.get_field('name').max_length
+
+ # This will hold the permissions we're looking for as
+ # (content_type, (codename, name))
+ searched_perms = list()
+ # The codenames and ctypes that should exist.
+ ctypes = set()
+ for klass in list(app_config.get_models()):
+ opts = klass._meta
+ permissions = (
+ ("list_" + opts.model_name,
+ string_concat(
+ _('Visualizaçao da lista de'), ' ',
+ opts.verbose_name_plural)),
+ ("detail_" + opts.model_name,
+ string_concat(
+ _('Visualização dos detalhes de'), ' ',
+ opts.verbose_name_plural)),
+ )
+ opts.permissions = tuple(
+ set(list(permissions) + list(opts.permissions)))
+
+ if opts.proxy:
+ # Force looking up the content types in the current database
+ # before creating foreign keys to them.
+ app_label, model = opts.app_label, opts.model_name
+
+ try:
+ ctype = ContentType.objects.db_manager(
+ using).get_by_natural_key(app_label, model)
+ except:
+ ctype = ContentType.objects.db_manager(
+ using).create(app_label=app_label, model=model)
+ else:
+ ctype = ContentType.objects.db_manager(using).get_for_model(klass)
+
+ ctypes.add(ctype)
+ for perm in _get_all_permissions(klass._meta, ctype):
+ searched_perms.append((ctype, perm))
+
+ # Find all the Permissions that have a content_type for a model we're
+ # looking for. We don't need to check for codenames since we already have
+ # a list of the ones we're going to create.
+ all_perms = set(Permission.objects.using(using).filter(
+ content_type__in=ctypes,
+ ).values_list(
+ "content_type", "codename"
+ ))
+
+ perms = [
+ Permission(codename=codename, name=name, content_type=ct)
+ for ct, (codename, name) in searched_perms
+ if (ct.pk, codename) not in all_perms
+ ]
+ # Validate the permissions before bulk_creation to avoid cryptic database
+ # error when the name is longer than 255 characters
+ for perm in perms:
+ if len(perm.name) > permission_name_max_length:
+ raise exceptions.ValidationError(
+ 'The permission name %s of %s.%s '
+ 'is longer than %s characters' % (
+ perm.name,
+ perm.content_type.app_label,
+ perm.content_type.model,
+ permission_name_max_length,
+ )
+ )
+ Permission.objects.using(using).bulk_create(perms)
+ if verbosity >= 2:
+ for perm in perms:
+ print("Adding permission '%s'" % perm)
+
+
+def update_groups(app_config, verbosity=2, interactive=True,
+ using=DEFAULT_DB_ALIAS, **kwargs):
+
+ if app_config != AppConfig and not isinstance(app_config, AppConfig):
+ return
+
+ from sapl.rules.map_rules import rules_patterns
+ from django.contrib.auth.models import Group, Permission
+ from django.contrib.contenttypes.models import ContentType
+
+ class Rules:
+
+ def __init__(self, rules_patterns):
+ self.rules_patterns = rules_patterns
+
+ def associar(self, g, model, tipo):
+ for t in tipo:
+ content_type = ContentType.objects.get_by_natural_key(
+ app_label=model._meta.app_label,
+ model=model._meta.model_name)
+
+ codename = (t[1:] + model._meta.model_name)\
+ if t[0] == '.' and t[-1] == '_' else t
+
+ p = Permission.objects.get(
+ content_type=content_type,
+ codename=codename)
+ g.permissions.add(p)
+ g.save()
+
+ def _config_group(self, group_name, rules_list):
+ if not group_name:
+ return
+
+ g = Group.objects.get_or_create(name=group_name)
+ if not isinstance(g, Group):
+ g = g[0]
+ g.permissions.clear()
+
+ try:
+
+ print(' ', group_name)
+ for model, perms in rules_list:
+ self.associar(g, model, perms)
+ except Exception as e:
+ print(group_name, e)
+
+ if settings.DEBUG:
+ user = ''
+ if group_name == SAPL_GROUP_ADMINISTRATIVO:
+ user = 'operador_administrativo'
+ elif group_name == SAPL_GROUP_PROTOCOLO:
+ user = 'operador_protocoloadm'
+ elif group_name == SAPL_GROUP_COMISSOES:
+ user = 'operador_comissoes'
+ elif group_name == SAPL_GROUP_MATERIA:
+ user = 'operador_materia'
+ elif group_name == SAPL_GROUP_NORMA:
+ user = 'operador_norma'
+ elif group_name == SAPL_GROUP_SESSAO:
+ user = 'operador_sessao'
+ elif group_name == SAPL_GROUP_PAINEL:
+ user = 'operador_painel'
+ elif group_name == SAPL_GROUP_GERAL:
+ user = 'operador_geral'
+
+ if user:
+ self.cria_usuario(user, g)
+
+ def groups_add_user(self, user, groups_name):
+ 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 groups_remove_user(self, user, groups_name):
+ 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 cria_usuario(self, nome, grupo):
+ nome_usuario = nome
+ usuario = get_user_model().objects.get_or_create(
+ username=nome_usuario)[0]
+ usuario.set_password('interlegis')
+ usuario.save()
+ grupo.user_set.add(usuario)
+
+ def update_groups(self):
+ print('')
+ print(string_concat('\033[93m\033[1m',
+ _('Atualizando grupos:'),
+ '\033[0m'))
+ for rules_group in self.rules_patterns:
+ group_name = rules_group['group']
+ rules_list = rules_group['rules']
+ self._config_group(group_name, rules_list)
+
+ rules = Rules(rules_patterns)
+ rules.update_groups()
+
+
+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")
diff --git a/sapl/rules/map_rules.py b/sapl/rules/map_rules.py
new file mode 100644
index 000000000..ff5208f4a
--- /dev/null
+++ b/sapl/rules/map_rules.py
@@ -0,0 +1,317 @@
+from sapl.base import models as base
+from sapl.comissoes import models as comissoes
+from sapl.compilacao import models as compilacao
+from sapl.lexml import models as lexml
+from sapl.materia import models as materia
+from sapl.norma import models as norma
+from sapl.painel import models as painel
+from sapl.parlamentares import models as parlamentares
+from sapl.protocoloadm import models as protocoloadm
+from sapl.rules import (SAPL_GROUP_ADMINISTRATIVO, SAPL_GROUP_ANONYMOUS,
+ 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_PROTOCOLO, SAPL_GROUP_SESSAO)
+from sapl.sessao import models as sessao
+
+"""
+Todas as permissões do django framework seguem o padrão
+
+ [app_label].[radical_de_permissao]_[model]
+
+ou seja, em sapl.norma.NormaJuridica, por exemplo, o django framework cria
+três permissões registadas na classe Permission:
+
+ definição uso
+
+ - add_normajuridica norma.add_normajuridica
+ - change_normajuridica norma.change_normajuridica
+ - delete_normajuridica norma.delete_normajuridica
+
+No SAPL foram acrescidas em todos os models as duas regras abaixo, adicionadas
+com o Signal post_migrate `create_proxy_permissions`
+localizado em sapl.rules.apps.py.
+
+ - list_normajuridica norma.list_normajuridica
+ - detail_normajuridica norma.detail_normajuridica
+
+Tanto o Crud implementado em sapl.crud.base.py quanto o Signal post_migrate
+`update_groups` que é responsável por ler o mapa deste
+arquivo (sapl.rules.map_rules.py) e criar os grupos definidos na regra de
+negócio trabalham com os cinco radiais de permissão
+e com qualquer outro tipo de permissão customizada, nesta ordem de precedência.
+
+Os cinco radicais de permissão completa são:
+
+ RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE =\
+ '.list_', '.detail_', '.add_', '.change_', '.delete_',
+
+Tanto a app crud quanto a app rules estão sempre ligadas a um model. Ao lidar
+com permissões, sempre é analisado se é apenas um radical ou permissão
+completa, sendo apenas um radical, a permissão completa é montada com base
+no model associado.
+"""
+
+RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE =\
+ '.list_', '.detail_', '.add_', '.change_', '.delete_',
+
+__base__ = [RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE]
+__listdetailchange__ = [RP_LIST, RP_DETAIL, RP_CHANGE]
+
+
+rules_group_administrativo = {
+ 'group': SAPL_GROUP_ADMINISTRATIVO,
+ 'rules': [
+ (protocoloadm.DocumentoAdministrativo, __base__),
+ (protocoloadm.DocumentoAcessorioAdministrativo, __base__),
+ (protocoloadm.TramitacaoAdministrativo, __base__),
+ ]
+}
+
+rules_group_protocolo = {
+ 'group': SAPL_GROUP_PROTOCOLO,
+ 'rules': [
+ (protocoloadm.Protocolo, __base__ + [
+ 'action_anular_protocolo']),
+ (protocoloadm.DocumentoAdministrativo,
+ [RP_ADD] + __listdetailchange__),
+ (protocoloadm.DocumentoAcessorioAdministrativo, __listdetailchange__),
+
+ (materia.MateriaLegislativa, __listdetailchange__),
+ (materia.DocumentoAcessorio, __listdetailchange__),
+ (materia.Anexada, __base__),
+ (materia.Autoria, __base__),
+
+ (materia.Proposicao, __listdetailchange__),
+ (compilacao.TextoArticulado, ['view_restricted_textoarticulado'])
+ ]
+}
+
+rules_group_comissoes = {
+ 'group': SAPL_GROUP_COMISSOES,
+ 'rules': [
+ (comissoes.Comissao, __base__),
+ (comissoes.Composicao, __base__),
+ (comissoes.Participacao, __base__),
+ ]
+}
+
+rules_group_materia = {
+ 'group': SAPL_GROUP_MATERIA,
+ 'rules': [
+ (materia.Anexada, __base__),
+ (materia.Autoria, __base__),
+ (materia.DespachoInicial, __base__),
+ (materia.DocumentoAcessorio, __base__),
+
+ (materia.MateriaLegislativa, __base__),
+ (materia.Numeracao, __base__),
+ (materia.Relatoria, __base__),
+ (materia.Tramitacao, __base__),
+ (materia.Relatoria, __base__),
+ (norma.LegislacaoCitada, __base__),
+ (compilacao.Dispositivo, __base__ + [
+ 'change_dispositivo_edicao_dinamica',
+
+ # TODO: adicionar 'change_dispositivo_registros_compilacao'
+ # quando testes forem feitos para permtir que matérias possam
+ # ser vinculadas a outras matérias via registro de compilação.
+ # Normalmente emendas e/ou projetos substitutivos podem alterar
+ # uma matéria original. Fazer esse registro de compilação ofereceria
+ # um autografo eletrônico pronto para ser convertido em Norma.
+ ])
+ ]
+}
+
+rules_group_norma = {
+ 'group': SAPL_GROUP_NORMA,
+ 'rules': [
+ (norma.NormaJuridica, __base__),
+ (norma.VinculoNormaJuridica, __base__),
+
+ # Publicacao está com permissão apenas para norma e não para matéria
+ # e proposições apenas por análise do contexto, não é uma limitação
+ # da ferramenta.
+ (compilacao.Publicacao, __base__),
+ (compilacao.Vide, __base__),
+ (compilacao.Nota, __base__),
+ (compilacao.Dispositivo, __base__ + [
+ 'view_dispositivo_notificacoes',
+ 'change_dispositivo_edicao_dinamica',
+ 'change_dispositivo_edicao_avancada',
+ 'change_dispositivo_registros_compilacao',
+ 'change_dispositivo_de_vigencia_global'
+ ])
+ ]
+}
+
+rules_group_sessao = {
+ 'group': SAPL_GROUP_SESSAO,
+ 'rules': [
+ (sessao.SessaoPlenaria, __base__),
+ (sessao.SessaoPlenariaPresenca, __base__),
+ (sessao.ExpedienteMateria, __base__),
+ (sessao.IntegranteMesa, __base__),
+ (sessao.ExpedienteSessao, __base__),
+ (sessao.Orador, __base__),
+ (sessao.OradorExpediente, __base__),
+ (sessao.OrdemDia, __base__),
+ (sessao.PresencaOrdemDia, __base__),
+ (sessao.RegistroVotacao, __base__),
+ (sessao.VotoParlamentar, __base__),
+ ]
+}
+
+rules_group_painel = {
+ 'group': SAPL_GROUP_PAINEL,
+ 'rules': [
+ (painel.Painel, __base__),
+ (painel.Cronometro, __base__),
+ ]
+}
+
+rules_group_autor = {
+ 'group': SAPL_GROUP_AUTOR,
+ 'rules': [
+ (materia.Proposicao, __base__),
+ (compilacao.Dispositivo, __base__ + [
+ 'change_your_dispositivo_edicao_dinamica',
+ ])
+ ]
+}
+
+rules_group_parlamentar = {
+ 'group': SAPL_GROUP_PARLAMENTAR,
+ 'rules': []
+}
+
+rules_group_geral = {
+ 'group': SAPL_GROUP_GERAL,
+ 'rules': [
+ (base.AppConfig, __base__ + [
+ 'menu_sistemas',
+ 'view_tabelas_auxiliares'
+ ]),
+
+ (base.CasaLegislativa, __listdetailchange__),
+ (base.ProblemaMigracao, []),
+ (base.TipoAutor, __base__),
+ (base.Autor, __base__),
+
+ (protocoloadm.StatusTramitacaoAdministrativo, __base__),
+ (protocoloadm.TipoDocumentoAdministrativo, __base__),
+
+ (comissoes.CargoComissao, __base__),
+ (comissoes.TipoComissao, __base__),
+ (comissoes.Periodo, __base__),
+
+ (materia.AssuntoMateria, __base__), # não há implementação
+ (materia.MateriaAssunto, __base__), # não há implementação
+ (materia.TipoProposicao, __base__),
+ (materia.TipoMateriaLegislativa, __base__),
+ (materia.RegimeTramitacao, __base__),
+ (materia.Origem, __base__),
+ (materia.TipoDocumento, __base__),
+ (materia.Orgao, __base__),
+ (materia.TipoFimRelatoria, __base__),
+ (materia.Parecer, __base__),
+ (materia.StatusTramitacao, __base__),
+ (materia.UnidadeTramitacao, __base__),
+
+ (norma.AssuntoNorma, __base__),
+ (norma.TipoNormaJuridica, __base__),
+
+ (parlamentares.Legislatura, __base__),
+ (parlamentares.SessaoLegislativa, __base__),
+ (parlamentares.Coligacao, __base__),
+ (parlamentares.ComposicaoColigacao, __base__),
+ (parlamentares.Partido, __base__),
+ (parlamentares.Municipio, __base__),
+ (parlamentares.NivelInstrucao, __base__),
+ (parlamentares.SituacaoMilitar, __base__),
+ (parlamentares.Parlamentar, __base__),
+ (parlamentares.TipoDependente, __base__),
+ (parlamentares.Dependente, __base__),
+ (parlamentares.Filiacao, __base__),
+ (parlamentares.TipoAfastamento, __base__),
+ (parlamentares.Mandato, __base__),
+ (parlamentares.CargoMesa, __base__),
+ (parlamentares.ComposicaoMesa, __base__),
+ (parlamentares.Frente, __base__),
+
+ (sessao.CargoBancada, __base__),
+ (sessao.Bancada, __base__),
+ (sessao.TipoSessaoPlenaria, __base__),
+ (sessao.TipoResultadoVotacao, __base__),
+ (sessao.TipoExpediente, __base__),
+ (sessao.Bloco, __base__),
+
+ (lexml.LexmlProvedor, __base__),
+ (lexml.LexmlPublicador, __base__),
+
+ (compilacao.VeiculoPublicacao, __base__),
+ (compilacao.TipoTextoArticulado, __base__),
+ (compilacao.TipoNota, __base__),
+ (compilacao.TipoVide, __base__),
+ (compilacao.TipoPublicacao, __base__),
+
+ # este model é um espelho do model integrado e sua edição pode
+ # confundir Autores, operadores de matéria e/ou norma.
+ # Por isso está adicionado apenas para o operador geral
+ (compilacao.TextoArticulado, __base__ + ['lock_unlock_textoarticulado']),
+
+ # estes tres models são complexos e a principio apenas o admin tem perm
+ (compilacao.TipoDispositivo, []),
+ (compilacao.TipoDispositivoRelationship, []),
+ (compilacao.PerfilEstruturalTextoArticulado, []),
+
+
+
+
+
+
+ ]
+}
+
+
+# não possui efeito e é usada nos testes que verificam se todos os models estão
+# neste arquivo rules.py
+rules_group_anonymous = {
+ 'group': SAPL_GROUP_ANONYMOUS,
+ 'rules': [
+ (materia.AcompanhamentoMateria, [RP_ADD, RP_DELETE]),
+ ]
+}
+
+rules_group_login_social = {
+ 'group': SAPL_GROUP_LOGIN_SOCIAL,
+ 'rules': []
+}
+
+rules_group_geral['rules'] = (rules_group_geral['rules'] +
+ rules_group_administrativo['rules'] +
+ rules_group_protocolo['rules'] +
+ rules_group_comissoes['rules'] +
+ rules_group_materia['rules'] +
+ rules_group_norma['rules'] +
+ rules_group_sessao['rules'] +
+ rules_group_painel['rules'] +
+ rules_group_login_social['rules'])
+
+
+rules_patterns = [
+ rules_group_administrativo,
+ rules_group_protocolo,
+ rules_group_comissoes,
+ rules_group_materia,
+ rules_group_norma,
+ rules_group_sessao,
+ rules_group_painel,
+ rules_group_geral,
+ rules_group_autor,
+ rules_group_parlamentar,
+
+ rules_group_anonymous, # anotação para validação do teste de rules
+ rules_group_login_social # TODO não implementado
+]
diff --git a/sapl/rules/migrations/__init__.py b/sapl/rules/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/sapl/rules/models.py b/sapl/rules/models.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/sapl/rules/tests/test_rules.py b/sapl/rules/tests/test_rules.py
new file mode 100644
index 000000000..3662477b9
--- /dev/null
+++ b/sapl/rules/tests/test_rules.py
@@ -0,0 +1,255 @@
+import pytest
+from django.apps import apps
+from django.conf import settings
+from django.contrib.auth.models import Permission
+from django.contrib.contenttypes.models import ContentType
+from django.utils import six
+from django.utils.translation import ugettext_lazy as _
+
+from sapl.base.models import CasaLegislativa, ProblemaMigracao
+from sapl.compilacao.models import (PerfilEstruturalTextoArticulado,
+ TipoDispositivo,
+ TipoDispositivoRelationship)
+from sapl.materia.models import AcompanhamentoMateria
+from sapl.rules import SAPL_GROUPS, map_rules
+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
+
+sapl_appconfs = [apps.get_app_config(n[5:]) for n in settings.SAPL_APPS]
+
+sapl_models = []
+for app in sapl_appconfs:
+ sapl_models.extend(app.get_models())
+sapl_models.reverse()
+
+
+@pytest.mark.parametrize('group_item', SAPL_GROUPS)
+def test_groups_in_rules_patterns(group_item):
+
+ test = False
+ for rules_group in map_rules.rules_patterns:
+ if rules_group['group'] == group_item:
+ test = True
+
+ assert test, _('O grupo (%s) não foi a rules_patterns.') % (group_item)
+
+
+@pytest.mark.parametrize('model_item', sapl_models)
+def test_models_in_rules_patterns(model_item):
+
+ test = False
+ for rules_group in map_rules.rules_patterns:
+ rules_model = rules_group['rules']
+ for rm in rules_model:
+ if rm[0] == model_item:
+ test = True
+ break
+
+ assert test, _('O model %s (%s) não foi adicionado em nenhum '
+ 'grupo padrão para regras de acesso.') % (
+ str(model_item),
+ model_item._meta.verbose_name)
+
+# __falsos_positivos__
+__fp__in__test_permission_of_models_in_rules_patterns = {
+ map_rules.RP_ADD: [CasaLegislativa,
+ ProblemaMigracao,
+ TipoDispositivo,
+ TipoDispositivoRelationship,
+ PerfilEstruturalTextoArticulado],
+
+ map_rules.RP_CHANGE: [ProblemaMigracao,
+ AcompanhamentoMateria,
+ TipoDispositivo,
+ TipoDispositivoRelationship,
+ PerfilEstruturalTextoArticulado],
+
+ map_rules.RP_DELETE: [CasaLegislativa,
+ ProblemaMigracao,
+ TipoDispositivo,
+ TipoDispositivoRelationship,
+ PerfilEstruturalTextoArticulado],
+
+ map_rules.RP_LIST: [ProblemaMigracao,
+ AcompanhamentoMateria,
+ TipoDispositivo,
+ TipoDispositivoRelationship,
+ PerfilEstruturalTextoArticulado],
+
+ map_rules.RP_DETAIL: [ProblemaMigracao,
+ AcompanhamentoMateria,
+ TipoDispositivo,
+ TipoDispositivoRelationship,
+ PerfilEstruturalTextoArticulado]
+
+}
+
+
+@pytest.mark.django_db(transaction=False)
+@pytest.mark.parametrize('model_item', sapl_models)
+def test_permission_of_models_in_rules_patterns(model_item):
+
+ create_perms_post_migrate(model_item._meta.app_config)
+ permissions = map_rules.__base__ + list(
+ filter(
+ lambda perm: not perm.startswith(
+ 'detail_') and not perm.startswith('list_'),
+ map(lambda x: x[0],
+ model_item._meta.permissions))
+ )
+
+ __fp__ = __fp__in__test_permission_of_models_in_rules_patterns
+ for perm in permissions:
+ if perm in __fp__ and model_item in __fp__[perm]:
+ continue
+
+ test = False
+ for rules_group in map_rules.rules_patterns:
+ rules_model = rules_group['rules']
+ for rm in rules_model:
+ model = rm[0]
+ rules = rm[1]
+ if model == model_item:
+ if perm in rules:
+ test = True
+ break
+
+ assert test, _('A permissão (%s) do model (%s) não foi adicionado em '
+ 'nenhum grupo padrão para regras de acesso.') % (
+ perm,
+ str(model_item))
+
+
+@pytest.mark.django_db(transaction=False)
+@pytest.mark.parametrize('model_item', sapl_models)
+def test_permission_of_rules_exists(model_item):
+
+ print(model_item)
+ create_perms_post_migrate(model_item._meta.app_config)
+
+ for rules_group in map_rules.rules_patterns:
+ rules_model = rules_group['rules']
+ for rm in rules_model:
+ model = rm[0]
+ rules = rm[1]
+
+ if model != model_item:
+ continue
+
+ for r in rules:
+ content_type = ContentType.objects.get_by_natural_key(
+ app_label=model._meta.app_label,
+ model=model._meta.model_name)
+
+ codename = (r[1:] + model._meta.model_name)\
+ if r[0] == '.' and r[-1] == '_' else r
+ p = Permission.objects.filter(
+ content_type=content_type,
+ codename=codename).exists()
+
+ assert p, _('Permissão (%s) associada ao model (%s) '
+ 'não está em _meta.permissions.') % (
+ codename,
+ model_item)
+
+
+_lista_urls = lista_urls()
+
+
+@pytest.mark.django_db(transaction=False)
+@pytest.mark.parametrize('url_item', _lista_urls)
+def test_permission_required_of_views_exists(url_item):
+ """
+ testa se, nas views que possuem atributo permission_required,
+ as permissões fixas escritas manualmente realmente exitem em Permission
+
+ Obs: isso não testa permissões escritas em anotações de método ou classe
+ """
+
+ for app in sapl_appconfs:
+ # readequa permissões dos models adicionando
+ # list e detail permissions
+ create_perms_post_migrate(app)
+
+ key, url, var, app_name = url_item
+ url = '/' + (url % {v: 1 for v in var})
+
+ assert '\n' not in url, """
+ A url (%s) da app (%s) está mal formada.
+ """ % (app_name, url)
+
+ view = None
+ if hasattr(key, 'view_class'):
+ view = key.view_class
+
+ if hasattr(view, 'permission_required'):
+ if isinstance(view.permission_required, six.string_types):
+ perms = (view.permission_required, )
+ else:
+ perms = view.permission_required
+
+ if not perms:
+ return
+
+ for perm in perms:
+ if perm[0] == '.' and perm[-1] == '_':
+ model = None
+ if hasattr(view, 'model') and view.model:
+ model = view.model
+ elif hasattr(view, 'filterset_class'):
+ model = view.fielterset_class._meta.model
+ elif hasattr(view, 'form_class'):
+ model = view.form_class._meta.model
+
+ assert model, _('model %s não localizado em %s'
+ ) % (model, view)
+
+ codename = perm[1:] + view.model._meta.model_name
+ else:
+ codename = perm
+
+ codename = codename.split('.')
+
+ if len(codename) == 1:
+ content_type = ContentType.objects.get_by_natural_key(
+ app_label=model._meta.app_label,
+ model=model._meta.model_name)
+ p = Permission.objects.filter(
+ content_type=content_type,
+ codename=codename[0]).exists()
+ elif len(codename) == 2:
+ 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.') % (
+ 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/sapl/sessao/forms.py b/sapl/sessao/forms.py
index f213e1b94..1ade99710 100644
--- a/sapl/sessao/forms.py
+++ b/sapl/sessao/forms.py
@@ -12,7 +12,9 @@ from sapl.crispy_layout_mixin import form_actions, to_row
from sapl.materia.forms import MateriaLegislativaFilterSet
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.parlamentares.models import Parlamentar
-from sapl.utils import RANGE_DIAS_MES, RANGE_MESES, autor_label, autor_modal
+from sapl.utils import (RANGE_DIAS_MES, RANGE_MESES,
+ MateriaPesquisaOrderingFilter, autor_label,
+ autor_modal)
from .models import (Bancada, ExpedienteMateria, Orador, OradorExpediente,
OrdemDia, SessaoPlenaria, SessaoPlenariaPresenca)
@@ -21,7 +23,10 @@ from .models import (Bancada, ExpedienteMateria, Orador, OradorExpediente,
def recupera_anos():
try:
anos_list = SessaoPlenaria.objects.all().dates('data_inicio', 'year')
- anos = [(k.year, k.year) for k in anos_list]
+ # a listagem deve ser em ordem descrescente, mas por algum motivo
+ # a adicao de .order_by acima depois do all() nao surte efeito
+ # apos a adicao do .dates(), por isso o reversed() abaixo
+ anos = [(k.year, k.year) for k in reversed(anos_list)]
return anos
except:
return []
@@ -203,6 +208,8 @@ class SessaoPlenariaFilterSet(django_filters.FilterSet):
class AdicionarVariasMateriasFilterSet(MateriaLegislativaFilterSet):
+ o = MateriaPesquisaOrderingFilter()
+
class Meta:
model = MateriaLegislativa
fields = ['numero',
@@ -212,20 +219,12 @@ class AdicionarVariasMateriasFilterSet(MateriaLegislativaFilterSet):
'data_apresentacao',
'data_publicacao',
'autoria__autor__tipo',
- # 'autoria__autor__partido',
+ # FIXME 'autoria__autor__partido',
'relatoria__parlamentar_id',
'local_origem_externa',
'em_tramitacao',
]
- order_by = (
- ('', 'Selecione'),
- ('dataC', 'Data, Tipo, Ano, Numero - Ordem Crescente'),
- ('dataD', 'Data, Tipo, Ano, Numero - Ordem Decrescente'),
- ('tipoC', 'Tipo, Ano, Numero, Data - Ordem Crescente'),
- ('tipoD', 'Tipo, Ano, Numero, Data - Ordem Decrescente')
- )
-
def __init__(self, *args, **kwargs):
super(MateriaLegislativaFilterSet, self).__init__(*args, **kwargs)
diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py
index 40a8cc751..bc52e4dfa 100644
--- a/sapl/sessao/views.py
+++ b/sapl/sessao/views.py
@@ -2,6 +2,7 @@ from datetime import datetime
from re import sub
from django.contrib import messages
+from django.contrib.auth.decorators import permission_required
from django.core.exceptions import (ObjectDoesNotExist, MultipleObjectsReturned,
ValidationError)
from django.core.urlresolvers import reverse
@@ -61,11 +62,9 @@ TipoResultadoVotacaoCrud = CrudAux.build(
def reordernar_materias_expediente(request, pk):
expedientes = ExpedienteMateria.objects.filter(
sessao_plenaria_id=pk)
- exp_num = 1
- for e in expedientes:
+ for exp_num, e in enumerate(expedientes, 1):
e.numero_ordem = exp_num
e.save()
- exp_num += 1
return HttpResponseRedirect(
reverse('sapl.sessao:expedientemateria_list', kwargs={'pk': pk}))
@@ -74,17 +73,15 @@ def reordernar_materias_expediente(request, pk):
def reordernar_materias_ordem(request, pk):
ordens = OrdemDia.objects.filter(
sessao_plenaria_id=pk)
- ordem_num = 1
- for o in ordens:
+ for ordem_num, o in enumerate(ordens, 1):
o.numero_ordem = ordem_num
o.save()
- ordem_num += 1
return HttpResponseRedirect(
reverse('sapl.sessao:ordemdia_list', kwargs={'pk': pk}))
-@permission_required_for_app(app_label=apps.AppConfig.label)
+@permission_required('sessao.change_expedientemateria')
def abrir_votacao_expediente_view(request, pk, spk):
existe_votacao_aberta = ExpedienteMateria.objects.filter(
sessao_plenaria_id=spk, votacao_aberta=True
@@ -101,7 +98,7 @@ def abrir_votacao_expediente_view(request, pk, spk):
reverse('sapl.sessao:expedientemateria_list', kwargs={'pk': spk}))
-@permission_required_for_app(app_label=apps.AppConfig.label)
+@permission_required('sessao.change_ordemdia')
def abrir_votacao_ordem_view(request, pk, spk):
existe_votacao_aberta = OrdemDia.objects.filter(
sessao_plenaria_id=spk, votacao_aberta=True
@@ -524,14 +521,12 @@ class PresencaView(FormMixin, PresencaMixin, DetailView):
_('Presença'), self.object)
return context
- @method_decorator(permission_required_for_app(AppConfig.label))
+ @method_decorator(permission_required(
+ 'sessao.add_sessaoplenariapresenca'))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
- if not self.request.user.has_module_perms(AppConfig.label):
- return self.form_invalid(form)
-
if form.is_valid():
# Pegar os presentes salvos no banco
presentes_banco = SessaoPlenariaPresenca.objects.filter(
@@ -604,7 +599,7 @@ class PresencaOrdemDiaView(FormMixin, PresencaMixin, DetailView):
_('Presença Ordem do Dia'), self.object)
return context
- @method_decorator(permission_required_for_app(AppConfig.label))
+ @method_decorator(permission_required('sessao.add_presencaordemdia'))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
@@ -684,7 +679,7 @@ class ListMateriaOrdemDiaView(FormMixin, DetailView):
return self.render_to_response(context)
- @method_decorator(permission_required_for_app(AppConfig.label))
+ @method_decorator(permission_required('sessao.change_ordemdia'))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
@@ -786,7 +781,7 @@ class MesaView(FormMixin, DetailView):
_('Mesa Diretora'), self.object)
return context
- @method_decorator(permission_required_for_app(AppConfig.label))
+ @method_decorator(permission_required('sessao.change_integrantemesa'))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = MesaForm(request.POST)
@@ -1041,7 +1036,7 @@ class ExpedienteView(FormMixin, DetailView):
_('Expediente Diversos'), self.object)
return context
- @method_decorator(permission_required_for_app(AppConfig.label))
+ @method_decorator(permission_required('sessao.add_expedientesessao'))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = ExpedienteForm(request.POST)
diff --git a/sapl/settings.py b/sapl/settings.py
index b0a7aaf76..cfeefdd0f 100644
--- a/sapl/settings.py
+++ b/sapl/settings.py
@@ -53,7 +53,10 @@ SAPL_APPS = (
'sapl.painel',
'sapl.protocoloadm',
'sapl.compilacao',
- 'sapl.api'
+ 'sapl.api',
+
+ 'sapl.rules'
+
)
INSTALLED_APPS = (
@@ -129,8 +132,8 @@ TEMPLATES = [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
- "django.core.context_processors.media",
- "django.core.context_processors.static",
+ "django.template.context_processors.media",
+ "django.template.context_processors.static",
'django.contrib.messages.context_processors.messages',
'sapl.context_processors.parliament_info',
],
diff --git a/sapl/static/styles/compilacao.scss b/sapl/static/styles/compilacao.scss
index 4cb6d0122..ac397b0a8 100644
--- a/sapl/static/styles/compilacao.scss
+++ b/sapl/static/styles/compilacao.scss
@@ -1513,8 +1513,13 @@ a:link:after, a:visited:after {
}
@media print {
- .cp .vigencias, .toggle-topbar, .menu-icon, .button {
+ .cp .vigencias, .toggle-topbar, .menu-icon, .button, .tipo-vigencias, .dne {
display:none;
}
-
+ #btn_font_menos, #btn_font_mais {
+ display:none;
+ }
+ .container {
+ width: 100%;
+ }
}
diff --git a/sapl/templates/base.html b/sapl/templates/base.html
index d14b35e37..d8c1d2c0c 100644
--- a/sapl/templates/base.html
+++ b/sapl/templates/base.html
@@ -146,6 +146,8 @@
{% endblock content_container %}
+
+ {% block footer_container %}
+ {% endblock footer_container %}
{% block foot_js %}
diff --git a/sapl/templates/base/appconfig_list.html b/sapl/templates/base/appconfig_list.html
deleted file mode 100644
index 3ffcc7f4b..000000000
--- a/sapl/templates/base/appconfig_list.html
+++ /dev/null
@@ -1,49 +0,0 @@
-{% extends "crud/list.html" %}
-{% load i18n %}
-{% load common_tags %}
-
-{% block base_content %}
-
-
-
-{% block extra_content %} {% endblock %}
-
-{% if not rows %}
- {{ NO_ENTRIES_MSG }}
-{% else %}
-
-
-
- {% for name in headers %}
- {{ name }}
- {% endfor %}
-
-
-
- {% for value_list in rows %}
-
- {% for value, href in value_list %}
-
- {% if href %}
- {{ value }}
- {% else %}
- {{ value|safe }}
- {% endif %}
-
- {% endfor %}
-
- {% endfor %}
-
-
-{% endif %}
-
-{% include "paginacao.html" %}
-
-{% endblock %}
diff --git a/sapl/templates/compilacao/ajax_actions_dinamic_edit.html b/sapl/templates/compilacao/ajax_actions_dinamic_edit.html
index c819723b7..55e0ba431 100644
--- a/sapl/templates/compilacao/ajax_actions_dinamic_edit.html
+++ b/sapl/templates/compilacao/ajax_actions_dinamic_edit.html
@@ -1,21 +1,23 @@
{% load i18n %}
@@ -61,11 +65,13 @@
{% endfor %}
{%endif%}
- {% if not object.ta_publicado and not object.dispositivo_subsequente and not object.tipo_dispositivo.dispositivo_de_alteracao%}
-
-
- DVt
-
-
- {%endif%}
+ {% if perms.compilacao.change_dispositivo_de_vigencia_global %}
+ {% if not object.ta_publicado and not object.dispositivo_subsequente and not object.tipo_dispositivo.dispositivo_de_alteracao%}
+
+
+ DVt
+
+
+ {% endif %}
+ {% endif %}
diff --git a/sapl/templates/compilacao/dispositivo_form.html b/sapl/templates/compilacao/dispositivo_form.html
index d9882b53a..3de5b5b40 100644
--- a/sapl/templates/compilacao/dispositivo_form.html
+++ b/sapl/templates/compilacao/dispositivo_form.html
@@ -10,10 +10,10 @@
{% url 'sapl.compilacao:dispositivo_edit_vigencia' object.ta_id object.pk as edit_vigencia_url %}
{% url 'sapl.compilacao:dispositivo_edit_alteracao' object.ta_id object.pk as edit_alteracao_url %}
{% url 'sapl.compilacao:dispositivo_edit_definidor_vigencia' object.ta_id object.pk as edit_definidor_vigencia_url %}
- {% trans 'Dados Básicos' %}
- {% trans 'Vigência' %}
- {% trans 'Definidor de Vigência' %}
- {% trans 'Alteração' %}
+ {% if perms.compilacao.change_dispositivo_edicao_avancada %}{% trans 'Dados Básicos' %} {% endif %}
+ {% if perms.compilacao.change_dispositivo_edicao_avancada %}{% trans 'Vigência' %} {% endif %}
+ {% if perms.compilacao.change_dispositivo_de_vigencia_global %}{% trans 'Definidor de Vigência' %} {% endif %}
+ {% if perms.compilacao.change_dispositivo_registros_compilacao %}{% trans 'Alteração' %} {% endif %}
{% endblock sections_nav %}{% trans '' %}
diff --git a/sapl/templates/compilacao/publicacao_detail.html b/sapl/templates/compilacao/publicacao_detail.html
index 9bd8bc2b4..d157e49f5 100644
--- a/sapl/templates/compilacao/publicacao_detail.html
+++ b/sapl/templates/compilacao/publicacao_detail.html
@@ -8,7 +8,7 @@
{% block actions %}
{% endblock actions %}
diff --git a/sapl/templates/compilacao/publicacao_list.html b/sapl/templates/compilacao/publicacao_list.html
index 3cdc0f785..975fbf1f4 100644
--- a/sapl/templates/compilacao/publicacao_list.html
+++ b/sapl/templates/compilacao/publicacao_list.html
@@ -5,11 +5,13 @@
{% block base_content %}
-
+ {% if perms.compilacao.add_publicacao %}
+
+ {% endif %}
{% if not object_list %}
{{ NO_ENTRIES_MSG }}
diff --git a/sapl/templates/compilacao/text_edit.html b/sapl/templates/compilacao/text_edit.html
index 013a7429d..899df7b20 100644
--- a/sapl/templates/compilacao/text_edit.html
+++ b/sapl/templates/compilacao/text_edit.html
@@ -11,18 +11,24 @@
{% endblock %}
{% block title%}
- {{object }}. {% trans 'Texto Multivigente em Edição' %}
+
{% endblock %}
{% block actions %}
-
-
-
-
-
{% field_verbose_name object 'ementa' %}
-
{{ object.ementa|safe}}
-
+
+
+
+
{% field_verbose_name object 'ementa' %}
+
{{ object.ementa|safe}}
+
{% endblock detail_content %}
{% endblock base_content %}
diff --git a/sapl/templates/compilacao/textoarticulado_list.html b/sapl/templates/compilacao/textoarticulado_list.html
index 645df8081..c6de147cd 100644
--- a/sapl/templates/compilacao/textoarticulado_list.html
+++ b/sapl/templates/compilacao/textoarticulado_list.html
@@ -8,12 +8,14 @@
{% endblock detail_content %}
{% block actions %}
-
+ {% if perms.compilacao.add_textoarticulado %}
+
+ {% endif %}
{% endblock actions %}
diff --git a/sapl/templates/compilacao/textoarticulado_menu_config.html b/sapl/templates/compilacao/textoarticulado_menu_config.html
index 332e1e61e..976f18148 100644
--- a/sapl/templates/compilacao/textoarticulado_menu_config.html
+++ b/sapl/templates/compilacao/textoarticulado_menu_config.html
@@ -4,11 +4,15 @@
diff --git a/sapl/templates/compilacao/tipotextoarticulado_detail.html b/sapl/templates/compilacao/tipotextoarticulado_detail.html
index 177c2e06a..afc0a0a85 100644
--- a/sapl/templates/compilacao/tipotextoarticulado_detail.html
+++ b/sapl/templates/compilacao/tipotextoarticulado_detail.html
@@ -9,8 +9,8 @@
{% block actions %}
{% endblock actions %}
@@ -19,33 +19,43 @@
{%trans 'Identificação Básica'%}
-
+
{% field_verbose_name object 'sigla' %}
{{ object.sigla}}
-
+
{% field_verbose_name object 'descricao' %}
{{ object.descricao}}
-
+
{% field_verbose_name object 'content_type' %}
{{ object.content_type|default:""}}
-
-
-
{% field_verbose_name object 'participacao_social' %}
-
{{ object.get_participacao_social_display}}
-
+
+
+ {%trans 'Funcionalidades'%}
+
+
+
+
{% field_verbose_name object 'participacao_social' %}
+
{{ object.get_participacao_social_display}}
+
+
+
+
+
{% field_verbose_name object 'publicacao_func' %}
+
{{ object.get_publicacao_func_display}}
+
{% endblock detail_content %}
{% endblock base_content %}
diff --git a/sapl/templates/compilacao/tipotextoarticulado_list.html b/sapl/templates/compilacao/tipotextoarticulado_list.html
index 28e4d7886..e89e8afd4 100644
--- a/sapl/templates/compilacao/tipotextoarticulado_list.html
+++ b/sapl/templates/compilacao/tipotextoarticulado_list.html
@@ -5,11 +5,13 @@
{% block base_content %}
{% block actions %}
-
+ {% if perms.compilacao.add_tipotextoarticulado %}
+
+ {% endif %}
{% endblock actions %}
{% if not object_list %}
diff --git a/sapl/templates/crud/detail.html b/sapl/templates/crud/detail.html
index 8d824b0f2..f425ed791 100644
--- a/sapl/templates/crud/detail.html
+++ b/sapl/templates/crud/detail.html
@@ -21,6 +21,15 @@
{% endif %}
+ {% if view.extras_url %}
+
+ {% for url, css_class, text in view.extras_url %}
+
+ {{text}}
+
+ {% endfor %}
+
+ {% endif %}
{% endblock sub_actions %}
@@ -41,30 +50,32 @@
{% endblock actions %}
- {% block detail_content %}
- {% for fieldset in view.layout_display %}
-
{{ fieldset.legend }}
- {% for row in fieldset.rows %}
-
- {% for column in row %}
-
-
{% block table_content %}
diff --git a/sapl/templates/crud/detail_detail.html b/sapl/templates/crud/detail_detail.html
index 219e287df..cb0b103ec 100644
--- a/sapl/templates/crud/detail_detail.html
+++ b/sapl/templates/crud/detail_detail.html
@@ -18,6 +18,17 @@
{% endif %}
+ {% if view.extras_url %}
+
+ {% for url, css_class, text in view.extras_url %}
+
+ {{text}}
+
+ {% endfor %}
+
+ {% endif %}
+
+
{% if view.update_url or view.delete_url %}
{% if view.update_url %}
diff --git a/sapl/templates/materia/confirmar_proposicao.html b/sapl/templates/materia/confirmar_proposicao.html
index e50c92649..0c66c1f0a 100644
--- a/sapl/templates/materia/confirmar_proposicao.html
+++ b/sapl/templates/materia/confirmar_proposicao.html
@@ -7,7 +7,9 @@
{% block actions %}{{block.super}}
{% endif %}
-
{% endblock detail_content %}
diff --git a/sapl/templates/materia/tipoproposicao_form.html b/sapl/templates/materia/tipoproposicao_form.html
index a19814e4e..ede423c40 100644
--- a/sapl/templates/materia/tipoproposicao_form.html
+++ b/sapl/templates/materia/tipoproposicao_form.html
@@ -22,13 +22,10 @@ $(document).ready(function(){
initial_select='';
radios.append(html_radio);
});
-
-
-
});
});
- $('#id_content_type').trigger('change');
$("#div_id_tipo_conteudo_related_radio .controls").addClass('controls-radio-checkbox');
+ $('#id_content_type').trigger('change');
});
diff --git a/sapl/templates/navbar.yaml b/sapl/templates/navbar.yaml
index 4b053ca55..391f55664 100644
--- a/sapl/templates/navbar.yaml
+++ b/sapl/templates/navbar.yaml
@@ -13,7 +13,7 @@
url: sapl.parlamentares:parlamentar_list
- title: {% trans 'Protocolo' %}
- check_permission: protolcoloadm.list_protocolo
+ check_permission: protocoloadm.list_protocolo
children:
- title: {% trans 'Pesquisar Protocolo' %}
url: sapl.protocoloadm:protocolo
diff --git a/sapl/templates/norma/layouts.yaml b/sapl/templates/norma/layouts.yaml
index e7eff947e..99e14640c 100644
--- a/sapl/templates/norma/layouts.yaml
+++ b/sapl/templates/norma/layouts.yaml
@@ -1,4 +1,9 @@
{% load i18n %}
+
+AssuntoNormaRelationship:
+ {% trans 'Assunto Norma Jurídica' %}:
+ - assunto
+
AssuntoNorma:
{% trans 'Assunto Norma Jurídica' %}:
- assunto descricao
@@ -17,6 +22,7 @@ NormaJuridica:
- ementa
- indexacao
- observacao
+ - assuntos
NormaJuridicaCreate:
{% trans 'Identificação Básica' %}:
@@ -28,6 +34,7 @@ NormaJuridicaCreate:
- ementa
- indexacao
- observacao
+ - assuntos
LegislacaoCitada:
{% trans 'Legislação Citada' %}:
diff --git a/sapl/templates/norma/list_pesquisa.html b/sapl/templates/norma/list_pesquisa.html
index c16cea1b6..8fb65745a 100644
--- a/sapl/templates/norma/list_pesquisa.html
+++ b/sapl/templates/norma/list_pesquisa.html
@@ -32,7 +32,6 @@
{% endfor %}
- {% include "paginacao.html" %}
{% else %}
Nenhum Registro recuperado
{% endif %}
diff --git a/sapl/templates/norma/normajuridica_form.html b/sapl/templates/norma/normajuridica_form.html
new file mode 100644
index 000000000..7a35b05a7
--- /dev/null
+++ b/sapl/templates/norma/normajuridica_form.html
@@ -0,0 +1,29 @@
+{% extends "crud/form.html" %}
+{% load i18n %}
+{% load crispy_forms_tags %}
+{% load common_tags %}
+
+{% block extra_js %}
+
+
+
+{% endblock %}
diff --git a/sapl/templates/norma/subnav.yaml b/sapl/templates/norma/subnav.yaml
index d35607335..be3cf3c0a 100644
--- a/sapl/templates/norma/subnav.yaml
+++ b/sapl/templates/norma/subnav.yaml
@@ -1,4 +1,5 @@
{% load i18n common_tags%}
+
- title: {% trans 'Início' %}
url: normajuridica_detail
@@ -6,6 +7,7 @@
# para integração foram necessárias apenas criar a url norma_ta em urls.py
# e a view NormaTaView(IntegracaoTaView) em views.py
# Em nada mais a integração interfere em NormaJuridica
+
{% if 'texto_articulado_norma'|get_config_attr %}
- title: {% trans 'Texto' %}
url: norma_ta
diff --git a/sapl/templates/painel/controlador.html b/sapl/templates/painel/controlador.html
index f4aaabc00..32ed27913 100644
--- a/sapl/templates/painel/controlador.html
+++ b/sapl/templates/painel/controlador.html
@@ -21,4 +21,8 @@ FECHADO
+
+
+
Voltar
+
{% endblock %}
diff --git a/sapl/templates/protocoloadm/MateriaTemplate.html b/sapl/templates/protocoloadm/MateriaTemplate.html
new file mode 100644
index 000000000..fc490fcc5
--- /dev/null
+++ b/sapl/templates/protocoloadm/MateriaTemplate.html
@@ -0,0 +1,29 @@
+{% extends "base.html" %}
+{% load i18n common_tags%}
+
+{% block base_content %}
+
+
Matéria procololada com sucesso!
+
+
+
+{% endblock base_content %}
diff --git a/sapl/templates/protocoloadm/anular_protocoloadm.html b/sapl/templates/protocoloadm/anular_protocoloadm.html
index 97e772690..b936ed7ad 100644
--- a/sapl/templates/protocoloadm/anular_protocoloadm.html
+++ b/sapl/templates/protocoloadm/anular_protocoloadm.html
@@ -1,7 +1,17 @@
{% extends "protocoloadm/protocoloadm_detail.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
+
+
+{% block actions %}
+
+ {{ block.super }}
+
+{% endblock %}
+
{% block detail_content %}
{{ message }}
{% crispy form %}
-{% endblock detail_content %}
\ No newline at end of file
+{% endblock detail_content %}
diff --git a/sapl/templates/protocoloadm/comprovante.html b/sapl/templates/protocoloadm/comprovante.html
index de26eaf78..9fd9b8b32 100644
--- a/sapl/templates/protocoloadm/comprovante.html
+++ b/sapl/templates/protocoloadm/comprovante.html
@@ -83,9 +83,5 @@
Número Páginas
{{ protocolo.numero_paginas }}
-
- Número Páginas
- {{ protocolo.numero_paginas }}
-
{% endblock detail_content %}
diff --git a/sapl/templates/protocoloadm/protocolar_documento.html b/sapl/templates/protocoloadm/protocolar_documento.html
index 87259a900..9f2213128 100644
--- a/sapl/templates/protocoloadm/protocolar_documento.html
+++ b/sapl/templates/protocoloadm/protocolar_documento.html
@@ -2,6 +2,15 @@
{% load i18n %}
{% load crispy_forms_tags %}
+
+{% block actions %}
+
+ {{ block.super }}
+
+{% endblock %}
+
{% block detail_content %}
{% crispy form %}
-{% endblock detail_content %}
\ No newline at end of file
+{% endblock detail_content %}
diff --git a/sapl/templates/protocoloadm/protocolar_materia.html b/sapl/templates/protocoloadm/protocolar_materia.html
index 87259a900..744126f0d 100644
--- a/sapl/templates/protocoloadm/protocolar_materia.html
+++ b/sapl/templates/protocoloadm/protocolar_materia.html
@@ -2,6 +2,14 @@
{% load i18n %}
{% load crispy_forms_tags %}
+{% block actions %}
+
+ {{ block.super }}
+
+{% endblock %}
+
{% block detail_content %}
{% crispy form %}
-{% endblock detail_content %}
\ No newline at end of file
+{% endblock detail_content %}
diff --git a/sapl/templates/protocoloadm/protocolo_filter.html b/sapl/templates/protocoloadm/protocolo_filter.html
index 19125ad95..a18ea7fb9 100644
--- a/sapl/templates/protocoloadm/protocolo_filter.html
+++ b/sapl/templates/protocoloadm/protocolo_filter.html
@@ -3,20 +3,25 @@
{% load crispy_forms_tags %}
{% load static %}
-{% block sections_nav %} {% endblock %}
-{% block base_content %}
-
Pesquisa de Protocolo
-
- {% if filter_url %}
-
- {% endif %}
+ {% block actions %}
- {% if not filter_url %}
- {% crispy filter.form %}
- {% endif %}
+ {{ block.super }}
+
+ {% if filter_url %}
+
+ {% endif %}
+
+
+ {% endblock %}
+
+ {% block detail_content %}
+
+ {% if not filter_url %}
+ {% crispy filter.form %}
+ {% endif %}
{% if filter_url %}
{% include "paginacao.html" %}
{% endif %}
-{% endblock base_content %}
+{% endblock detail_content %}
+
+{% block table_content %}
+{% endblock table_content %}
diff --git a/sapl/templates/protocoloadm/protocolo_mostrar.html b/sapl/templates/protocoloadm/protocolo_mostrar.html
index 58882a39e..df35a92e6 100644
--- a/sapl/templates/protocoloadm/protocolo_mostrar.html
+++ b/sapl/templates/protocoloadm/protocolo_mostrar.html
@@ -15,9 +15,28 @@
-
Documento Vinculado:
-
Criar Documento
+
Documento Vinculado:
+
+ {% if protocolo.tipo_documento %}
+ {% if documento %}
+
{{documento}}
+
+ {% else %}
+
+
Criar Documento
+ {% endif %}
+ {% elif protocolo.tipo_materia %}
+ {% if materia %}
+
{{materia}}
+
+ {% else %}
+
+
Criar Matéria
+ {% endif %}
+ {% endif %}
+
+
-
Comprovante
+ Comprovante
-{% endblock detail_content %}
\ No newline at end of file
+{% endblock detail_content %}
diff --git a/sapl/templates/protocoloadm/protocoloadm_detail.html b/sapl/templates/protocoloadm/protocoloadm_detail.html
index 07ae81c47..dc91ac252 100644
--- a/sapl/templates/protocoloadm/protocoloadm_detail.html
+++ b/sapl/templates/protocoloadm/protocoloadm_detail.html
@@ -1,9 +1,9 @@
{% extends "crud/detail.html" %}
{% load i18n %}
-{% block actions %}
-
-{% endblock actions %}
\ No newline at end of file
+{% block editions %}
+
+{% endblock editions %}
diff --git a/sapl/templates/sessao/painel.html b/sapl/templates/sessao/painel.html
index 48e422423..febf0a667 100644
--- a/sapl/templates/sessao/painel.html
+++ b/sapl/templates/sessao/painel.html
@@ -18,7 +18,7 @@
-
+
Operação do Painel Eletrônico
diff --git a/sapl/test_urls.py b/sapl/test_urls.py
index 51c6f5e2e..c38355126 100644
--- a/sapl/test_urls.py
+++ b/sapl/test_urls.py
@@ -9,7 +9,7 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import string_concat
from sapl.crud.base import PermissionRequiredForAppCrudMixin
-from scripts.inicializa_grupos_autorizacoes import cria_grupos_permissoes
+from sapl.rules.apps import AppConfig, update_groups
from scripts.lista_urls import lista_urls
from .settings import SAPL_APPS
@@ -376,7 +376,7 @@ def test_permissions_urls_for_users_by_apps(url_item, client):
# list e detail permissions
create_perms_post_migrate(app)
# cria usuários de perfil do sapl
- cria_grupos_permissoes()
+ update_groups(AppConfig)
users = get_user_model().objects.order_by(
'username').values_list('username', flat=True)
diff --git a/sapl/urls.py b/sapl/urls.py
index 649265aaa..ee512b31f 100644
--- a/sapl/urls.py
+++ b/sapl/urls.py
@@ -17,7 +17,7 @@ from django.conf import settings
from django.conf.urls import include, url
from django.conf.urls.static import static
from django.contrib import admin
-from django.views.generic.base import TemplateView
+from django.views.generic.base import TemplateView, RedirectView
from django.views.static import serve as view_static_server
import sapl.api.urls
@@ -33,6 +33,7 @@ import sapl.protocoloadm.urls
import sapl.relatorios.urls
import sapl.sessao.urls
+
urlpatterns = [
url(r'^$', TemplateView.as_view(template_name='index.html')),
url(r'^admin/', include(admin.site.urls)),
@@ -53,6 +54,9 @@ urlpatterns = [
url(r'', include(sapl.base.urls)),
url(r'', include(sapl.api.urls)),
+
+ url(r'^favicon\.ico$', RedirectView.as_view(
+ url='/static/img/favicon.ico', permanent=True)),
]
diff --git a/sapl/utils.py b/sapl/utils.py
index 573dffb46..e61d68f85 100644
--- a/sapl/utils.py
+++ b/sapl/utils.py
@@ -1,13 +1,11 @@
import hashlib
import logging
+import re
from datetime import date
from functools import wraps
from unicodedata import normalize as unicodedata_normalize
-import hashlib
-import logging
-import re
-
+import django_filters
import magic
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Button
@@ -15,17 +13,12 @@ from django import forms
from django.apps import apps
from django.conf import settings
from django.contrib import admin
-from django.contrib.auth.decorators import user_passes_test
-from django.contrib.auth.models import Permission
-from django.contrib.contenttypes.fields import GenericRelation, GenericRel,\
- GenericForeignKey
-from django.contrib.contenttypes.models import ContentType
-from django.core.exceptions import PermissionDenied, ValidationError
+from django.contrib.contenttypes.fields import (GenericForeignKey, GenericRel,
+ GenericRelation)
+from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from floppyforms import ClearableFileInput
-import magic
-
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row
from sapl.settings import BASE_DIR
@@ -103,7 +96,12 @@ def montar_helper_autor(self):
class SaplGenericForeignKey(GenericForeignKey):
- def __init__(self, ct_field='content_type', fk_field='object_id', for_concrete_model=True, verbose_name=''):
+ def __init__(
+ self,
+ ct_field='content_type',
+ fk_field='object_id',
+ for_concrete_model=True,
+ verbose_name=''):
super().__init__(ct_field, fk_field, for_concrete_model)
self.verbose_name = verbose_name
@@ -383,7 +381,7 @@ def intervalos_tem_intersecao(a_inicio, a_fim, b_inicio, b_fim):
menor_fim = min(a_fim, b_fim)
return maior_inicio <= menor_fim
-
+"""
def permissoes(nome_grupo, app_label):
lista_permissoes = []
try:
@@ -398,12 +396,12 @@ def permissoes(nome_grupo, app_label):
def permission_required_for_app(app_label, login_url=None,
raise_exception=False):
- """
+
Decorator for views that checks whether a user has a particular permission
enabled, redirecting to the log-in page if necessary.
If the raise_exception parameter is given the PermissionDenied exception
is raised.
- """
+
def check_perms(user):
if user.has_module_perms(app_label):
return True
@@ -414,7 +412,6 @@ def permission_required_for_app(app_label, login_url=None,
return False
return user_passes_test(check_perms, login_url=login_url)
-
def permissoes_materia():
return permissoes('Operador de Matéria', 'materia')
@@ -467,6 +464,54 @@ def permissao_tb_aux(self):
else:
return False
+"""
+
+
+class MateriaPesquisaOrderingFilter(django_filters.OrderingFilter):
+
+ choices = (
+ ('', 'Selecione'),
+ ('dataC', 'Data, Tipo, Ano, Numero - Ordem Crescente'),
+ ('dataD', 'Data, Tipo, Ano, Numero - Ordem Decrescente'),
+ ('tipoC', 'Tipo, Ano, Numero, Data - Ordem Crescente'),
+ ('tipoD', 'Tipo, Ano, Numero, Data - Ordem Decrescente')
+ )
+ order_by_mapping = {
+ '': [],
+ 'dataC': ['data_apresentacao', 'tipo__sigla', 'ano', 'numero'],
+ 'dataD': ['-data_apresentacao', '-tipo__sigla', '-ano', '-numero'],
+ 'tipoC': ['tipo__sigla', 'ano', 'numero', 'data_apresentacao'],
+ 'tipoD': ['-tipo__sigla', '-ano', '-numero', '-data_apresentacao'],
+ }
+
+ def __init__(self, *args, **kwargs):
+ kwargs['choices'] = self.choices
+ super(MateriaPesquisaOrderingFilter, self).__init__(*args, **kwargs)
+
+ def filter(self, qs, value):
+ _value = self.order_by_mapping[value[0]] if value else value
+ return super().filter(qs, _value)
+
+
+class AnoNumeroOrderingFilter(django_filters.OrderingFilter):
+
+ choices = (('', 'Selecione...'),
+ ('CRE', 'Ordem Crescente'),
+ ('DEC', 'Ordem Decrescente'),)
+ order_by_mapping = {
+ '': [],
+ 'CRE': ['ano', 'numero'],
+ 'DEC': ['-ano', '-numero'],
+ }
+
+ def __init__(self, *args, **kwargs):
+ kwargs['choices'] = self.choices
+ super(AnoNumeroOrderingFilter, self).__init__(*args, **kwargs)
+
+ def filter(self, qs, value):
+ _value = self.order_by_mapping[value[0]] if value else value
+ return super().filter(qs, _value)
+
def gerar_hash_arquivo(arquivo, pk, block_size=2**20):
md5 = hashlib.md5()
diff --git a/scripts/inicializa_grupos_autorizacoes.py b/scripts/inicializa_grupos_autorizacoes.py
deleted file mode 100644
index 38606b0a3..000000000
--- a/scripts/inicializa_grupos_autorizacoes.py
+++ /dev/null
@@ -1,135 +0,0 @@
-import os
-
-import django
-
-os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sapl.settings")
-django.setup()
-
-if True:
- from django.apps import apps
- from django.contrib.auth import get_user_model
- from django.contrib.auth.models import Group, Permission
- from django.contrib.contenttypes.models import ContentType
-
-
-class InicializaGruposAutorizacoes():
-
- def cria_ou_reseta_grupo(self, nome):
- grupo = Group.objects.get_or_create(name=nome)[0]
- for p in list(grupo.permissions.all()):
- grupo.permissions.remove(p)
- return grupo
-
- def cria_usuario(self, nome, grupo):
- nome_usuario = nome
- usuario = get_user_model().objects.get_or_create(
- username=nome_usuario)[0]
- usuario.set_password('interlegis')
- usuario.save()
- grupo.user_set.add(usuario)
-
- def cria_grupos_permissoes(self):
-
- nomes_apps = ['base', 'parlamentares', 'comissoes',
- 'materia', 'norma', 'sessao', 'painel']
-
- permissoes = {app: list(Permission.objects.filter(
- content_type__in=ContentType.objects.filter(app_label=app)))
- for app in nomes_apps}
-
- # permissoes específicas para protocolo e documento administrativo
- cts = ContentType.objects.filter(app_label='protocoloadm')
-
- # documento administrativo
- permissoes['documento_administrativo'] = list(
- Permission.objects.filter(content_type__in=cts))
- nome_grupo = 'Operador Administrativo'
- grupo = self.cria_ou_reseta_grupo(nome_grupo)
- for p in permissoes['documento_administrativo']:
- grupo.permissions.add(p)
-
- nome_usuario = 'operador_administrativo'
- self.cria_usuario(nome_usuario, grupo)
-
- # prolocolo administrativo
- cts = cts.exclude(model__icontains='tramitacao').exclude(
- model__icontains='documentoadministrativo')
- permissoes['protocoloadm'] = list(
- Permission.objects.filter(content_type__in=cts))
- nome_grupo = 'Operador de Protocolo Administrativo'
- grupo = self.cria_ou_reseta_grupo(nome_grupo)
- for p in permissoes['protocoloadm']:
- grupo.permissions.add(p)
-
- nome_usuario = 'operador_protocoloadm'
- self.cria_usuario(nome_usuario, grupo)
-
- # permissoes do base
- cts = ContentType.objects.filter(app_label='base')
- permissoes['base'] = list(
- Permission.objects.filter(content_type__in=cts))
-
- for nome_app in nomes_apps:
-
- if nome_app not in {'base', 'parlamentares'}:
- # Elimina casos especificos
-
- # Cria Grupo
- nome_grupo = 'Operador de %s' % apps.get_app_config(
- nome_app).verbose_name
- grupo = self.cria_ou_reseta_grupo(nome_grupo)
-
- # Elimina o acesso a proposicoes pelo Operador de Matérias
- if nome_app == 'materia':
- cts = ContentType.objects.filter(
- app_label='materia').exclude(model='proposicao')
- permissoes['materia'] = list(
- Permission.objects.filter(content_type__in=cts))
-
- # Configura as permissoes
- for p in permissoes[nome_app]:
- grupo.permissions.add(p)
-
- # Cria o Usuario
- nome_usuario = 'operador_%s' % nome_app
- usuario = get_user_model().objects.get_or_create(
- username=nome_usuario)[0]
- usuario.set_password('interlegis')
- usuario.save()
- grupo.user_set.add(usuario)
-
- # Operador Geral
- grupo_geral = self.cria_ou_reseta_grupo('Operador Geral')
- for lista in permissoes.values():
- for p in lista:
- grupo_geral.permissions.add(p)
-
- nome_usuario = 'operador_geral'
- self.cria_usuario(nome_usuario, grupo_geral)
-
- # Autor
- grupo = self.cria_ou_reseta_grupo('Autor')
-
- list(map(lambda permissao: grupo.permissions.add(permissao),
- list(Permission.objects.filter(
- content_type=ContentType.objects.get_by_natural_key(
- app_label='materia', model='proposicao')))))
-
- """
- Mesmo para teste, um usuário com perfil Autor criado via /admin
- não deverá ser criado pois esse é um papel do operador_geral fazer
- nas tabelas auxiliares.
- A tentativa de acesso a qualquer container (hoje apenas proposições)
- do SAPL de Usuários com perfil Autor mas sem um Autor cadastrado
- nas tabelas auxiliares será negado e notificado via front-end.
- """
- # nome_usuario = 'operador_autor'
- # self.cria_usuario(nome_usuario, grupo)
-
- def __call__(self):
- self.cria_grupos_permissoes()
-
-
-cria_grupos_permissoes = InicializaGruposAutorizacoes()
-if __name__ == '__main__':
- cria_grupos_permissoes.cria_grupos_permissoes()
diff --git a/scripts/lista_permissions_in_decorators.py b/scripts/lista_permissions_in_decorators.py
new file mode 100644
index 000000000..4d504e6c0
--- /dev/null
+++ b/scripts/lista_permissions_in_decorators.py
@@ -0,0 +1,141 @@
+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)
diff --git a/scripts/test_inicializa_grupos_autorizacoes.py b/scripts/test_inicializa_grupos_autorizacoes.py
deleted file mode 100644
index d9f7d90a2..000000000
--- a/scripts/test_inicializa_grupos_autorizacoes.py
+++ /dev/null
@@ -1,116 +0,0 @@
-import pytest
-from django.apps import apps
-from django.contrib.auth.management import _get_all_permissions
-from django.contrib.auth.models import Group, Permission
-from django.contrib.contenttypes.models import ContentType
-from django.utils.encoding import force_text
-from django.utils.translation import ugettext_lazy as _
-from django.utils.translation import string_concat
-
-from inicializa_grupos_autorizacoes import cria_grupos_permissoes
-
-pytestmark = pytest.mark.django_db
-
-apps_com_permissao_padrao = [
- 'comissoes', 'norma', 'sessao', 'painel']
-
-
-def create_perms_post_migrate(app):
-
- searched_perms = list()
- # The codenames and ctypes that should exist.
- ctypes = set()
-
- for klass in list(app.get_models()):
- opts = klass._meta
- permissions = (
- ("list_" + opts.model_name,
- string_concat(
- _('Visualizaçao da lista de'), ' ',
- opts.verbose_name_plural)),
- ("detail_" + opts.model_name,
- string_concat(
- _('Visualização dos detalhes de'), ' ',
- opts.verbose_name_plural)),
- )
- opts.permissions = tuple(
- set(list(permissions) + list(opts.permissions)))
-
- if opts.proxy:
- # Force looking up the content types in the current database
- # before creating foreign keys to them.
- app_label, model = opts.app_label, opts.model_name
-
- try:
- ctype = ContentType.objects.get_by_natural_key(
- app_label, model)
- except:
- ctype = ContentType.objects.create(
- app_label=app_label, model=model)
- else:
- ctype = ContentType.objects.get_for_model(klass)
-
- ctypes.add(ctype)
- for perm in _get_all_permissions(klass._meta, ctype):
- searched_perms.append((ctype, perm))
-
- all_perms = set(Permission.objects.filter(
- content_type__in=ctypes,
- ).values_list(
- "content_type", "codename"
- ))
-
- perms = [
- Permission(codename=codename, name=name, content_type=ct)
- for ct, (codename, name) in searched_perms
- if (ct.pk, codename) not in all_perms
- ]
- Permission.objects.bulk_create(perms)
-
-
-@pytest.mark.parametrize('app_label', apps_com_permissao_padrao)
-def test_grupo_padrao_tem_permissoes_sobre_todo_o_app(app_label):
-
- app = apps.get_app_config(app_label)
-
- create_perms_post_migrate(app)
-
- # código testado
- cria_grupos_permissoes()
-
- def gerar_permissoes(app):
- for model in app.get_models():
- for op in ['add', 'change', 'delete', ]:
- yield model, 'Can %s %s' % (op, model._meta.verbose_name)
- yield model, force_text(string_concat(
- _('Visualizaçao da lista de'), ' ',
- model._meta.verbose_name_plural))
- yield model, force_text(string_concat(
- _('Visualização dos detalhes de'),
- ' ',
- model._meta.verbose_name_plural))
- grupo = Group.objects.get(name='Operador de %s' % app.verbose_name)
- esperado = set(gerar_permissoes(app))
-
- real = set((p.content_type.model_class(), p.name)
- for p in grupo.permissions.all())
- assert real == esperado
-
-
-@pytest.mark.parametrize('app_label', apps_com_permissao_padrao)
-def test_permissoes_extras_sao_apagadas(app_label):
-
- app = apps.get_app_config(app_label)
-
- # create_perms_post_migrate(app)
-
- grupo = Group.objects.create(name='Operador de %s' % app.verbose_name)
-
- permissao_errada = Permission.objects.create(
- name='STUB', content_type=ContentType.objects.first())
- grupo.permissions.add(permissao_errada)
-
- # código testado
- cria_grupos_permissoes()
-
- assert not grupo.permissions.filter(id=permissao_errada.id).exists()