diff --git a/README.rst b/README.rst index a40fae4f5..43df9a125 100644 --- a/README.rst +++ b/README.rst @@ -190,24 +190,20 @@ Instalação e configuração das dependências do projeto http://localhost:8000/ -Instruções para criação dos grupos de perfis de usuários e os usuários de testes +Instruções para criação do super usuário e de usuários de testes =========================================================================== * Criar super usuário do django-contrib-admin (Será solicitado alguns dados para criação):: ./manage.py createsuperuser -Os perfis semânticos do SAPL devem ser criados manualmente através da execução de um script que gera esses perfis e adiciona um usuário padrão em cada perfil. Para testar o comportamento de cada perfil é necessário executar este script: +* `Os perfis semânticos do SAPL `_ são fixos e atualizados a cada execução do comando: -* Execute:: - - ./manage.py shell_plus - -* Será aberto um prompt do python customizado com diversas funcionalidades do django e do sapl. Execute dentro do prompt:: + ./manage.py migrate - %run scripts/inicializa_grupos_autorizacoes.py + * Os perfis fixos não aceitam customização via admin, porém outros grupos podem ser criados. O SAPL não interferirá no conjunto de permissões definidas em grupos customizados e se comportará diante de usuários segundo seus grupos e suas permissões. -* Os usuários criados, todos com senha "interlegis", serão:: +* Os usuários de testes de perfil são criados apenas se o SAPL estiver rodando em modo DEBUG=True. Todos com senha "interlegis", serão:: operador_administrativo operador_protocoloadm @@ -217,7 +213,6 @@ Os perfis semânticos do SAPL devem ser criados manualmente através da execuç operador_sessao operador_painel operador_geral - operador_autor Instruções para Tradução ======================== diff --git a/sapl/compilacao/models.py b/sapl/compilacao/models.py index 6752a82c1..9e589f65b 100644 --- a/sapl/compilacao/models.py +++ b/sapl/compilacao/models.py @@ -1,3 +1,5 @@ +from datetime import datetime + from django.contrib import messages from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType @@ -6,9 +8,11 @@ from django.db.models import F, Q from django.db.models.aggregates import Max from django.http.response import Http404 from django.template import defaultfilters +from django.utils.decorators import classonlymethod from django.utils.translation import ugettext_lazy as _ -from sapl.compilacao.utils import int_to_letter, int_to_roman +from sapl.compilacao.utils import int_to_letter, int_to_roman,\ + get_integrations_view_names from sapl.utils import YES_NO_CHOICES, get_settings_auth_user_model @@ -280,6 +284,129 @@ class TextoArticulado(TimestampedMixin): return True + @classonlymethod + def update_or_create(cls, view_integracao, obj): + + map_fields = view_integracao.map_fields + ta_values = getattr(view_integracao, 'ta_values', {}) + + related_object_type = ContentType.objects.get_for_model(obj) + ta = TextoArticulado.objects.filter( + object_id=obj.pk, + content_type=related_object_type) + + ta_exists = bool(ta.exists()) + + if not ta_exists: + tipo_ta = TipoTextoArticulado.objects.filter( + content_type=related_object_type).first() + + ta = TextoArticulado() + ta.tipo_ta = tipo_ta + ta.content_object = obj + + ta.privacidade = ta_values.get('privacidade', STATUS_TA_EDITION) + ta.editing_locked = ta_values.get('editing_locked', False) + ta.editable_only_by_owners = ta_values.get( + 'editable_only_by_owners', False) + + else: + ta = ta[0] + + if not ta.data: + ta.data = getattr(obj, map_fields['data'] + if map_fields['data'] else 'xxx', + datetime.now()) + if not ta.data: + ta.data = datetime.now() + + ta.ementa = getattr( + obj, map_fields['ementa'] + if map_fields['ementa'] else 'xxx', _( + 'Integração com %s sem ementa.') % obj) + + ta.observacao = getattr( + obj, map_fields['observacao'] + if map_fields['observacao'] else 'xxx', '') + + ta.numero = getattr( + obj, 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)))) + + ta.ano = getattr(obj, map_fields['ano'] + if map_fields['ano'] else 'xxx', datetime.now().year) + + ta.save() + return ta + + def clone_for(self, obj): + # O clone gera um texto válido original dada a base self, + # mesmo sendo esta base um texto compilado. + # Os dispositivos a clonar será com base no texto compilado + + assert self.tipo_ta and self.tipo_ta.content_type, _( + 'Não é permitido chamar o método clone_for ' + 'para Textos Articulados independentes.') + + view_integracao = list(filter(lambda x: + x.model == obj._meta.model, + get_integrations_view_names())) + + assert len(view_integracao) > 0, _( + 'Não é permitido chamar o método clone_for ' + 'se não existe integração.') + + assert len(view_integracao) == 1, _( + 'Não é permitido haver mais de uma integração para um Model.') + + view_integracao = view_integracao[0] + + ta = TextoArticulado.update_or_create(view_integracao, obj) + + dispositivos = Dispositivo.objects.filter(ta=self).order_by('ordem') + + map_ids = {} + for d in dispositivos: + id_old = d.id + + # TODO + # validar isso: é o suficiente para pegar apenas o texto válido? + # exemplo: + # quando uma matéria for alterada por uma emenda + # ao usar esta função para gerar uma norma deve vir apenas + # o texto válido, compilado... + if d.dispositivo_subsequente: + continue + + d.id = None + d.inicio_vigencia = ta.data + d.fim_vigencia = None + d.inicio_eficacia = ta.data + d.fim_eficacia = None + d.publicacao = None + d.ta = ta + d.ta_publicado = None + d.dispositivo_subsequente = None + d.dispositivo_substituido = None + d.dispositivo_vigencia = None + d.dispositivo_atualizador = None + d.save() + map_ids[id_old] = d.id + + dispositivos = Dispositivo.objects.filter(ta=ta).order_by('ordem') + + for d in dispositivos: + if not d.dispositivo_pai: + continue + + d.dispositivo_pai_id = map_ids[d.dispositivo_pai_id] + d.save() + + return ta + def reagrupar_ordem_de_dispositivos(self): dpts = Dispositivo.objects.filter(ta=self) diff --git a/sapl/compilacao/utils.py b/sapl/compilacao/utils.py index 83f44e8f3..aa1793484 100644 --- a/sapl/compilacao/utils.py +++ b/sapl/compilacao/utils.py @@ -1,3 +1,4 @@ +import sys DISPOSITIVO_SELECT_RELATED = ( 'tipo_dispositivo', @@ -52,3 +53,16 @@ def int_to_letter(int_value): result = chr(rest + 65) + result result = chr(int_value + 65) + result return result + + +def get_integrations_view_names(): + result = [] + modules = sys.modules + for key, value in modules.items(): + if key.endswith('.views'): + for v in value.__dict__.values(): + if hasattr(v, '__bases__'): + for base in v.__bases__: + if 'IntegracaoTaView' in str(base): + result.append(v) + return result diff --git a/sapl/compilacao/views.py b/sapl/compilacao/views.py index 3b25eb045..1d5e6d459 100644 --- a/sapl/compilacao/views.py +++ b/sapl/compilacao/views.py @@ -45,7 +45,8 @@ from sapl.compilacao.models import (Dispositivo, Nota, VeiculoPublicacao, Vide, STATUS_TA_EDITION, STATUS_TA_PRIVATE, STATUS_TA_PUBLIC) from sapl.compilacao.utils import (DISPOSITIVO_SELECT_RELATED, - DISPOSITIVO_SELECT_RELATED_EDIT) + DISPOSITIVO_SELECT_RELATED_EDIT, + get_integrations_view_names) from sapl.crud.base import Crud, CrudListView, make_pagination from sapl.settings import BASE_DIR @@ -60,19 +61,6 @@ TipoDispositivoCrud = Crud.build( logger = logging.getLogger(BASE_DIR.name) -def get_integrations_view_names(): - result = [] - modules = sys.modules - for key, value in modules.items(): - if key.endswith('.views'): - for v in value.__dict__.values(): - if hasattr(v, '__bases__'): - for base in v.__bases__: - if base == IntegracaoTaView: - result.append(v) - return result - - def choice_models_in_extenal_views(): integrations_view_names = get_integrations_view_names() result = [(None, '-------------'), ] @@ -165,54 +153,35 @@ class IntegracaoTaView(TemplateView): object_id=item.pk, content_type=related_object_type) - tipo_ta = TipoTextoArticulado.objects.filter( - content_type=related_object_type) - ta_exists = bool(ta.exists()) - if not ta_exists: - ta = TextoArticulado() - tipo_ta = TipoTextoArticulado.objects.filter( - content_type=related_object_type)[:1] - if tipo_ta.exists(): - ta.tipo_ta = tipo_ta[0] - ta.content_object = item - - ta.privacidade = ta_values.get('privacidade', STATUS_TA_EDITION) + if (ta_exists or + (request.user.has_perm( + 'compilacao.change_dispositivo_edicao_dinamica') and + ta_values.get('privacidade', STATUS_TA_EDITION + ) != STATUS_TA_PRIVATE) or + (request.user.has_perm( + 'compilacao.change_your_dispositivo_edicao_dinamica') and + ta_values.get('privacidade', STATUS_TA_EDITION + ) == STATUS_TA_PUBLIC)): + """ + o texto articulado será criado/atualizado se: + - texto articulado já foi criado. - ta.editing_locked = ta_values.get('editing_locked', False) - ta.editable_only_by_owners = ta_values.get( - 'editable_only_by_owners', False) + - não foi criado e o usuário possui permissão para criar + desde que o texto não seja um texto privado pois a permissão + para criar textos privados é diferente. + - não foi criado e o usuário possui permissão para criar desde + que o texto seja privado e a permissão seja específica para + textos privados. + """ + pass else: - ta = ta[0] - - if not ta.data: - ta.data = getattr(item, map_fields['data'] - if map_fields['data'] else 'xxx', - datetime.now()) - if not ta.data: - ta.data = datetime.now() - - ta.ementa = getattr( - item, map_fields['ementa'] - if map_fields['ementa'] else 'xxx', _( - 'Integração com %s sem ementa.') % item) - - 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)))) - - ta.ano = getattr(item, map_fields['ano'] - if map_fields['ano'] else 'xxx', datetime.now().year) - - ta.save() + messages.info(request, _('%s não possui %s.') % ( + item, TextoArticulado._meta.verbose_name)) + return redirect('/message') + + ta = TextoArticulado.update_or_create(self, item) if not ta_exists: if ta.editable_only_by_owners and\ diff --git a/sapl/legacy_migration_settings.py b/sapl/legacy_migration_settings.py index aef9cc90b..0aab3baa9 100644 --- a/sapl/legacy_migration_settings.py +++ b/sapl/legacy_migration_settings.py @@ -1,19 +1,30 @@ -# Settings for data migration from mysql legacy to new postgres database +import os + +from decouple import Config, RepositoryEnv, AutoConfig +from dj_database_url import parse as db_url from .settings import * # flake8: noqa + +config = AutoConfig() +config.config = Config(RepositoryEnv(os.path.abspath('sapl/legacy/.env'))) + + INSTALLED_APPS += ( 'sapl.legacy', # legacy reversed model definitions ) -DATABASES['legacy'] = { +DATABASES['legacy'] = config('DATABASE_URL', cast=db_url,) + +"""DATABASES['legacy'] = { 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'sapl25', + 'NAME': 'legacy_interlegis', 'USER': 'root', - 'PASSWORD': 'admin', - 'HOST': 'localhost', # Or an IP Address that your DB is hosted on + 'PASSWORD': '', + 'HOST': '', # Or an IP Address that your DB is hosted on 'PORT': '3306', } +""" DATABASE_ROUTERS = ['sapl.legacy.router.LegacyRouter', ] diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index aedb90532..e6c6f1260 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -22,7 +22,7 @@ import django_filters from sapl.base.models import Autor from sapl.comissoes.models import Comissao from sapl.compilacao.models import STATUS_TA_PRIVATE,\ - STATUS_TA_IMMUTABLE_PUBLIC, TextoArticulado + STATUS_TA_IMMUTABLE_PUBLIC, TextoArticulado, STATUS_TA_PUBLIC from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column, to_row) from sapl.materia.models import TipoProposicao, MateriaLegislativa,\ @@ -472,7 +472,7 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet): ementa = django_filters.CharFilter(lookup_expr='icontains') em_tramitacao = django_filters.ChoiceFilter(required=False, - label=u'Ano da Matéria', + label=u'Em tramitação', choices=em_tramitacao) o = MateriaPesquisaOrderingFilter() @@ -1212,14 +1212,10 @@ class ConfirmarProposicaoForm(ProposicaoForm): if proposicao.texto_articulado.exists(): ta = proposicao.texto_articulado.first() - - ta.id = None - ta.content_object = materia - ta.save() - - pass - # FIXME - gerar texto_articulado da materia com base na prop. - # materia.texto_articulo = proposicao.texto_articulado + ta_materia = ta.clone_for(materia) + ta_materia.editing_locked = True + ta_materia.privacidade = STATUS_TA_IMMUTABLE_PUBLIC + ta_materia.save() self.instance.results['messages']['success'].append(_( 'Matéria Legislativa registrada com sucesso (%s)' diff --git a/sapl/templates/sessao/adicionar_varias_materias_expediente.html b/sapl/templates/sessao/adicionar_varias_materias_expediente.html index 8a56acdc7..697a809db 100644 --- a/sapl/templates/sessao/adicionar_varias_materias_expediente.html +++ b/sapl/templates/sessao/adicionar_varias_materias_expediente.html @@ -52,7 +52,7 @@ {% for m in page_obj %} - + {{m.tipo.sigla}} {{m.numero}}/{{m.ano}} - {{m.tipo}}
Autores: {% for a in m.autoria_set.all %} diff --git a/sapl/urls.py b/sapl/urls.py index ee512b31f..1869e760c 100644 --- a/sapl/urls.py +++ b/sapl/urls.py @@ -36,6 +36,7 @@ import sapl.sessao.urls urlpatterns = [ url(r'^$', TemplateView.as_view(template_name='index.html')), + url(r'^message$', TemplateView.as_view(template_name='base.html')), url(r'^admin/', include(admin.site.urls)), url(r'', include(sapl.comissoes.urls)),