From 62c809fdf2ecf300f51b7f704be73832398837a9 Mon Sep 17 00:00:00 2001 From: LeandroRoberto Date: Wed, 5 Oct 2016 13:07:13 -0300 Subject: [PATCH 01/22] Add teste de tabelas auxiliares MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Teste de padrão da url de telas auxiliares que implementam CrudAux - Teste de presença da chamada de todos os CrudAux na interface de Tabelas auxiliares. --- sapl/crud/base.py | 3 +- sapl/test_general.py | 257 +++++++++++++++++++++++++++++++++++++++++- scripts/lista_urls.py | 48 ++++++++ 3 files changed, 306 insertions(+), 2 deletions(-) create mode 100644 scripts/lista_urls.py diff --git a/sapl/crud/base.py b/sapl/crud/base.py index 97aef7461..c6283aa5c 100644 --- a/sapl/crud/base.py +++ b/sapl/crud/base.py @@ -13,8 +13,8 @@ from django.db import models from django.http.response import Http404 from django.utils.decorators import classonlymethod 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 import (CreateView, DeleteView, DetailView, ListView, UpdateView) from django.views.generic.base import ContextMixin @@ -23,6 +23,7 @@ from django.views.generic.list import MultipleObjectMixin from sapl.crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display from sapl.utils import normalize + logger = logging.getLogger(__name__) ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \ diff --git a/sapl/test_general.py b/sapl/test_general.py index da2e17ae6..15b589a80 100644 --- a/sapl/test_general.py +++ b/sapl/test_general.py @@ -1,13 +1,81 @@ -import pytest from django.apps import apps +from django.contrib.auth import get_user_model +from django.contrib.auth.management import _get_all_permissions +from django.contrib.auth.models import Permission, User +from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ObjectDoesNotExist +from django.core.urlresolvers import reverse from django.db.models import CharField, TextField +from django.http.response import HttpResponseNotFound +from django.utils.translation import string_concat +from django.utils.translation import ugettext_lazy as _ from model_mommy import mommy +import pytest + +from sapl.crud.base import PermissionRequiredForAppCrudMixin, CrudAux +from scripts.inicializa_grupos_autorizacoes import cria_grupos_permissoes +from scripts.lista_urls import lista_urls from .settings import SAPL_APPS + pytestmark = pytest.mark.django_db sapl_appconfs = [apps.get_app_config(n[5:]) for n in SAPL_APPS] +_lista_urls = lista_urls() + + +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) def test_charfield_textfield(): @@ -34,3 +102,190 @@ def test_str_sanity(): msg = '%s.%s.__str__ is broken.' % ( model.__module__, model.__name__) raise AssertionError(msg, exc) + +btn_login = ('') + + +@pytest.mark.parametrize('url_item', _lista_urls) +def test_crudaux_formato_inicio_urls_associadas(url_item): + + # Verifica se um crud é do tipo CrudAux, se sim, sua url deve começar + # com /sistema/ + key, url, var, app_name = url_item + url = '/' + (url % {v: 1 for v in var}) + + view_class = None + if hasattr(key, 'view_class'): + view_class = key.view_class + + # se não tem view_class, possivelmente é não é uma classed base view + if not view_class: + return + + # se não tem atributo crud, não é será nenhum tipo de crud + if not hasattr(view_class, 'crud'): + return + + # se o crud da view_class relativa a url a ser testada, + # implementa a classe CrudAux, seu link deve iniciar com /sistema + for string_class in list(map(str, type.mro(view_class.crud))): + + if 'CrudAux' in string_class: + assert url.startswith('/sistema'), """ + A url (%s) foi gerada a partir de um CrudAux, + o que diz que está é uma implementação de uma + tabela auxiliar, porém a url em questão, está fora + do padrão, que é iniciar com /sistema. + """ % (url) + + +@pytest.mark.parametrize('url_item', _lista_urls) +def test_crudaux_list_do_crud_esta_na_pagina_sistema(url_item, admin_client): + + # Verifica se um crud é do tipo CrudAux, se sim, sua url deve começar + # com /sistema/ + key, url, var, app_name = url_item + url = '/' + (url % {v: 1 for v in var}) + + view_class = None + if hasattr(key, 'view_class'): + view_class = key.view_class + + # se não tem view_class, possivelmente não é uma classed base view + if not view_class: + return + + # se não tem atributo crud, não é será nenhum tipo de crud + if not hasattr(view_class, 'crud'): + return + + herancas_crud = list(map(str, type.mro(view_class.crud))) + for string_class in herancas_crud: + if 'CrudAux' in string_class: + + herancas_view = list(map(str, type.mro(view_class))) + + for string_view_class in herancas_view: + # verifica se o link para manutenção do crud está em /sistema + if 'ListView' in string_view_class: + response = admin_client.get('/sistema', {}, follow=True) + assert url in str(response.content), """ + A url (%s) não consta nas Tabelas Auxiliares, + porem é uma implementação de ListView de CrudAux. + Se encontra em %s.urls + """ % (url, app_name) + + +@pytest.mark.parametrize('urls_app', _lista_urls) +def em_construcao_crud_permissions_urls(urls_app, client): + if not get_user_model().objects.exists(): + for app in sapl_appconfs: + # readequa permissões dos models adicionando + # list e detail permissions + create_perms_post_migrate(app) + # cria usuários de perfil do sapl + cria_grupos_permissoes() + users = get_user_model().objects.values_list('username', flat=True) + + for url_item in _lista_urls[urls_app]: + + key, url, var, app_name = url_item + url = '/' + (url % {v: 1 for v in var}) + + app_labels = app_name.split('.')[1] + + view_class = None + if hasattr(key, 'view_class'): + view_class = key.view_class + + """ + A classe PermissionRequiredForAppCrudMixin pode ser usada em uma + app mas envolver permissoes para outras + como é o caso de PainelView que está na app 'sessao' + mas é um redirecionamento para 'painel'... aqui é feita + a troca a urls_app a ser testada, por essas outras possíveis + """ + if PermissionRequiredForAppCrudMixin in type.mro(view_class): + # essa classe deve informar app_label + assert hasattr(view_class, 'app_label') + # app_label deve ter conteudo + assert view_class.app_label + app_labels = view_class.app_label + + if isinstance(app_labels, str): + app_labels = app_labels, + + for app in app_labels: + + # monta o username correspondente de a app da url a ser testada + user_for_url_atual_app = 'operador_%s' + if app in ['base', 'parlamentares']: + user_for_url_atual_app = user_for_url_atual_app % 'geral' + elif app in 'protocoloadm': + user_for_url_atual_app = user_for_url_atual_app % 'administrativo' + elif app in ['compilacao']: + return # TODO implementar teste para compilacao + else: + user_for_url_atual_app = user_for_url_atual_app % app + + for username in users: + print(username, user_for_url_atual_app, url) + + client.login(username=username, password='interlegis') + + rg = None + try: + rg = client.get(url, {}, follow=True) + except: + pass + + rp = None + try: + rp = client.post(url, {}, follow=True) + except: + pass + + """ + devido às urls serem incompletas ou com pks e outras valores + inexistentes na base, iniciar a execução da view, seja por get, + post ou qualquer outro método pode causar o erro... + por isso o "try ... except" acima. + No entanto, o objetivo do teste é validar o acesso de toda url. + Independente do erro que vá acontecer, esse erro não ocorrerá + se o user não tiver permissão de acesso pelo fato de que "AS + VIEWS BEM FORMADAS PARA VALIDAÇÃO DE ACESSO DEVEM SEMPRE REDIRECIONAR PARA + LOGIN ANTES DE SUA EXECUÇÃO", desta forma nunca gerando erro + interno dada qualquer incoerência de parâmetros nas urls + """ + if rg: + """ + Se o usuário a ser testado é o usuário da app da url de get + espera-se que não tenha recebido uma tela de login + """ + if username == user_for_url_atual_app and\ + not url.startswith('/sistema/'): + assert btn_login not in str(rg.content) + elif username != 'operador_geral' and\ + url.startswith('/sistema/'): + assert btn_login in str(rg.content) + elif username == 'operador_geral' and\ + url.startswith('/sistema/'): + assert btn_login not in str(rg.content) + + if rp: + """ + Se o usuário a ser testado é o usuário da app da url de + post espera-se que não tenha recebido uma tela de login + """ + if username == user_for_url_atual_app and\ + not url.startswith('/sistema/'): + assert btn_login not in str(rp.content) + elif username != 'operador_geral' and\ + url.startswith('/sistema/'): + assert btn_login in str(rp.content) + elif username == 'operador_geral' and\ + url.startswith('/sistema/'): + assert btn_login not in str(rp.content) + + logout = client.get('/logout/', follow=True) diff --git a/scripts/lista_urls.py b/scripts/lista_urls.py new file mode 100644 index 000000000..e53169080 --- /dev/null +++ b/scripts/lista_urls.py @@ -0,0 +1,48 @@ +import os + +if __name__ == '__main__': + + import django + + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sapl.settings") + django.setup() + +if True: + from django.apps import apps + from sapl.urls import urlpatterns + from django.core.urlresolvers import RegexURLResolver, RegexURLPattern + + +class ListaUrls(): + + def lista_urls(self, _urls): + urls = [] + for item in _urls: + if isinstance(item, RegexURLResolver) and \ + item.app_name.startswith('sapl'): + + for key, value in item.reverse_dict.items(): + if not isinstance(key, str): + if value: + url = value[0][0][0] + var = value[0][0][1] + urls.append((key, url, var, item.app_name)) + urls.sort(key=lambda x: x[1]) + return urls + + def __call__(self): + return self.lista_urls(urlpatterns) + + +lista_urls = ListaUrls() +if __name__ == '__main__': + _lista_urls = lista_urls() + for url_item in _lista_urls: + + params = {} + + for v in url_item[2]: + params[v] = 1 + + u = '/' + url_item[1] % params + print(url_item[3], u) From 80783c1ed5a2cb4dc4fba04e4901937d7d7f9025 Mon Sep 17 00:00:00 2001 From: LeandroRoberto Date: Wed, 5 Oct 2016 13:55:31 -0300 Subject: [PATCH 02/22] Fix #683 e #690 --- sapl/sessao/views.py | 25 +++++++++++++++++-- sapl/templates/crud/detail.html | 3 +++ .../sessao/sessaoplenaria_filter.html | 4 +-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index fef41ce94..e2e20a01f 100644 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -12,6 +12,7 @@ from django.utils.decorators import method_decorator from django.utils.html import strip_tags from django.utils.translation import ugettext_lazy as _ from django.views.generic import ListView, TemplateView +from django.views.generic.base import RedirectView from django.views.generic.detail import DetailView from django.views.generic.edit import FormMixin from django_filters.views import FilterView @@ -45,6 +46,7 @@ from .models import (Bancada, Bloco, CargoBancada, CargoMesa, SessaoPlenariaPresenca, TipoExpediente, TipoResultadoVotacao, TipoSessaoPlenaria, VotoParlamentar) + # OrdemDiaCrud = Crud.build(OrdemDia, '') # RegistroVotacaoCrud = Crud.build(RegistroVotacao, '') TipoSessaoCrud = CrudAux.build(TipoSessaoPlenaria, 'tipo_sessao_plenaria') @@ -427,8 +429,27 @@ class SessaoCrud(Crud): list_field_names = ['data_inicio', 'legislatura', 'sessao_legislativa', 'tipo'] - class ListView(Crud.ListView): - ordering = ['-data_inicio'] + @property + def list_url(self): + return '' + + @property + def cancel_url(self): + return self.search_url + + @property + def search_url(self): + namespace = self.model._meta.app_config.name + return reverse('%s:%s' % (namespace, 'pesquisar_sessao')) + + class ListView(Crud.ListView, RedirectView): + + def get_redirect_url(self, *args, **kwargs): + namespace = self.model._meta.app_config.name + return reverse('%s:%s' % (namespace, 'pesquisar_sessao')) + + def get(self, request, *args, **kwargs): + return RedirectView.get(self, request, *args, **kwargs) class CreateView(Crud.CreateView): diff --git a/sapl/templates/crud/detail.html b/sapl/templates/crud/detail.html index 1eab8100c..af0c8c05a 100644 --- a/sapl/templates/crud/detail.html +++ b/sapl/templates/crud/detail.html @@ -9,6 +9,9 @@ {% if view.list_url %} {% trans 'Listar' %} {{view.verbose_name_plural}} {% endif %} + {% if view.search_url %} + {% trans 'Fazer Nova Pesquisa' %} + {% endif %} {% if view.create_url %} {% blocktrans with verbose_name=view.verbose_name %} Adicionar {{ verbose_name }} {% endblocktrans %} diff --git a/sapl/templates/sessao/sessaoplenaria_filter.html b/sapl/templates/sessao/sessaoplenaria_filter.html index 5069735c7..b4aa4ca98 100644 --- a/sapl/templates/sessao/sessaoplenaria_filter.html +++ b/sapl/templates/sessao/sessaoplenaria_filter.html @@ -6,7 +6,7 @@
{% if perms.sessao %} - {% blocktrans with verbose_name=view.verbose_name %} Adicionar Sessão Plenária {% endblocktrans %} + {% blocktrans with verbose_name=view.verbose_name %} Adicionar Sessão Plenária {% endblocktrans %} {% endif %} {% if filter_url %} @@ -50,4 +50,4 @@ {% endif %} {% endblock detail_content %} {% block table_content %} -{% endblock table_content %} \ No newline at end of file +{% endblock table_content %} From eae797de157414a986bcd2d25242933381478f11 Mon Sep 17 00:00:00 2001 From: Eduardo Edson Batista Cordeiro Alves Date: Wed, 5 Oct 2016 14:42:51 -0300 Subject: [PATCH 03/22] Remove TipoInstituicao --- .../migrations/0002_delete_tipoinstituicao.py | 18 ++++++++++++++++++ sapl/protocoloadm/models.py | 11 ----------- sapl/protocoloadm/urls.py | 5 +---- sapl/protocoloadm/views.py | 3 +-- sapl/templates/protocoloadm/layouts.yaml | 4 ---- 5 files changed, 20 insertions(+), 21 deletions(-) create mode 100644 sapl/protocoloadm/migrations/0002_delete_tipoinstituicao.py diff --git a/sapl/protocoloadm/migrations/0002_delete_tipoinstituicao.py b/sapl/protocoloadm/migrations/0002_delete_tipoinstituicao.py new file mode 100644 index 000000000..064981161 --- /dev/null +++ b/sapl/protocoloadm/migrations/0002_delete_tipoinstituicao.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-10-05 17:42 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('protocoloadm', '0001_initial'), + ] + + operations = [ + migrations.DeleteModel( + name='TipoInstituicao', + ), + ] diff --git a/sapl/protocoloadm/models.py b/sapl/protocoloadm/models.py index 013ba89e2..6c65dfb6f 100644 --- a/sapl/protocoloadm/models.py +++ b/sapl/protocoloadm/models.py @@ -9,17 +9,6 @@ from sapl.materia.models import (Autor, TipoMateriaLegislativa, from sapl.utils import RANGE_ANOS, YES_NO_CHOICES -class TipoInstituicao(models.Model): - descricao = models.CharField(max_length=50, verbose_name=_('Descrição')) - - class Meta: - verbose_name = _('Tipo de Instituição') - verbose_name_plural = _('Tipos de Instituições') - - def __str__(self): - return self.descricao - - class TipoDocumentoAdministrativo(models.Model): sigla = models.CharField(max_length=5, verbose_name=_('Sigla')) descricao = models.CharField(max_length=50, verbose_name=_('Descrição')) diff --git a/sapl/protocoloadm/urls.py b/sapl/protocoloadm/urls.py index c98afe4c1..d44ce9ce3 100644 --- a/sapl/protocoloadm/urls.py +++ b/sapl/protocoloadm/urls.py @@ -17,8 +17,7 @@ from sapl.protocoloadm.views import (AnularProtocoloAdmView, ProtocoloPesquisaView, StatusTramitacaoAdministrativoCrud, TipoDocumentoAdministrativoCrud, - TipoInstituicaoCrud, TramitacaoAdmCrud, - pesquisa_autores) + TramitacaoAdmCrud, pesquisa_autores) from .apps import AppConfig @@ -35,8 +34,6 @@ urlpatterns = [ include(TramitacaoAdmCrud.get_urls())), url(r'^protocoloadm/status-tramitacao-adm/', include(StatusTramitacaoAdministrativoCrud.get_urls())), - url(r'^protocoloadm/tipo-instituicao/', - include(TipoInstituicaoCrud.get_urls())), url(r'^protocoloadm/protocolo-doc/', include(ProtocoloDocumentoCrud.get_urls())), url(r'^protocoloadm/protocolo-mat/', diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index 48f354b17..1121b1397 100644 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -29,12 +29,11 @@ from .forms import (AnularProcoloAdmForm, DocumentoAcessorioAdministrativoForm, from .models import (Autor, DocumentoAcessorioAdministrativo, DocumentoAdministrativo, Protocolo, StatusTramitacaoAdministrativo, - TipoDocumentoAdministrativo, TipoInstituicao, + TipoDocumentoAdministrativo, TramitacaoAdministrativo) TipoDocumentoAdministrativoCrud = CrudAux.build( TipoDocumentoAdministrativo, '') -TipoInstituicaoCrud = CrudAux.build(TipoInstituicao, '') ProtocoloDocumentoCrud = Crud.build(Protocolo, '') diff --git a/sapl/templates/protocoloadm/layouts.yaml b/sapl/templates/protocoloadm/layouts.yaml index c9f872b6f..907d3174d 100644 --- a/sapl/templates/protocoloadm/layouts.yaml +++ b/sapl/templates/protocoloadm/layouts.yaml @@ -46,7 +46,3 @@ Protocolo: - assunto_ementa - autor - observacao - -TipoInstituicao: - {% trans 'Tipo de Instituição' %}: - - descricao From ab044e9192b95ba712f0a051658a721df5cdb4a4 Mon Sep 17 00:00:00 2001 From: Eduardo Edson Batista Cordeiro Alves Date: Wed, 5 Oct 2016 14:49:12 -0300 Subject: [PATCH 04/22] Fix #698 --- sapl/sessao/views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index e2e20a01f..b3be0df54 100644 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -852,9 +852,10 @@ class ResumoView(DetailView): context.update({'basica': [ _('Tipo de Sessão: %(tipo)s') % {'tipo': self.object.tipo}, - _('Abertura: %(abertura)s') % {'abertura': abertura}, - _('Encerramento: %(encerramento)s') % { - 'encerramento': encerramento}, + _('Abertura: %(abertura)s - %(hora_inicio)s') % { + 'abertura': abertura, 'hora_inicio': self.object.hora_inicio}, + _('Encerramento: %(encerramento)s - %(hora_fim)s') % { + 'encerramento': encerramento, 'hora_fim': self.object.hora_fim}, ]}) # ===================================================================== # Conteúdo Multimídia From 43b01186f1437c522aa29fece0b72099bf989cbf Mon Sep 17 00:00:00 2001 From: Eduardo Edson Batista Cordeiro Alves Date: Wed, 5 Oct 2016 14:52:19 -0300 Subject: [PATCH 05/22] =?UTF-8?q?Remove=20link=20para=20TipoInstui=C3=A7?= =?UTF-8?q?=C3=A3o=20da=20tabela=20auxilar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/templates/sistema.html | 1 - 1 file changed, 1 deletion(-) diff --git a/sapl/templates/sistema.html b/sapl/templates/sistema.html index 837198e03..0b80f85ad 100644 --- a/sapl/templates/sistema.html +++ b/sapl/templates/sistema.html @@ -95,7 +95,6 @@

Módulo Administrativo

From 4ec74a8f7857420f53a3e7dd2bcfab8830c40438 Mon Sep 17 00:00:00 2001 From: LeandroRoberto Date: Wed, 5 Oct 2016 15:05:22 -0300 Subject: [PATCH 06/22] Ref menus.py para renderizar urls sem arg root_pk --- sapl/base/templatetags/menus.py | 79 ++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/sapl/base/templatetags/menus.py b/sapl/base/templatetags/menus.py index 3cbf5e46c..36287fec4 100644 --- a/sapl/base/templatetags/menus.py +++ b/sapl/base/templatetags/menus.py @@ -1,6 +1,7 @@ -import yaml from django import template from django.core.urlresolvers import reverse +import yaml + register = template.Library() @@ -28,43 +29,42 @@ def subnav(context, path=None): if obj: root_pk = obj.pk - if root_pk: - request = context['request'] + request = context['request'] + + """ + As implementações das Views de Modelos que são dados auxiliares e + de diversas app's estão concentradas em urls com prefixo 'sistema'. + Essas Views não possuem submenu de navegação e são incompativeis com a + execução deste subnav. Inicialmente, a maneira mais prática encontrada + de isolar foi com o teste abaixo. + """ + + rm = request.resolver_match + app_template = rm.app_name.rsplit('.', 1)[-1] + + if path: + yaml_path = path + elif 'subnav_template_name' in context: + yaml_path = context['subnav_template_name'] + else: + yaml_path = '%s/%s' % (app_template, 'subnav.yaml') + + if not yaml_path: + return + try: """ - As implementações das Views de Modelos que são dados auxiliares e - de diversas app's estão concentradas em urls com prefixo 'sistema'. - Essas Views não possuem submenu de navegação e são incompativeis com a - execução deste subnav. Inicialmente, a maneira mais prática encontrada - de isolar foi com o teste abaixo. + Por padrão, são carragados dois Loaders, + filesystem.Loader - busca em TEMPLATE_DIRS do projeto atual + app_directories.Loader - busca em todas apps instaladas + A função nativa abaixo busca em todos os Loaders Configurados. """ - - rm = request.resolver_match - app_template = rm.app_name.rsplit('.', 1)[-1] - - if path: - yaml_path = path - elif 'subnav_template_name' in context: - yaml_path = context['subnav_template_name'] - else: - yaml_path = '%s/%s' % (app_template, 'subnav.yaml') - - if not yaml_path: - return - - try: - """ - Por padrão, são carragados dois Loaders, - filesystem.Loader - busca em TEMPLATE_DIRS do projeto atual - app_directories.Loader - busca em todas apps instaladas - A função nativa abaixo busca em todos os Loaders Configurados. - """ - yaml_template = template.loader.get_template(yaml_path) - rendered = yaml_template.render() - menu = yaml.load(rendered) - resolve_urls_inplace(menu, root_pk, rm, context) - except Exception as e: - print(e) + yaml_template = template.loader.get_template(yaml_path) + rendered = yaml_template.render() + menu = yaml.load(rendered) + resolve_urls_inplace(menu, root_pk, rm, context) + except Exception as e: + print(e) return {'menu': menu} @@ -88,8 +88,13 @@ def resolve_urls_inplace(menu, pk, rm, context): menu['url'] = '' menu['active'] = '' else: - menu['url'] = reverse( - '%s:%s' % (rm.app_name, menu['url']), kwargs={'pk': pk}) + try: + menu['url'] = reverse('%s:%s' % ( + rm.app_name, menu['url']), + kwargs={'pk': pk}) + except: + menu['url'] = reverse('%s:%s' % (rm.app_name, menu['url'])) + menu['active'] = 'active'\ if context['request'].path == menu['url'] else '' From c4242e8e9bd093f96aae9515fe8fc76593be0d75 Mon Sep 17 00:00:00 2001 From: LeandroRoberto Date: Wed, 5 Oct 2016 15:20:37 -0300 Subject: [PATCH 07/22] Reverte alter em menus.py por imcompatibilidades --- sapl/base/templatetags/menus.py | 76 ++++++++++++++++----------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/sapl/base/templatetags/menus.py b/sapl/base/templatetags/menus.py index 36287fec4..61c468acf 100644 --- a/sapl/base/templatetags/menus.py +++ b/sapl/base/templatetags/menus.py @@ -29,42 +29,43 @@ def subnav(context, path=None): if obj: root_pk = obj.pk - request = context['request'] + if root_pk: + request = context['request'] - """ - As implementações das Views de Modelos que são dados auxiliares e - de diversas app's estão concentradas em urls com prefixo 'sistema'. - Essas Views não possuem submenu de navegação e são incompativeis com a - execução deste subnav. Inicialmente, a maneira mais prática encontrada - de isolar foi com o teste abaixo. - """ - - rm = request.resolver_match - app_template = rm.app_name.rsplit('.', 1)[-1] - - if path: - yaml_path = path - elif 'subnav_template_name' in context: - yaml_path = context['subnav_template_name'] - else: - yaml_path = '%s/%s' % (app_template, 'subnav.yaml') - - if not yaml_path: - return - - try: """ - Por padrão, são carragados dois Loaders, - filesystem.Loader - busca em TEMPLATE_DIRS do projeto atual - app_directories.Loader - busca em todas apps instaladas - A função nativa abaixo busca em todos os Loaders Configurados. + As implementações das Views de Modelos que são dados auxiliares e + de diversas app's estão concentradas em urls com prefixo 'sistema'. + Essas Views não possuem submenu de navegação e são incompativeis com a + execução deste subnav. Inicialmente, a maneira mais prática encontrada + de isolar foi com o teste abaixo. """ - yaml_template = template.loader.get_template(yaml_path) - rendered = yaml_template.render() - menu = yaml.load(rendered) - resolve_urls_inplace(menu, root_pk, rm, context) - except Exception as e: - print(e) + + rm = request.resolver_match + app_template = rm.app_name.rsplit('.', 1)[-1] + + if path: + yaml_path = path + elif 'subnav_template_name' in context: + yaml_path = context['subnav_template_name'] + else: + yaml_path = '%s/%s' % (app_template, 'subnav.yaml') + + if not yaml_path: + return + + try: + """ + Por padrão, são carragados dois Loaders, + filesystem.Loader - busca em TEMPLATE_DIRS do projeto atual + app_directories.Loader - busca em todas apps instaladas + A função nativa abaixo busca em todos os Loaders Configurados. + """ + yaml_template = template.loader.get_template(yaml_path) + rendered = yaml_template.render() + menu = yaml.load(rendered) + resolve_urls_inplace(menu, root_pk, rm, context) + except Exception as e: + print(e) return {'menu': menu} @@ -88,13 +89,8 @@ def resolve_urls_inplace(menu, pk, rm, context): menu['url'] = '' menu['active'] = '' else: - try: - menu['url'] = reverse('%s:%s' % ( - rm.app_name, menu['url']), - kwargs={'pk': pk}) - except: - menu['url'] = reverse('%s:%s' % (rm.app_name, menu['url'])) - + menu['url'] = reverse( + '%s:%s' % (rm.app_name, menu['url']), kwargs={'pk': pk}) menu['active'] = 'active'\ if context['request'].path == menu['url'] else '' From 4a3febe9aae9960e717acc87b098b347ac26a701 Mon Sep 17 00:00:00 2001 From: Eduardo Edson Batista Cordeiro Alves Date: Wed, 5 Oct 2016 15:56:07 -0300 Subject: [PATCH 08/22] Fix #693 --- sapl/sessao/views.py | 2 -- sapl/templates/sessao/mesa.html | 19 +++++++++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index b3be0df54..7d3501ab5 100644 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -47,8 +47,6 @@ from .models import (Bancada, Bloco, CargoBancada, CargoMesa, TipoResultadoVotacao, TipoSessaoPlenaria, VotoParlamentar) -# OrdemDiaCrud = Crud.build(OrdemDia, '') -# RegistroVotacaoCrud = Crud.build(RegistroVotacao, '') TipoSessaoCrud = CrudAux.build(TipoSessaoPlenaria, 'tipo_sessao_plenaria') TipoExpedienteCrud = CrudAux.build(TipoExpediente, 'tipo_expediente') CargoBancadaCrud = CrudAux.build(CargoBancada, '') diff --git a/sapl/templates/sessao/mesa.html b/sapl/templates/sessao/mesa.html index 9bdef7b1c..913135561 100644 --- a/sapl/templates/sessao/mesa.html +++ b/sapl/templates/sessao/mesa.html @@ -10,7 +10,7 @@
- {% for i in integrantes %}
{% if view.get_cargos_mesa %} @@ -47,3 +47,18 @@ {% endblock detail_content %} + +{% block extra_js %} + +{% endblock extra_js %} From 7584e24194974d5c66fbf3117a8251dcac9a2c49 Mon Sep 17 00:00:00 2001 From: Eduardo Edson Batista Cordeiro Alves Date: Wed, 5 Oct 2016 16:04:35 -0300 Subject: [PATCH 09/22] Fix #661 --- sapl/materia/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 132f48fac..7c2b0d8f5 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -581,6 +581,7 @@ class TramitacaoCrud(MasterDetailCrud): if local: self.initial['unidade_tramitacao_local' ] = local.unidade_tramitacao_destino.pk + self.initial['data_tramitacao'] = datetime.now() return self.initial def post(self, request, *args, **kwargs): From 817782a8532d0e4cd5dffb5b94777e2d35aa2057 Mon Sep 17 00:00:00 2001 From: Eduardo Edson Batista Cordeiro Alves Date: Wed, 5 Oct 2016 16:08:46 -0300 Subject: [PATCH 10/22] Adiciona link a campos URL no detail e listagem --- sapl/templates/crud/detail_detail.html | 7 +++++-- sapl/templates/crud/list.html | 6 +++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/sapl/templates/crud/detail_detail.html b/sapl/templates/crud/detail_detail.html index 006ec1b57..c72be069a 100644 --- a/sapl/templates/crud/detail_detail.html +++ b/sapl/templates/crud/detail_detail.html @@ -37,8 +37,11 @@

{{ column.verbose_name }}

-
{{ column.text|safe }}
-
+ {% if column.text|url %} + + {% else %} +
{{ column.text|safe }}
+ {% endif %}
{% endfor %} diff --git a/sapl/templates/crud/list.html b/sapl/templates/crud/list.html index d89caab5d..0d71b7760 100644 --- a/sapl/templates/crud/list.html +++ b/sapl/templates/crud/list.html @@ -60,7 +60,11 @@ {% if href %} {{ value|safe }} {% elif valu != 'core.Cep.None' %} - {{ value|safe }} + {% if value|url %} + {{ value|safe }} + {% else %} + {{ value|safe }} + {% endif %} {% endif %} {% endfor %} From 0211cabb0855d97312ba05c39cdb3155f15fcef8 Mon Sep 17 00:00:00 2001 From: Eduardo Edson Batista Cordeiro Alves Date: Wed, 5 Oct 2016 16:41:37 -0300 Subject: [PATCH 11/22] Remove linhas duplicadas --- sapl/sessao/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index 7d3501ab5..1f233a0ca 100644 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -50,8 +50,6 @@ from .models import (Bancada, Bloco, CargoBancada, CargoMesa, TipoSessaoCrud = CrudAux.build(TipoSessaoPlenaria, 'tipo_sessao_plenaria') TipoExpedienteCrud = CrudAux.build(TipoExpediente, 'tipo_expediente') CargoBancadaCrud = CrudAux.build(CargoBancada, '') -TipoSessaoCrud = CrudAux.build(TipoSessaoPlenaria, 'tipo_sessao_plenaria') -TipoSessaoCrud = CrudAux.build(TipoSessaoPlenaria, 'tipo_sessao_plenaria') BlocoCrud = CrudAux.build( Bloco, '', list_field_names=['nome', 'data_criacao', 'partidos']) From 1c70a863538846dad15fa2ead8ee595c36c50663 Mon Sep 17 00:00:00 2001 From: LeandroRoberto Date: Wed, 5 Oct 2016 18:24:45 -0300 Subject: [PATCH 12/22] =?UTF-8?q?Ref=20cons=20de=20mat=20em=20tramit=20den?= =?UTF-8?q?tro=20de=20uma=20comiss=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/comissoes/views.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/sapl/comissoes/views.py b/sapl/comissoes/views.py index ae11cdbf7..b0c2551c3 100644 --- a/sapl/comissoes/views.py +++ b/sapl/comissoes/views.py @@ -1,9 +1,10 @@ from django.core.urlresolvers import reverse +from django.db.models import F from django.views.generic import ListView from sapl.crud.base import Crud, CrudAux, MasterDetailCrud -from sapl.materia.models import MateriaLegislativa +from sapl.materia.models import MateriaLegislativa, Tramitacao from .models import (CargoComissao, Comissao, Composicao, Participacao, Periodo, TipoComissao) @@ -67,16 +68,19 @@ class MateriasTramitacaoListView(ListView): def get_queryset(self): # FIXME: Otimizar consulta - lista = [] + ts = Tramitacao.objects.order_by( + 'materia', '-data_tramitacao', '-id').annotate( + comissao=F('unidade_tramitacao_destino__comissao')).distinct( + 'materia').values_list('materia', 'comissao') + + ts = list(filter(lambda x: x[1] == int(self.kwargs['pk']), ts)) + ts = list(zip(*ts)) + ts = ts[0] if ts else [] + materias = MateriaLegislativa.objects.filter( - tramitacao__isnull=False).order_by('tipo', 'ano', 'numero') - for materia in materias: - comissao = materia.tramitacao_set.last( - ).unidade_tramitacao_destino.comissao - if (comissao and materia not in lista and - comissao.pk == int(self.kwargs['pk'])): - lista.append(materia) - return lista + pk__in=ts).order_by('tipo', '-ano', '-numero') + + return materias def get_context_data(self, **kwargs): context = super( From c5af7bc7746c2a4af305e35e7a5b54af2c913197 Mon Sep 17 00:00:00 2001 From: LeandroRoberto Date: Thu, 6 Oct 2016 10:27:53 -0300 Subject: [PATCH 13/22] Troca btns de listagem por nova pesquisa em details MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Foi desabilitada as funções de ListView para NormaCrud, MateriaCrud, SessaoCrud, sendo substituídos os botões de listar por pesquisar. Onde,por exemplo, aparecia "Listar Sessão Plenária" agora "Fazer NovaPesquisa" --- sapl/materia/views.py | 26 ++++++++++++++++++++++++ sapl/norma/views.py | 47 ++++++++++++++++++++++++++++++++----------- sapl/sessao/views.py | 12 +++++------ 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 7c2b0d8f5..4efbc599b 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -21,6 +21,7 @@ from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode 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_filters.views import FilterView from sapl.base.models import CasaLegislativa @@ -52,6 +53,7 @@ from .models import (AcompanhamentoMateria, Anexada, Autor, Autoria, TipoFimRelatoria, TipoMateriaLegislativa, TipoProposicao, Tramitacao, UnidadeTramitacao) + OrigemCrud = Crud.build(Origem, '') TipoMateriaCrud = CrudAux.build( @@ -810,6 +812,30 @@ class MateriaLegislativaCrud(Crud): class BaseMixin(Crud.BaseMixin): list_field_names = ['tipo', 'numero', 'ano', 'data_apresentacao'] + @property + def list_url(self): + return '' + + @property + def search_url(self): + namespace = self.model._meta.app_config.name + return reverse('%s:%s' % (namespace, 'pesquisar_materia')) + + class CreateView(Crud.CreateView): + + @property + def cancel_url(self): + return self.search_url + + class ListView(Crud.ListView, RedirectView): + + def get_redirect_url(self, *args, **kwargs): + namespace = self.model._meta.app_config.name + return reverse('%s:%s' % (namespace, 'pesquisar_materia')) + + def get(self, request, *args, **kwargs): + return RedirectView.get(self, request, *args, **kwargs) + # FIXME - qual a finalidade dessa classe?? class DocumentoAcessorioView(PermissionRequiredMixin, CreateView): diff --git a/sapl/norma/views.py b/sapl/norma/views.py index 47813a3b0..a93bb012d 100644 --- a/sapl/norma/views.py +++ b/sapl/norma/views.py @@ -1,7 +1,9 @@ from datetime import datetime +from django.core.urlresolvers import reverse from django.shortcuts import redirect from django.views.generic import FormView, ListView +from django.views.generic.base import RedirectView from sapl.compilacao.views import IntegracaoTaView from sapl.crud.base import RP_DETAIL, RP_LIST, Crud, CrudAux, make_pagination @@ -10,9 +12,8 @@ from sapl.norma.forms import NormaJuridicaForm from .forms import NormaJuridicaPesquisaForm from .models import AssuntoNorma, NormaJuridica, TipoNormaJuridica -# LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '') - +# LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '') AssuntoNormaCrud = CrudAux.build(AssuntoNorma, 'assunto_norma_juridica', list_field_names=['assunto', 'descricao']) @@ -27,6 +28,38 @@ class NormaCrud(Crud): help_path = 'norma_juridica' public = [RP_LIST, RP_DETAIL] + class BaseMixin(Crud.BaseMixin): + list_field_names = ['tipo', 'numero', 'ano', 'ementa'] + + @property + def list_url(self): + return '' + + @property + def search_url(self): + namespace = self.model._meta.app_config.name + return reverse('%s:%s' % (namespace, 'norma_pesquisa')) + + class CreateView(Crud.CreateView): + form_class = NormaJuridicaForm + + @property + def cancel_url(self): + return self.search_url + + @property + def layout_key(self): + return 'NormaJuridicaCreate' + + class ListView(Crud.ListView, RedirectView): + + def get_redirect_url(self, *args, **kwargs): + namespace = self.model._meta.app_config.name + return reverse('%s:%s' % (namespace, 'norma_pesquisa')) + + def get(self, request, *args, **kwargs): + return RedirectView.get(self, request, *args, **kwargs) + class UpdateView(Crud.UpdateView): form_class = NormaJuridicaForm @@ -42,16 +75,6 @@ class NormaCrud(Crud): self.initial['numero_materia'] = norma.materia.numero return self.initial.copy() - class CreateView(Crud.CreateView): - form_class = NormaJuridicaForm - - @property - def layout_key(self): - return 'NormaJuridicaCreate' - - class BaseMixin(Crud.BaseMixin): - list_field_names = ['tipo', 'numero', 'ano', 'ementa'] - class NormaPesquisaView(FormView): template_name = "norma/pesquisa.html" diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index 1f233a0ca..30603347d 100644 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -429,10 +429,6 @@ class SessaoCrud(Crud): def list_url(self): return '' - @property - def cancel_url(self): - return self.search_url - @property def search_url(self): namespace = self.model._meta.app_config.name @@ -449,6 +445,10 @@ class SessaoCrud(Crud): class CreateView(Crud.CreateView): + @property + def cancel_url(self): + return self.search_url + def get_initial(self): legislatura = Legislatura.objects.order_by('-data_inicio')[0] return {'legislatura': legislatura} @@ -849,9 +849,9 @@ class ResumoView(DetailView): context.update({'basica': [ _('Tipo de Sessão: %(tipo)s') % {'tipo': self.object.tipo}, _('Abertura: %(abertura)s - %(hora_inicio)s') % { - 'abertura': abertura, 'hora_inicio': self.object.hora_inicio}, + 'abertura': abertura, 'hora_inicio': self.object.hora_inicio}, _('Encerramento: %(encerramento)s - %(hora_fim)s') % { - 'encerramento': encerramento, 'hora_fim': self.object.hora_fim}, + 'encerramento': encerramento, 'hora_fim': self.object.hora_fim}, ]}) # ===================================================================== # Conteúdo Multimídia From e2cb37c8586ab756d7f4b3dc337b193e5f7add28 Mon Sep 17 00:00:00 2001 From: LeandroRoberto Date: Tue, 20 Sep 2016 16:48:57 -0300 Subject: [PATCH 14/22] =?UTF-8?q?Add=20registro=20de=20revoga=C3=A7=C3=A3o?= =?UTF-8?q?=20unit=C3=A1ria=20de=20dispositivos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/compilacao/forms.py | 48 ++++++++++++++++ ...55_dispositivo_dispositivo_de_revogacao.py | 20 +++++++ sapl/compilacao/models.py | 38 ++++++++++++- .../templatetags/compilacao_filters.py | 2 +- sapl/compilacao/views.py | 57 ++++++++++++++----- sapl/static/js/compilacao_edit.js | 53 +++++++++++++++++ .../compilacao/ajax_actions_dinamic_edit.html | 4 +- .../templates/compilacao/text_list_bloco.html | 12 +++- 8 files changed, 214 insertions(+), 20 deletions(-) create mode 100644 sapl/compilacao/migrations/0055_dispositivo_dispositivo_de_revogacao.py diff --git a/sapl/compilacao/forms.py b/sapl/compilacao/forms.py index f93f71855..c3405eb7f 100644 --- a/sapl/compilacao/forms.py +++ b/sapl/compilacao/forms.py @@ -1280,3 +1280,51 @@ class DispositivoRegistroAlteracaoForm(Form): super(DispositivoRegistroAlteracaoForm, self).__init__(*args, **kwargs) self.fields['dispositivo_alterado'].choices = [] + + +class DispositivoRegistroRevogacaoForm(Form): + + dispositivo_revogado = forms.ModelChoiceField( + label=_('Dispositivo a ser revogado'), + required=False, + queryset=Dispositivo.objects.all()) + + dispositivo_search_form = forms.CharField(widget=forms.HiddenInput(), + required=False) + + def __init__(self, *args, **kwargs): + + layout = [] + kwargs.pop('instance') + kwargs['initial'].pop('editor_type') + + row_dispositivo = Field( + 'dispositivo_revogado', + data_sapl_ta='DispositivoSearch', + data_field='dispositivo_revogado', + data_type_selection='radio', + template="compilacao/layout/dispositivo_radio.html") + + layout.append(Fieldset(_('Registro de Revogação - ' + 'Seleção do Dispositivo a ser Revogado'), + row_dispositivo, + css_class="col-md-12")) + layout.append(Field('dispositivo_search_form')) + + more = [ + HTML('%s' % + _('Cancelar')), + ] + more.append(Submit('salvar', _('Salvar'), css_class='pull-right')) + + buttons = FormActions(*more, css_class='form-group') + + _fields = [Div(*layout, css_class="row-fluid")] + \ + [to_row([(buttons, 12)])] + + self.helper = FormHelper() + self.helper.layout = Layout(*_fields) + + super(DispositivoRegistroRevogacaoForm, self).__init__(*args, **kwargs) + + self.fields['dispositivo_revogado'].choices = [] diff --git a/sapl/compilacao/migrations/0055_dispositivo_dispositivo_de_revogacao.py b/sapl/compilacao/migrations/0055_dispositivo_dispositivo_de_revogacao.py new file mode 100644 index 000000000..daf297852 --- /dev/null +++ b/sapl/compilacao/migrations/0055_dispositivo_dispositivo_de_revogacao.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-09-20 11:57 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('compilacao', '0054_auto_20160916_1424'), + ] + + operations = [ + migrations.AddField( + model_name='dispositivo', + name='dispositivo_de_revogacao', + field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Dispositivo de Revogação'), + ), + ] diff --git a/sapl/compilacao/models.py b/sapl/compilacao/models.py index 7bcc3ae39..e2425bfcf 100644 --- a/sapl/compilacao/models.py +++ b/sapl/compilacao/models.py @@ -132,7 +132,7 @@ class TextoArticulado(TimestampedMixin): 'numero': self.numero, 'data': defaultfilters.date(self.data, "d \d\e F \d\e Y")} - def ordenar_dispositivos(self): + def reagrupar_ordem_de_dispositivos(self): dpts = Dispositivo.objects.filter(ta=self) @@ -150,6 +150,37 @@ class TextoArticulado(TimestampedMixin): count += Dispositivo.INTERVALO_ORDEM Dispositivo.objects.filter(pk=d).update(ordem=count) + def reordenar_dispositivos(self): + + dpts = Dispositivo.objects.filter(ta=self) + + if not dpts.exists(): + return + + ordem_max = dpts.last().ordem + dpts.update(ordem=F('ordem') + ordem_max) + + raizes = Dispositivo.objects.filter( + ta=self, + dispositivo_pai__isnull=True).values_list( + 'pk', flat=True).order_by('ordem') + + count = [] + count.append(Dispositivo.INTERVALO_ORDEM) + + def update(dpk): + Dispositivo.objects.filter(pk=dpk).update(ordem=count[0]) + count[0] = count[0] + Dispositivo.INTERVALO_ORDEM + filhos = Dispositivo.objects.filter( + dispositivo_pai_id=dpk).values_list( + 'pk', flat=True).order_by('ordem') + + for dpk in filhos: + update(dpk) + + for dpk in raizes: + update(dpk) + class TipoNota(models.Model): sigla = models.CharField( @@ -587,6 +618,11 @@ class Dispositivo(BaseModel, TimestampedMixin): choices=YES_NO_CHOICES, verbose_name=_('Visibilidade no Texto Articulado Publicado')) + dispositivo_de_revogacao = models.BooleanField( + default=False, + choices=YES_NO_CHOICES, + verbose_name=_('Dispositivo de Revogação')) + tipo_dispositivo = models.ForeignKey( TipoDispositivo, related_name='dispositivos_do_tipo_set', diff --git a/sapl/compilacao/templatetags/compilacao_filters.py b/sapl/compilacao/templatetags/compilacao_filters.py index d7929c559..de8b70530 100644 --- a/sapl/compilacao/templatetags/compilacao_filters.py +++ b/sapl/compilacao/templatetags/compilacao_filters.py @@ -86,7 +86,7 @@ def nota_automatica(dispositivo, ta_pub_list): ta_publicado = ta_pub_list[dispositivo.ta_publicado_id] if\ ta_pub_list else dispositivo.ta_publicado - if dispositivo.texto == Dispositivo.TEXTO_PADRAO_DISPOSITIVO_REVOGADO: + if dispositivo.dispositivo_de_revogacao: return _('Revogado pelo %s - %s.') % ( d, ta_publicado) elif not dispositivo.dispositivo_substituido_id: diff --git a/sapl/compilacao/views.py b/sapl/compilacao/views.py index ec26d5996..23d698465 100644 --- a/sapl/compilacao/views.py +++ b/sapl/compilacao/views.py @@ -1,6 +1,6 @@ -import sys from collections import OrderedDict from datetime import datetime, timedelta +import sys from braces.views import FormMessagesMixin from django import forms @@ -30,7 +30,8 @@ from sapl.compilacao.forms import (DispositivoDefinidorVigenciaForm, DispositivoRegistroAlteracaoForm, DispositivoSearchModalForm, NotaForm, PublicacaoForm, TaForm, - TextNotificacoesForm, TipoTaForm, VideForm) + TextNotificacoesForm, TipoTaForm, VideForm, + DispositivoRegistroRevogacaoForm) from sapl.compilacao.models import (Dispositivo, Nota, PerfilEstruturalTextoArticulado, Publicacao, TextoArticulado, @@ -41,6 +42,7 @@ from sapl.compilacao.utils import (DISPOSITIVO_SELECT_RELATED, DISPOSITIVO_SELECT_RELATED_EDIT) from sapl.crud.base import Crud, CrudListView, make_pagination + TipoNotaCrud = Crud.build(TipoNota, 'tipo_nota') TipoVideCrud = Crud.build(TipoVide, 'tipo_vide') TipoPublicacaoCrud = Crud.build(TipoPublicacao, 'tipo_publicacao') @@ -983,8 +985,7 @@ class TextEditView(TemplateView): ta_publicado = lista_ta_publicado[dispositivo.ta_publicado_id] if\ lista_ta_publicado else dispositivo.ta_publicado - if dispositivo.texto == \ - Dispositivo.TEXTO_PADRAO_DISPOSITIVO_REVOGADO: + if dispositivo.dispositivo_de_revogacao: return _('Revogado pelo %s - %s.') % ( d, ta_publicado) elif not dispositivo.dispositivo_substituido_id: @@ -1170,7 +1171,7 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin): else: self.set_message(data, 'success', _( 'Exclusão efetuada com sucesso!'), modal=True) - ta_base.ordenar_dispositivos() + ta_base.reagrupar_ordem_de_dispositivos() except Exception as e: data['pk'] = self.kwargs['dispositivo_id'] self.set_message(data, 'danger', str(e), modal=True) @@ -2066,7 +2067,17 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin, return perfis[0].pk return None - def registra_alteracao(self, bloco_alteracao, dispositivo_a_alterar): + def registra_revogacao(self, bloco_alteracao, dispositivo_a_revogar): + return self.registra_alteracao( + bloco_alteracao, + dispositivo_a_revogar, + revogacao=True + ) + + def registra_alteracao(self, + bloco_alteracao, + dispositivo_a_alterar, + revogacao=False): """ Caracteristicas: 1 - Se é um dispositivo simples e sem subsequente @@ -2096,10 +2107,10 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin, for d in history: """FIXME: A comparação "<" deverá ser mudada para - "<=" caso um seja necessário permitir duas alterações + "<=" caso seja necessário permitir duas alterações com mesmo inicio_vigencia no mesmo dispositivo. Neste Caso, - a sequencia correta ficará a cargo dos reposicionamentos entre - dispositivos de mesmo nível, + a sequencia correta ficará a cargo dos reposicionamentos e + (a ser implementado) entre dispositivos de mesmo nível, """ if d.inicio_vigencia < bloco_alteracao.inicio_vigencia: dispositivo_a_alterar = d @@ -2118,8 +2129,12 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin, dispositivo_a_alterar, dispositivo_a_alterar.tipo_dispositivo) ndp.rotulo = dispositivo_a_alterar.rotulo - ndp.texto = dispositivo_a_alterar.texto ndp.publicacao = bloco_alteracao.publicacao + if not revogacao: + ndp.texto = dispositivo_a_alterar.texto + else: + ndp.texto = Dispositivo.TEXTO_PADRAO_DISPOSITIVO_REVOGADO + ndp.dispositivo_de_revogacao = True ndp.dispositivo_vigencia = bloco_alteracao.dispositivo_vigencia if ndp.dispositivo_vigencia: @@ -2167,9 +2182,16 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin, d.dispositivo_pai = ndp d.save() - self.set_message( - data, 'success', - _('Dispositivo de Alteração adicionado com sucesso.')) + ndp.ta.reordenar_dispositivos() + + if not revogacao: + self.set_message( + data, 'success', + _('Dispositivo de Alteração adicionado com sucesso.')) + else: + self.set_message( + data, 'success', + _('Dispositivo de Revogação adicionado com sucesso.')) except Exception as e: print(e) @@ -2221,6 +2243,8 @@ class DispositivoDinamicEditView( self.form_class = DispositivoEdicaoBasicaForm elif self.action.endswith('_alteracao'): self.form_class = DispositivoRegistroAlteracaoForm + elif self.action.endswith('_revogacao'): + self.form_class = DispositivoRegistroRevogacaoForm context = self.get_context_data() return self.render_to_response(context) elif self.action.startswith('get_actions'): @@ -2267,6 +2291,13 @@ class DispositivoDinamicEditView( data = self.registra_alteracao(d, dispositivo_a_alterar) + if formtype == 'get_form_revogacao': + + dispositivo_a_revogar = Dispositivo.objects.get( + pk=request.POST['dispositivo_revogado']) + + data = self.registra_revogacao(d, dispositivo_a_revogar) + elif formtype == 'get_form_base': texto = request.POST['texto'].strip() texto_atualizador = request.POST['texto_atualizador'].strip() diff --git a/sapl/static/js/compilacao_edit.js b/sapl/static/js/compilacao_edit.js index 94c279067..b78ad897a 100644 --- a/sapl/static/js/compilacao_edit.js +++ b/sapl/static/js/compilacao_edit.js @@ -138,6 +138,28 @@ function DispositivoEdit() { }); } + instance.get_form_revogacao = function () { + var _this = $(this); + _this.off('get_form_revogacao'); + $('.dpt-actions, .dpt-actions-bottom').html(''); + + var dpt_form = _this.children().filter('.dpt-form').children().first(); + var url_search = dpt_form[0]['id_dispositivo_search_form'].value; + DispostivoSearch({ + 'url_form': url_search, + 'text_button': 'Selecionar' + }); + + instance.scrollTo(_this); + dpt_form.submit(instance.onSubmitFormRegistraRevogacao); + + var btn_fechar = _this.find('.btn-fechar'); + btn_fechar.on('click', function() { + instance.clearEditSelected(); + instance.triggerBtnDptEdit(_this.attr('pk')); + }); + } + instance.loadActionsEdit = function(dpt) { var pk = dpt.attr('pk'); var url = pk+'/refresh?action=get_actions'; @@ -258,8 +280,39 @@ function DispositivoEdit() { }); if (event != null) event.preventDefault(); + } + + instance.onSubmitFormRegistraRevogacao = function(event) { + var _this = this; + var form_data = { + 'csrfmiddlewaretoken' : this['csrfmiddlewaretoken'].value, + 'dispositivo_revogado' : this['dispositivo_revogado'].value, + 'formtype': 'get_form_revogacao', + }; + var url = $(this).closest('.dpt').attr( "pk" )+'/refresh'; + + instance.waitShow(); + + $.post(url, form_data) + .done(function(data) { + instance.clearEditSelected(); + + if (data.pk != null) { + instance.refreshScreenFocusPk(data); + instance.message(data); + } + else { + alert('Erro na resposta!'); + } + + }).always(function() { + instance.waitHide(); + }); + if (event != null) + event.preventDefault(); } + instance.onSubmitEditFormBase = function(event) { var _this = this; diff --git a/sapl/templates/compilacao/ajax_actions_dinamic_edit.html b/sapl/templates/compilacao/ajax_actions_dinamic_edit.html index 3231fdd1d..2d746740e 100644 --- a/sapl/templates/compilacao/ajax_actions_dinamic_edit.html +++ b/sapl/templates/compilacao/ajax_actions_dinamic_edit.html @@ -53,7 +53,7 @@ {% endif %}
- {% if object.dispositivo_subsequente == None %} + {% if not object.dispositivo_subsequente %} {% for perfil in perfil_estrutural_list%}
- {% if not object.ta_publicado%} + {% if not object.ta_publicado and not object.dispositivo_subsequente %}