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/base/templatetags/common_tags.py b/sapl/base/templatetags/common_tags.py index d023104d5..b4f4bb23d 100644 --- a/sapl/base/templatetags/common_tags.py +++ b/sapl/base/templatetags/common_tags.py @@ -3,6 +3,7 @@ from django import template from sapl.base.models import AppConfig from sapl.parlamentares.models import Filiacao + register = template.Library() 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/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/models.py b/sapl/compilacao/models.py index f026222c3..189dea8f2 100644 --- a/sapl/compilacao/models.py +++ b/sapl/compilacao/models.py @@ -81,6 +81,12 @@ class TipoTextoArticulado(models.Model): choices=YES_NO_CHOICES, verbose_name=_('Participação Social')) + publicacao_func = models.NullBooleanField( + default=True, + 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') @@ -635,7 +641,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( @@ -702,8 +708,10 @@ class Dispositivo(BaseModel, TimestampedMixin): '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', _( + ('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): @@ -1260,7 +1268,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..aaeb712b0 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) diff --git a/sapl/compilacao/views.py b/sapl/compilacao/views.py index 9b7f79d95..addbd9400 100644 --- a/sapl/compilacao/views.py +++ b/sapl/compilacao/views.py @@ -1,16 +1,17 @@ -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.decorators import login_required, permission_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 @@ -20,8 +21,8 @@ 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, @@ -46,14 +47,15 @@ from sapl.compilacao.models import (Dispositivo, Nota, VeiculoPublicacao, Vide) from sapl.compilacao.utils import (DISPOSITIVO_SELECT_RELATED, DISPOSITIVO_SELECT_RELATED_EDIT) -from sapl.crud.base import Crud, CrudListView, make_pagination +from sapl.crud.base import CrudAux, 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') -VeiculoPublicacaoCrud = Crud.build(VeiculoPublicacao, 'veiculo_publicacao') -TipoDispositivoCrud = Crud.build( + +TipoNotaCrud = CrudAux.build(TipoNota, 'tipo_nota') +TipoVideCrud = CrudAux.build(TipoVide, 'tipo_vide') +TipoPublicacaoCrud = CrudAux.build(TipoPublicacao, 'tipo_publicacao') +VeiculoPublicacaoCrud = CrudAux.build(VeiculoPublicacao, 'veiculo_publicacao') +TipoDispositivoCrud = CrudAux.build( TipoDispositivo, 'tipo_dispositivo') logger = logging.getLogger(BASE_DIR.name) @@ -87,12 +89,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 +121,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 +139,25 @@ 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 + item = get_object_or_404(self.model, pk=kwargs['pk']) related_object_type = ContentType.objects.get_for_model(item) @@ -128,71 +178,43 @@ class IntegracaoTaView(TemplateView): 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.data = getattr(item, map_fields['data'] + if map_fields['data'] else 'xxx', datetime.now()) - if hasattr(item, 'observacao') and item.observacao: - ta.observacao = item.observacao - else: - ta.observacao = _('Integração com %s sem observacao.') % item + ta.ementa = getattr( + item, map_fields['ementa'] + if map_fields['ementa'] else 'xxx', _( + 'Integração com %s sem ementa.') % item) - if hasattr(item, 'numero') and item.numero: - ta.numero = item.numero - else: - ta.numero = int('%s%s%s' % ( + ta.observacao = getattr( + item, map_fields['observacao'] + if map_fields['observacao'] else 'xxx', '') + + 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))) + int(datetime.now().day)))) - 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.ano = getattr(item, map_fields['ano'] + if map_fields['ano'] else 'xxx', datetime.now().year) ta.save() - return redirect(to=reverse_lazy('sapl.compilacao:ta_text', - kwargs={'ta_id': ta.pk})) - - """msg = messages.error if not request.user.is_anonymous( - ) else messages.info - - msg(request, - _('A funcionalidade de Textos Articulados está desativada.')) - - if not request.user.is_anonymous(): - msg( - request, - _('Para ativá-la, os Tipos de Textos devem ser criados.')) - - msg(request, - _('Sua tela foi redirecionada para a tela de ' - 'cadastro de Textos Articulados.')) - - return redirect(to=reverse_lazy('sapl.compilacao:tipo_ta_list', - kwargs={})) + if Dispositivo.objects.filter(ta_id=ta.pk).exists(): + return redirect(to=reverse_lazy('sapl.compilacao:ta_text', + 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_edit', + 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 +273,19 @@ 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['ta_id']) + return ta def get_context_data(self, **kwargs): context = super(CompMixin, self).get_context_data(**kwargs) @@ -271,6 +305,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 +322,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 +344,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 +374,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 +389,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): @@ -389,6 +429,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 +444,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 +467,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 +507,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 +546,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 +558,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 +578,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 +607,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 +634,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 +650,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 +675,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 +705,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): @@ -709,42 +737,7 @@ class TextView(CompMixin, ListView): 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))) - - 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() - + self.object = self.ta return super(TextView, self).get(request, *args, **kwargs) def get_context_data(self, **kwargs): @@ -936,8 +929,9 @@ class DispositivoView(TextView): return itens -class TextEditView(TemplateView): +class TextEditView(CompMixin, TemplateView): template_name = 'compilacao/text_edit.html' + permission_required = 'compilacao.change_dispositivo_edicao_dinamica' def get_context_data(self, **kwargs): dispositivo_id = int(self.kwargs['dispositivo_id']) \ @@ -946,7 +940,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 +1715,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 +2417,12 @@ 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 + permission_required = 'compilacao.change_dispositivo_edicao_dinamica', def get_initial(self): initial = UpdateView.get_initial(self) @@ -2615,7 +2616,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 +2799,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 +2873,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 +2898,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 +2958,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 +2992,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/crud/base.py b/sapl/crud/base.py index 945ddf5ae..a5c029655 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 @@ -32,10 +34,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) diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index 8ba0e5477..c46a6eb81 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -1,7 +1,8 @@ -from datetime import date, datetime import os +from datetime import date, datetime +import django_filters from crispy_forms.bootstrap import (Alert, FormActions, InlineCheckboxes, InlineRadios) from crispy_forms.helper import FormHelper @@ -17,22 +18,23 @@ from django.db.models import Max from django.forms import ModelForm, widgets from django.forms.forms import Form from django.utils.translation import ugettext_lazy as _ -import django_filters +import sapl from sapl.base.models import Autor from sapl.comissoes.models import Comissao from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column, to_row) -from sapl.materia.models import RegimeTramitacao, TipoDocumento, TipoProposicao +from sapl.materia.models import (MateriaLegislativa, RegimeTramitacao, + TipoDocumento, TipoProposicao) 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, YES_NO_CHOICES, - ChoiceWithoutValidationField, RangeWidgetOverride, + ChoiceWithoutValidationField, + MateriaPesquisaOrderingFilter, RangeWidgetOverride, autor_label, autor_modal, models_with_gr_for_model) -import sapl from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial, DocumentoAcessorio, MateriaLegislativa, Numeracao, @@ -65,6 +67,33 @@ class ReceberProposicaoForm(Form): super(ReceberProposicaoForm, self).__init__(*args, **kwargs) +class MateriaSimplificadaForm(ModelForm): + + class Meta: + model = MateriaLegislativa + fields = ['tipo', 'numero', 'ano', 'data_apresentacao', + 'numero_origem_externa', '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_origem_externa', 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: @@ -444,6 +473,8 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet): label=u'Ano da Matéria', choices=em_tramitacao) + o = MateriaPesquisaOrderingFilter() + class Meta: model = MateriaLegislativa fields = ['numero', @@ -453,7 +484,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', @@ -461,29 +492,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) diff --git a/sapl/materia/tests/test_materia.py b/sapl/materia/tests/test_materia.py index 5455792f4..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 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 30d038f23..48fdce626 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -28,11 +28,11 @@ from sapl.crud.base import (ACTION_CREATE, ACTION_DELETE, ACTION_DETAIL, ACTION_LIST, ACTION_UPDATE, RP_DETAIL, RP_LIST, Crud, CrudAux, MasterDetailCrud, PermissionRequiredForAppCrudMixin, make_pagination) -from sapl.materia import apps 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) @@ -40,8 +40,9 @@ import sapl from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, DocumentoAcessorioForm, MateriaLegislativaFilterSet, - PrimeiraTramitacaoEmLoteFilterSet, ReceberProposicaoForm, - TramitacaoEmLoteFilterSet, filtra_tramitacao_destino, + MateriaSimplificadaForm, PrimeiraTramitacaoEmLoteFilterSet, + ReceberProposicaoForm, TramitacaoEmLoteFilterSet, + filtra_tramitacao_destino, filtra_tramitacao_destino_and_status, filtra_tramitacao_status) from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial, @@ -67,9 +68,49 @@ 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_origem_externa'].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 + } def get(self, request, *args, **kwargs): """ @@ -86,8 +127,15 @@ 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_recebimento', + 'ementa': 'descricao', + 'observacao': None, + 'numero': 'numero_proposicao', + 'ano': 'ano', + } + map_funcs = { + 'publicacao_func': False } def get(self, request, *args, **kwargs): diff --git a/sapl/norma/forms.py b/sapl/norma/forms.py index a2410f9ce..77e9604a4 100644 --- a/sapl/norma/forms.py +++ b/sapl/norma/forms.py @@ -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' ) diff --git a/sapl/norma/views.py b/sapl/norma/views.py index b76d6b517..1cddc0ca9 100644 --- a/sapl/norma/views.py +++ b/sapl/norma/views.py @@ -28,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): """ diff --git a/sapl/painel/views.py b/sapl/painel/views.py index d43555f51..6c43708a4 100644 --- a/sapl/painel/views.py +++ b/sapl/painel/views.py @@ -16,7 +16,6 @@ from sapl.sessao.models import (ExpedienteMateria, OrdemDia, PresencaOrdemDia, from .models import Cronometro - CronometroPainelCrud = Crud.build(Cronometro, '') # FIXME mudar lógica diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index 4e937bd54..38228d920 100644 --- a/sapl/parlamentares/views.py +++ b/sapl/parlamentares/views.py @@ -64,8 +64,7 @@ class ProposicaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView): class ListView(CrudBaseForListAndDetailExternalAppView.ListView): def get_queryset(self): - return super().get_queryset().filter( - data_envio__isnull=False) + return super().get_queryset().filter(data_envio__isnull=False) class ParticipacaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView): diff --git a/sapl/protocoloadm/forms.py b/sapl/protocoloadm/forms.py index de17af830..b439ffb88 100644 --- a/sapl/protocoloadm/forms.py +++ b/sapl/protocoloadm/forms.py @@ -1,5 +1,6 @@ from datetime import datetime +import django_filters from crispy_forms.bootstrap import InlineRadios from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML, Button, Fieldset, Layout, Submit @@ -8,19 +9,17 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.db import models from django.forms import ModelForm from django.utils.translation import ugettext_lazy as _ -import django_filters 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')] NATUREZA_PROCESSO = [('', 'Ambos'), @@ -69,6 +68,8 @@ class ProtocoloFilterSet(django_filters.FilterSet): widget=forms.Select( attrs={'class': 'selector'})) + o = AnoNumeroOrderingFilter() + class Meta: model = Protocolo fields = ['numero', @@ -77,25 +78,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) @@ -163,6 +145,8 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet): interessado = django_filters.CharFilter(lookup_expr='icontains') + o = AnoNumeroOrderingFilter() + class Meta: model = DocumentoAdministrativo fields = ['tipo', @@ -172,25 +156,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) diff --git a/sapl/protocoloadm/urls.py b/sapl/protocoloadm/urls.py index 07c89029e..21c627b06 100644 --- a/sapl/protocoloadm/urls.py +++ b/sapl/protocoloadm/urls.py @@ -9,7 +9,8 @@ from sapl.protocoloadm.views import (AnularProtocoloAdmView, DocumentoAcessorioAdministrativoView, DocumentoAdministrativoCrud, PesquisarDocumentoAdministrativoView, - ProtocoloDocumentoView, ProtocoloListView, + ProtocoloDocumentoView, + ProtocoloMateriaTemplateView, ProtocoloMateriaView, ProtocoloMostrarView, ProtocoloPesquisaView, @@ -60,6 +61,8 @@ urlpatterns_protocolo = [ + url(r'^protocoloadm/(?P\d+)/continuar$', + ProtocoloMateriaTemplateView.as_view(), name='materia_continuar'), url(r'^protocoloadm/anular-protocolo', diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index 36c15718f..dd478b93a 100644 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -12,10 +12,10 @@ from django.views.generic import CreateView, DetailView, FormView, ListView from django.views.generic.base import TemplateView from django_filters.views import FilterView +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) -import sapl +from sapl.utils import create_barcode, get_client_ip from .forms import (AnularProcoloAdmForm, DocumentoAcessorioAdministrativoForm, DocumentoAdministrativoFilterSet, @@ -26,14 +26,13 @@ from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo, Protocolo, StatusTramitacaoAdministrativo, TipoDocumentoAdministrativo, TramitacaoAdministrativo) - 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( @@ -47,8 +46,7 @@ class DocumentoAdministrativoMixin: if app_config and app_config.documentos_administrativos == 'O': return True - return self.request.user.has_module_perms( - sapl.base.models.AppConfig.label) + return super().has_permission() class DocumentoAdministrativoCrud(Crud): @@ -60,10 +58,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 @@ -314,8 +312,9 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView): form_valid_message = _('Matéria cadastrada com sucesso!') 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: @@ -353,12 +352,25 @@ 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 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(PermissionRequiredMixin, - FilterView, - DocumentoAdministrativoMixin): + +class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin, + PermissionRequiredMixin, + FilterView): model = DocumentoAdministrativo filterset_class = DocumentoAdministrativoFilterSet paginate_by = 10 @@ -565,65 +577,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/rules/map_rules.py b/sapl/rules/map_rules.py index ab3ceb846..ccc056aca 100644 --- a/sapl/rules/map_rules.py +++ b/sapl/rules/map_rules.py @@ -1,7 +1,6 @@ from sapl.base import models as base from sapl.comissoes import models as comissoes from sapl.compilacao import models as compilacao -from sapl.crud.base import RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL, RP_LIST from sapl.lexml import models as lexml from sapl.materia import models as materia from sapl.norma import models as norma @@ -16,7 +15,47 @@ from sapl.rules import (SAPL_GROUP_ADMINISTRATIVO, SAPL_GROUP_ANONYMOUS, SAPL_GROUP_PROTOCOLO, SAPL_GROUP_SESSAO) from sapl.sessao import models as sessao -# RP = Radicao de Permissão +""" +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] @@ -74,10 +113,12 @@ rules_group_materia = { (compilacao.Dispositivo, __base__ + [ 'change_dispositivo_edicao_dinamica', - # sobre a regra abaixo deve ser pensada sobre isso - # abre possibilidade pra haver compilacao de emenda com projeto - # mas testes devem ser feitos especificamente para materia - # 'change_dispositivo_registros_compilacao' + # 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 + # oferecia um autografo eletrônico pronto ser convertido em Norma. ]) ] } @@ -95,10 +136,11 @@ rules_group_norma = { (compilacao.Vide, __base__), (compilacao.Nota, __base__), (compilacao.Dispositivo, __base__ + [ - 'change_dispositivo_notificacoes', + 'view_dispositivo_notificacoes', 'change_dispositivo_edicao_dinamica', 'change_dispositivo_edicao_avancada', - 'change_dispositivo_registros_compilacao' + 'change_dispositivo_registros_compilacao', + 'change_dispositivo_de_vigencia_global' ]) ] } @@ -163,8 +205,8 @@ rules_group_geral = { (comissoes.TipoComissao, __base__), (comissoes.Periodo, __base__), - (materia.AssuntoMateria, []), # não há implementação - (materia.MateriaAssunto, []), # não há implementação + (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__), diff --git a/sapl/rules/tests/test_rules.py b/sapl/rules/tests/test_rules.py index be145e3ea..3662477b9 100644 --- a/sapl/rules/tests/test_rules.py +++ b/sapl/rules/tests/test_rules.py @@ -1,19 +1,22 @@ +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 _ -import pytest -from sapl.rules import SAPL_GROUPS -from sapl.rules.map_rules import rules_patterns +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 = [] @@ -26,7 +29,7 @@ sapl_models.reverse() def test_groups_in_rules_patterns(group_item): test = False - for rules_group in rules_patterns: + for rules_group in map_rules.rules_patterns: if rules_group['group'] == group_item: test = True @@ -37,7 +40,7 @@ def test_groups_in_rules_patterns(group_item): def test_models_in_rules_patterns(model_item): test = False - for rules_group in rules_patterns: + for rules_group in map_rules.rules_patterns: rules_model = rules_group['rules'] for rm in rules_model: if rm[0] == model_item: @@ -49,6 +52,75 @@ def test_models_in_rules_patterns(model_item): 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) @@ -57,7 +129,7 @@ def test_permission_of_rules_exists(model_item): print(model_item) create_perms_post_migrate(model_item._meta.app_config) - for rules_group in rules_patterns: + for rules_group in map_rules.rules_patterns: rules_model = rules_group['rules'] for rm in rules_model: model = rm[0] @@ -77,7 +149,8 @@ def test_permission_of_rules_exists(model_item): content_type=content_type, codename=codename).exists() - assert p, _('Permissão (%s) no model (%s) não existe.') % ( + assert p, _('Permissão (%s) associada ao model (%s) ' + 'não está em _meta.permissions.') % ( codename, model_item) 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 f36381233..d5ce1bdad 100644 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -29,7 +29,6 @@ from sapl.materia.views import MateriaLegislativaPesquisaView from sapl.norma.models import NormaJuridica from sapl.parlamentares.models import (Legislatura, Parlamentar, SessaoLegislativa) - from sapl.sessao.apps import AppConfig from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm @@ -45,7 +44,6 @@ from .models import (Bancada, Bloco, CargoBancada, CargoMesa, SessaoPlenariaPresenca, TipoExpediente, TipoResultadoVotacao, TipoSessaoPlenaria, VotoParlamentar) - TipoSessaoCrud = CrudAux.build(TipoSessaoPlenaria, 'tipo_sessao_plenaria') TipoExpedienteCrud = CrudAux.build(TipoExpediente, 'tipo_expediente') CargoBancadaCrud = CrudAux.build(CargoBancada, '') @@ -61,11 +59,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,11 +70,9 @@ 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})) diff --git a/sapl/settings.py b/sapl/settings.py index 4bb3a985b..cfeefdd0f 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -132,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/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 %} + {%endif%} + {% endif %}
@@ -61,11 +65,13 @@ {% endfor %} {%endif%} - {% if not object.ta_publicado and not object.dispositivo_subsequente and not object.tipo_dispositivo.dispositivo_de_alteracao%} -
- -
- {%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%} +
+ +
+ {% 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_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..c93042c7f 100644 --- a/sapl/templates/compilacao/text_edit.html +++ b/sapl/templates/compilacao/text_edit.html @@ -15,14 +15,16 @@ {% endblock %} {% block actions %} -
    -