diff --git a/Dockerfile b/Dockerfile index c192ccab4..693230025 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM alpine:3.5 ENV BUILD_PACKAGES postgresql-dev graphviz-dev graphviz build-base git pkgconfig \ python3-dev libxml2-dev jpeg-dev libressl-dev libffi-dev libxslt-dev nodejs py3-lxml \ -py3-magic postgresql-client poppler-utils vim +py3-magic postgresql-client poppler-utils antiword vim RUN apk --update add fontconfig ttf-dejavu && fc-cache -fv diff --git a/create_admin.py b/create_admin.py old mode 100644 new mode 100755 index bc8aa09e9..f03bbd7c8 --- a/create_admin.py +++ b/create_admin.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python import os import sys diff --git a/docker-compose.yml b/docker-compose.yml index 3afd6dc62..97c5cb011 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ sapldb: ports: - "5432:5432" sapl: - image: interlegis/sapl:3.1.52 + image: interlegis/sapl:3.1.66 restart: always environment: ADMIN_PASSWORD: interlegis diff --git a/docs/instalacao31.rst b/docs/instalacao31.rst index f12b92d9c..0b159b91d 100644 --- a/docs/instalacao31.rst +++ b/docs/instalacao31.rst @@ -28,7 +28,7 @@ Instalar as seguintes dependências do sistema:: pkg-config postgresql postgresql-contrib pgadmin3 python-psycopg2 \ software-properties-common build-essential libxml2-dev libjpeg-dev \ libmysqlclient-dev libssl-dev libffi-dev libxslt1-dev python3-setuptools \ - python3-pip curl poppler-utils default-jre + python3-pip curl poppler-utils antiword default-jre sudo -i curl -sL https://deb.nodesource.com/setup_6.x | bash - @@ -74,6 +74,12 @@ Clonar o projeto do github, ou fazer um fork e depois clonar * Para apenas clonar do repositório do Interlegis:: cd /var/interlegis + + git clone -b 3.1.x --single-branch git://github.com/interlegis/sapl + + O comando acima irá clonar a última versão estável do SAPL (3.1.x) + Para clonar todo o repositório utilize o comando abaixo: + git clone git://github.com/interlegis/sapl * Para fazer um fork e depois clonar, siga as instruções em https://help.github.com/articles/fork-a-repo que basicamente são: @@ -187,9 +193,9 @@ Copie a chave que aparecerá, edite o arquivo .env e altere o valor do parâmetr * Subir o servidor do django:: ./manage.py runserver 0.0.0.0:8001 - + * Compilar os arquivos de estilização:: - + ./manage.py compilescss ./manage.py collectstatic @@ -202,7 +208,7 @@ 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 + python3 manage.py createsuperuser * `Os perfis semânticos do SAPL `_ são fixos e atualizados a cada execução do comando:: @@ -211,7 +217,7 @@ Instruções para criação do super usuário e de usuários de testes * 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. * Para criar os usuários de teste, deve-se seguir os seguintes passos:: - + ./manage.py shell_plus from sapl.rules.apps import cria_usuarios_padrao cria_usuarios_padrao() diff --git a/sapl/api/forms.py b/sapl/api/forms.py index 2ee441d34..94bc80de4 100644 --- a/sapl/api/forms.py +++ b/sapl/api/forms.py @@ -7,6 +7,7 @@ from django_filters.filters import DateFilter, MethodFilter, ModelChoiceFilter from rest_framework import serializers from rest_framework.compat import django_filters from rest_framework.filters import FilterSet + from sapl.base.models import Autor, TipoAutor from sapl.parlamentares.models import Legislatura from sapl.utils import generic_relations_for_model diff --git a/sapl/api/serializers.py b/sapl/api/serializers.py index 9e08ea991..9219bf0e4 100644 --- a/sapl/api/serializers.py +++ b/sapl/api/serializers.py @@ -1,4 +1,5 @@ from rest_framework import serializers + from sapl.base.models import Autor, CasaLegislativa from sapl.materia.models import MateriaLegislativa from sapl.sessao.models import OrdemDia, SessaoPlenaria diff --git a/sapl/api/urls.py b/sapl/api/urls.py index 005a71f5c..1f19e9d1f 100644 --- a/sapl/api/urls.py +++ b/sapl/api/urls.py @@ -1,6 +1,7 @@ from django.conf import settings from django.conf.urls import include, url from rest_framework.routers import DefaultRouter + from sapl.api.views import (AutoresPossiveisListView, AutoresProvaveisListView, AutorListView, MateriaLegislativaViewSet, ModelChoiceView, SessaoPlenariaViewSet) diff --git a/sapl/api/views.py b/sapl/api/views.py index 2adffa7e5..9853e6af0 100644 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -8,6 +8,7 @@ from rest_framework.mixins import ListModelMixin, RetrieveModelMixin from rest_framework.permissions import (AllowAny, IsAuthenticated, IsAuthenticatedOrReadOnly) from rest_framework.viewsets import GenericViewSet + from sapl.api.forms import (AutorChoiceFilterSet, AutoresPossiveisFilterSet, AutorSearchForFieldFilterSet) from sapl.api.serializers import (AutorChoiceSerializer, AutorSerializer, diff --git a/sapl/base/admin.py b/sapl/base/admin.py index 00f9e104b..88633b0fb 100644 --- a/sapl/base/admin.py +++ b/sapl/base/admin.py @@ -3,34 +3,14 @@ from django.core.urlresolvers import reverse from django.shortcuts import redirect from django.utils.translation import ugettext_lazy as _ from reversion.models import Revision -from sapl.base.models import ProblemaMigracao from sapl.utils import register_all_models_in_admin register_all_models_in_admin(__name__) -admin.site.unregister(ProblemaMigracao) - admin.site.site_title = 'Administração - SAPL' admin.site.site_header = 'Administração - SAPL' -@admin.register(ProblemaMigracao) -class ProblemaMigracaoAdmin(admin.ModelAdmin): - list_display = ["content_type", "object_id", "nome_campo", "problema", - "descricao", "get_url"] - - def get_url(self, obj): - - info = (obj.content_object._meta.app_label, - obj.content_object._meta.model_name) - endereco = reverse('admin:%s_%s_change' % info, - args=(obj.content_object.pk,)) - return "%s" % (endereco, endereco) - - get_url.short_description = "Endereço" - get_url.allow_tags = True - - class RevisionAdmin(admin.ModelAdmin): list_display = ('user', 'comment', 'date_created') search_fields = ('=user__username', '=user__email') diff --git a/sapl/base/forms.py b/sapl/base/forms.py index 1181650ee..03d158075 100644 --- a/sapl/base/forms.py +++ b/sapl/base/forms.py @@ -13,16 +13,17 @@ from django.db import models, transaction from django.forms import Form, ModelForm from django.utils.translation import ugettext_lazy as _ from django.utils.translation import string_concat + from sapl.base.models import Autor, TipoAutor from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column, to_row) from sapl.materia.models import MateriaLegislativa from sapl.sessao.models import SessaoPlenaria from sapl.settings import MAX_IMAGE_UPLOAD_SIZE -from sapl.utils import (RANGE_ANOS, ChoiceWithoutValidationField, - ImageThumbnailFileInput, RangeWidgetOverride, - autor_label, autor_modal, models_with_gr_for_model, - qs_override_django_filter) +from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, + ChoiceWithoutValidationField, ImageThumbnailFileInput, + RangeWidgetOverride, autor_label, autor_modal, + models_with_gr_for_model, qs_override_django_filter) from .models import AppConfig, CasaLegislativa @@ -41,6 +42,113 @@ STATUS_USER_CHOICE = [ ] +def get_roles(): + return [(g.id, g.name) for g in Group.objects.all().order_by('name')] + + +class UsuarioCreateForm(ModelForm): + + username = forms.CharField(required=True, label="Nome de usuário") + firstname = forms.CharField(required=True, label="Nome") + lastname = forms.CharField(required=True, label="Sobrenome") + password1 = forms.CharField(required=True, widget=forms.PasswordInput, label='Senha') + password2 = forms.CharField(required=True, widget=forms.PasswordInput, label='Confirmar senha') + user_active = forms.ChoiceField(required=False, choices=YES_NO_CHOICES, + label="Usuário ativo?", initial='True') + + #ROLES = [(g.id, g.name) for g in Group.objects.all().order_by('name')] + + roles = forms.MultipleChoiceField( + required=True, widget=forms.CheckboxSelectMultiple(), choices=get_roles) + + class Meta: + model = get_user_model() + fields = ['username', 'firstname', 'lastname', 'email', + 'password1', 'password2', 'user_active', 'roles'] + + def clean(self): + super(UsuarioCreateForm, self).clean() + + if not self.is_valid(): + return self.cleaned_data + + data = self.cleaned_data + if data['password1'] != data['password2']: + raise ValidationError('Senhas informadas são diferentes') + + def __init__(self, *args, **kwargs): + + super(UsuarioCreateForm, self).__init__(*args, **kwargs) + + row0 = to_row([('username', 12)]) + + row1 = to_row([('firstname', 6), + ('lastname', 6)]) + + row2 = to_row([('email', 6), + ('user_active', 6)]) + row3 = to_row( + [('password1', 6), + ('password2', 6)]) + + row4 = to_row([(form_actions(label='Confirmar'), 6)]) + + self.helper = FormHelper() + self.helper.layout = Layout( + row0, + row1, + row3, + row2, + 'roles', + row4) + + +class UsuarioEditForm(ModelForm): + # ROLES = [(g.id, g.name) for g in Group.objects.all().order_by('name')] + + ROLES = [] + + password1 = forms.CharField(required=False, widget=forms.PasswordInput, label='Senha') + password2 = forms.CharField(required=False, widget=forms.PasswordInput, label='Confirmar senha') + user_active = forms.ChoiceField(choices=YES_NO_CHOICES, required=True, + label="Usuário ativo?", initial='True') + roles = forms.MultipleChoiceField( + required=True, widget=forms.CheckboxSelectMultiple(), choices=get_roles) + + class Meta: + model = get_user_model() + fields = ['email', 'password1', 'password2', 'user_active', 'roles'] + + def __init__(self, *args, **kwargs): + + super(UsuarioEditForm, self).__init__(*args, **kwargs) + + row1 = to_row([('email', 6), + ('user_active', 6)]) + row2 = to_row( + [('password1', 6), + ('password2', 6)]) + + row3 = to_row([(form_actions(label='Salvar Alterações'), 6)]) + + self.helper = FormHelper() + self.helper.layout = Layout( + row1, + row2, + 'roles', + row3) + + def clean(self): + super(UsuarioEditForm, self).clean() + + if not self.is_valid(): + return self.cleaned_data + + data = self.cleaned_data + if data['password1'] and data['password1'] != data['password2']: + raise ValidationError('Senhas informadas são diferentes') + + class TipoAutorForm(ModelForm): class Meta: @@ -206,6 +314,9 @@ class AutorForm(ModelForm): def clean(self): super(AutorForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + User = get_user_model() cd = self.cleaned_data @@ -466,7 +577,47 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet): self.form.helper = FormHelper() self.form.helper.form_method = 'GET' self.form.helper.layout = Layout( - Fieldset(_('Histórico de Tramita'), + Fieldset(_('Histórico de Tramitação'), + row1, row2, + form_actions(label='Pesquisar')) + ) + + +class RelatorioDataFimPrazoTramitacaoFilterSet(django_filters.FilterSet): + + filter_overrides = {models.DateField: { + 'filter_class': django_filters.DateFromToRangeFilter, + 'extra': lambda f: { + 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), + 'widget': RangeWidgetOverride} + }} + + @property + def qs(self): + parent = super(RelatorioDataFimPrazoTramitacaoFilterSet, self).qs + return parent.distinct().order_by('-ano', 'tipo', 'numero') + + class Meta: + model = MateriaLegislativa + fields = ['tipo', 'tramitacao__unidade_tramitacao_local', + 'tramitacao__status', 'tramitacao__data_fim_prazo'] + + def __init__(self, *args, **kwargs): + super(RelatorioDataFimPrazoTramitacaoFilterSet, self).__init__( + *args, **kwargs) + + self.filters['tipo'].label = 'Tipo de Matéria' + + row1 = to_row([('tramitacao__data_fim_prazo', 12)]) + row2 = to_row( + [('tipo', 4), + ('tramitacao__unidade_tramitacao_local', 4), + ('tramitacao__status', 4)]) + + self.form.helper = FormHelper() + self.form.helper.form_method = 'GET' + self.form.helper.layout = Layout( + Fieldset(_('Tramitações por fim de prazo'), row1, row2, form_actions(label='Pesquisar')) ) @@ -686,6 +837,9 @@ class RecuperarSenhaForm(PasswordResetForm): def clean(self): super(RecuperarSenhaForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + email_existente = User.objects.filter( email=self.data['email']).exists() @@ -747,6 +901,9 @@ class AlterarSenhaForm(Form): def clean(self): super(AlterarSenhaForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + data = self.cleaned_data new_password1 = data['new_password1'] diff --git a/sapl/base/migrations/0016_auto_20180326_1840.py b/sapl/base/migrations/0016_auto_20180326_1840.py new file mode 100644 index 000000000..870b58dc8 --- /dev/null +++ b/sapl/base/migrations/0016_auto_20180326_1840.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-26 21:40 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0015_appconfig_receber_recibo_proposicao'), + ] + + operations = [ + migrations.RemoveField( + model_name='argumento', + name='constraint', + ), + migrations.RemoveField( + model_name='problemamigracao', + name='content_type', + ), + migrations.DeleteModel( + name='Argumento', + ), + migrations.DeleteModel( + name='Constraint', + ), + migrations.DeleteModel( + name='ProblemaMigracao', + ), + ] diff --git a/sapl/base/models.py b/sapl/base/models.py index 3a80e4d7b..d2a6035a8 100644 --- a/sapl/base/models.py +++ b/sapl/base/models.py @@ -5,7 +5,6 @@ from django.db import models from django.db.models.signals import post_migrate from django.db.utils import DEFAULT_DB_ALIAS from django.utils.translation import ugettext_lazy as _ - from sapl.utils import (LISTA_DE_UFS, YES_NO_CHOICES, get_settings_auth_user_model, models_with_gr_for_model) @@ -59,53 +58,6 @@ class CasaLegislativa(models.Model): 'municipio': self.municipio} -@reversion.register() -class ProblemaMigracao(models.Model): - content_type = models.ForeignKey(ContentType, - verbose_name=_('Tipo de Content')) - object_id = models.PositiveIntegerField(verbose_name=_('ID do Objeto')) - content_object = GenericForeignKey('content_type', 'object_id') - nome_campo = models.CharField(max_length=100, - blank=True, - verbose_name=_('Nome do(s) Campo(s)')) - problema = models.CharField(max_length=300, verbose_name=_('Problema')) - descricao = models.CharField(max_length=300, verbose_name=_('Descrição')) - eh_stub = models.BooleanField(verbose_name=_('É stub?')) - critico = models.BooleanField( - default=False, verbose_name=_('Crítico')) - - class Meta: - verbose_name = _('Problema na Migração') - verbose_name_plural = _('Problemas na Migração') - - -@reversion.register() -class Constraint(models.Model): - nome_tabela = models.CharField( - max_length=50, verbose_name=_('Nome da tabela')) - nome_constraint = models.CharField( - max_length=100, verbose_name=_('Nome da constraint')) - nome_model = models.CharField( - max_length=50, verbose_name=_('Nome da model')) - tipo_constraint = models.CharField( - max_length=50, verbose_name=_('Tipo da constraint')) - - class Meta: - verbose_name = _('Constraint removida') - verbose_name_plural = _('Constraints removidas') - - -@reversion.register() -class Argumento(models.Model): - constraint = models.ForeignKey(Constraint) - argumento = models.CharField( - max_length=50, verbose_name=_('Argumento')) - - class Meta: - verbose_name = _('Argumento da constraint') - verbose_name_plural = _('Argumentos da constraint') - - @reversion.register() class AppConfig(models.Model): diff --git a/sapl/base/search_indexes.py b/sapl/base/search_indexes.py index 3deb42eac..f685cb002 100644 --- a/sapl/base/search_indexes.py +++ b/sapl/base/search_indexes.py @@ -13,13 +13,14 @@ from haystack.constants import Indexable from haystack.fields import CharField from haystack.indexes import SearchIndex from haystack.utils import get_model_ct_tuple +from textract.exceptions import ExtensionNotSupported + from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC, STATUS_TA_PUBLIC, Dispositivo, TextoArticulado) from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa from sapl.norma.models import NormaJuridica from sapl.settings import BASE_DIR, SOLR_URL -from textract.exceptions import ExtensionNotSupported logger = logging.getLogger(BASE_DIR.name) diff --git a/sapl/base/templatetags/common_tags.py b/sapl/base/templatetags/common_tags.py index 00ab405cf..9e095f072 100644 --- a/sapl/base/templatetags/common_tags.py +++ b/sapl/base/templatetags/common_tags.py @@ -1,11 +1,12 @@ from compressor.utils import get_class from django import template +from django.template.defaultfilters import stringfilter + from sapl.base.models import AppConfig from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa from sapl.norma.models import NormaJuridica from sapl.parlamentares.models import Filiacao from sapl.utils import filiacao_data -from django.template.defaultfilters import stringfilter register = template.Library() diff --git a/sapl/base/templatetags/menus.py b/sapl/base/templatetags/menus.py index 34294fbd8..f90a6940f 100644 --- a/sapl/base/templatetags/menus.py +++ b/sapl/base/templatetags/menus.py @@ -1,14 +1,18 @@ +import yaml from django import template from django.core.urlresolvers import reverse from django.utils.translation import ugettext_lazy as _ -import yaml from sapl.utils import sapl_logger - register = template.Library() +@register.inclusion_tag('menus/menu.html', takes_context=True) +def menu(context, path=None): + return nav_run(context, path) + + @register.inclusion_tag('menus/subnav.html', takes_context=True) def subnav(context, path=None): return nav_run(context, path) diff --git a/sapl/base/tests/test_form.py b/sapl/base/tests/test_form.py index fcaecdaaf..35f1c82bf 100644 --- a/sapl/base/tests/test_form.py +++ b/sapl/base/tests/test_form.py @@ -1,5 +1,6 @@ import pytest from django.utils.translation import ugettext_lazy as _ + from sapl.base.forms import CasaLegislativaForm diff --git a/sapl/base/tests/teststub_urls.py b/sapl/base/tests/teststub_urls.py index 3d85e2d9b..9796768f2 100644 --- a/sapl/base/tests/teststub_urls.py +++ b/sapl/base/tests/teststub_urls.py @@ -1,5 +1,6 @@ from django.conf.urls import patterns, url from django.views.generic.base import TemplateView + from sapl.urls import urlpatterns as original_patterns ptrn = patterns('', diff --git a/sapl/base/urls.py b/sapl/base/urls.py index 143a89ba8..0f720071f 100644 --- a/sapl/base/urls.py +++ b/sapl/base/urls.py @@ -5,13 +5,16 @@ from django.contrib.auth.views import (password_reset, password_reset_complete, password_reset_confirm, password_reset_done) from django.views.generic.base import TemplateView + from sapl.base.views import AutorCrud, ConfirmarEmailView, TipoAutorCrud from sapl.settings import EMAIL_SEND_USER from .apps import AppConfig from .forms import LoginForm, NovaSenhaForm, RecuperarSenhaForm from .views import (AlterarSenha, AppConfigCrud, CasaLegislativaCrud, - HelpTopicView, RelatorioAtasView, + CreateUsuarioView, DeleteUsuarioView, EditUsuarioView, + HelpTopicView, ListarUsuarioView, RelatorioAtasView, + RelatorioDataFimPrazoTramitacaoView, RelatorioHistoricoTramitacaoView, RelatorioMateriasPorAnoAutorTipoView, RelatorioMateriasPorAutorView, @@ -20,6 +23,13 @@ from .views import (AlterarSenha, AppConfigCrud, CasaLegislativaCrud, app_name = AppConfig.name +admin_user = [ + url(r'^sistema/usuario/$', ListarUsuarioView.as_view(), name='user_list'), + url(r'^sistema/usuario/create$', CreateUsuarioView.as_view(), name='user_create'), + url(r'^sistema/usuario/(?P\d+)/edit$', EditUsuarioView.as_view(), name='user_edit'), + url(r'^sistema/usuario/(?P\d+)/delete$', DeleteUsuarioView.as_view(), name='user_delete') +] + alterar_senha = [ url(r'^sistema/alterar-senha/$', AlterarSenha.as_view(), @@ -83,6 +93,9 @@ urlpatterns = [ url(r'^sistema/relatorios/historico-tramitacoes$', RelatorioHistoricoTramitacaoView.as_view(), name='historico_tramitacoes'), + url(r'^sistema/relatorios/data-fim-prazo-tramitacoes$', + RelatorioDataFimPrazoTramitacaoView.as_view(), + name='data_fim_prazo_tramitacoes'), url(r'^sistema/relatorios/presenca$', RelatorioPresencaSessaoView.as_view(), name='presenca_sessao'), @@ -107,4 +120,4 @@ urlpatterns = [ url(r'^sistema/search/', SaplSearchView(), name='haystack_search'), -] + recuperar_senha + alterar_senha +] + recuperar_senha + alterar_senha + admin_user diff --git a/sapl/base/views.py b/sapl/base/views.py index f31ed1c58..4dbf50ae2 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -1,5 +1,6 @@ from django.conf import settings from django.contrib.auth import get_user_model, update_session_auth_hash +from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.models import Group from django.contrib.auth.tokens import default_token_generator from django.core.exceptions import ObjectDoesNotExist, PermissionDenied @@ -11,15 +12,17 @@ from django.template import TemplateDoesNotExist from django.template.loader import get_template 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.utils.translation import string_concat -from django.views.generic import FormView +from django.utils.translation import ugettext_lazy as _ +from django.views.generic import (CreateView, DeleteView, DetailView, FormView, + ListView, UpdateView) from django.views.generic.base import TemplateView from django_filters.views import FilterView from haystack.views import SearchView + from sapl.base.forms import AutorForm, AutorFormForAdmin, TipoAutorForm from sapl.base.models import Autor, TipoAutor -from sapl.crud.base import CrudAux +from sapl.crud.base import CrudAux, make_pagination from sapl.materia.models import (Autoria, MateriaLegislativa, TipoMateriaLegislativa) from sapl.sessao.models import (PresencaOrdemDia, SessaoPlenaria, @@ -29,11 +32,13 @@ from sapl.utils import (parlamentares_ativos, sapl_logger, from .forms import (AlterarSenhaForm, CasaLegislativaForm, ConfiguracoesAppForm, RelatorioAtasFilterSet, + RelatorioDataFimPrazoTramitacaoFilterSet, RelatorioHistoricoTramitacaoFilterSet, RelatorioMateriasPorAnoAutorTipoFilterSet, RelatorioMateriasPorAutorFilterSet, RelatorioMateriasTramitacaoilterSet, - RelatorioPresencaSessaoFilterSet) + RelatorioPresencaSessaoFilterSet, UsuarioCreateForm, + UsuarioEditForm) from .models import AppConfig, CasaLegislativa @@ -68,6 +73,8 @@ class TipoAutorCrud(CrudAux): return vn class ListView(CrudAux.ListView): + template_name = "base/tipoautor_list.html" + def get_queryset(self): qs = CrudAux.ListView.get_queryset(self) qs = qs.filter(content_type__isnull=True) @@ -82,11 +89,12 @@ class TipoAutorCrud(CrudAux): return context class TipoAutorMixin: + def dispatch(self, request, *args, **kwargs): object = self.get_object() if object.content_type: raise PermissionDenied() - return super().get(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) class UpdateView(TipoAutorMixin, CrudAux.UpdateView): pass @@ -358,6 +366,23 @@ class RelatorioHistoricoTramitacaoView(FilterView): return context +class RelatorioDataFimPrazoTramitacaoView(FilterView): + model = MateriaLegislativa + filterset_class = RelatorioDataFimPrazoTramitacaoFilterSet + template_name = 'base/RelatorioDataFimPrazoTramitacao_filter.html' + + def get_context_data(self, **kwargs): + context = super(RelatorioDataFimPrazoTramitacaoView, + self).get_context_data(**kwargs) + context['title'] = _('Fim de Prazo de Tramitações') + qr = self.request.GET.copy() + context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' + + context['show_results'] = show_results_filter_set(qr) + + return context + + class RelatorioMateriasTramitacaoView(FilterView): model = MateriaLegislativa filterset_class = RelatorioMateriasTramitacaoilterSet @@ -503,6 +528,119 @@ class RelatorioMateriasPorAutorView(FilterView): return context +class ListarUsuarioView(PermissionRequiredMixin, ListView): + model = get_user_model() + template_name = 'auth/user_list.html' + context_object_name = 'user_list' + permission_required = ('base.list_appconfig',) + paginate_by = 10 + + def get_queryset(self): + qs = super(ListarUsuarioView, self).get_queryset() + return qs.order_by('username') + + def get_context_data(self, **kwargs): + context = super(ListarUsuarioView, self).get_context_data(**kwargs) + paginator = context['paginator'] + page_obj = context['page_obj'] + context['page_range'] = make_pagination( + page_obj.number, paginator.num_pages) + context['NO_ENTRIES_MSG'] = 'Nenhum usuário cadastrado.' + return context + + +class CreateUsuarioView(PermissionRequiredMixin, CreateView): + model = get_user_model() + form_class = UsuarioCreateForm + success_message = 'Usuário criado com sucesso' + permission_required = ('base.add_appconfig',) + + def get_success_url(self): + return reverse('sapl.base:user_list') + + def form_valid(self, form): + + data = form.cleaned_data + + new_user = get_user_model().objects.create( + username=data['username'], email=data['email']) + new_user.first_name = data['firstname'] + new_user.last_name = data['lastname'] + new_user.set_password(data['password1']) + new_user.is_superuser = False + new_user.is_staff = False + new_user.save() + + groups = Group.objects.filter(id__in=data['roles']) + for g in groups: + g.user_set.add(new_user) + + return HttpResponseRedirect(self.get_success_url()) + + +class DeleteUsuarioView(PermissionRequiredMixin, DeleteView): + + model = get_user_model() + permission_required = ('base.delete_appconfig',) + + def get_success_url(self): + return reverse('sapl.base:user_list') + + def get(self, request, *args, **kwargs): + return self.post(request, *args, **kwargs) + + def get_queryset(self): + qs = super(DeleteUsuarioView, self).get_queryset() + return qs.filter(id=self.kwargs['pk']) + + +class EditUsuarioView(PermissionRequiredMixin, UpdateView): + model = get_user_model() + form_class = UsuarioEditForm + success_message = 'Usuário editado com sucesso' + permission_required = ('base.change_appconfig',) + + def get_success_url(self): + return reverse('sapl.base:user_list') + + def get_initial(self): + initial = super(EditUsuarioView, self).get_initial() + + user = get_user_model().objects.get(id=self.kwargs['pk']) + roles = [str(g.id) for g in user.groups.all()] + initial['roles'] = roles + initial['user_active'] = user.is_active + + return initial + + def form_valid(self, form): + + user = form.save(commit=False) + data = form.cleaned_data + + # new_user.first_name = data['firstname'] + # new_user.last_name = data['lastname'] + + if data['password1']: + user.set_password(data['password1']) + + if data['user_active'] == 'True' and not user.is_active: + user.is_active = True + elif data['user_active'] == 'False' and user.is_active: + user.is_active = False + + user.save() + + for g in user.groups.all(): + g.user_set.remove(user) + + groups = Group.objects.filter(id__in=data['roles']) + for g in groups: + g.user_set.add(user) + + return super(EditUsuarioView, self).form_valid(form) + + class CasaLegislativaCrud(CrudAux): model = CasaLegislativa @@ -547,13 +685,8 @@ class AppConfigCrud(CrudAux): class BaseMixin(CrudAux.BaseMixin): form_class = ConfiguracoesAppForm - @property - def list_url(self): - return '' - - @property - def create_url(self): - return '' + list_url = '' + create_url = '' class CreateView(CrudAux.CreateView): diff --git a/sapl/comissoes/forms.py b/sapl/comissoes/forms.py index 2ce7dcfaa..abcce24f7 100644 --- a/sapl/comissoes/forms.py +++ b/sapl/comissoes/forms.py @@ -3,9 +3,12 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db import transaction from django.db.models import Q +from django.forms import ModelForm from django.utils.translation import ugettext_lazy as _ + from sapl.base.models import Autor, TipoAutor -from sapl.comissoes.models import Comissao, Composicao, Participacao +from sapl.comissoes.models import (Comissao, Composicao, DocumentoAcessorio, + Participacao, Reuniao) from sapl.parlamentares.models import Legislatura, Mandato, Parlamentar @@ -15,6 +18,7 @@ class ParticipacaoCreateForm(forms.ModelForm): class Meta: model = Participacao + fields = '__all__' exclude = ['composicao'] def __init__(self, user=None, **kwargs): @@ -48,6 +52,21 @@ class ParticipacaoCreateForm(forms.ModelForm): qs = Parlamentar.objects.filter(id__in=ids) self.fields['parlamentar'].queryset = qs + + def clean(self): + cleaned_data = super(ParticipacaoCreateForm, self).clean() + + if not self.is_valid(): + return cleaned_data + + composicao = Composicao.objects.get(id=self.initial['parent_pk']) + cargos_unicos = [c.cargo.nome for c in composicao.participacao_set.filter(cargo__unico=True)] + + if cleaned_data['cargo'].nome in cargos_unicos: + msg = _('Este cargo é único para esta Comissão.') + raise ValidationError(msg) + + def create_participacao(self): composicao = Composicao.objects.get(id=self.initial['parent_pk']) data_inicio_comissao = composicao.periodo.data_inicio @@ -61,15 +80,12 @@ class ParticipacaoCreateForm(forms.ModelForm): qs = q1 | q2 | q3 return qs - def clean(self): - super(ParticipacaoCreateForm, self).clean() - return self.cleaned_data - def verifica(self): composicao = Composicao.objects.get(id=self.initial['parent_pk']) participantes = composicao.participacao_set.all() participantes_id = [p.parlamentar.id for p in participantes] - parlamentares = Parlamentar.objects.all().exclude(id__in=participantes_id).order_by('nome_completo') + parlamentares = Parlamentar.objects.all().exclude( + id__in=participantes_id).order_by('nome_completo') parlamentares = [p for p in parlamentares if p.ativo] lista = [] @@ -121,6 +137,9 @@ class ComissaoForm(forms.ModelForm): def clean(self): super(ComissaoForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + if self.cleaned_data['data_extincao']: if (self.cleaned_data['data_extincao'] < self.cleaned_data['data_criacao']): @@ -142,3 +161,62 @@ class ComissaoForm(forms.ModelForm): nome=nome ) return comissao + + +class ReuniaoForm(ModelForm): + + comissao = forms.ModelChoiceField(queryset=Comissao.objects.all(), + widget=forms.HiddenInput()) + + class Meta: + model = Reuniao + exclude = ['cod_andamento_reuniao'] + + def clean(self): + super(ReuniaoForm, self).clean() + + if not self.is_valid(): + return self.cleaned_data + + if self.cleaned_data['hora_fim']: + if (self.cleaned_data['hora_fim'] < + self.cleaned_data['hora_inicio']): + msg = _('A hora de término da reunião não pode ser menor que a de início') + raise ValidationError(msg) + return self.cleaned_data + +class DocumentoAcessorioCreateForm(forms.ModelForm): + + parent_pk = forms.CharField(required=False) # widget=forms.HiddenInput()) + + class Meta: + model = DocumentoAcessorio + exclude = ['reuniao'] + + def __init__(self, user=None, **kwargs): + super(DocumentoAcessorioCreateForm, self).__init__(**kwargs) + + if self.instance: + reuniao = Reuniao.objects.get(id=self.initial['parent_pk']) + comissao = reuniao.comissao + comissao_pk = comissao.id + documentos = reuniao.documentoacessorio_set.all() + return self.create_documentoacessorio() + + + def create_documentoacessorio(self): + reuniao = Reuniao.objects.get(id=self.initial['parent_pk']) + + +class DocumentoAcessorioEditForm(forms.ModelForm): + + parent_pk = forms.CharField(required=False) # widget=forms.HiddenInput()) + + class Meta: + model = DocumentoAcessorio + fields = ['nome', 'data', 'autor', 'ementa', + 'indexacao', 'arquivo'] + + def __init__(self, user=None, **kwargs): + super(DocumentoAcessorioEditForm, self).__init__(**kwargs) + diff --git a/sapl/comissoes/migrations/0003_reuniao.py b/sapl/comissoes/migrations/0003_reuniao.py new file mode 100644 index 000000000..acdf8bf76 --- /dev/null +++ b/sapl/comissoes/migrations/0003_reuniao.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2017-11-23 13:07 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import sapl.comissoes.models +import sapl.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0002_auto_20170809_1236'), + ] + + operations = [ + migrations.CreateModel( + name='Reuniao', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('numero', models.PositiveIntegerField(verbose_name='Número')), + ('nome', models.CharField(max_length=100, verbose_name='Nome da Reunião')), + ('tema', models.CharField(max_length=100, verbose_name='Tema da Reunião')), + ('data', models.DateField(verbose_name='Data')), + ('hora_inicio', models.CharField(max_length=5, verbose_name='Horário (hh:mm)')), + ('hora_fim', models.CharField(max_length=5, verbose_name='Horário (hh:mm)')), + ('local_reuniao', models.CharField(blank=True, max_length=100, verbose_name='Local Reunião')), + ('observacao', models.CharField(blank=True, max_length=150, verbose_name='Observação')), + ('url_audio', models.URLField(blank=True, max_length=150, verbose_name='URL Arquivo Áudio (Formatos MP3 / AAC)')), + ('url_video', models.URLField(blank=True, max_length=150, verbose_name='URL Arquivo Vídeo (Formatos MP4 / FLV / WebM)')), + ('upload_pauta', models.FileField(blank=True, null=True, upload_to=sapl.comissoes.models.pauta_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Pauta da Reunião')), + ('upload_ata', models.FileField(blank=True, null=True, upload_to=sapl.comissoes.models.ata_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Ata da Reunião')), + ('upload_anexo', models.FileField(blank=True, null=True, upload_to=sapl.comissoes.models.anexo_upload_path, verbose_name='Anexo da Reunião')), + ('comissao', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='comissoes.Comissao', verbose_name='Comissão')), + ('periodo', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='comissoes.Periodo', verbose_name='Periodo da Composicão da Comissão')), + ('tipo', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='comissoes.TipoComissao', verbose_name='Tipo')), + ], + options={ + 'verbose_name': 'Reunião de Comissão', + 'verbose_name_plural': 'Reuniões de Comissão', + }, + ), + ] diff --git a/sapl/comissoes/migrations/0005_merge.py b/sapl/comissoes/migrations/0005_merge.py new file mode 100644 index 000000000..f171eb151 --- /dev/null +++ b/sapl/comissoes/migrations/0005_merge.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-02-26 10:41 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0004_auto_20180102_1652'), + ('comissoes', '0003_reuniao'), + ] + + operations = [ + ] diff --git a/sapl/comissoes/migrations/0006_auto_20180227_0842.py b/sapl/comissoes/migrations/0006_auto_20180227_0842.py new file mode 100644 index 000000000..ebe3e0029 --- /dev/null +++ b/sapl/comissoes/migrations/0006_auto_20180227_0842.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-02-27 11:42 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0005_merge'), + ] + + operations = [ + migrations.AlterField( + model_name='reuniao', + name='url_audio', + field=models.URLField(blank=True, max_length=150, null=True, verbose_name='URL Arquivo Áudio (Formatos MP3 / AAC)'), + ), + migrations.AlterField( + model_name='reuniao', + name='url_video', + field=models.URLField(blank=True, max_length=150, null=True, verbose_name='URL Arquivo Vídeo (Formatos MP4 / FLV / WebM)'), + ), + ] diff --git a/sapl/comissoes/migrations/0007_auto_20180227_1025.py b/sapl/comissoes/migrations/0007_auto_20180227_1025.py new file mode 100644 index 000000000..fb13ba123 --- /dev/null +++ b/sapl/comissoes/migrations/0007_auto_20180227_1025.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-02-27 13:25 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0006_auto_20180227_0842'), + ] + + operations = [ + migrations.AlterField( + model_name='reuniao', + name='hora_fim', + field=models.CharField(max_length=5, verbose_name='Horário de Término (hh:mm)'), + ), + migrations.AlterField( + model_name='reuniao', + name='hora_inicio', + field=models.CharField(max_length=5, verbose_name='Horário de Início (hh:mm)'), + ), + ] diff --git a/sapl/comissoes/migrations/0008_auto_20180227_1111.py b/sapl/comissoes/migrations/0008_auto_20180227_1111.py new file mode 100644 index 000000000..478576a1b --- /dev/null +++ b/sapl/comissoes/migrations/0008_auto_20180227_1111.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-02-27 14:11 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0007_auto_20180227_1025'), + ] + + operations = [ + migrations.AlterField( + model_name='reuniao', + name='url_audio', + field=models.URLField(blank=True, max_length=150, verbose_name='URL Arquivo Áudio (Formatos MP3 / AAC)'), + ), + migrations.AlterField( + model_name='reuniao', + name='url_video', + field=models.URLField(blank=True, max_length=150, verbose_name='URL Arquivo Vídeo (Formatos MP4 / FLV / WebM)'), + ), + ] diff --git a/sapl/comissoes/migrations/0009_auto_20180301_1011.py b/sapl/comissoes/migrations/0009_auto_20180301_1011.py new file mode 100644 index 000000000..db559de75 --- /dev/null +++ b/sapl/comissoes/migrations/0009_auto_20180301_1011.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-01 13:11 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0008_auto_20180227_1111'), + ] + + operations = [ + migrations.AlterField( + model_name='reuniao', + name='local_reuniao', + field=models.CharField(blank=True, max_length=100, verbose_name='Local da Reunião'), + ), + migrations.AlterField( + model_name='reuniao', + name='observacao', + field=models.TextField(blank=True, max_length=150, verbose_name='Observação'), + ), + migrations.AlterField( + model_name='reuniao', + name='tipo', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='comissoes.TipoComissao', verbose_name='Tipo de Comissão'), + ), + migrations.AlterField( + model_name='reuniao', + name='url_audio', + field=models.URLField(blank=True, max_length=150, verbose_name='URL do Arquivo de Áudio (Formatos MP3 / AAC)'), + ), + migrations.AlterField( + model_name='reuniao', + name='url_video', + field=models.URLField(blank=True, max_length=150, verbose_name='URL do Arquivo de Vídeo (Formatos MP4 / FLV / WebM)'), + ), + ] diff --git a/sapl/comissoes/migrations/0010_auto_20180306_0918.py b/sapl/comissoes/migrations/0010_auto_20180306_0918.py new file mode 100644 index 000000000..b2dd48f87 --- /dev/null +++ b/sapl/comissoes/migrations/0010_auto_20180306_0918.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-06 12:18 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0009_auto_20180301_1011'), + ] + + operations = [ + migrations.AlterField( + model_name='reuniao', + name='hora_fim', + field=models.TimeField(verbose_name='Horário de Término (hh:mm)'), + ), + migrations.AlterField( + model_name='reuniao', + name='hora_inicio', + field=models.TimeField(verbose_name='Horário de Início (hh:mm)'), + ), + ] diff --git a/sapl/comissoes/migrations/0010_auto_20180307_1645.py b/sapl/comissoes/migrations/0010_auto_20180307_1645.py new file mode 100644 index 000000000..45b86bec4 --- /dev/null +++ b/sapl/comissoes/migrations/0010_auto_20180307_1645.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-07 19:45 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0009_auto_20180301_1011'), + ] + + operations = [ + migrations.RemoveField( + model_name='reuniao', + name='tipo', + ), + migrations.AlterField( + model_name='reuniao', + name='nome', + field=models.CharField(max_length=150, verbose_name='Nome da Reunião'), + ), + migrations.AlterField( + model_name='reuniao', + name='tema', + field=models.CharField(blank=True, max_length=150, verbose_name='Tema da Reunião'), + ), + ] diff --git a/sapl/comissoes/migrations/0011_merge.py b/sapl/comissoes/migrations/0011_merge.py new file mode 100644 index 000000000..a5a3c7b7a --- /dev/null +++ b/sapl/comissoes/migrations/0011_merge.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-09 10:27 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0010_auto_20180307_1645'), + ('comissoes', '0010_auto_20180306_0918'), + ] + + operations = [ + ] diff --git a/sapl/comissoes/migrations/0012_documentoacessorio.py b/sapl/comissoes/migrations/0012_documentoacessorio.py new file mode 100644 index 000000000..ed296ac21 --- /dev/null +++ b/sapl/comissoes/migrations/0012_documentoacessorio.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-09 12:38 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import sapl.comissoes.models +import sapl.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0011_merge'), + ] + + operations = [ + migrations.CreateModel( + name='DocumentoAcessorio', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nome', models.CharField(max_length=50, verbose_name='Nome')), + ('data', models.DateField(blank=True, default=None, null=True, verbose_name='Data')), + ('autor', models.CharField(blank=True, max_length=50, verbose_name='Autor')), + ('ementa', models.TextField(blank=True, verbose_name='Ementa')), + ('indexacao', models.TextField(blank=True)), + ('arquivo', models.FileField(blank=True, null=True, upload_to=sapl.comissoes.models.anexo_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Integral')), + ('data_ultima_atualizacao', models.DateTimeField(auto_now=True, null=True, verbose_name='Data')), + ('reuniao', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='documentoacessorio_set', to='comissoes.Reuniao')), + ], + options={ + 'verbose_name': 'Documento Acessório', + 'verbose_name_plural': 'Documentos Acessórios', + }, + ), + ] diff --git a/sapl/comissoes/migrations/0013_auto_20180312_1533.py b/sapl/comissoes/migrations/0013_auto_20180312_1533.py new file mode 100644 index 000000000..2210053ff --- /dev/null +++ b/sapl/comissoes/migrations/0013_auto_20180312_1533.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-12 18:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0012_documentoacessorio'), + ] + + operations = [ + migrations.AlterField( + model_name='documentoacessorio', + name='autor', + field=models.CharField(max_length=100, verbose_name='Autor'), + ), + ] diff --git a/sapl/comissoes/models.py b/sapl/comissoes/models.py index 078097cab..f0b1b8d8b 100644 --- a/sapl/comissoes/models.py +++ b/sapl/comissoes/models.py @@ -1,11 +1,12 @@ - import reversion from django.db import models from django.utils.translation import ugettext_lazy as _ from model_utils import Choices + from sapl.base.models import Autor from sapl.parlamentares.models import Parlamentar -from sapl.utils import YES_NO_CHOICES, SaplGenericRelation +from sapl.utils import (YES_NO_CHOICES, SaplGenericRelation, + restringe_tipos_de_arquivo_txt, texto_upload_path) @reversion.register() @@ -52,22 +53,18 @@ class Comissao(models.Model): secretario = models.CharField( max_length=30, blank=True, verbose_name=_('Secretário')) telefone_reuniao = models.CharField( - max_length=15, - blank=True, + max_length=15, blank=True, verbose_name=_('Tel. Sala Reunião')) endereco_secretaria = models.CharField( - max_length=100, - blank=True, + max_length=100, blank=True, verbose_name=_('Endereço Secretaria')) telefone_secretaria = models.CharField( - max_length=15, - blank=True, + max_length=15, blank=True, verbose_name=_('Tel. Secretaria')) fax_secretaria = models.CharField( max_length=15, blank=True, verbose_name=_('Fax Secretaria')) agenda_reuniao = models.CharField( - max_length=100, - blank=True, + max_length=100, blank=True, verbose_name=_('Data/Hora Reunião')) local_reuniao = models.CharField( max_length=100, blank=True, verbose_name=_('Local Reunião')) @@ -83,7 +80,6 @@ class Comissao(models.Model): default=False, choices=YES_NO_CHOICES, verbose_name=_('Comissão Ativa?')) - autor = SaplGenericRelation(Autor, related_query_name='comissao_set', fields_search=( @@ -170,8 +166,7 @@ class Participacao(models.Model): # ComposicaoComissao null=True, verbose_name=_('Data Desligamento')) motivo_desligamento = models.CharField( - max_length=150, - blank=True, + max_length=150, blank=True, verbose_name=_('Motivo Desligamento')) observacao = models.CharField( max_length=150, blank=True, verbose_name=_('Observação')) @@ -182,3 +177,165 @@ class Participacao(models.Model): # ComposicaoComissao def __str__(self): return '%s : %s' % (self.cargo, self.parlamentar) + + +def get_comissao_media_path(instance, subpath, filename): + return './sapl/comissao/%s/%s/%s' % (instance.numero, subpath, filename) + +def pauta_upload_path(instance, filename): + + return texto_upload_path(instance, filename, subpath='pauta', pk_first=True) + +def ata_upload_path(instance, filename): + return texto_upload_path(instance, filename, subpath='ata', pk_first=True) + +def anexo_upload_path(instance, filename): + return texto_upload_path(instance, filename, subpath='anexo', pk_first=True) + + +class Reuniao(models.Model): + periodo = models. ForeignKey( + Periodo, + on_delete=models.PROTECT, + verbose_name=_('Periodo da Composicão da Comissão')) + comissao = models.ForeignKey( + Comissao, + on_delete=models.PROTECT, + verbose_name=_('Comissão')) + numero = models.PositiveIntegerField(verbose_name=_('Número')) + nome = models.CharField( + max_length=150, verbose_name=_('Nome da Reunião')) + tema = models.CharField( + max_length=150, blank=True, verbose_name=_('Tema da Reunião')) + data = models.DateField(verbose_name=_('Data')) + hora_inicio = models.TimeField( + verbose_name=_('Horário de Início (hh:mm)')) + hora_fim = models.TimeField( + verbose_name=_('Horário de Término (hh:mm)')) + local_reuniao = models.CharField( + max_length=100, blank=True, verbose_name=_('Local da Reunião')) + observacao = models.TextField( + max_length=150, blank=True, verbose_name=_('Observação')) + url_audio = models.URLField( + max_length=150, blank=True, + verbose_name=_('URL do Arquivo de Áudio (Formatos MP3 / AAC)')) + url_video = models.URLField( + max_length=150, blank=True, + verbose_name=_('URL do Arquivo de Vídeo (Formatos MP4 / FLV / WebM)')) + upload_pauta = models.FileField( + blank=True, null=True, + upload_to=pauta_upload_path, + verbose_name=_('Pauta da Reunião'), + validators=[restringe_tipos_de_arquivo_txt]) + upload_ata = models.FileField( + blank=True, null=True, + upload_to=ata_upload_path, + verbose_name=_('Ata da Reunião'), + validators=[restringe_tipos_de_arquivo_txt]) + upload_anexo = models.FileField( + blank=True, null=True, + upload_to=anexo_upload_path, + verbose_name=_('Anexo da Reunião')) + + class Meta: + verbose_name = _('Reunião de Comissão') + verbose_name_plural = _('Reuniões de Comissão') + + def __str__(self): + return self.nome + + def delete(self, using=None, keep_parents=False): + if self.upload_pauta: + self.upload_pauta.delete() + + if self.upload_ata: + self.upload_ata.delete() + + if self.upload_anexo: + self.upload_anexo.delete() + + return models.Model.delete( + self, using=using, keep_parents=keep_parents) + + def save(self, force_insert=False, force_update=False, using=None, + update_fields=None): + + if not self.pk and (self.upload_pauta or self.upload_ata or + self.upload_anexo): + upload_pauta = self.upload_pauta + upload_ata = self.upload_ata + upload_anexo = self.upload_anexo + self.upload_pauta = None + self.upload_ata = None + self.upload_anexo = None + models.Model.save(self, force_insert=force_insert, + force_update=force_update, + using=using, + update_fields=update_fields) + + self.upload_pauta = upload_pauta + self.upload_ata = upload_ata + self.upload_anexo = upload_anexo + + return models.Model.save(self, force_insert=force_insert, + force_update=force_update, + using=using, + update_fields=update_fields) + + +@reversion.register() +class DocumentoAcessorio(models.Model): + reuniao = models.ForeignKey(Reuniao, + related_name='documentoacessorio_set', + on_delete=models.PROTECT) + nome = models.CharField(max_length=50, verbose_name=_('Nome')) + + data = models.DateField(blank=True, null=True, default=None, verbose_name=_('Data')) + autor = models.CharField( + max_length=100, verbose_name=_('Autor')) + ementa = models.TextField(blank=True, verbose_name=_('Ementa')) + indexacao = models.TextField(blank=True) + arquivo = models.FileField( + blank=True, + null=True, + upload_to=anexo_upload_path, + verbose_name=_('Texto Integral'), + validators=[restringe_tipos_de_arquivo_txt]) + + data_ultima_atualizacao = models.DateTimeField( + blank=True, null=True, + auto_now=True, + verbose_name=_('Data')) + + class Meta: + verbose_name = _('Documento Acessório') + verbose_name_plural = _('Documentos Acessórios') + + def __str__(self): + return _('%(nome)s por %(autor)s') % { + 'nome': self.nome, + 'autor': self.autor} + + def delete(self, using=None, keep_parents=False): + if self.arquivo: + self.arquivo.delete() + + return models.Model.delete( + self, using=using, keep_parents=keep_parents) + + def save(self, force_insert=False, force_update=False, using=None, + update_fields=None): + + if not self.pk and self.arquivo: + arquivo = self.arquivo + self.arquivo = None + models.Model.save(self, force_insert=force_insert, + force_update=force_update, + using=using, + update_fields=update_fields) + self.arquivo = arquivo + + return models.Model.save(self, force_insert=force_insert, + force_update=force_update, + using=using, + update_fields=update_fields) diff --git a/sapl/comissoes/tests/test_comissoes.py b/sapl/comissoes/tests/test_comissoes.py index 0b040b914..fee303192 100644 --- a/sapl/comissoes/tests/test_comissoes.py +++ b/sapl/comissoes/tests/test_comissoes.py @@ -1,6 +1,7 @@ import pytest from django.core.urlresolvers import reverse from model_mommy import mommy + from sapl.comissoes.models import Comissao, Composicao, Periodo, TipoComissao from sapl.parlamentares.models import Filiacao, Parlamentar, Partido diff --git a/sapl/comissoes/urls.py b/sapl/comissoes/urls.py index 128cb7647..72886f1f1 100644 --- a/sapl/comissoes/urls.py +++ b/sapl/comissoes/urls.py @@ -1,7 +1,7 @@ from django.conf.urls import include, url from sapl.comissoes.views import (CargoCrud, ComissaoCrud, ComposicaoCrud, - MateriasTramitacaoListView, ParticipacaoCrud, - PeriodoComposicaoCrud, TipoComissaoCrud) + DocumentoAcessorioCrud, MateriasTramitacaoListView, ParticipacaoCrud, + PeriodoComposicaoCrud, ReuniaoCrud, TipoComissaoCrud) from .apps import AppConfig @@ -10,7 +10,9 @@ app_name = AppConfig.name urlpatterns = [ url(r'^comissao/', include(ComissaoCrud.get_urls() + ComposicaoCrud.get_urls() + - ParticipacaoCrud.get_urls())), + ReuniaoCrud.get_urls() + + ParticipacaoCrud.get_urls() + + DocumentoAcessorioCrud.get_urls())), url(r'^comissao/(?P\d+)/materias-em-tramitacao$', MateriasTramitacaoListView.as_view(), name='materias_em_tramitacao'), diff --git a/sapl/comissoes/views.py b/sapl/comissoes/views.py index eb32b977a..b085eb83d 100644 --- a/sapl/comissoes/views.py +++ b/sapl/comissoes/views.py @@ -1,15 +1,27 @@ from django.core.urlresolvers import reverse from django.db.models import F +from django.http.response import HttpResponseRedirect from django.views.decorators.clickjacking import xframe_options_exempt from django.views.generic import ListView -from sapl.comissoes.forms import ParticipacaoCreateForm, ParticipacaoEditForm -from sapl.crud.base import RP_DETAIL, RP_LIST, Crud, CrudAux, MasterDetailCrud +from django.views.generic.base import RedirectView +from django.views.generic.detail import DetailView +from django.views.generic.edit import FormMixin + + +from sapl.base.models import AppConfig as AppsAppConfig +from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, + CrudAux, MasterDetailCrud, + PermissionRequiredForAppCrudMixin) +from sapl.comissoes.forms import (ComissaoForm, DocumentoAcessorioCreateForm, + DocumentoAcessorioEditForm, ParticipacaoCreateForm, + ParticipacaoEditForm, ReuniaoForm) from sapl.materia.models import MateriaLegislativa, Tramitacao -from .forms import ComissaoForm -from .models import (CargoComissao, Comissao, Composicao, Participacao, - Periodo, TipoComissao) + +from .models import (CargoComissao, Comissao, Composicao, DocumentoAcessorio, + Participacao, Periodo, TipoComissao, Reuniao) +from sapl.comissoes.apps import AppConfig def pegar_url_composicao(pk): @@ -18,6 +30,11 @@ def pegar_url_composicao(pk): url = reverse('sapl.comissoes:composicao_detail', kwargs={'pk': comp_pk}) return url +def pegar_url_reuniao(pk): + documentoacessorio = DocumentoAcessorio.objects.get(id=pk) + r_pk = documentoacessorio.reuniao.pk + url = reverse('sapl.comissoes:reuniao_detail', kwargs={'pk': r_pk}) + return url CargoCrud = CrudAux.build(CargoComissao, 'cargo_comissao') PeriodoComposicaoCrud = CrudAux.build(Periodo, 'periodo_composicao_comissao') @@ -55,7 +72,7 @@ class ParticipacaoCrud(MasterDetailCrud): composicao_pk = self.object.composicao.pk return '{}?pk={}'.format(reverse('sapl.comissoes:composicao_list', args=[composicao_comissao_pk]), - ) + composicao_pk) class ComposicaoCrud(MasterDetailCrud): @@ -136,3 +153,86 @@ class MateriasTramitacaoListView(ListView): MateriasTramitacaoListView, self).get_context_data(**kwargs) context['object'] = Comissao.objects.get(id=self.kwargs['pk']) return context + +class ReuniaoCrud(MasterDetailCrud): + model = Reuniao + parent_field = 'comissao' + model_set = 'documentoacessorio_set' + public = [RP_LIST, RP_DETAIL, ] + + class BaseMixin(MasterDetailCrud.BaseMixin): + list_field_names = [ 'nome', 'tema', 'data'] + + class ListView(MasterDetailCrud.ListView): + paginate_by = 10 + + def take_reuniao_pk(self): + try: + return int(self.request.GET['pk']) + except: + return 0 + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + reuniao_pk = self.take_reuniao_pk() + + if reuniao_pk == 0: + ultima_reuniao = list(context['reuniao_list']) + if len(ultima_reuniao) > 0: + ultimo = ultima_reuniao[-1] + context['reuniao_pk'] = ultimo.pk + else: + context['reuniao_pk'] = 0 + else: + context['reuniao_pk'] = reuniao_pk + + context['documentoacessorio_set'] = DocumentoAcessorio.objects.filter( + reuniao__pk=context['reuniao_pk'] + ).order_by('id') + return context + + class UpdateView(MasterDetailCrud.UpdateView): + form_class = ReuniaoForm + + def get_initial(self): + return {'comissao': self.object.comissao} + + class CreateView(MasterDetailCrud.CreateView): + form_class = ReuniaoForm + + def get_initial(self): + comissao = Comissao.objects.get(id=self.kwargs['pk']) + + return {'comissao': comissao} + + +class DocumentoAcessorioCrud(MasterDetailCrud): + model = DocumentoAcessorio + parent_field = 'reuniao__comissao' + public = [RP_DETAIL, ] + ListView = None + link_return_to_parent_field = True + + class BaseMixin(MasterDetailCrud.BaseMixin): + list_field_names = ['nome', 'tipo', 'data', 'autor', 'arquivo'] + + class CreateView(MasterDetailCrud.CreateView): + form_class = DocumentoAcessorioCreateForm + + def get_initial(self): + initial = super().get_initial() + initial['parent_pk'] = self.kwargs['pk'] + return initial + + class UpdateView(MasterDetailCrud.UpdateView): + layout_key = 'DocumentoAcessorioEdit' + form_class = DocumentoAcessorioEditForm + + class DeleteView(MasterDetailCrud.DeleteView): + def delete(self, *args, **kwargs): + obj = self.get_object() + obj.delete() + return HttpResponseRedirect( + reverse('sapl.comissoes:reuniao_detail', + kwargs={'pk': obj.reuniao.pk})) \ No newline at end of file diff --git a/sapl/compilacao/apps.py b/sapl/compilacao/apps.py index 85c8ba349..96bd10b87 100644 --- a/sapl/compilacao/apps.py +++ b/sapl/compilacao/apps.py @@ -1,8 +1,102 @@ +import logging + from django import apps +from django.conf import settings +from django.db import connection, models +from django.db.utils import DEFAULT_DB_ALIAS, IntegrityError from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import string_concat + +from sapl.settings import BASE_DIR + +logger = logging.getLogger(BASE_DIR.name) class AppConfig(apps.AppConfig): name = 'sapl.compilacao' label = 'compilacao' verbose_name = _('Compilação') + + @staticmethod + def import_pattern(): + + from sapl.compilacao.models import TipoTextoArticulado + from sapl.compilacao.utils import get_integrations_view_names + + from django.contrib.contenttypes.models import ContentType + from unipath import Path + + compilacao_app = Path(__file__).ancestor(1) + # 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] + + with connection.cursor() as cursor: + for line in lines: + line = line.strip() + if not line or line.startswith('#'): + continue + + try: + cursor.execute(line) + except IntegrityError as e: + if not settings.DEBUG: + logger.error( + string_concat( + _('Ocorreu erro na importação: '), + line, + str(e))) + except Exception as ee: + print(ee) + + integrations_view_names = get_integrations_view_names() + + def cria_sigla(verbose_name): + verbose_name = verbose_name.upper().split() + if len(verbose_name) == 1: + verbose_name = verbose_name[0] + sigla = '' + for letra in verbose_name: + if letra in 'BCDFGHJKLMNPQRSTVWXYZ': + sigla += letra + else: + sigla = ''.join([palavra[0] for palavra in verbose_name]) + return sigla[:3] + + for view in integrations_view_names: + try: + tipo = TipoTextoArticulado() + tipo.sigla = cria_sigla( + view.model._meta.verbose_name + if view.model._meta.verbose_name + else view.model._meta.model_name) + tipo.descricao = view.model._meta.verbose_name + tipo.content_type = ContentType.objects.get_by_natural_key( + view.model._meta.app_label, view.model._meta.model_name) + tipo.save() + except IntegrityError as e: + if not settings.DEBUG: + logger.error( + string_concat( + _('Ocorreu erro na criação tipo de ta: '), + str(e))) + + +def init_compilacao_base(app_config, verbosity=2, interactive=True, + using=DEFAULT_DB_ALIAS, **kwargs): + + if app_config != AppConfig and not isinstance(app_config, AppConfig): + return + from sapl.compilacao.models import TipoDispositivo + if not TipoDispositivo.objects.exists(): + + print('') + print(string_concat('\033[93m\033[1m', + _('Iniciando Textos Articulados...'), + '\033[0m')) + AppConfig.import_pattern() + + +models.signals.post_migrate.connect( + receiver=init_compilacao_base) diff --git a/sapl/compilacao/forms.py b/sapl/compilacao/forms.py index 0748b1423..dadf617b2 100644 --- a/sapl/compilacao/forms.py +++ b/sapl/compilacao/forms.py @@ -14,6 +14,7 @@ from django.forms.forms import Form from django.forms.models import ModelForm from django.template import defaultfilters from django.utils.translation import ugettext_lazy as _ + from sapl import utils from sapl.compilacao.models import (NOTAS_PUBLICIDADE_CHOICES, PARTICIPACAO_SOCIAL_CHOICES, Dispositivo, @@ -1176,6 +1177,11 @@ class DispositivoEdicaoAlteracaoForm(ModelForm): inst.dispositivo_atualizador)] def clean(self): + super(DispositivoEdicaoAlteracaoForm, self).clean() + + if not self.is_valid(): + return self.cleaned_data + """os cleans individuais do framework não puderam ser usados devido a última validação compor dois valores """ diff --git a/sapl/compilacao/migrations/0005_auto_20180319_1041.py b/sapl/compilacao/migrations/0005_auto_20180319_1041.py new file mode 100644 index 000000000..fdd69bc8a --- /dev/null +++ b/sapl/compilacao/migrations/0005_auto_20180319_1041.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-19 13:41 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +def adjust_dispositivo_raiz(apps, schema_editor): + Dispositivo = apps.get_model('compilacao', 'Dispositivo') + + articulacoes = Dispositivo.objects.filter( + dispositivo_pai__isnull=True) + + def adicionar_raiz_aos_filhos(raiz, dispositivo): + for d in dispositivo.dispositivos_filhos_set.all(): + d.dispositivo_raiz = raiz + d.save() + adicionar_raiz_aos_filhos(raiz, d) + + for artic in articulacoes: + adicionar_raiz_aos_filhos(artic, artic) + + +class Migration(migrations.Migration): + + dependencies = [ + ('compilacao', '0004_auto_20171031_1327'), + ] + + operations = [ + migrations.AddField( + model_name='dispositivo', + name='dispositivo_raiz', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, + related_name='nodes', to='compilacao.Dispositivo', verbose_name='Dispositivo Raiz'), + ), + migrations.AlterUniqueTogether( + name='dispositivo', + unique_together=set([('ta', 'dispositivo0', 'dispositivo1', 'dispositivo2', 'dispositivo3', 'dispositivo4', 'dispositivo5', + 'tipo_dispositivo', 'dispositivo_raiz', 'dispositivo_pai', 'dispositivo_atualizador', 'ta_publicado', 'publicacao'), ('ta', 'ordem')]), + ), + migrations.RunPython(adjust_dispositivo_raiz), + ] diff --git a/sapl/compilacao/migrations/0006_auto_20180321_1054.py b/sapl/compilacao/migrations/0006_auto_20180321_1054.py new file mode 100644 index 000000000..12c6bec3b --- /dev/null +++ b/sapl/compilacao/migrations/0006_auto_20180321_1054.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-21 13:54 +from __future__ import unicode_literals + +from django.db import migrations, models + + +def adjust_contagem_continua(apps, schema_editor): + Dispositivo = apps.get_model('compilacao', 'Dispositivo') + + Dispositivo.objects.filter( + tipo_dispositivo__contagem_continua=True + ).update(contagem_continua=True) + + +class Migration(migrations.Migration): + + dependencies = [ + ('compilacao', '0005_auto_20180319_1041'), + ] + + operations = [ + migrations.AddField( + model_name='dispositivo', + name='contagem_continua', + field=models.BooleanField(choices=[( + True, 'Sim'), (False, 'Não')], default=False, verbose_name='Contagem contínua'), + ), + migrations.AlterUniqueTogether( + name='dispositivo', + unique_together=set([('ta', 'ordem'), ('ta', 'dispositivo0', 'dispositivo1', 'dispositivo2', 'dispositivo3', 'dispositivo4', 'dispositivo5', 'tipo_dispositivo', 'contagem_continua', 'dispositivo_raiz', 'dispositivo_atualizador', 'ta_publicado', + 'publicacao'), ('ta', 'dispositivo0', 'dispositivo1', 'dispositivo2', 'dispositivo3', 'dispositivo4', 'dispositivo5', 'tipo_dispositivo', 'dispositivo_raiz', 'dispositivo_pai', 'dispositivo_atualizador', 'ta_publicado', 'publicacao')]), + ), + migrations.RunPython(adjust_contagem_continua), + ] diff --git a/sapl/compilacao/models.py b/sapl/compilacao/models.py index ec420c135..f677cf97a 100644 --- a/sapl/compilacao/models.py +++ b/sapl/compilacao/models.py @@ -978,6 +978,11 @@ class Dispositivo(BaseModel, TimestampedMixin): blank=True, null=True, default=None, related_name='dispositivos_filhos_set', verbose_name=_('Dispositivo Pai')) + dispositivo_raiz = models.ForeignKey( + 'self', + blank=True, null=True, default=None, + related_name='nodes', + verbose_name=_('Dispositivo Raiz')) dispositivo_vigencia = models.ForeignKey( 'self', blank=True, null=True, default=None, @@ -990,6 +995,10 @@ class Dispositivo(BaseModel, TimestampedMixin): related_name='dispositivos_alterados_set', verbose_name=_('Dispositivo Atualizador')) + contagem_continua = models.BooleanField( + default=False, + choices=YES_NO_CHOICES, verbose_name=_('Contagem contínua')) + class Meta: verbose_name = _('Dispositivo') verbose_name_plural = _('Dispositivos') @@ -1004,10 +1013,24 @@ class Dispositivo(BaseModel, TimestampedMixin): 'dispositivo4', 'dispositivo5', 'tipo_dispositivo', + 'dispositivo_raiz', 'dispositivo_pai', 'dispositivo_atualizador', 'ta_publicado', 'publicacao',), + ('ta', + 'dispositivo0', + 'dispositivo1', + 'dispositivo2', + 'dispositivo3', + 'dispositivo4', + 'dispositivo5', + 'tipo_dispositivo', + 'contagem_continua', + 'dispositivo_raiz', + 'dispositivo_atualizador', + 'ta_publicado', + 'publicacao',), ) permissions = ( ('change_dispositivo_edicao_dinamica', _( @@ -1027,10 +1050,61 @@ class Dispositivo(BaseModel, TimestampedMixin): 'Permissão alteração global do dispositivo de vigência')), ) + def clean(self): + """ + Check for instances with null values in unique_together fields. + """ + from django.core.exceptions import ValidationError + + for field_tuple in self._meta.unique_together[:]: + unique_filter = {} + unique_fields = [] + null_found = False + for field_name in field_tuple: + field_value = getattr(self, field_name) + if getattr(self, field_name) is None: + unique_filter['%s__isnull' % field_name] = True + null_found = True + else: + unique_filter['%s' % field_name] = field_value + unique_fields.append(field_name) + if null_found: + unique_queryset = self.__class__.objects.filter( + **unique_filter) + if self.pk: + unique_queryset = unique_queryset.exclude(pk=self.pk) + if not self.contagem_continua and \ + 'contagem_continua' in field_tuple: + continue + + if unique_queryset.exists(): + msg = self.unique_error_message( + self.__class__, tuple(unique_fields)) + raise ValidationError(msg) + + def save(self, force_insert=False, force_update=False, using=None, + update_fields=None, clean=True): + + self.dispositivo_raiz = self.get_raiz() + if self.dispositivo_raiz == self: + self.dispositivo_raiz = None + + self.contagem_continua = self.tipo_dispositivo.contagem_continua + + return super().save( + force_insert=force_insert, force_update=force_update, using=using, + update_fields=update_fields, clean=clean) + def __str__(self): return '%(rotulo)s' % { 'rotulo': (self.rotulo if self.rotulo else self.tipo_dispositivo)} + def get_raiz(self): + dp = self + while dp.dispositivo_pai is not None: + dp = dp.dispositivo_pai + return dp + def rotulo_padrao(self, local_insert=0, for_insert_in=0): """ 0 = Sem inserção - com nomeclatura padrao @@ -1210,7 +1284,7 @@ class Dispositivo(BaseModel, TimestampedMixin): if not numero[i]: continue - if i > profundidade: + if i < profundidade: continue numero[i] -= 1 @@ -1516,12 +1590,6 @@ class Dispositivo(BaseModel, TimestampedMixin): return True return False - def get_raiz(self): - dp = self - while dp.dispositivo_pai is not None: - dp = dp.dispositivo_pai - return dp - def history(self): ultimo = self while ultimo.dispositivo_subsequente: diff --git a/sapl/compilacao/templatetags/compilacao_filters.py b/sapl/compilacao/templatetags/compilacao_filters.py index 58c6dfaec..e56478bae 100644 --- a/sapl/compilacao/templatetags/compilacao_filters.py +++ b/sapl/compilacao/templatetags/compilacao_filters.py @@ -4,6 +4,7 @@ from django.core.signing import Signer from django.db.models import Q from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ + from sapl.compilacao.models import Dispositivo register = template.Library() diff --git a/sapl/compilacao/tests/test_tipo_texto_articulado_form.py b/sapl/compilacao/tests/test_tipo_texto_articulado_form.py index 84593650f..6c64adc78 100644 --- a/sapl/compilacao/tests/test_tipo_texto_articulado_form.py +++ b/sapl/compilacao/tests/test_tipo_texto_articulado_form.py @@ -1,6 +1,7 @@ import pytest from django.utils.translation import ugettext as _ from model_mommy import mommy + from sapl.compilacao import forms from sapl.compilacao.models import PerfilEstruturalTextoArticulado, TipoNota from sapl.compilacao.views import choice_models_in_extenal_views @@ -21,25 +22,6 @@ def test_valida_campos_obrigatorios_tipo_texto_articulado_form(): assert len(errors) == 4 -_content_types = choice_models_in_extenal_views() - - -@pytest.mark.parametrize('content_type', _content_types) -@pytest.mark.django_db(transaction=False) -def test_tipo_texto_articulado_form_valid(content_type): - perfil = mommy.make(PerfilEstruturalTextoArticulado) - - form = forms.TipoTaForm(data={'sigla': 'si', - 'descricao': 'teste', - 'content_type': content_type[0], - 'participacao_social': True, - 'publicacao_func': True, - 'perfis': [perfil.pk, ] - }) - - assert form.is_valid(), form.errors - - def test_valida_campos_obrigatorios_nota_form(): form = forms.NotaForm(data={}) diff --git a/sapl/compilacao/urls.py b/sapl/compilacao/urls.py index 69b6fd654..781a6d80e 100644 --- a/sapl/compilacao/urls.py +++ b/sapl/compilacao/urls.py @@ -1,4 +1,5 @@ from django.conf.urls import include, url + from sapl.compilacao import views from sapl.compilacao.views import (TipoDispositivoCrud, TipoNotaCrud, TipoPublicacaoCrud, TipoVideCrud, @@ -98,31 +99,28 @@ urlpatterns_compilacao = [ views.PublicacaoDeleteView.as_view(), name='ta_pub_delete'), - url(r'^config/tipo-textoarticulado$', - views.TipoTaListView.as_view(), name='tipo_ta_list'), - url(r'^config/tipo-textoarticulado/create$', - views.TipoTaCreateView.as_view(), name='tipo_ta_create'), - url(r'^config/tipo-textoarticulado/(?P[0-9]+)$', - views.TipoTaDetailView.as_view(), name='tipo_ta_detail'), - url(r'^config/tipo-textoarticulado/(?P[0-9]+)/edit$', - views.TipoTaUpdateView.as_view(), name='tipo_ta_edit'), - url(r'^config/tipo-textoarticulado/(?P[0-9]+)/delete$', - views.TipoTaDeleteView.as_view(), name='tipo_ta_delete'), ] urlpatterns = [ url(r'^ta/', include(urlpatterns_compilacao)), - url(r'^ta/config/tipo-nota/', + url(r'^sistema/ta/config/tipo-nota/', include(TipoNotaCrud.get_urls())), - url(r'^ta/config/tipo-vide/', + url(r'^sistema/ta/config/tipo-vide/', include(TipoVideCrud.get_urls())), - url(r'^ta/config/tipo-publicacao/', + url(r'^sistema/ta/config/tipo-publicacao/', include(TipoPublicacaoCrud.get_urls())), - url(r'^ta/config/veiculo-publicacao/', + url(r'^sistema/ta/config/veiculo-publicacao/', include(VeiculoPublicacaoCrud.get_urls())), - url(r'^ta/config/tipo-dispositivo/', - include(TipoDispositivoCrud.get_urls())), - + url(r'^sistema/ta/config/tipo-textoarticulado$', + views.TipoTaListView.as_view(), name='tipo_ta_list'), + url(r'^sistema/ta/config/tipo-textoarticulado/create$', + views.TipoTaCreateView.as_view(), name='tipo_ta_create'), + url(r'^sistema/ta/config/tipo-textoarticulado/(?P[0-9]+)$', + views.TipoTaDetailView.as_view(), name='tipo_ta_detail'), + url(r'^sistema/ta/config/tipo-textoarticulado/(?P[0-9]+)/edit$', + views.TipoTaUpdateView.as_view(), name='tipo_ta_edit'), + url(r'^sistema/ta/config/tipo-textoarticulado/(?P[0-9]+)/delete$', + views.TipoTaDeleteView.as_view(), name='tipo_ta_delete'), ] diff --git a/sapl/compilacao/views.py b/sapl/compilacao/views.py index 523de19a2..a68c456a1 100644 --- a/sapl/compilacao/views.py +++ b/sapl/compilacao/views.py @@ -27,6 +27,7 @@ from django.views.generic.edit import (CreateView, DeleteView, FormView, UpdateView) from django.views.generic.list import ListView +from sapl.compilacao.apps import AppConfig from sapl.compilacao.forms import (DispositivoDefinidorVigenciaForm, DispositivoEdicaoAlteracaoForm, DispositivoEdicaoBasicaForm, @@ -47,15 +48,15 @@ from sapl.compilacao.models import (STATUS_TA_EDITION, STATUS_TA_PRIVATE, from sapl.compilacao.utils import (DISPOSITIVO_SELECT_RELATED, DISPOSITIVO_SELECT_RELATED_EDIT, get_integrations_view_names) -from sapl.crud.base import Crud, CrudListView, make_pagination +from sapl.crud.base import Crud, 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) @@ -107,7 +108,7 @@ class IntegracaoTaView(TemplateView): try: if settings.DEBUG or not TipoDispositivo.objects.exists(): - self.import_pattern() + AppConfig.import_pattern() if hasattr(self, 'map_funcs'): tipo_ta = TipoTextoArticulado.objects.get( @@ -195,66 +196,6 @@ class IntegracaoTaView(TemplateView): return redirect(to=reverse_lazy('sapl.compilacao:ta_text', kwargs={'ta_id': ta.pk})) - def import_pattern(self): - - from unipath import Path - - compilacao_app = Path(__file__).ancestor(1) - # print(compilacao_app) - with open(compilacao_app + '/compilacao_data_tables.sql', 'r') as f: - lines = f.readlines() - lines = [line.rstrip('\n') for line in lines] - - with connection.cursor() as cursor: - for line in lines: - line = line.strip() - if not line or line.startswith('#'): - continue - - try: - cursor.execute(line) - except IntegrityError as e: - if not settings.DEBUG: - logger.error( - string_concat( - _('Ocorreu erro na importação: '), - line, - str(e))) - except Exception as ee: - print(ee) - - integrations_view_names = get_integrations_view_names() - - def cria_sigla(verbose_name): - verbose_name = verbose_name.upper().split() - if len(verbose_name) == 1: - verbose_name = verbose_name[0] - sigla = '' - for letra in verbose_name: - if letra in 'BCDFGHJKLMNPQRSTVWXYZ': - sigla += letra - else: - sigla = ''.join([palavra[0] for palavra in verbose_name]) - return sigla[:3] - - for view in integrations_view_names: - try: - tipo = TipoTextoArticulado() - tipo.sigla = cria_sigla( - view.model._meta.verbose_name - if view.model._meta.verbose_name - else view.model._meta.model_name) - tipo.descricao = view.model._meta.verbose_name - tipo.content_type = ContentType.objects.get_by_natural_key( - view.model._meta.app_label, view.model._meta.model_name) - tipo.save() - except IntegrityError as e: - if not settings.DEBUG: - logger.error( - string_concat( - _('Ocorreu erro na criação tipo de ta: '), - str(e))) - class Meta: abstract = True @@ -1721,10 +1662,13 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin): base.delete() for irmao in irmaos_posteriores: - irmao.transform_in_prior( - profundidade=profundidade_base) - irmao.rotulo = irmao.rotulo_padrao() - irmao.save() + try: + irmao.transform_in_prior( + profundidade=profundidade_base) + irmao.rotulo = irmao.rotulo_padrao() + irmao.save() + except: + break irmaos = pai_base.dispositivos_filhos_set.\ filter(tipo_dispositivo=base.tipo_dispositivo) @@ -1826,17 +1770,37 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin): dcc_a_religar = dcc_a_religar.exclude( ordem__gte=proxima_articulacao.ordem) - primeiro_a_religar = 0 + primeiro_a_religar = True + profundidade = d.get_profundidade() for dr in dcc_a_religar: - if not primeiro_a_religar: - primeiro_a_religar = dr.dispositivo0 - base.delete() - - dr.dispositivo0 = ( - dr.dispositivo0 - - primeiro_a_religar + d.dispositivo0) + if primeiro_a_religar: + primeiro_a_religar = False + d_pk = d.pk + d.delete() + if base.pk == d_pk: + base = d + + dr.transform_in_prior(profundidade=profundidade) dr.rotulo = dr.rotulo_padrao() - dr.save(clean=base != dr) + try: + dr.save(clean=base != dr) + except: + break + + # Pode não ser religavável + # Exemplo, numa sequencia com variáção: + # Art. 1º + # ... + # Art. 1º-A + # ... + # Art. 2º + # ... + # Ao tentar excluir o Art. 1º-A, o algoritmo + # de religação tentará reduzir Art. 2º para 1º + # e o método clean lançará um erro visto que + # já existe um, por outro lado, não é lógico + # reduzir Art 2º para Art. 1º-A, ou seja, + # em caso de variação não há o que reduzir if base.tipo_dispositivo.dispositivo_de_alteracao: dpts = base.dispositivos_alterados_set.all().order_by( @@ -1845,7 +1809,19 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin): self.remover_dispositivo(dpt, False) if base.pk: - base.delete() + """ + Um registro a ser excluido em bloco que não é um + dispositivo de contagem contínua, neste ponto, teve todos + os seus filhos excluídos mas ainda não foi e, tão pouco, + foi seus imãos (anterior e posterior) religados + numericamente. + A exclusão em bloco religa apenas dispositivos de contagem + continua internos extra bloco. + Depois do bloco limpo, a função é chamada novamente para + excluir realmente a escolha do usuário + e religar seus irmaos + """ + self.remover_dispositivo(base, False) return '' @@ -2295,7 +2271,7 @@ class ActionDispositivoCreateMixin(ActionsCommonsMixin): if count_auto_insert: ordem = dp.criar_espaco( - espaco_a_criar=count_auto_insert, local=local_add) + espaco_a_criar=count_auto_insert, local='json_add_in') dp_pk = dp.pk dp.ordem = ordem diff --git a/sapl/crispy_layout_mixin.py b/sapl/crispy_layout_mixin.py index e582ba6c1..9b2f0867b 100644 --- a/sapl/crispy_layout_mixin.py +++ b/sapl/crispy_layout_mixin.py @@ -1,5 +1,6 @@ from math import ceil +import rtyaml from crispy_forms.bootstrap import FormActions from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML, Div, Fieldset, Layout, Submit @@ -7,7 +8,6 @@ from django import template from django.core.urlresolvers import reverse, reverse_lazy from django.utils import formats from django.utils.translation import ugettext as _ -import rtyaml def heads_and_tails(list_of_lists): @@ -96,7 +96,7 @@ def get_field_display(obj, fieldname): if value is None: display = '' - elif 'date' in str_type_from_value: + elif '.date' in str_type_from_value: display = formats.date_format(value, "SHORT_DATE_FORMAT") elif 'bool' in str_type_from_value: display = _('Sim') if value else _('Não') @@ -132,6 +132,8 @@ def get_field_display(obj, fieldname): value._meta.app_config.name, obj.content_type.model), args=(value.id,)), value) + elif 'TextField' in str_type_from_field: + display = value.replace('\n', '
') else: display = str(value) return verbose_name, display diff --git a/sapl/crud/base.py b/sapl/crud/base.py index c94820a03..8bb8c3794 100644 --- a/sapl/crud/base.py +++ b/sapl/crud/base.py @@ -23,6 +23,7 @@ from django.views.generic import (CreateView, DeleteView, DetailView, ListView, UpdateView) 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) @@ -936,6 +937,9 @@ class CrudAux(Crud): """ permission_required = ('base.view_tabelas_auxiliares',) + class ListView(Crud.ListView): + template_name = "crud/list_tabaux.html" + class BaseMixin(Crud.BaseMixin): subnav_template_name = None diff --git a/sapl/crud/tests/test_base.py b/sapl/crud/tests/test_base.py index 8a8a26af4..8e1d10965 100644 --- a/sapl/crud/tests/test_base.py +++ b/sapl/crud/tests/test_base.py @@ -1,6 +1,7 @@ import pytest from django.core.urlresolvers import reverse from model_mommy import mommy + from sapl.crud.base import (CrispyLayoutFormMixin, CrudListView, from_to, get_field_display, make_pagination) from sapl.crud.tests.stub_app.models import Continent, Country diff --git a/sapl/hashers.py b/sapl/hashers.py index a514f8f19..e80642def 100644 --- a/sapl/hashers.py +++ b/sapl/hashers.py @@ -44,6 +44,8 @@ ZOPE_SHA1_PREFIX = '{SSHA}' def zope_encoded_password_to_django(encoded): + "Migra um hash de senha do zope para uso com o ZopeSHA1PasswordHasher" + if encoded.startswith(ZOPE_SHA1_PREFIX): data = encoded[len(ZOPE_SHA1_PREFIX):] salt = get_salt_from_zope_sha1(data) diff --git a/sapl/legacy/management/commands/migracao_25_31.py b/sapl/legacy/management/commands/migracao_25_31.py index 9e7f0a32a..27591e058 100644 --- a/sapl/legacy/management/commands/migracao_25_31.py +++ b/sapl/legacy/management/commands/migracao_25_31.py @@ -1,6 +1,7 @@ from django.core import management from django.core.management.base import BaseCommand -from sapl.legacy import migration + +from sapl.legacy.migracao import migrar, migrar_dados class Command(BaseCommand): @@ -9,13 +10,24 @@ class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument( - '-f', + '--force', action='store_true', default=False, dest='force', help='Não interativa: pula confirmação de exclusão dos dados', ) + parser.add_argument( + '--dados', + action='store_true', + default=False, + dest='dados', + help='migra somente dados', + ) def handle(self, *args, **options): management.call_command('migrate') - migration.migrate(interativo=not options['force']) + somente_dados, interativo = options['dados'], not options['force'] + if somente_dados: + migrar_dados(interativo=interativo) + else: + migrar(interativo=interativo) diff --git a/sapl/legacy/management/commands/migracao_documentos.py b/sapl/legacy/management/commands/migracao_documentos.py index f9e81a29c..7cba09d5a 100644 --- a/sapl/legacy/management/commands/migracao_documentos.py +++ b/sapl/legacy/management/commands/migracao_documentos.py @@ -1,4 +1,5 @@ from django.core.management.base import BaseCommand + from sapl.legacy.migracao_documentos import migrar_documentos diff --git a/sapl/legacy/management/commands/recria_constraints.py b/sapl/legacy/management/commands/recria_constraints.py deleted file mode 100644 index d1d8d606d..000000000 --- a/sapl/legacy/management/commands/recria_constraints.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.core.management.base import BaseCommand - - -class Command(BaseCommand): - - help = (u'Recria constraints do PostgreSQL excluidas durante ' - 'migração de dados') - - def handle(self, *args, **options): - pass diff --git a/sapl/legacy/migracao.py b/sapl/legacy/migracao.py new file mode 100644 index 000000000..2690e9a53 --- /dev/null +++ b/sapl/legacy/migracao.py @@ -0,0 +1,42 @@ +import subprocess +import tarfile + +from django.conf import settings + +from sapl.legacy.migracao_dados import migrar_dados +from sapl.legacy.migracao_documentos import migrar_documentos +from sapl.legacy.migracao_usuarios import migrar_usuarios + + +def migrar(interativo=False): + migrar_dados(interativo=interativo) + migrar_usuarios() + migrar_documentos() + + +# fonte: https://stackoverflow.com/a/17081026/1877490 +def make_tarfile(output_filename, source_dir): + with tarfile.open(output_filename, "w:gz") as tar: + tar.add(source_dir, arcname=os.path.basename(source_dir)) + + +def gerar_pacote(): + banco = settings.DATABASES['legacy']['NAME'] + + # backup do banco + print('Gerando backup do banco... ', end='', flush=True) + arq_backup = settings.MEDIA_ROOT.child('{}.backup'.format(banco)) + backup_cmd = ''' + pg_dump --host localhost --port 5432 --username postgres --no-password + --format custom --blobs --verbose --file {} {}'''.format( + arq_backup, banco) + subprocess.check_output(backup_cmd.split(), stderr=subprocess.DEVNULL) + print('SUCESSO') + + # tar de media/sapl + print('Criando tar de media... ', end='', flush=True) + tar_media = settings.MEDIA_ROOT.child('{}.media.tgz'.format(banco)) + dir_media = settings.MEDIA_ROOT.child('sapl') + with tarfile.open(tar_media, "w:gz") as tar: + tar.add(dir_media, arcname=dir_media.name) + print('SUCESSO') diff --git a/sapl/legacy/migration.py b/sapl/legacy/migracao_dados.py similarity index 66% rename from sapl/legacy/migration.py rename to sapl/legacy/migracao_dados.py index e5d98147b..6aa051e62 100644 --- a/sapl/legacy/migration.py +++ b/sapl/legacy/migracao_dados.py @@ -1,5 +1,5 @@ -import os import re +from collections import defaultdict from datetime import date from functools import lru_cache, partial from itertools import groupby @@ -15,13 +15,13 @@ from django.contrib.auth.models import Group from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist from django.db import connections, transaction -from django.db.models import Count, Max +from django.db.models import Max, Q from django.db.models.base import ModelBase from pytz import timezone +from unipath import Path from sapl.base.models import AppConfig as AppConf -from sapl.base.models import (Autor, ProblemaMigracao, TipoAutor, - cria_models_tipo_autor) +from sapl.base.models import Autor, TipoAutor, cria_models_tipo_autor from sapl.comissoes.models import Comissao, Composicao, Participacao from sapl.legacy.models import TipoNumeracaoProtocolo from sapl.materia.models import (AcompanhamentoMateria, Proposicao, @@ -56,19 +56,19 @@ unique_constraints = [] one_to_one_constraints = [] primeira_vez = [] -name_sets = [set(m.__name__ for m in ac.get_models()) for ac in appconfs] - -# apps do not overlap -for s1 in name_sets: - for s2 in name_sets: - if s1 is not s2: - assert not s1.intersection(s2) +# apps quase não têm interseção +name_sets = [(ac.label, set(m.__name__ for m in ac.get_models())) + for ac in appconfs] +for a1, s1 in name_sets: + for a2, s2 in name_sets: + if a1 is not a2: + # existe uma interseção de nomes entre comissoes e materia + if {a1, a2} == {'comissoes', 'materia'}: + assert s1.intersection(s2) == {'DocumentoAcessorio'} + else: + assert not s1.intersection(s2) -# apps include all legacy models legacy_app = apps.get_app_config('legacy') -legacy_model_names = set(m.__name__ for m in legacy_app.get_models()) - -model_dict = {m.__name__: m for ac in appconfs for m in ac.get_models()} # RENAMES ################################################################### @@ -114,14 +114,30 @@ def get_renames(): def info(msg): print('INFO: ' + msg) +ocorrencias = defaultdict(list) -def warn(msg): - print('CUIDADO! ' + msg) + +def warn(tipo, msg, dados): + ocorrencias[tipo].append(dados) + print('CUIDADO! ' + msg.format(**dados)) class ForeignKeyFaltando(ObjectDoesNotExist): 'Uma FK aponta para um registro inexistente' - pass + + def __init__(self, field, value, label): + self.field = field + self.value = value + self.label = label + + msg = 'FK [{field}] não encontrada para o valor {value} (em {model} / {label})' # noqa + + @property + def dados(self): + return {'field': self.field.name, + 'value': self.value, + 'model': self.field.model.__name__, + 'label': self.label} @lru_cache() @@ -130,7 +146,7 @@ def _get_all_ids_from_model(model): return set(model.objects.values_list('id', flat=True)) -def get_fk_related(field, value, label=None): +def get_fk_related(field, value, label='---'): if value is None and field.null: return None @@ -141,10 +157,7 @@ def get_fk_related(field, value, label=None): # consideramos zeros como nulos, se não está entre os ids anteriores return None else: - msg = 'FK [%s] não encontrada para o valor %s (em %s %s)' % ( - field.name, value, field.model.__name__, label or '---') - warn(msg) - raise ForeignKeyFaltando(msg) + raise ForeignKeyFaltando(field=field, value=value, label=label) def exec_sql(sql, db='default'): @@ -243,6 +256,21 @@ def reverte_exclusao_de_autores_referenciados_no_legado(): 'update autor set ind_excluido = 0 where cod_autor in {}', autores_referenciados) + # propaga exclusões para autores não referenciados + for tabela, fk in [('parlamentar', 'cod_parlamentar'), + ('comissao', 'cod_comissao')]: + sql = ''' + update autor set ind_excluido = 1 + where {cod_parlamentar} is not null + and {cod_parlamentar} not in ( + select {cod_parlamentar} from {parlamentar} + where ind_excluido <> 1) + '''.format(parlamentar=tabela, cod_parlamentar=fk) + if autores_referenciados: + sql += ' and cod_autor not in {}'.format( + tuple(autores_referenciados)) + exec_legado(sql) + def get_reapontamento_de_autores_repetidos(autores): """ Dada uma lista ordenada de pares (cod_zzz, cod_autor) retorna: @@ -315,11 +343,12 @@ def unifica_autores_repetidos_no_legado(campo_agregador): exec_legado_em_subconjunto('delete ' + from_autoria, reapontamento) # e depois inserimos apenas as sem repetições c ind_primeiro_autor ajustado nova_autoria = get_autorias_sem_repeticoes(autoria, reapontamento) - exec_legado(''' - insert into autoria - (cod_autor, cod_materia, ind_primeiro_autor, ind_excluido) - values {}'''.format(', '.join([str((a, m, i, 0)) - for a, m, i in nova_autoria]))) + if nova_autoria: + exec_legado(''' + insert into autoria + (cod_autor, cod_materia, ind_primeiro_autor, ind_excluido) + values {}'''.format(', '.join([str((a, m, i, 0)) + for a, m, i in nova_autoria]))) # Reaponta outras tabelas que referenciam autor for tabela, _ in TABELAS_REFERENCIANDO_AUTOR: @@ -350,9 +379,95 @@ def anula_tipos_origem_externa_invalidos(): where tip_origem_externa not in {};''', tipos_validos) +def get_ids_registros_votacao_para(tabela): + sql = ''' + select r.cod_votacao from {} o + inner join registro_votacao r on + o.cod_ordem = r.cod_ordem and o.cod_materia = r.cod_materia + where o.ind_excluido != 1 and r.ind_excluido != 1 + order by o.cod_sessao_plen, num_ordem + '''.format(tabela) + return set(primeira_coluna(exec_legado(sql))) + + +def checa_registros_votacao_ambiguos_e_remove_nao_usados(): + """Interrompe a migração caso restem registros de votação + que apontam para uma ordem_dia e um expediente_materia ao mesmo tempo. + + Remove do legado registros de votação que não têm + nem ordem_dia nem expediente_materia associados.""" + + ordem, expediente = [ + get_ids_registros_votacao_para(tabela) + for tabela in ('ordem_dia', 'expediente_materia')] + + # interrompe migração se houver registros ambíguos + ambiguos = ordem.intersection(expediente) + assert not ambiguos, '''Existe(m) RegistroVotacao ambíguo(s): {} + Corrija os dados originais antes de migrar!'''.format( + ambiguos) + + # exclui registros não usados (zumbis) + todos = set(primeira_coluna(exec_legado( + 'select cod_votacao from registro_votacao'))) + nao_usados = todos - ordem.union(expediente) + exec_legado_em_subconjunto(''' + update registro_votacao set ind_excluido = 1 + where cod_votacao in {}''', nao_usados) + + +PROPAGACOES_DE_EXCLUSAO = [ + # sessao_legislativa + ('sessao_legislativa', 'composicao_mesa', 'cod_sessao_leg'), + + # parlamentar + ('parlamentar', 'dependente', 'cod_parlamentar'), + ('parlamentar', 'filiacao', 'cod_parlamentar'), + ('parlamentar', 'mandato', 'cod_parlamentar'), + + # comissao + ('comissao', 'composicao_comissao', 'cod_comissao'), + ('periodo_comp_comissao', 'composicao_comissao', 'cod_periodo_comp'), + + # sessao + ('sessao_plenaria', 'ordem_dia', 'cod_sessao_plen'), + ('sessao_plenaria', 'expediente_materia', 'cod_sessao_plen'), + ('sessao_plenaria', 'expediente_sessao_plenaria', 'cod_sessao_plen'), + ('registro_votacao', 'registro_votacao_parlamentar', 'cod_votacao'), + # as consultas no código do sapl 2.5 + # votacao_ordem_dia_obter_zsql e votacao_expediente_materia_obter_zsql + # indicam que os registros de votação de matérias excluídas não são + # exibidos... + ('materia_legislativa', 'registro_votacao', 'cod_materia'), + # as exclusões de registro_votacao sem referência + # nem a ordem_dia nem a expediente_materia são feitas num método à parte + + # materia + ('materia_legislativa', 'tramitacao', 'cod_materia'), + ('materia_legislativa', 'autoria', 'cod_materia'), + ('materia_legislativa', 'anexada', 'cod_materia_principal'), + ('materia_legislativa', 'anexada', 'cod_materia_anexada'), + ('materia_legislativa', 'documento_acessorio', 'cod_materia'), + + # documento administrativo + ('documento_administrativo', 'tramitacao_administrativo', 'cod_documento'), +] + + +def propaga_exclusoes(): + for tabela_pai, tabela_filha, fk in PROPAGACOES_DE_EXCLUSAO: + [pk_pai] = get_pk_legado(tabela_pai) + exec_legado(''' + update {} set ind_excluido = 1 where {} not in ( + select {} from {} where ind_excluido != 1) + '''.format(tabela_filha, fk, pk_pai, tabela_pai)) + + def uniformiza_banco(): - # desliga todas as checagens do mysql - exec_legado('SET SESSION sql_mode = "";') + exec_legado('SET SESSION sql_mode = "";') # desliga checagens do mysql + + checa_registros_votacao_ambiguos_e_remove_nao_usados() + propaga_exclusoes() garante_coluna_no_legado('proposicao', 'num_proposicao int(11) NULL') @@ -437,10 +552,10 @@ relatoria | tip_fim_relatoria = NULL | tip_fim_relatoria = 0 anula_tipos_origem_externa_invalidos() -def iter_sql_records(sql, db): +def iter_sql_records(sql): class Record: pass - cursor = exec_sql(sql, db) + cursor = exec_legado(sql) fieldnames = [name[0] for name in cursor.description] for row in cursor.fetchall(): record = Record() @@ -448,14 +563,6 @@ def iter_sql_records(sql, db): yield record -def save_relation(obj, nome_campo='', problema='', descricao='', - eh_stub=False, critico=False): - link = ProblemaMigracao( - content_object=obj, nome_campo=nome_campo, problema=problema, - descricao=descricao, eh_stub=eh_stub, critico=critico) - link.save() - - def fill_vinculo_norma_juridica(): lista = [('A', 'Altera o(a)', 'Alterado(a) pelo(a)'), @@ -506,52 +613,6 @@ def fill_dados_basicos(): appconf.save() -# Uma anomalia no sapl 2.5 causa a duplicação de registros de votação. -# Essa duplicação deve ser eliminada para que não haja erro no sapl 3.1 -def excluir_registrovotacao_duplicados(): - duplicatas_ids = RegistroVotacao.objects.values( - 'materia', 'ordem', 'expediente').annotate( - Count('id')).order_by().filter(id__count__gt=1) - duplicatas_queryset = RegistroVotacao.objects.filter( - materia__in=[item['materia'] for item in duplicatas_ids]) - - for dup in duplicatas_queryset: - lista_dups = duplicatas_queryset.filter( - materia=dup.materia, expediente=dup.expediente, ordem=dup.ordem) - primeiro_registro = lista_dups[0] - lista_dups = lista_dups.exclude(pk=primeiro_registro.pk) - for objeto in lista_dups: - if (objeto.pk > primeiro_registro.pk): - try: - objeto.delete() - except: - assert 0 - else: - try: - primeiro_registro.delete() - primeiro_registro = objeto - except: - assert 0 - - -def delete_old(legacy_model, cols_values): - # ajuste necessário por conta de cósigos html em txt_expediente - if legacy_model.__name__ == 'ExpedienteSessaoPlenaria': - cols_values.pop('txt_expediente') - - def eq_clause(col, value): - if value is None: - return '{} IS NULL'.format(col) - else: - return '{}="{}"'.format(col, value) - - delete_sql = 'delete from {} where {}'.format( - legacy_model._meta.db_table, - ' and '.join([eq_clause(col, value) - for col, value in cols_values.items()])) - exec_sql(delete_sql, 'legacy') - - def get_last_pk(model): last_value = model.objects.all().aggregate(Max('pk')) return last_value['pk__max'] or 0 @@ -563,6 +624,17 @@ def reinicia_sequence(model, id): sequence_name, id)) +def get_pk_legado(tabela): + res = exec_legado( + 'show index from {} WHERE Key_name = "PRIMARY"'.format(tabela)) + return [r[4] for r in res] + + +DIR_DADOS_MIGRACAO = Path('~/migracao_sapl/').expand() +PATH_TABELA_TIMEZONES = DIR_DADOS_MIGRACAO.child('tabela_timezones.yaml') +DIR_RESULTADOS = DIR_DADOS_MIGRACAO.child('resultados') + + class DataMigrator: def __init__(self): @@ -570,10 +642,10 @@ class DataMigrator: self.choice_valida = {} # configura timezone de migração - nome_legado = DATABASES['legacy']['NAME'] - match = re.match('sapl_cm_(.*)', nome_legado) + self.nome_banco_legado = DATABASES['legacy']['NAME'] + match = re.match('sapl_cm_(.*)', self.nome_banco_legado) sigla_casa = match.group(1) - with open(os.path.expanduser('~/sapl_dumps/tabela_timezones.yaml'), 'r') as arq: + with open(PATH_TABELA_TIMEZONES, 'r') as arq: tabela_timezones = yaml.load(arq) municipio, uf, nome_timezone = tabela_timezones[sigla_casa] if nome_timezone: @@ -593,7 +665,7 @@ class DataMigrator: if field_type == 'ForeignKey': # not necessarily a model if hasattr(old, '_meta') and old._meta.pk.name != 'id': - label = old.pk + label = 'pk = {}'.format(old.pk) else: label = '-- SEM PK --' fk_field_name = '{}_id'.format(field.name) @@ -619,7 +691,7 @@ class DataMigrator: setattr(new, field.name, value) - def migrate(self, obj=appconfs, interativo=True): + def migrar(self, obj=appconfs, interativo=True): # warning: model/app migration order is of utmost importance uniformiza_banco() @@ -636,7 +708,7 @@ class DataMigrator: else: info('Migração cancelada.') return 0 - info('Excluindo entradas antigas do banco.') + info('Excluindo entradas antigas do banco destino.') call([PROJECT_DIR.child('manage.py'), 'flush', '--database=default', '--no-input'], stdout=PIPE) @@ -646,27 +718,42 @@ class DataMigrator: fill_vinculo_norma_juridica() fill_dados_basicos() info('Começando migração: %s...' % obj) - self._do_migrate(obj) - - info('Excluindo possíveis duplicações em RegistroVotacao...') - excluir_registrovotacao_duplicados() + try: + ocorrencias.clear() + dir_ocorrencias = DIR_RESULTADOS.child(date.today().isoformat()) + dir_ocorrencias.mkdir(parents=True) + self._do_migrate(obj) + finally: + # grava ocorrências + arq_ocorrencias = dir_ocorrencias.child( + self.nome_banco_legado + '.yaml') + with open(arq_ocorrencias, 'w') as arq: + yaml.safe_dump(dict(ocorrencias), arq, allow_unicode=True) + info('Ocorrências salvas em\n {}'.format(arq_ocorrencias)) # recria tipos de autor padrão que não foram criados pela migração cria_models_tipo_autor() def _do_migrate(self, obj): if isinstance(obj, AppConfig): - models_to_migrate = (model for model in obj.models.values() - if model in self.field_renames) - self._do_migrate(models_to_migrate) + models = [model for model in obj.models.values() + if model in self.field_renames] + + if obj.label == 'materia': + # Devido à referência TipoProposicao.tipo_conteudo_related + # a migração de TipoProposicao precisa ser feita + # após TipoMateriaLegislativa e TipoDocumento + # (porém antes de Proposicao) + models.remove(TipoProposicao) + pos_tipo_proposicao = max( + models.index(TipoMateriaLegislativa), + models.index(TipoDocumento)) + 1 + models.insert(pos_tipo_proposicao, TipoProposicao) + assert models.index(TipoProposicao) < models.index(Proposicao) + + self._do_migrate(models) elif isinstance(obj, ModelBase): - # A migração vai pular TipoProposicao e só vai migrar essa model - # antes de migrar Proposicao. Isso deve acontecer por causa da - # GenericRelation existente em TipoProposicao. - if not obj.__name__ == 'TipoProposicao': - if obj.__name__ == 'Proposicao': - self.migrate_model(TipoProposicao) - self.migrate_model(obj) + self.migrate_model(obj) elif hasattr(obj, '__iter__'): for item in obj: self._do_migrate(item) @@ -677,73 +764,85 @@ class DataMigrator: def migrate_model(self, model): print('Migrando %s...' % model.__name__) - legacy_model_name = self.model_renames.get(model, model.__name__) - legacy_model = legacy_app.get_model(legacy_model_name) - legacy_pk_name = legacy_model._meta.pk.name - - # setup migration strategy for tables with or without a pk - if legacy_pk_name == 'id': - deve_ajustar_sequence_ao_final = False - # There is no pk in the legacy table - - def save(new, old): - with reversion.create_revision(): - new.save() - reversion.set_comment('Objeto criado pela migração') - - # apaga registro do legado - delete_old(legacy_model, old.__dict__) + nome_model = self.model_renames.get(model, model.__name__) + model_legado = legacy_app.get_model(nome_model) + tabela_legado = model_legado._meta.db_table + campos_pk = get_pk_legado(tabela_legado) + + if len(campos_pk) == 1: + # a pk no legado tem um único campo + nome_pk = model_legado._meta.pk.name + if 'ind_excluido' in {f.name for f in model_legado._meta.fields}: + # se o model legado tem o campo ind_excluido + # enumera apenas os não excluídos + old_records = model_legado.objects.filter(~Q(ind_excluido=1)) + else: + old_records = model_legado.objects.all() + old_records = old_records.order_by(nome_pk) - old_records = iter_sql_records( - 'select * from ' + legacy_model._meta.db_table, 'legacy') + def get_id_do_legado(old): + return getattr(old, nome_pk) else: - deve_ajustar_sequence_ao_final = True - - def save(new, old): - with reversion.create_revision(): - # salva new com id de old - new.id = getattr(old, legacy_pk_name) - new.save() - reversion.set_comment('Objeto criado pela migração') + # a pk no legado tem mais de um campo + sql = 'select * from ' + tabela_legado + if existe_coluna_no_legado(tabela_legado, 'ind_excluido'): + sql += ' where ind_excluido != 1' + old_records = iter_sql_records(sql) - # apaga registro do legado - delete_old(legacy_model, {legacy_pk_name: new.id}) - - old_records = legacy_model.objects.all().order_by(legacy_pk_name) + get_id_do_legado = None ajuste_antes_salvar = AJUSTE_ANTES_SALVAR.get(model) ajuste_depois_salvar = AJUSTE_DEPOIS_SALVAR.get(model) # convert old records to new ones with transaction.atomic(): + sql_delete_legado = '' for old in old_records: - if getattr(old, 'ind_excluido', False): - # não migramos registros marcados como excluídos - continue new = model() try: self.populate_renamed_fields(new, old) if ajuste_antes_salvar: ajuste_antes_salvar(new, old) - except ForeignKeyFaltando: + except ForeignKeyFaltando as e: # tentamos preencher uma FK e o ojeto relacionado # não existe # então este é um objeo órfão: simplesmente ignoramos + warn('fk', e.msg, e.dados) continue else: - save(new, old) + if get_id_do_legado: + new.id = get_id_do_legado(old) + # validação do model + new.clean() + # salva novo registro + with reversion.create_revision(): + new.save() + reversion.set_comment('Objeto criado pela migração') + + # acumula deleção do registro no legado + sql_delete_legado += 'delete from {} where {};\n'.format( + tabela_legado, + ' and '.join( + '{} = "{}"'.format(campo, + getattr(old, campo)) + for campo in campos_pk)) + if ajuste_depois_salvar: ajuste_depois_salvar(new, old) - # reinicia sequence - if deve_ajustar_sequence_ao_final: + # se configuramos ids explicitamente devemos reiniciar a sequence + if get_id_do_legado: last_pk = get_last_pk(model) reinicia_sequence(model, last_pk + 1) + # apaga registros migrados do legado + if sql_delete_legado: + exec_legado(sql_delete_legado) + -def migrate(obj=appconfs, interativo=True): +def migrar_dados(obj=appconfs, interativo=True): dm = DataMigrator() - dm.migrate(obj, interativo) + dm.migrar(obj, interativo) # MIGRATION_ADJUSTMENTS ##################################################### @@ -752,24 +851,59 @@ def adjust_acompanhamentomateria(new, old): new.confirmado = True +NOTA_DOCADM = ''' +## NOTA DE MIGRAÇÃO DE DADOS DO SAPL 2.5 ## +O número de protocolo original deste documento era [{num_protocolo}], ano {ano_original}. +'''.strip() # noqa + + def adjust_documentoadministrativo(new, old): if old.num_protocolo: + nota = None + ano_original = new.ano protocolo = Protocolo.objects.filter( numero=old.num_protocolo, ano=new.ano) if not protocolo: - protocolo = Protocolo.objects.filter( - numero=old.num_protocolo, ano=new.ano + 1) - print('PROTOCOLO ENCONTRADO APENAS PARA O ANO SEGUINTE!!!!! ' - 'DocumentoAdministrativo: {}, numero_protocolo: {}, ' - 'ano doc adm: {}'.format( - old.cod_documento, old.num_protocolo, new.ano)) - if not protocolo: - raise ForeignKeyFaltando( - 'Protocolo {} faltando ' - '(referenciado no documento administrativo {}'.format( - old.num_protocolo, old.cod_documento)) - assert len(protocolo) == 1 - new.protocolo = protocolo[0] + # tentamos encontrar o protocolo no ano seguinte + ano_novo = ano_original + 1 + protocolo = Protocolo.objects.filter(numero=old.num_protocolo, + ano=ano_novo) + if protocolo: + nota = NOTA_DOCADM + ''' +O protocolo vinculado é o de mesmo número, porém do ano seguinte ({ano_novo}), +pois não existe protocolo no sistema com este número no ano {ano_original}. +''' + nota = nota.strip().format(num_protocolo=old.num_protocolo, + ano_original=ano_original, + ano_novo=ano_novo) + msg = 'PROTOCOLO ENCONTRADO APENAS PARA O ANO SEGUINTE!!!!! '\ + 'DocumentoAdministrativo: {cod_documento}, '\ + 'numero_protocolo: {num_protocolo}, '\ + 'ano doc adm: {ano_original}' + warn('protocolo_ano_seguinte', msg, + {'cod_documento': old.cod_documento, + 'num_protocolo': old.num_protocolo, + 'ano_original': ano_original, + 'nota': nota}) + else: + nota = NOTA_DOCADM + ''' +Não existe no sistema nenhum protocolo com estes dados +e portanto nenhum protocolo foi vinculado a este documento.''' + nota = nota.format( + num_protocolo=old.num_protocolo, + ano_original=ano_original) + msg = 'Protocolo {num_protocolo} faltando (referenciado ' \ + 'no documento administrativo {cod_documento})' + warn('protocolo_faltando', msg, + {'num_protocolo': old.num_protocolo, + 'cod_documento': old.cod_documento, + 'nota': nota}) + if protocolo: + assert len(protocolo) == 1, 'mais de um protocolo encontrado' + [new.protocolo] = protocolo + # adiciona nota ao final da observação + if nota: + new.observacao += ('\n\n' if new.observacao else '') + nota def adjust_mandato(new, old): @@ -792,18 +926,10 @@ def adjust_ordemdia_antes_salvar(new, old): if old.num_ordem is None: new.numero_ordem = 999999999 - - -def adjust_ordemdia_depois_salvar(new, old): - if old.num_ordem is None and new.numero_ordem == 999999999: - with reversion.create_revision(): - problema = 'OrdemDia de PK %s tinha seu valor de numero ordem'\ - ' nulo.' % old.pk - descricao = 'O valor %s foi colocado no lugar.' % new.numero_ordem - warn(problema + ' => ' + descricao) - save_relation(obj=new, problema=problema, - descricao=descricao, eh_stub=False) - reversion.set_comment('OrdemDia sem número da ordem.') + warn('ordem_dia_num_ordem_nulo', + 'OrdemDia de PK {pk} tinha numero ordem nulo. ' + 'O valor %s foi colocado no lugar.' % new.numero_ordem, + {'pk': old.pk}) def adjust_parlamentar(new, old): @@ -813,7 +939,10 @@ def adjust_parlamentar(new, old): # but data includes null values # => transform None to False if value is None: - warn('nulo convertido para falso') + warn('unidade_deliberativa_nulo_p_false', + 'nulo convertido para falso na unidade_deliberativa ' + 'do parlamentar {pk_parlamentar}', + {'pk_parlamentar': old.cod_parlamentar}) new.unidade_deliberativa = False # migra município de residência if old.cod_localidade_resid: @@ -827,7 +956,11 @@ def adjust_parlamentar(new, old): def adjust_participacao(new, old): composicao = Composicao() composicao.comissao_id, composicao.periodo_id = [ - get_fk_related(Composicao._meta.get_field(name), value) + get_fk_related(Composicao._meta.get_field(name), + value, + 'composicao_comissao.cod_comp_comissao = {}'.format( + old.pk + )) for name, value in (('comissao', old.cod_comissao), ('periodo', old.cod_periodo_comp))] # check if there is already an "equal" one in the db @@ -855,19 +988,12 @@ def adjust_normarelacionada(new, old): def adjust_protocolo_antes_salvar(new, old): - if old.num_protocolo is None: + if new.numero is None: new.numero = old.cod_protocolo - - -def adjust_protocolo_depois_salvar(new, old): - if old.num_protocolo is None: - with reversion.create_revision(): - problema = 'Número do protocolo de PK %s é nulo' % new.pk - descricao = 'Número do protocolo alterado para %s!' % new.numero - warn(problema + ' => ' + descricao) - save_relation(obj=new, problema=problema, - descricao=descricao, eh_stub=False) - reversion.set_comment('Número de protocolo teve que ser alterado') + warn('num_protocolo_nulo', + 'Número do protocolo de PK {cod_protocolo} era nulo ' + 'e foi alterado para sua pk ({cod_protocolo})', + {'cod_protocolo': old.cod_protocolo}) def adjust_registrovotacao_antes_salvar(new, old): @@ -882,38 +1008,27 @@ def adjust_registrovotacao_antes_salvar(new, old): new.expediente = expediente_materia[0] -def adjust_registrovotacao_depois_salvar(new, old): - if not new.ordem and not new.expediente: - with reversion.create_revision(): - problema = 'RegistroVotacao de PK %s não possui nenhuma OrdemDia'\ - ' ou ExpedienteMateria.' % old.pk - descricao = 'RevistroVotacao deve ter no mínimo uma ordem do dia'\ - ' ou expediente vinculado.' - warn(problema + ' => ' + descricao) - save_relation(obj=new, problema=problema, - descricao=descricao, eh_stub=False) - reversion.set_comment('RegistroVotacao sem ordem ou expediente') - - def adjust_tipoafastamento(new, old): if old.ind_afastamento == 1: new.indicador = 'A' +MODEL_TIPO_MATERIA_OU_DOCUMENTO = {'M': TipoMateriaLegislativa, + 'D': TipoDocumento} + + def adjust_tipoproposicao(new, old): - if old.ind_mat_ou_doc == 'M': - tipo_materia = TipoMateriaLegislativa.objects.filter( - pk=old.tip_mat_ou_doc) - if tipo_materia: - new.tipo_conteudo_related = tipo_materia[0] - else: - raise ForeignKeyFaltando - elif old.ind_mat_ou_doc == 'D': - tipo_documento = TipoDocumento.objects.filter(pk=old.tip_mat_ou_doc) - if tipo_documento: - new.tipo_conteudo_related = tipo_documento[0] - else: - raise ForeignKeyFaltando + "Aponta para o tipo relacionado de matéria ou documento" + value = old.tip_mat_ou_doc + model_tipo = MODEL_TIPO_MATERIA_OU_DOCUMENTO[old.ind_mat_ou_doc] + tipo = model_tipo.objects.filter(pk=value) + if tipo: + new.tipo_conteudo_related = tipo[0] + else: + raise ForeignKeyFaltando( + field=TipoProposicao.tipo_conteudo_related, + value=(model_tipo.__name__, value), + label='ind_mat_ou_doc = {}'.format(old.ind_mat_ou_doc)) def adjust_statustramitacao(new, old): @@ -975,8 +1090,12 @@ def vincula_autor(new, old, model_relacionado, campo_relacionado, campo_nome): new.autor_related = model_relacionado.objects.get(pk=pk_rel) except ObjectDoesNotExist: # ignoramos o autor órfão - raise ForeignKeyFaltando('{} inexiste para autor'.format( - model_relacionado._meta.verbose_name)) + nome_model_relacionado = model_relacionado._meta.model.__name__ + raise ForeignKeyFaltando( + field=Autor.autor_related, + value=(nome_model_relacionado, pk_rel), + label='{} [pk={}] inexistente para autor'.format( + nome_model_relacionado, pk_rel)) else: new.nome = getattr(new.autor_related, campo_nome) return True @@ -1010,8 +1129,8 @@ def adjust_autor(new, old): def adjust_comissao(new, old): if not old.dat_extincao and not old.dat_fim_comissao: new.ativa = True - elif old.dat_extincao and date.today() < new.data_extincao or \ - old.dat_fim_comissao and date.today() < new.data_fim_comissao: + elif (old.dat_extincao and date.today() < new.data_extincao or + old.dat_fim_comissao and date.today() < new.data_fim_comissao): new.ativa = True else: new.ativa = False @@ -1041,21 +1160,6 @@ AJUSTE_ANTES_SALVAR = { AJUSTE_DEPOIS_SALVAR = { NormaJuridica: adjust_normajuridica_depois_salvar, - OrdemDia: adjust_ordemdia_depois_salvar, - Protocolo: adjust_protocolo_depois_salvar, - RegistroVotacao: adjust_registrovotacao_depois_salvar, } # CHECKS #################################################################### - - -def get_ind_excluido(new): - legacy_model = legacy_app.get_model(type(new).__name__) - old = legacy_model.objects.get(**{legacy_model._meta.pk.name: new.id}) - return getattr(old, 'ind_excluido', False) - - -def check_app_no_ind_excluido(app): - for model in app.models.values(): - assert not any(get_ind_excluido(new) for new in model.objects.all()) - print('OK!') diff --git a/sapl/legacy/migracao_documentos.py b/sapl/legacy/migracao_documentos.py index 1f5ca2cd0..9ae46ef5d 100644 --- a/sapl/legacy/migracao_documentos.py +++ b/sapl/legacy/migracao_documentos.py @@ -6,7 +6,7 @@ from glob import glob import yaml from sapl.base.models import CasaLegislativa -from sapl.legacy.migration import exec_legado, warn +from sapl.legacy.migracao_dados import exec_legado, warn from sapl.materia.models import (DocumentoAcessorio, MateriaLegislativa, Proposicao) from sapl.norma.models import NormaJuridica diff --git a/sapl/legacy/migracao_usuarios.py b/sapl/legacy/migracao_usuarios.py index bd7d413e5..106a2def6 100644 --- a/sapl/legacy/migracao_usuarios.py +++ b/sapl/legacy/migracao_usuarios.py @@ -1,12 +1,13 @@ import yaml from django.contrib.auth.models import Group, User +from sapl.hashers import zope_encoded_password_to_django from sapl.settings import MEDIA_ROOT PERFIL_LEGADO_PARA_NOVO = {legado: Group.objects.get(name=novo) for legado, novo in [ ('Autor', 'Autor'), - ('Operador', 'Operador Geral'), + ('Operador', 'Operador Geral'), ('Operador Comissao', 'Operador de Comissões'), ('Operador Materia', 'Operador de Matéria'), ('Operador Modulo Administrativo', 'Operador Administrativo'), @@ -43,7 +44,7 @@ def decode_nome(nome): return nome -def migra_usuarios(): +def migrar_usuarios(): """ Lê o arquivo media/usuarios.yaml e importa os usuários nele listados, com senhas e perfis. @@ -81,14 +82,36 @@ def migra_usuarios(): set(dados['roles']) - IGNORADOS) for nome, dados in usuarios.items()] + admins = [] for nome, senha, perfis in usuarios: usuario = User.objects.get_or_create(username=nome)[0] + usuario.password = zope_encoded_password_to_django(senha) for perfil in perfis: if perfil in ADMINISTRADORES: - # Manager - usuario.is_staff = True - usuario.save() + # todos os administradores ganham perfil "Operador Geral" + usuario.groups.add(PERFIL_LEGADO_PARA_NOVO['Operador']) + admins.append(usuario) else: usuario.groups.add(PERFIL_LEGADO_PARA_NOVO[perfil]) - # apaga arquivo (importante pois contém senhas) - ARQUIVO_USUARIOS.remove() + usuario.save() + + # restringe e configura administradores + if len(admins) > 2: + admins = ( + # ususários com admin no nome + [u for u in admins if 'admin' in u.username] + # senão, o usuário saploper, apenas + or [u for u in admins if 'saploper' == u.username] + # senão, simplesmente até os dois primeiros da lista + or admins[:2] + ) + for admin in admins: + admin.is_superuser = True + admin.save() + + print('Usuários migrados com sucesso.') + print('#' * 100) + print('Uusários administradores:') + for admin in admins: + print(admin.username) + print('#' * 100) diff --git a/sapl/legacy/scripts/exporta_zope/exporta_zope.py b/sapl/legacy/scripts/exporta_zope/exporta_zope.py index e31aa4d81..31c4c3188 100755 --- a/sapl/legacy/scripts/exporta_zope/exporta_zope.py +++ b/sapl/legacy/scripts/exporta_zope/exporta_zope.py @@ -14,6 +14,7 @@ from functools import partial import magic import yaml + import ZODB.DB import ZODB.FileStorage from ZODB.broken import Broken @@ -22,6 +23,7 @@ EXTENSOES = { 'application/msword': '.doc', 'application/pdf': '.pdf', 'application/vnd.oasis.opendocument.text': '.odt', + 'application/vnd.ms-excel': '.xls', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx', # noqa 'application/xml': '.xml', 'text/xml': '.xml', @@ -219,6 +221,7 @@ DUMP_FUNCTIONS = { 'Folder': partial(dump_folder, enum=enumerate_folder), 'BTreeFolder2': partial(dump_folder, enum=enumerate_btree), 'SDE-Document': partial(dump_sde, tipo='sde.document'), + 'StrDoc': partial(dump_sde, tipo='sde.document'), 'SDE-Template': partial(dump_sde, tipo='sde.template'), # explicitamente ignorados diff --git a/sapl/legacy/scripts/migra_dbs.sh b/sapl/legacy/scripts/migra_dbs.sh index 25a78e01c..3620da089 100755 --- a/sapl/legacy/scripts/migra_dbs.sh +++ b/sapl/legacy/scripts/migra_dbs.sh @@ -2,8 +2,13 @@ # rodar esse script na raiz do projeto + if [ $# -ge 1 ]; then + # mysql com senha parallel -eta --verbose -j+0 ./sapl/legacy/scripts/migra_um_db.sh :::: <(mysql -u $1 -p$2 -e 'show databases;' | grep '^sapl_') ::: $1 ::: $2 +elif [ $# -ge 0 ]; then + # mysql sem senha + parallel -eta --verbose -j+0 ./sapl/legacy/scripts/migra_um_db.sh :::: <(mysql -u $1 -e 'show databases;' | grep '^sapl_') ::: $1 else echo "USO:" echo " $0 [senha mysql]" diff --git a/sapl/legacy/scripts/migra_um_db.sh b/sapl/legacy/scripts/migra_um_db.sh index 3d41624d3..577a2c000 100755 --- a/sapl/legacy/scripts/migra_um_db.sh +++ b/sapl/legacy/scripts/migra_um_db.sh @@ -4,13 +4,15 @@ if [ $# -ge 2 ]; then # proteje pasta com dumps de alterações acidentais - chmod -R -w ~/sapl_dumps + # chmod -R -w ~/migracao_sapl/sapl_dumps + + DIR_MIGRACAO=~/migracao_sapl DATE=$(date +%Y-%m-%d) - DIR=~/${DATE}_logs_migracao - mkdir -p $DIR + DIR_LOGS=$DIR_MIGRACAO/logs/$DATE + mkdir -p $DIR_LOGS - LOG="$DIR/$1.migracao.log" + LOG="$DIR_LOGS/$1.migracao.log" rm -f $LOG echo "########################################" | tee -a $LOG @@ -20,12 +22,12 @@ if [ $# -ge 2 ]; then if [ $3 ]; then # se há senha do mysql - mysql -u $2 -p "$3" -N -s -e "DROP DATABASE IF EXISTS $1; CREATE DATABASE $1;" - mysql -u $2 -p "$3" < ~/sapl_dumps/$1.sql + mysql -u$2 -p"$3" -N -s -e "DROP DATABASE IF EXISTS $1; CREATE DATABASE $1;" + mysql -u$2 -p"$3" < $DIR_MIGRACAO/dumps_mysql/$1.sql else # se não há senha do mysql - mysql -u $2 -N -s -e "DROP DATABASE IF EXISTS $1; CREATE DATABASE $1;" - mysql -u $2 < ~/sapl_dumps/$1.sql + mysql -u$2 -N -s -e "DROP DATABASE IF EXISTS $1; CREATE DATABASE $1;" + mysql -u$2 < $DIR_MIGRACAO/dumps_mysql/$1.sql fi; echo "O banco legado foi restaurado" |& tee -a $LOG echo >> $LOG @@ -35,9 +37,9 @@ if [ $# -ge 2 ]; then DATABASE_NAME=$1 ./manage.py migrate --settings sapl.legacy_migration_settings echo >> $LOG - echo "--- MIGRACAO DE DADOS ---" | tee -a $LOG + echo "--- MIGRACAO ---" | tee -a $LOG echo >> $LOG - DATABASE_NAME=$1 ./manage.py migracao_25_31 -f --settings sapl.legacy_migration_settings |& tee -a $LOG + DATABASE_NAME=$1 ./manage.py migracao_25_31 --force --settings sapl.legacy_migration_settings 2>&1 | tee -a $LOG echo >> $LOG else echo "USO:" diff --git a/sapl/legacy/scripts/old_names_adjustments.yaml b/sapl/legacy/scripts/old_names_adjustments.yaml deleted file mode 100644 index 75c6b8a68..000000000 --- a/sapl/legacy/scripts/old_names_adjustments.yaml +++ /dev/null @@ -1,8 +0,0 @@ -Comissao: - ind_unidade_deliberativa: unidade_deliberativa - -Legislatura: - num_legislatura: id - -Parlamentar: - municipio: municipio_residencia diff --git a/sapl/legacy/scripts/original_forms/Afastamento.html b/sapl/legacy/scripts/original_forms/Afastamento.html deleted file mode 100644 index 12512ca9a..000000000 --- a/sapl/legacy/scripts/original_forms/Afastamento.html +++ /dev/null @@ -1,219 +0,0 @@ - - - Formulário de Afastamento - - - - - - - - - - - - - - - - - - - - - -

Afastamento / Licença

- - - -

Alexandre Neu

- -
-
- - - - -
- - -
-
- - -
-
- -
- - -
-
-
- - -
-
- - -
- -
- - -
- - - -
diff --git a/sapl/legacy/scripts/original_forms/Anexada.html b/sapl/legacy/scripts/original_forms/Anexada.html deleted file mode 100644 index 1630de4e2..000000000 --- a/sapl/legacy/scripts/original_forms/Anexada.html +++ /dev/null @@ -1,289 +0,0 @@ - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - -
-
- - -

- Logotipo da Casa Legislativa -

-
-

Camara Municipal de Demonstracao

-

Sistema de Apoio ao Processo Legislativo

-
-
-
-
- Busca por palavra-chave - - -
-
-
-
-
- -
- usuário: saploper - -
-
- -
- - -
- - - - - - - - -

Matéria Legislativa

- -
- - - -
Matéria Legislativa - - - - - - - - - - - - -
- Tipo: IND - - Número: 1 - - Ano: 2015 -
- Ementa: TESTE -
- -
Matéria Anexada - - - - - - - - - - -
-  
- -
-  
- -
-  
- -
-  
- - -
-
- - -
-
-

-    -

- - -

 

-
- -
- -
-
-
-
-
- Av. George Washington, 3580 - - São José da Lagoa Tapada - PB - - CEP: 12345-678 - - Telefone: (12)3456-7890 - - Fax: (09)8765-4321 -
- - Portal: http://www.camaramunicipal.gov.br - - E-mail: faleconosco@camaramunicipal.gov.br -
-
- Desenvolvido pelo Interlegis - Desenvolvido em Zope -
-
-
- - - - diff --git a/sapl/legacy/scripts/original_forms/AssuntoNorma.html b/sapl/legacy/scripts/original_forms/AssuntoNorma.html deleted file mode 100644 index 99756a7ad..000000000 --- a/sapl/legacy/scripts/original_forms/AssuntoNorma.html +++ /dev/null @@ -1,383 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -
- - -

Tabelas Auxiliares

- - - - - -
| Início |
- - -
Assunto Norma Jurídica - - - - -
- - - - - - - -
Assunto (*)
-
Descrição
- -
-

- -    -

-
-
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/Autor.html b/sapl/legacy/scripts/original_forms/Autor.html deleted file mode 100644 index 417105dbe..000000000 --- a/sapl/legacy/scripts/original_forms/Autor.html +++ /dev/null @@ -1,491 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -
- - -

Tabelas Auxiliares

- - - - - -
| Início
- - - - - - - -
Autor - - - - - - - -
Tipo (*)
- -
Autor (*)
- -
- - -
- Acesso ao SAPL - - - - - - - -
- Conceder ao Autor acesso especial ao SAPL como usuário do perfil "Autor"?
- Sim - Não -
-  Login:   -
-
-
-
- -    -
-
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/Autoria.html b/sapl/legacy/scripts/original_forms/Autoria.html deleted file mode 100644 index abf614573..000000000 --- a/sapl/legacy/scripts/original_forms/Autoria.html +++ /dev/null @@ -1,487 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -Ajuda - -

Matéria Legislativa

- - - -
- - - - - - -
- -
- - - - - - - - - - -
- Tipo: Emenda - - Número: 6 - - Ano: 2014 -
- Ementa: DEPOIS DA PARALISAÇÃO DA REFORMA DO AUTÓDROMO INTERNACIONAL NELSON PIQUET E O CONSEQUENTE CANCELAMENTO DA ETAPA DE ABERTURA DA FÓRMULA INDY EM BRASÍLIA, O GOVERNADOR DO DISTRITO FEDERAL, RODRIGO ROLLEMBERG, E O PRESIDENTE DO TRIBUNAL DE CONTAS DO DF, RENATO RAINHA, FIZERAM UMA VISITA TÉCNICA AO LOCAL NA MANHÃ DESTA QUINTA-FEIRA (12/2). APESAR DA EXPECTATIVA, NÃO HOUVE O ANÚNCIO DE UMA DATA PARA A RETOMADA DAS OBRAS. EM CONVERSA COM OS REPÓRTERES PRESENTES, ROLLEMBERG EVITOU FALAR SOBRE DATAS. “O PRAZO É O DA SEGURANÇA JURÍDICA. NÓS TEMOS A DETERMINAÇÃO DE RECUPERAR O AUTÓDROMO COMO UM EQUIPAMENTO PÚBLICO IMPORTANTE PARA A CIDADE, MAS QUEREMOS FAZER ISSO COM TODA SEGURANÇA. -
- - -
Autoria - - - - - - -
 
- -
 
- -
-  
- - Sim - - Não -
-
-

- -

-
- -
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/Bancada.html b/sapl/legacy/scripts/original_forms/Bancada.html deleted file mode 100644 index 41042f9a9..000000000 --- a/sapl/legacy/scripts/original_forms/Bancada.html +++ /dev/null @@ -1,645 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - - - - - Ajuda - -

Tabelas Auxiliares

- - - - - -
| Voltar |
- -
- - - -
- Cadastro de Bancada, Bloco, Frente ou Grupo - - - - - - - - - - - - - - - -

- -

- -

- -

- -

- -

- -
- -

- -

- -
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/CasaLegislativa.html b/sapl/legacy/scripts/original_forms/CasaLegislativa.html deleted file mode 100644 index 17f94aba0..000000000 --- a/sapl/legacy/scripts/original_forms/CasaLegislativa.html +++ /dev/null @@ -1,544 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - -
- - - - - - -

Tabelas Auxiliares

- -
Casa Legislativa - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

-

-

-

-

-

- - -

- -

-

-

-

- cor

- cor

- cor

- - -

-

-

-
-

- -   

-
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/Coligacao.html b/sapl/legacy/scripts/original_forms/Coligacao.html deleted file mode 100644 index 1b0baef09..000000000 --- a/sapl/legacy/scripts/original_forms/Coligacao.html +++ /dev/null @@ -1,486 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -
- - -

Tabelas Auxiliares

- - - - - - -
- | Composição || Início |
-
Coligação - - - - - - - - -
- - - - - - -
Nome (*)
-
Nº Legislatura (*)
- -
Nº Votos Recebidos
-

-

- -    -

-
-
-
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/Comissao.html b/sapl/legacy/scripts/original_forms/Comissao.html deleted file mode 100644 index 1d5d419ba..000000000 --- a/sapl/legacy/scripts/original_forms/Comissao.html +++ /dev/null @@ -1,604 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - - - - -Ajuda -

Comissões

- - -
- - - - - - -
- Dados Básicos - - - - - - - - - - - -
Nome da Comissâo  
- -
Sigla  
- -
Tipo  
- -
Data Criação  
- -
Unidade Deliberativa 
- - Não - - Sim -
Data Extinção
- -
-
- -
- Dados Complementares - - - - - - - - - - - - - - - - - - - -
Local Reunião
- -
Data/Hora Reunião
- -
Tel. Sala Reunião
- -
Endereço Secretaria
- -
Tel. Secretaria
- -
Fax Secretaria
- -
Secretário
- -
E-mail
- -
Finalidade - -
-
- -
- Temporária - - - - - - - - - - -
Apelido
- -
Data Instalação
- -
Data Prevista Término
- -
Novo Prazo
- -
Data Término
- -
-
-

- -

-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/ComposicaoComissao.html b/sapl/legacy/scripts/original_forms/ComposicaoComissao.html deleted file mode 100644 index 460443369..000000000 --- a/sapl/legacy/scripts/original_forms/ComposicaoComissao.html +++ /dev/null @@ -1,508 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - - -

Comissão de Constituição e Justiça

-
- - - - - - - -
- Composição - - - - - - - - - - - - - - - - -
Parlamentar  
- -
Titular  
- - Não - - Sim -
Cargo  
- -
Data Designação  
- -
Data Desligamento
- -
Motivo Desligamento
- -
Observação
- -
-
- -

- -    -

-
-
-
- -
-
- - -
-
-
- - - \ No newline at end of file diff --git a/sapl/legacy/scripts/original_forms/Dependente.html b/sapl/legacy/scripts/original_forms/Dependente.html deleted file mode 100644 index 81b14d925..000000000 --- a/sapl/legacy/scripts/original_forms/Dependente.html +++ /dev/null @@ -1,485 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - - - - -Ajuda - - - - -

Dependente

- -
- - - - - - - -
Dependentes - - - - - - - - - - - - - - - -
Nome  
- -
Tipo  
- -
Sexo  
- - Masculino - - Feminino -
Data Nascimento
- -
CPF
- -
RG
- -
Nº Título Eleitor
- -
-
-

- -    -


-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/DocumentoAcessorio.html b/sapl/legacy/scripts/original_forms/DocumentoAcessorio.html deleted file mode 100644 index 13eae1195..000000000 --- a/sapl/legacy/scripts/original_forms/DocumentoAcessorio.html +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - - - - - - - - -Ajuda - -

Matéria Legislativa

- - - -
- -
- - - - - - - - - - -
- Tipo: Emenda - - Número: 6 - - Ano: 2014 -
- Ementa: DEPOIS DA PARALISAÇÃO DA REFORMA DO AUTÓDROMO INTERNACIONAL NELSON PIQUET E O CONSEQUENTE CANCELAMENTO DA ETAPA DE ABERTURA DA FÓRMULA INDY EM BRASÍLIA, O GOVERNADOR DO DISTRITO FEDERAL, RODRIGO ROLLEMBERG, E O PRESIDENTE DO TRIBUNAL DE CONTAS DO DF, RENATO RAINHA, FIZERAM UMA VISITA TÉCNICA AO LOCAL NA MANHÃ DESTA QUINTA-FEIRA (12/2). APESAR DA EXPECTATIVA, NÃO HOUVE O ANÚNCIO DE UMA DATA PARA A RETOMADA DAS OBRAS. EM CONVERSA COM OS REPÓRTERES PRESENTES, ROLLEMBERG EVITOU FALAR SOBRE DATAS. “O PRAZO É O DA SEGURANÇA JURÍDICA. NÓS TEMOS A DETERMINAÇÃO DE RECUPERAR O AUTÓDROMO COMO UM EQUIPAMENTO PÚBLICO IMPORTANTE PARA A CIDADE, MAS QUEREMOS FAZER ISSO COM TODA SEGURANÇA. -
- - -
-
- Documento Acessório - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-   -
- -
-  
-
-  
- -
-  
- - Pesquisar -
-
- -
-
-
-
- -
-
- -
- - -
- -

- -    - -

-
- - -
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/DocumentoAcessorioAdministrativo.html b/sapl/legacy/scripts/original_forms/DocumentoAcessorioAdministrativo.html deleted file mode 100644 index e6c0b0a3a..000000000 --- a/sapl/legacy/scripts/original_forms/DocumentoAcessorioAdministrativo.html +++ /dev/null @@ -1,333 +0,0 @@ - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - -
-
- - -

- Logotipo da Casa Legislativa -

-
-

Câmara Municipal de Demonstração

-

Sistema de Apoio ao Processo Legislativo

-
-
-
-
- Busca por palavra-chave - - -
-
-
-
-
- -
- usuário: saploper - -
-
- -
- - -
- - - - - - - - - -

Documento Administrativo

- -
- -
Documento Administrativo - - - - - - - - - - - -
- Tipo: CNV - - Número: 1 - - Ano: 2015 -
- Assunto: AAAAA -
- - -
Documento Acessório - - - - - - - - - - - - - - - - - - -

- -

- -

- - -

- -

- -
-

- -
- - -
- -

- -    -

- - - -
-
-
-
-
-
- Av. George Washington, 3580 - - São José da Lagoa Tapada - PB - - CEP: 12345-678 - - Telefone: (12)3456-7890 - - Fax: (09)8765-4321 -
- - Portal: http://www.camaramunicipal.gov.br - - E-mail: faleconosco@camaramunicipal.gov.br -
-
- Desenvolvido pelo Interlegis - Desenvolvido em Zope -
-
-
- - - - diff --git a/sapl/legacy/scripts/original_forms/DocumentoAdministrativo.html b/sapl/legacy/scripts/original_forms/DocumentoAdministrativo.html deleted file mode 100644 index 319ce746a..000000000 --- a/sapl/legacy/scripts/original_forms/DocumentoAdministrativo.html +++ /dev/null @@ -1,429 +0,0 @@ - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - -
-
- - -

- Logotipo da Casa Legislativa -

-
-

Câmara Municipal de Demonstração

-

Sistema de Apoio ao Processo Legislativo

-
-
-
-
- Busca por palavra-chave - - -
-
-
-
-
- -
- usuário: saploper - -
-
- -
- - -
- - - - - - - - -Ajuda -

Documentos Administrativos

- -
- -
- Formulário de Cadastro - - - - - -
- Indentificação Básica - - - - - - - - - - - - - - - - - - - - - -

- -

- - -

- - -

- - - -

- -
  - -

- -
 
- - Sim - - Não -

- - -
-
- -
- Outras Informações - - - - - - - -

- -

- - -
- -
-
-

- -    -

-
-
- -
-
-
-
-
- Av. George Washington, 3580 - - São José da Lagoa Tapada - PB - - CEP: 12345-678 - - Telefone: (12)3456-7890 - - Fax: (09)8765-4321 -
- - Portal: http://www.camaramunicipal.gov.br - - E-mail: faleconosco@camaramunicipal.gov.br -
-
- Desenvolvido pelo Interlegis - Desenvolvido em Zope -
-
-
- - - - diff --git a/sapl/legacy/scripts/original_forms/ExpedienteMateria.html b/sapl/legacy/scripts/original_forms/ExpedienteMateria.html deleted file mode 100644 index 18688b1b4..000000000 --- a/sapl/legacy/scripts/original_forms/ExpedienteMateria.html +++ /dev/null @@ -1,250 +0,0 @@ - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - -
-Ajuda -

Matérias do Expediente

- -

2ª Reunião Ordinária da 3ª Sessão Legislativa da 14ª Legislatura
14 de Abril de 2015 (Terça-feira) - -

- -
-
- Cadastro de Matérias do Expediente - - - - - - - - - - - - - - - - - - -
-
- -
-
- -
-
- - -
-
- -
-
- -
- -
- -
-
- - - Simbólica - - Nominal - - Secreta -
-
-
- -
-
-

- -   -   - -

- - - -
-
- diff --git a/sapl/legacy/scripts/original_forms/Filiacao.html b/sapl/legacy/scripts/original_forms/Filiacao.html deleted file mode 100644 index 94988e2d4..000000000 --- a/sapl/legacy/scripts/original_forms/Filiacao.html +++ /dev/null @@ -1,656 +0,0 @@ - - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - - -Ajuda - - - - -

Filiação

- -
- - - - - - -
- Filiações Partidárias - - - - - - -
Partido  
-
Data Filiação  
- -
Data Desfiliação
- - -
- -

- -    - -

- - - - - - - - - - -
-
- -
-
- -
-
- - -
-
-
- - - - - diff --git a/sapl/legacy/scripts/original_forms/LegislacaoCitada.html b/sapl/legacy/scripts/original_forms/LegislacaoCitada.html deleted file mode 100644 index 5fec95b9e..000000000 --- a/sapl/legacy/scripts/original_forms/LegislacaoCitada.html +++ /dev/null @@ -1,529 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - -

Matéria Legislativa

- -
- - - -
Legislação Citada - - - - - - - - - - - - - - - - - - - - - - - -
-  
-
-  
-
-  
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- - - - - - - - - -
- -
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/Legislatura.html b/sapl/legacy/scripts/original_forms/Legislatura.html deleted file mode 100644 index 66d1588cc..000000000 --- a/sapl/legacy/scripts/original_forms/Legislatura.html +++ /dev/null @@ -1,437 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -
- - -

Tabelas Auxiliares

- - - - - -
| Início |
- -
Legislatura - - - - -
- - - - - - - - - - -
Nº Legislatura (*)
-
Data Início (*)
- -
(dd/mm/aaaa)
Data Fim (*)
- -
(dd/mm/aaaa)
Data Eleição (*)
- -
(dd/mm/aaaa)
- -

- -    -

-
- -
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/LexmlRegistroProvedor.html b/sapl/legacy/scripts/original_forms/LexmlRegistroProvedor.html deleted file mode 100644 index bff82389c..000000000 --- a/sapl/legacy/scripts/original_forms/LexmlRegistroProvedor.html +++ /dev/null @@ -1,376 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -
- - -

Tabelas Auxiliares

- - -
- Provedor LexML - - - - - - - - - - - - - - - - - - - - - - - - -
Id do provedor (*)
- -
Nome do provedor
- -
Id do responsável
- -
Nome do responsável
- -
E-mail do responsável
- -
- Endereço do provedor OAI - - http://sapl3.interlegis.leg.br/oai -
XML fornecido pela equipe do LexML:
- -
- -
-
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/LexmlRegistroPublicador.html b/sapl/legacy/scripts/original_forms/LexmlRegistroPublicador.html deleted file mode 100644 index 6336408b8..000000000 --- a/sapl/legacy/scripts/original_forms/LexmlRegistroPublicador.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -
- - -

Tabelas Auxiliares

-
- Publicador LexML - - - - - - - - - - - - - - -
Id do publicador (*)
- -
Nome do publicador
- -
Sigla do publicador
- -
Id do responsável
- -
Nome do responsável
- -
E-mail do responsável
- -
- -
-
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/Mandato.html b/sapl/legacy/scripts/original_forms/Mandato.html deleted file mode 100644 index fe966bee9..000000000 --- a/sapl/legacy/scripts/original_forms/Mandato.html +++ /dev/null @@ -1,611 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - - - - - - - - - - - - - - - -

Mandato

- - - - -
- - - - -
Mandato - - - - - - - - - - - - - - - -
Legislatura  
- -
Coligação
- -
Votos Recebidos
-
- Natureza do Mandato 
- - Titular - - Suplente -
Início do Mandato  
- -
Fim do Mandato  
- -
Expedição do Diploma
- -
Observação
-
-
-

- -

- - - - - - -

- -
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/MateriaLegislativa.html b/sapl/legacy/scripts/original_forms/MateriaLegislativa.html deleted file mode 100644 index 07c174401..000000000 --- a/sapl/legacy/scripts/original_forms/MateriaLegislativa.html +++ /dev/null @@ -1,952 +0,0 @@ - - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Demonstração - - - DF -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - - - - - - -Ajuda -

Matéria Legislativa

- - - -
- - - - - - -
- Identificação Básica - - - - - - - - - - - - - - - - - - - -

- - - -

- - -

- -

- -

- - -

- - Oral - - Escrita -

- - -
-
- - Gerar ODT -
-
-
- Proposição Eletrônica - - - - -
- Esta matéria não foi gerada a partir de uma proposição eletrônica. -
-
- - -
- Outras Informações - - - - - - - - - - - - - - - - - -

- -

- -

- - Sim - - Não -

- -

-

- - Sim - - Não -

- -

- -

- - Sim - - Não -

- -
-
-
- Origem Externa - - - - - - - - - - -

- -

- -

-
-

- -

- -
-
-
- Dados Textuais - - - - - - - - - - -
  - -
- -
- -
-
- -

- - -

-
- -
-
- -
-
- - -
-
-
- - - - - diff --git a/sapl/legacy/scripts/original_forms/NormaJuridica.html b/sapl/legacy/scripts/original_forms/NormaJuridica.html deleted file mode 100644 index 4c1a1f4ab..000000000 --- a/sapl/legacy/scripts/original_forms/NormaJuridica.html +++ /dev/null @@ -1,788 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - - - - - - - - -Ajuda - -

Norma Jurídica

- - - - -
- -
- Identificação Básica - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
- -
 
- -
 
- - -
 
- -
 
- -

- - Sim - - Não -

-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -

- - - -
-
- -
 
- -

- -

- -
-
- -
-
- Assuntos (Classificação) [+] -
- - - - - - - - - - -
- -
- -
- -
-
-
-
- - -

- - -

- - - - - - -
- -
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/Numeracao.html b/sapl/legacy/scripts/original_forms/Numeracao.html deleted file mode 100644 index 494a31e33..000000000 --- a/sapl/legacy/scripts/original_forms/Numeracao.html +++ /dev/null @@ -1,542 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - - - - -Ajuda - -

Matéria Legislativa

- -
- - - - - -
- -
- - - - - - - - - - -
- Tipo: Emenda - - Número: 6 - - Ano: 2014 -
- Ementa: DEPOIS DA PARALISAÇÃO DA REFORMA DO AUTÓDROMO INTERNACIONAL NELSON PIQUET E O CONSEQUENTE CANCELAMENTO DA ETAPA DE ABERTURA DA FÓRMULA INDY EM BRASÍLIA, O GOVERNADOR DO DISTRITO FEDERAL, RODRIGO ROLLEMBERG, E O PRESIDENTE DO TRIBUNAL DE CONTAS DO DF, RENATO RAINHA, FIZERAM UMA VISITA TÉCNICA AO LOCAL NA MANHÃ DESTA QUINTA-FEIRA (12/2). APESAR DA EXPECTATIVA, NÃO HOUVE O ANÚNCIO DE UMA DATA PARA A RETOMADA DAS OBRAS. EM CONVERSA COM OS REPÓRTERES PRESENTES, ROLLEMBERG EVITOU FALAR SOBRE DATAS. “O PRAZO É O DA SEGURANÇA JURÍDICA. NÓS TEMOS A DETERMINAÇÃO DE RECUPERAR O AUTÓDROMO COMO UM EQUIPAMENTO PÚBLICO IMPORTANTE PARA A CIDADE, MAS QUEREMOS FAZER ISSO COM TODA SEGURANÇA. -
- - -
Numeração - - - - - - - - - -
-  
- -
-  
-
-  
-
-
- -
-
- -

- -

- -
- -
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/Oradores.html b/sapl/legacy/scripts/original_forms/Oradores.html deleted file mode 100644 index c050e8dd2..000000000 --- a/sapl/legacy/scripts/original_forms/Oradores.html +++ /dev/null @@ -1,572 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - -Ajuda -

Oradores das Explicações Pessoais

-

2ª Reunião Ordinária da 3ª Sessão Legislativa da 14ª Legislatura
14 de Abril de 2015 (Terça-feira) - -

- -
- - - - - - - - - - - - - - - - -
Nenhum orador cadastrado.
-
-
-
-Cadastro de Oradores do Expediente - - - - - - - - - - - - - - -
- - - - - - - - -
- -
-
-
-Cadastro de Discurso - - - - - - - - -
- - - - - -
- - - - - - - -
-
- -
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/OradoresExpediente.html b/sapl/legacy/scripts/original_forms/OradoresExpediente.html deleted file mode 100644 index c81fba1a4..000000000 --- a/sapl/legacy/scripts/original_forms/OradoresExpediente.html +++ /dev/null @@ -1,572 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - -Ajuda -

Oradores do Expediente

-

2ª Reunião Ordinária da 3ª Sessão Legislativa da 14ª Legislatura
14 de Abril de 2015 (Terça-feira) - -

- -
- - - - - - - - - - - - - - - - -
Nenhum orador cadastrado.
-
-
-
-Cadastro de Oradores do Expediente - - - - - - - - - - - - - - -
- - - - - - - - -
- -
-
-
-Cadastro de Discurso - - - - - - - - -
- - - - - -
- - - - - - - -
-
- -
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/OrdemDia.html b/sapl/legacy/scripts/original_forms/OrdemDia.html deleted file mode 100644 index 6d86301e0..000000000 --- a/sapl/legacy/scripts/original_forms/OrdemDia.html +++ /dev/null @@ -1,249 +0,0 @@ - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - -
- Ajuda -

Matérias da Ordem do Dia

- -

2ª Reunião Ordinária da 3ª Sessão Legislativa da 14ª Legislatura
14 de Abril de 2015 (Terça-feira) - -

- -
-
- Cadastro de Matérias da Ordem do Dia - - - - - - - - - - - - - - - - - - -
-
- -
-
- -
-
- - -
-
- -
-
- -
- -
- -
-
- - Simbólica - - Nominal - - Secreta -
-
-
- -
-
-

- -   -   - -

- - - -
-
- diff --git a/sapl/legacy/scripts/original_forms/Orgao.html b/sapl/legacy/scripts/original_forms/Orgao.html deleted file mode 100644 index 5711e7965..000000000 --- a/sapl/legacy/scripts/original_forms/Orgao.html +++ /dev/null @@ -1,406 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -
- - -

Tabelas Auxiliares

- - - - - -
| Início |
-
Órgão - - - - - -
- - - - - - - - - - -
Nome (*)
-
Sigla (*) - Unidade Deliberativa (*)
- - Não - - Sim -
Endereço
-
Telefone
-
-

- -    -

-
-
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/Origem.html b/sapl/legacy/scripts/original_forms/Origem.html deleted file mode 100644 index 2efaf6c19..000000000 --- a/sapl/legacy/scripts/original_forms/Origem.html +++ /dev/null @@ -1,386 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -
- - -

Tabelas Auxiliares

- - - - - -
| Início |
- -
Origem - - - - -
- - - - - -
Nome (*)
-
Sigla (*)
-
-

- -    -

-
-
-
- -
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/PainelEletronico.html b/sapl/legacy/scripts/original_forms/PainelEletronico.html deleted file mode 100644 index 6976877ed..000000000 --- a/sapl/legacy/scripts/original_forms/PainelEletronico.html +++ /dev/null @@ -1,646 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - -
- - - - -

Tabelas Auxiliares

- -
- Propriedades do Painel Eletrônico - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
-
- - cor da fonte do painel -
-
- - cor da fonte do painel -
-
- - cor da fonte do painel -
-
- -
-
- -
-
- - cor da Fonte do painel -
-
- -
-
- -
-
- - cor da fonte do painel -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- - cor da fonte do painel -
-
- -
-
- - cor da fonte do painel -
-
- -
-
- - cor da fonte do painel -
-
- -
-
- - cor da fonte do painel -
-
- -
-
- - cor da fonte do painel -
-
- -
-
- - cor da fonte do painel -
-
- -
-
- - cor da fonte do painel -
-
- -
-
- - cor da fonte do painel -
-
- -
-
- - cor da fonte do painel -
-
- -
-
- - cor da fonte do painel -
-
- -
-
- - cor da fonte do painel -
-
- -
-
- - cor da fonte do painel -
-
- -
-
- - cor da fonte do painel -
-
- -
-
- -
-
- - Texto IntegralAbrir arquivo de som - -
-
-
- -
-
- -
-
- -
-

- -   

-
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/Parlamentar.html b/sapl/legacy/scripts/original_forms/Parlamentar.html deleted file mode 100644 index 2c634f4cd..000000000 --- a/sapl/legacy/scripts/original_forms/Parlamentar.html +++ /dev/null @@ -1,763 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - - - - - - - -Ajuda - -

Parlamentares

- -
- - - - - - - -
- Cadastro do Parlamentar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Nome Parlamentar  
- -
- Login  
- -
- Ativo na Casa? 
- - Sim - - Não -
Nome Completo 
- -
Nível Instrução
- -
Sexo 
- - Masculino - - Feminino -
Data Nascimento
- - (dd/mm/aaaa) -
C.P.F
- -
R.G.
- -
Título de Eleitor
-
Situação Militar
-
Profissão
-
HomePage
-
Correio Eletrônico
-
Nº Gabinete
-
Telefone
-
Fax
-
Endereço Residencial
-
CEP
-
Município
- -
UF
-
Telefone Residencial
-
Fax Residencial
-
Locais de Atuação
-
Fotografia:
- - -
Biografia
- -
Observação
- -
- - -
- -
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/Partido.html b/sapl/legacy/scripts/original_forms/Partido.html deleted file mode 100644 index a91710511..000000000 --- a/sapl/legacy/scripts/original_forms/Partido.html +++ /dev/null @@ -1,436 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - -
- - -

Tabelas Auxiliares

- - - - - -
| Início |
- -
- -
Partido Político - - - - -
- - - - - - - - - -
Nome (*)
-
Sigla (*)
-
Data Criação
- - (dd/mm/aaaa)
Data Extinção
- - (dd/mm/aaaa)
-
-

- -    -

-
-
-
- -
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/PeriodoCompComissao.html b/sapl/legacy/scripts/original_forms/PeriodoCompComissao.html deleted file mode 100644 index 0c0d5b859..000000000 --- a/sapl/legacy/scripts/original_forms/PeriodoCompComissao.html +++ /dev/null @@ -1,412 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -
- - -

Tabelas Auxiliares

- - - - - -
| Início |
- - -
Período Composição de Comissão - - - - -
- - - - - -
Data Início (*)
- - (dd/mm/aaaa)
Data Fim (*)
- - (dd/mm/aaaa)
-

- -    -

-
-
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/PeriodoCompMesa.html b/sapl/legacy/scripts/original_forms/PeriodoCompMesa.html deleted file mode 100644 index a88375b01..000000000 --- a/sapl/legacy/scripts/original_forms/PeriodoCompMesa.html +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - - - - -

Tabelas Auxiliares

- - - - - -
| Iní­cio |
- -
- - - - - -
Perí­odo Composição da Mesa Diretora - - - - - -
Data Iní­cio  
-
Data Fim  
-
-

- -

-
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/Proposicao.html b/sapl/legacy/scripts/original_forms/Proposicao.html deleted file mode 100644 index f3570355e..000000000 --- a/sapl/legacy/scripts/original_forms/Proposicao.html +++ /dev/null @@ -1,646 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - - - - - -Ajuda - -

Proposição

- - - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - -
- Tipo  
- -
- - - -
Descrição  
- -
Matéria Vinculada
- -
Número
- -
Ano
- -
- Texto original (PDF)
- -
- Modelo ODT
- -
- -

- - -

- -
- -
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/Protocolo.html b/sapl/legacy/scripts/original_forms/Protocolo.html deleted file mode 100644 index c1f97eb70..000000000 --- a/sapl/legacy/scripts/original_forms/Protocolo.html +++ /dev/null @@ -1,429 +0,0 @@ - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - -
-
- - -

- Logotipo da Casa Legislativa -

-
-

Câmara Municipal de Demonstração

-

Sistema de Apoio ao Processo Legislativo

-
-
-
-
- Busca por palavra-chave - - -
-
-
-
-
- -
- usuário: saploper - -
-
- -
- - -
- - - - - - - - -Ajuda -

Documentos Administrativos

- -
- -
- Formulário de Cadastro - - - - - -
- Indentificação Básica - - - - - - - - - - - - - - - - - - - - - -

- -

- - -

- - -

- - - -

- -
  - -

- -
 
- - Sim - - Não -

- - -
-
- -
- Outras Informações - - - - - - - -

- -

- - -
- -
-
-

- -    -

-
-
- -
-
-
-
-
- Av. George Washington, 3580 - - São José da Lagoa Tapada - PB - - CEP: 12345-678 - - Telefone: (12)3456-7890 - - Fax: (09)8765-4321 -
- - Portal: http://www.camaramunicipal.gov.br - - E-mail: faleconosco@camaramunicipal.gov.br -
-
- Desenvolvido pelo Interlegis - Desenvolvido em Zope -
-
-
- - - - diff --git a/sapl/legacy/scripts/original_forms/RegistroVotacao.html b/sapl/legacy/scripts/original_forms/RegistroVotacao.html deleted file mode 100644 index 56814e2e2..000000000 --- a/sapl/legacy/scripts/original_forms/RegistroVotacao.html +++ /dev/null @@ -1,280 +0,0 @@ - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - -
-Ajuda -

Votação

-

1ª Reunião Ordinária da 3ª Sessão Legislativa da 14ª Legislatura
12 de Fevereiro de 2015 (Quinta-feira) - -

- -
- - - - - - - - - - - - -
- Votação Simbólica - - - - - - - - - - - - - - - - - - - - - -
- Matéria: IND 90 2009 - Indicação
- Ementa: INDICAÇÃO -
-   - - -   - - -   - - -   - -
- - -   - - Não - - Sim -
-   - -
- - -
-
- -     -
-
-
-
- - diff --git a/sapl/legacy/scripts/original_forms/Relatoria.html b/sapl/legacy/scripts/original_forms/Relatoria.html deleted file mode 100644 index c80c41e2e..000000000 --- a/sapl/legacy/scripts/original_forms/Relatoria.html +++ /dev/null @@ -1,515 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - - - -

Matéria Legislativa

- -
- - - - - - -
- -
- - - - - - - - - - -
- Tipo: Emenda - - Número: 6 - - Ano: 2014 -
- Ementa: DEPOIS DA PARALISAÇÃO DA REFORMA DO AUTÓDROMO INTERNACIONAL NELSON PIQUET E O CONSEQUENTE CANCELAMENTO DA ETAPA DE ABERTURA DA FÓRMULA INDY EM BRASÍLIA, O GOVERNADOR DO DISTRITO FEDERAL, RODRIGO ROLLEMBERG, E O PRESIDENTE DO TRIBUNAL DE CONTAS DO DF, RENATO RAINHA, FIZERAM UMA VISITA TÉCNICA AO LOCAL NA MANHÃ DESTA QUINTA-FEIRA (12/2). APESAR DA EXPECTATIVA, NÃO HOUVE O ANÚNCIO DE UMA DATA PARA A RETOMADA DAS OBRAS. EM CONVERSA COM OS REPÓRTERES PRESENTES, ROLLEMBERG EVITOU FALAR SOBRE DATAS. “O PRAZO É O DA SEGURANÇA JURÍDICA. NÓS TEMOS A DETERMINAÇÃO DE RECUPERAR O AUTÓDROMO COMO UM EQUIPAMENTO PÚBLICO IMPORTANTE PARA A CIDADE, MAS QUEREMOS FAZER ISSO COM TODA SEGURANÇA. -
- - -
Relatoria - - - - - - - - - - - - - - - -
-  
- -
-
- -
-  
- -
-
- -
-
- -
-
-

Não é possível incluir relatoria, a matéria deve estar em uma Comissão!
- -

- -
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/SessaoLegislativa.html b/sapl/legacy/scripts/original_forms/SessaoLegislativa.html deleted file mode 100644 index 6940da731..000000000 --- a/sapl/legacy/scripts/original_forms/SessaoLegislativa.html +++ /dev/null @@ -1,468 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - -
- - -

Tabelas Auxiliares

- - - - - -
| Início |
- -
-
Sessão Legislativa - - - - - - - -
- - - - - - - - - - - -
Número (*)
-
Tipo
-
Data Início (*)
- - (dd/mm/aaaa)
Data Fim (*)
- - (dd/mm/aaaa)
Início Intervalo
- - (dd/mm/aaaa)
Fim Intervalo
- - (dd/mm/aaaa)

-

- -    -

-
-
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/SessaoPlenaria.html b/sapl/legacy/scripts/original_forms/SessaoPlenaria.html deleted file mode 100644 index 9890277bd..000000000 --- a/sapl/legacy/scripts/original_forms/SessaoPlenaria.html +++ /dev/null @@ -1,734 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - - - - - - - -Ajuda - -

Sessão Plenária

- -

3ª Reunião Ordinária da 3ª Sessão Legislativa da 14ª Legislatura
08 de Maio de 2015 (Sexta-feira) - -

- - - - - - - - - - - - - -
- - - - - - - -
-Dados Básicos - - - - - - - - - - - - - - - - - - - - - - - - - -
-  
- - -      -
-  
- -
-
- -
-  
- -
-  
- Data: - -  Horário: (hh:mm) - -  Sessão iniciada? -
 
-
- Data: - - -  Horário: (hh:mm) - -  Sessão finalizada? -
 

- - - - -

- - - - -
-
-
-
-
-
-
-

- - - -

-
-
-
-
- -
-
- - -
-
-
- - - - - diff --git a/sapl/legacy/scripts/original_forms/StatusTramitacao.html b/sapl/legacy/scripts/original_forms/StatusTramitacao.html deleted file mode 100644 index f468514d2..000000000 --- a/sapl/legacy/scripts/original_forms/StatusTramitacao.html +++ /dev/null @@ -1,398 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -
- - -

Tabelas Auxiliares

- - - - - -
| Início |
-
Status Tramitação - - - - - -
- - - - - - - - -
Sigla (*)
-
Indicador da Tramitação
-
Descrição (*)
-
-
-
- -    -

-
-
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/StatusTramitacaoAdministrativo.html b/sapl/legacy/scripts/original_forms/StatusTramitacaoAdministrativo.html deleted file mode 100644 index ab83b12e2..000000000 --- a/sapl/legacy/scripts/original_forms/StatusTramitacaoAdministrativo.html +++ /dev/null @@ -1,226 +0,0 @@ - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - -
-
- - -

- Logotipo da Casa Legislativa -

-
-

Câmara Municipal de Demonstração

-

Sistema de Apoio ao Processo Legislativo

-
-
-
-
- Busca por palavra-chave - - -
-
-
-
-
- -
- usuário: saploper - -
-
- -
- - -
- - - - - -
- - -

Tabelas Auxiliares

- - - - - -
| Início |
-
Status Tramitação Administrativo - - - - - -
- - - - - - - - -
Sigla (*)
-
Indicador da Tramitação
-
Descrição (*)
-
-
-
- -    -

-
-
-
-
-
-
-
-
- Av. George Washington, 3580 - - São José da Lagoa Tapada - PB - - CEP: 12345-678 - - Telefone: (12)3456-7890 - - Fax: (09)8765-4321 -
- - Portal: http://www.camaramunicipal.gov.br - - E-mail: faleconosco@camaramunicipal.gov.br -
-
- Desenvolvido pelo Interlegis - Desenvolvido em Zope -
-
-
- - - - diff --git a/sapl/legacy/scripts/original_forms/TipoAutor.html b/sapl/legacy/scripts/original_forms/TipoAutor.html deleted file mode 100644 index 959bcb866..000000000 --- a/sapl/legacy/scripts/original_forms/TipoAutor.html +++ /dev/null @@ -1,412 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -
- - -

Tabelas Auxiliares

- - - - - -
| Início |
-
Tipo Autor - - - - -
- - - - - -
Tipo (*) -
- - Parlamentar -
- - Comissão -
- - Outros -
Descrição (*)
- - -
-

- -    - -

- -
-
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/TipoComissao.html b/sapl/legacy/scripts/original_forms/TipoComissao.html deleted file mode 100644 index 7ad6c2d91..000000000 --- a/sapl/legacy/scripts/original_forms/TipoComissao.html +++ /dev/null @@ -1,404 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -
- - -

Tabelas Auxiliares

- - - - - -
| Início |
- - -
Tipo Comissão - - - - -
- - - - - - - - - -
Nome (*)
-
Sigla (*)
-
Dispositivo Regimental
- -
Natureza (*)
-
-

- -    -

-
-
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/TipoDocumentoAdministrativo.html b/sapl/legacy/scripts/original_forms/TipoDocumentoAdministrativo.html deleted file mode 100644 index d0615cd13..000000000 --- a/sapl/legacy/scripts/original_forms/TipoDocumentoAdministrativo.html +++ /dev/null @@ -1,388 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -
- - -

Tabelas Auxiliares

- - - - - -
| Início |
- - - -
Tipo Documento Administrativo - - - - -
- - - - - -
Sigla (*)
-
Descrição (*)
-
-

- -    -

-
-
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/TipoExpediente.html b/sapl/legacy/scripts/original_forms/TipoExpediente.html deleted file mode 100644 index 4bba50675..000000000 --- a/sapl/legacy/scripts/original_forms/TipoExpediente.html +++ /dev/null @@ -1,368 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -Ajuda -

Tabelas Auxiliares

- - - - - - - -
- | Início | -
- -
-
- Tipo de Expediente - - - - - - - - - -
- - -
- -    -
-
-
- -
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/TipoNormaJuridica.html b/sapl/legacy/scripts/original_forms/TipoNormaJuridica.html deleted file mode 100644 index 26b69ca2e..000000000 --- a/sapl/legacy/scripts/original_forms/TipoNormaJuridica.html +++ /dev/null @@ -1,432 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -
- - -

Tabelas Auxiliares

- - - - - -
| Início |
- - -
Tipo Norma Jurídica - - - - -
- - - - - - -
Descrição (*)
-
Sigla (*)
-
Equivalente LexML (*)
- -
-

- -    -

-
-
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/TipoProposicao.html b/sapl/legacy/scripts/original_forms/TipoProposicao.html deleted file mode 100644 index e12faf6db..000000000 --- a/sapl/legacy/scripts/original_forms/TipoProposicao.html +++ /dev/null @@ -1,460 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -
- - -

Tabelas Auxiliares

- - - - - -
| Início |
- -
Tipo Proposição - - - - -
- - - - - - - - - - - - - - - - - -
Descrição
-
Gera
- Matéria
- Documento
-
Tipo Matéria
-
Modelo XML
-
-

- -    -

-
-
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/TipoResultadoVotacao.html b/sapl/legacy/scripts/original_forms/TipoResultadoVotacao.html deleted file mode 100644 index 5d40db62b..000000000 --- a/sapl/legacy/scripts/original_forms/TipoResultadoVotacao.html +++ /dev/null @@ -1,368 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -Ajuda -

Tabelas Auxiliares

- - - - - - - -
- | Início | -
- -
-
- Tipo de Resultado da Votação - - - - - - - - - -
- - -
- -    -
-
-
- -
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/TipoSessaoPlenaria.html b/sapl/legacy/scripts/original_forms/TipoSessaoPlenaria.html deleted file mode 100644 index 0defb9d54..000000000 --- a/sapl/legacy/scripts/original_forms/TipoSessaoPlenaria.html +++ /dev/null @@ -1,378 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -Ajuda -

Tabelas Auxiliares

- - - - - - - -
- | Início | -
- -
-
- Tipo de Sessão Plenária - - - - - - - - - - -
- - - - - -
- -    -
-
-
- -
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/TipoSituacaoNorma.html b/sapl/legacy/scripts/original_forms/TipoSituacaoNorma.html deleted file mode 100644 index b0e1bd8a9..000000000 --- a/sapl/legacy/scripts/original_forms/TipoSituacaoNorma.html +++ /dev/null @@ -1,357 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -Ajuda - -

Tabelas Auxiliares

- - - - - -
| Início |
- -
- -
Situação de Norma Jurídica - - - - -
Situação  
-
-
-

- -

-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/Tramitacao.html b/sapl/legacy/scripts/original_forms/Tramitacao.html deleted file mode 100644 index 80529566f..000000000 --- a/sapl/legacy/scripts/original_forms/Tramitacao.html +++ /dev/null @@ -1,842 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - -Ajuda -

Matéria Legislativa

- - - - - - - - - -
- - - - - - -
- -
- - - - - - - - - - -
- Tipo: Emenda - - Número: 6 - - Ano: 2014 -
- Ementa: DEPOIS DA PARALISAÇÃO DA REFORMA DO AUTÓDROMO INTERNACIONAL NELSON PIQUET E O CONSEQUENTE CANCELAMENTO DA ETAPA DE ABERTURA DA FÓRMULA INDY EM BRASÍLIA, O GOVERNADOR DO DISTRITO FEDERAL, RODRIGO ROLLEMBERG, E O PRESIDENTE DO TRIBUNAL DE CONTAS DO DF, RENATO RAINHA, FIZERAM UMA VISITA TÉCNICA AO LOCAL NA MANHÃ DESTA QUINTA-FEIRA (12/2). APESAR DA EXPECTATIVA, NÃO HOUVE O ANÚNCIO DE UMA DATA PARA A RETOMADA DAS OBRAS. EM CONVERSA COM OS REPÓRTERES PRESENTES, ROLLEMBERG EVITOU FALAR SOBRE DATAS. “O PRAZO É O DA SEGURANÇA JURÍDICA. NÓS TEMOS A DETERMINAÇÃO DE RECUPERAR O AUTÓDROMO COMO UM EQUIPAMENTO PÚBLICO IMPORTANTE PARA A CIDADE, MAS QUEREMOS FAZER ISSO COM TODA SEGURANÇA. -
- - -
Tramitação - - - - - - - - - - - - - - - - - - - -
-  
- - - -
-  
- -
-  
- -
-
- -
-  
- - Sim - - Não -
-
- -
-
- -
-
- -
-
- -
-
- -

- -

- -
- -
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/TramitacaoAdministrativo.html b/sapl/legacy/scripts/original_forms/TramitacaoAdministrativo.html deleted file mode 100644 index 3541373cd..000000000 --- a/sapl/legacy/scripts/original_forms/TramitacaoAdministrativo.html +++ /dev/null @@ -1,410 +0,0 @@ - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - -
-
- - -

- Logotipo da Casa Legislativa -

-
-

Câmara Municipal de Demonstração

-

Sistema de Apoio ao Processo Legislativo

-
-
-
-
- Busca por palavra-chave - - -
-
-
-
-
- -
- usuário: saploper - -
-
- -
- - -
- - - - -

Documento Administrativo

- - - - - - - - -
- - - - - - - -
Documento Administrativo - - - - - - - - - - - -
- Tipo: CNV - - Número: 1 - - Ano: 2015 -
- Assunto: AAAAA -
- - -
Tramitação - - - - - - - - - - - - - - - - - - - - -

- - - -

- -

-

-

- -

- - -

- -
-
-

-    -

-
-
-
-
-
-
-
- Av. George Washington, 3580 - - São José da Lagoa Tapada - PB - - CEP: 12345-678 - - Telefone: (12)3456-7890 - - Fax: (09)8765-4321 -
- - Portal: http://www.camaramunicipal.gov.br - - E-mail: faleconosco@camaramunicipal.gov.br -
-
- Desenvolvido pelo Interlegis - Desenvolvido em Zope -
-
-
- - - - diff --git a/sapl/legacy/scripts/original_forms/UnidadeTramitacao.html b/sapl/legacy/scripts/original_forms/UnidadeTramitacao.html deleted file mode 100644 index ed2fea4ae..000000000 --- a/sapl/legacy/scripts/original_forms/UnidadeTramitacao.html +++ /dev/null @@ -1,854 +0,0 @@ - - - - - - - - - - - - - Sistema de Apoio ao Processo Legislativo - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-

- Câmara Municipal de Piraí - - - RJ -

-

Sistema de Apoio ao Processo Legislativo

-
-
-
- -
- - - - - -
- - -

Tabelas Auxiliares

- - - - - -
| Início |
-
- Unidade Tramitação - - - - - - - - - - - -
Órgão
-
Correspondente SPDO
- -
Comissão
- -
Parlamentar
- -
-
-

- -    -

- -
-
-
-
- -
-
- - -
-
-
- - - diff --git a/sapl/legacy/scripts/original_forms/_auxiliares.txt b/sapl/legacy/scripts/original_forms/_auxiliares.txt deleted file mode 100644 index 992ec93ce..000000000 --- a/sapl/legacy/scripts/original_forms/_auxiliares.txt +++ /dev/null @@ -1,24 +0,0 @@ -AssuntoNormaJuridica -Autor -Bancada -CasaLegislativa -Coligacao -LexmlProvedor -LexmlPublicador -Orgao -Origem -PainelEletronico -Partido -PeriodoCompComissao -PeriodoCompMesa -SessaoLegislativa -StatusTramitacao -TipoAutor -TipoComissao -TipoExpediente -TipoNormaJuridica -TipoProposicao -TipoResultadoVotacao -TipoSessaoPlenaria -TipoSituacaoNorma -UnidadeTramitacao diff --git a/sapl/legacy/scripts/original_forms/_not_found_among_sapl25_models.txt b/sapl/legacy/scripts/original_forms/_not_found_among_sapl25_models.txt deleted file mode 100644 index 0f2f2e62c..000000000 --- a/sapl/legacy/scripts/original_forms/_not_found_among_sapl25_models.txt +++ /dev/null @@ -1,6 +0,0 @@ -CasaLegislativa -Bancada -PainelEletronico - -TipoSituacaoNorma -PeriodoCompMesa diff --git a/sapl/legacy/scripts/original_forms/_principais.txt b/sapl/legacy/scripts/original_forms/_principais.txt deleted file mode 100644 index ad6343feb..000000000 --- a/sapl/legacy/scripts/original_forms/_principais.txt +++ /dev/null @@ -1,7 +0,0 @@ -Comissao -MateriaLegislativa -NormaJuridica -Parlamentar -Proposicao -Protocolo -SessaoPlenaria diff --git a/sapl/legacy/scripts/scrap_original_forms.py b/sapl/legacy/scripts/scrap_original_forms.py deleted file mode 100644 index fb6927cdc..000000000 --- a/sapl/legacy/scripts/scrap_original_forms.py +++ /dev/null @@ -1,283 +0,0 @@ -import os -import pprint -import re -import string - -import pkg_resources -import yaml -from bs4 import BeautifulSoup -from bs4.element import NavigableString, Tag - -from django.apps.config import AppConfig -from sapl.crispy_layout_mixin import heads_and_tails -from sapl.legacy.migration import appconfs, get_renames -from sapl.legacy.scripts.utils import getsourcelines -from sapl.utils import listify - -# to prevent removal by automatic organize imports on this file -assert appconfs - -field_renames, model_renames = get_renames() - - -def _read_line(tr): - for td in tr.find_all('td'): - label = td.text.strip().split('\n')[0].strip( - '\xa0' + string.whitespace) - if label.endswith('(*)'): - label = label[:-3].strip() - names = [c.attrs['name'] - for c in td.findAll() - if isinstance(c, Tag) and 'name' in c.attrs] - if names: - name = names[0].split('_', 1)[-1] - yield name, label - - -def extract_title_and_fieldsets(model): - filename = os.path.join(os.path.dirname(__file__), - 'original_forms/%s.html' % model.__name__) - try: - with open(filename, 'r') as file: - html_doc = file.read() - except IOError: - return None, [] - - soup = BeautifulSoup(html_doc, 'html.parser') - forms = soup.find_all('form') - [form] = [f for f in forms if ('method', 'post') in f.attrs.items()] - # children are either tags or strings... - assert set(type(c) for c in form.children) == {Tag, NavigableString} - # ... and all strings are empty - assert all(not c.strip() - for c in form.children if isinstance(c, NavigableString)) - - title = soup.find('h1', {'class': 'firstHeading'}) - title = title.text.strip() if title else None - fieldsets = [dict( - legend=fieldset.find('legend').text if fieldset.find('legend') else '', - lines=[list(_read_line(tr)) for tr in fieldset.find_all('tr')]) - for fieldset in form.find_all('fieldset')] - - return title, fieldsets - - -def get_names_labels(fieldsets): - for fieldset in fieldsets: - for line in fieldset['lines']: - for name, label in line: - yield name, label - - -def print_title_and_fieldsets(model): - title, fieldsets = extract_title_and_fieldsets(model) - print('#### %s ####\n' % title) - for fieldset in fieldsets: - print(fieldset['legend']) - for line in fieldset['lines']: - print(' ' + ' | '.join('%s : %s' % (id, label) - for id, label in line)) - - -def extract_verbose_names(model): - title, fieldsets = extract_title_and_fieldsets(model) - names_to_labels = dict(get_names_labels(fieldsets)) - - field_names = [f.name for f in model._meta.fields if f.name != 'id'] - - labels = {} - field_names_to_old = field_renames[model] - for name in field_names: - old_name = field_names_to_old[name] - label = names_to_labels.get(old_name, None) - if label: - labels[name] = label - del names_to_labels[old_name] - for name, label in labels.items(): - field_names.remove(name) - non_matched = field_names, names_to_labels - return title, labels, non_matched - - -@listify -def source_with_verbose_names(model): - source = getsourcelines(model) - title, labels, non_matched = extract_verbose_names(model) - - field_regex = ' *(.+) = (models\.[^\(]*)\((.*verbose_name=_\(.*\)|.*)\)' - new_lines = [] - class_meta_already_exists = False - for line in source[1:]: - for regex, split in [ - (field_regex + ' *# (.+)', lambda groups: groups), - (field_regex, lambda groups: groups + ('',))]: - match = re.match(regex, line) - if match: - name, path, args, legacy_name = split(match.groups()) - if name in labels and 'verbose_name' not in args: - args = [args] if args.strip() else [] - args.append("verbose_name=_('%s')" % labels[name]) - args = ', '.join(args) - new_lines.append( - (' %s = %s(%s)' % (name, path, args), legacy_name)) - break - else: - if 'class Meta:' in line: - class_meta_already_exists = True - new_lines.append((line, '')) - yield source[0].rstrip() - cols = max(map(len, [line for line, _ in new_lines])) - for line, legacy_name in new_lines: - line = line.rstrip().ljust(cols) - if legacy_name: - yield line + ' # ' + legacy_name - else: - yield line - - # class Meta - if class_meta_already_exists: - return - - if title == 'Tabelas Auxiliares': - title = '' - title = title if title else '' - - def add_s(name): - return ' '.join( - p if p.endswith('s') else p + 's' for p in name.split()) - - def remove_s(name): - return ' '.join(p[:-1] if p.endswith('s') else p for p in name.split()) - - if not title: - # default title from model name - title_singular = ' '.join(re.findall('[A-Z][^A-Z]*', model.__name__)) - title_singular = re.sub('cao\\b', 'ção', title_singular) - title_singular = re.sub('ao\\b', 'ão', title_singular) - title_plural = add_s( - title_singular.replace('ção', 'ções').replace('ão', 'ões')) - - elif title.endswith('s'): - title_singular = remove_s( - title.replace('ções', 'ção').replace('ões', 'ão')) - title_plural = title - else: - title_singular = title - title_plural = add_s(title.replace('ção', 'ções').replace('ão', 'ões')) - - yield """ - class Meta: - verbose_name = _('%s') - verbose_name_plural = _('%s')""" % (title_singular, title_plural) - - -def print_app_with_verbose_names(app): - print('##################################################################') - header = '# -*- coding: utf-8 -*-\n' - for line in getsourcelines(app.models_module): - if line in ['# -*- coding: utf-8 -*-', - 'from django.utils.translation import ugettext as _', ]: - continue - elif line == 'from django.db import models': - header += '''from django.db import models -from django.utils.translation import ugettext_lazy as _ -''' - elif 'class' in line: - break - else: - header += line + '\n' - print(header.strip()) - for model in app.models.values(): - print('\n') - for p in source_with_verbose_names(model): - print(p) - - -def list_models_with_no_scrapped_data(app): - for model in app.models.values(): - if not any(extract_verbose_names(model)[:2]): - print(model.__name__) - - -@listify -def colsplit(names): - n = len(names) - d, r = 12 // n, 12 % n - spans = [d + 1] * r + [d] * (n - r) - return zip(names, spans) - - -def model_name_as_snake(model): - return re.sub('([A-Z]+)', r'_\1', model.__name__).lower().strip('_') - - -old_names_adjustments = yaml.load(pkg_resources.resource_string( - __name__, 'old_names_adjustments.yaml')) - - -@listify -def extract_fieldsets_for_current(model): - __, fieldsets = extract_title_and_fieldsets(model) - if not fieldsets: - return - - try: - reverse_field_renames = {v: k for k, v in field_renames[model].items()} - adjustments = old_names_adjustments.get(model.__name__) - if adjustments: - reverse_field_renames.update(adjustments) - - for fieldset in fieldsets: - rows = [ - colsplit( - [reverse_field_renames.get(name, '%s_FIXME' % name) - for name, ___ in line]) - for line in fieldset['lines'] if line - ] - yield [fieldset['legend']] + rows - except Exception as e: - print_title_and_fieldsets(model) - raise Exception(e, model) - - -class Under: - - def __init__(self, arg): - self.arg = arg - - def __repr__(self): - return "_('%s')" % self.arg - - -GAP = 12 -pretty_printer = pprint.PrettyPrinter(width=80 - GAP) - - -def print_crispy_form(model_or_app): - if isinstance(model_or_app, AppConfig): - for model in model_or_app.models.values(): - print_crispy_form(model) - else: - model = model_or_app - - fieldsets = extract_fieldsets_for_current(model) - if fieldsets: - print(""" -class %(name)sForm(forms.ModelForm): - - class Meta: - model = %(name)s - exclude = [] - - def __init__(self, *args, **kwargs): - super(%(name)sForm, self).__init__(*args, **kwargs) - self.helper = FormHelper() - self.helper.layout = SaplFormLayout( -""" % {'name': model.__name__}) - - for legend, rows in heads_and_tails(fieldsets): - lines = pretty_printer.pformat([Under(legend)] + rows) + ',\n\n' - for line in lines.splitlines(): - print(' ' * GAP + line if line.strip() else '') - - print(" )") diff --git a/sapl/legacy/scripts/study.py b/sapl/legacy/scripts/study.py deleted file mode 100644 index a28428c7d..000000000 --- a/sapl/legacy/scripts/study.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.apps import apps -from sapl.legacy.migration import legacy_app - -for model in apps.get_app_config('legacy').get_models(): - if 'ind_excluido' in [f.name for f in model._meta.fields]: - print(model, model.objects.values_list( - 'ind_excluido', flat=True).distinct()) - -legacy_models_without_ind_excluido = [ - m for m in legacy_app.models.values() - if not any(f.name == 'ind_excluido' for f in m._meta.fields)] diff --git a/sapl/legacy/scripts/utils.py b/sapl/legacy/scripts/utils.py index 21f74ce52..0cf623be5 100644 --- a/sapl/legacy/scripts/utils.py +++ b/sapl/legacy/scripts/utils.py @@ -1,19 +1,14 @@ import inspect from sapl.base.models import Autor -from sapl.legacy.migration import appconfs +from sapl.legacy.migracao_dados import appconfs -def getsourcelines(model): - return [line.rstrip('\n').decode('utf-8') - for line in inspect.getsourcelines(model)[0]] +def get_models_com_referencia_a(apontado): - -def get_models_com_referencia_a_autor(): - - def tem_referencia_a_autor(model): - return any(getattr(field, 'related_model', None) == Autor + def tem_referencia_a_apontado(model): + return any(getattr(field, 'related_model', None) == apontado for field in model._meta.get_fields()) return [model for app in appconfs for model in app.models.values() - if tem_referencia_a_autor(model)] + if tem_referencia_a_apontado(model)] diff --git a/sapl/legacy/test_migration.py b/sapl/legacy/test_migracao_dados.py similarity index 91% rename from sapl/legacy/test_migration.py rename to sapl/legacy/test_migracao_dados.py index efaa2e6d6..061264165 100644 --- a/sapl/legacy/test_migration.py +++ b/sapl/legacy/test_migracao_dados.py @@ -1,7 +1,8 @@ from random import shuffle -from .migration import (_formatar_lista_para_sql, get_autorias_sem_repeticoes, - get_reapontamento_de_autores_repetidos) +from .migracao_dados import (_formatar_lista_para_sql, + get_autorias_sem_repeticoes, + get_reapontamento_de_autores_repetidos) def test_unifica_autores_repetidos_no_legado(): diff --git a/sapl/legacy/test_renames.py b/sapl/legacy/test_renames.py index 27987aeeb..7a8766da0 100644 --- a/sapl/legacy/test_renames.py +++ b/sapl/legacy/test_renames.py @@ -3,7 +3,7 @@ import sapl.materia import sapl.norma import sapl.sessao -from .migration import appconfs, get_renames, legacy_app +from .migracao_dados import appconfs, get_renames, legacy_app RENAMING_IGNORED_MODELS = [ sapl.comissoes.models.Composicao, diff --git a/sapl/lexml/urls.py b/sapl/lexml/urls.py index a10299ca0..e7d582030 100644 --- a/sapl/lexml/urls.py +++ b/sapl/lexml/urls.py @@ -1,4 +1,5 @@ from django.conf.urls import include, url + from sapl.lexml.views import LexmlProvedorCrud, LexmlPublicadorCrud from .apps import AppConfig diff --git a/sapl/materia/admin.py b/sapl/materia/admin.py index 8da7408b1..6fb649296 100644 --- a/sapl/materia/admin.py +++ b/sapl/materia/admin.py @@ -1,15 +1,18 @@ +from django.conf import settings from django.contrib import admin + +from sapl.base.models import TipoAutor +from sapl.comissoes.models import TipoComissao from sapl.materia.models import Proposicao -from sapl.settings import DEBUG +from sapl.parlamentares.models import (SituacaoMilitar, TipoAfastamento, + TipoDependente) from sapl.utils import register_all_models_in_admin register_all_models_in_admin(__name__) -if not DEBUG: - - admin.site.unregister(Proposicao) +if not settings.DEBUG: - class ProposicaoAdmin(admin.ModelAdmin): + class RestricaoAdmin(admin.ModelAdmin): def has_add_permission(self, request, obj=None): return False @@ -20,4 +23,16 @@ if not DEBUG: def has_delete_permission(self, request, obj=None): return False - admin.site.register(Proposicao, ProposicaoAdmin) + models = ( + Proposicao, + TipoAutor, + TipoComissao, + TipoAfastamento, + SituacaoMilitar, + TipoDependente + ) + + for model in models: + if admin.site.is_registered(model): + admin.site.unregister(model) + admin.site.register(model, RestricaoAdmin) diff --git a/sapl/materia/email_utils.py b/sapl/materia/email_utils.py index a28fcbc5f..3dc6b220d 100644 --- a/sapl/materia/email_utils.py +++ b/sapl/materia/email_utils.py @@ -4,6 +4,7 @@ from django.core.mail import EmailMultiAlternatives, get_connection, send_mail from django.core.urlresolvers import reverse from django.template import Context, loader from django.utils import timezone + from sapl.base.models import CasaLegislativa from sapl.settings import EMAIL_SEND_USER diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index 038a24d82..7f9e8d114 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -2,7 +2,6 @@ import os import django_filters -import sapl from crispy_forms.bootstrap import Alert, FormActions, InlineRadios from crispy_forms.helper import FormHelper from crispy_forms.layout import (HTML, Button, Column, Div, Field, Fieldset, @@ -23,7 +22,9 @@ from django.utils.encoding import force_text from django.utils.html import format_html from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ -from sapl.base.models import Autor, TipoAutor + +import sapl +from sapl.base.models import AppConfig, Autor, TipoAutor from sapl.comissoes.models import Comissao from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC, STATUS_TA_PRIVATE) @@ -40,8 +41,8 @@ from sapl.settings import MAX_DOC_UPLOAD_SIZE from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, ChoiceWithoutValidationField, MateriaPesquisaOrderingFilter, RangeWidgetOverride, - autor_label, autor_modal, models_with_gr_for_model, - qs_override_django_filter, gerar_hash_arquivo) + autor_label, autor_modal, gerar_hash_arquivo, + models_with_gr_for_model, qs_override_django_filter) from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial, DocumentoAcessorio, Numeracao, Proposicao, Relatoria, @@ -144,6 +145,9 @@ class MateriaSimplificadaForm(ModelForm): def clean(self): super(MateriaSimplificadaForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + cleaned_data = self.cleaned_data data_apresentacao = cleaned_data['data_apresentacao'] @@ -166,6 +170,9 @@ class MateriaLegislativaForm(ModelForm): def clean(self): super(MateriaLegislativaForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + cleaned_data = self.cleaned_data data_apresentacao = cleaned_data['data_apresentacao'] @@ -196,6 +203,9 @@ class UnidadeTramitacaoForm(ModelForm): def clean(self): super(UnidadeTramitacaoForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + cleaned_data = self.cleaned_data for key in list(cleaned_data.keys()): @@ -254,6 +264,9 @@ class RelatoriaForm(ModelForm): def clean(self): super(RelatoriaForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + cleaned_data = self.cleaned_data try: @@ -286,7 +299,12 @@ class TramitacaoForm(ModelForm): self.fields['data_tramitacao'].initial = timezone.now().date() def clean(self): - cleaned_data = super(TramitacaoForm, self).clean() + super(TramitacaoForm, self).clean() + + if not self.is_valid(): + return self.cleaned_data + + cleaned_data = self.cleaned_data if 'data_encaminhamento' in cleaned_data: data_enc_form = cleaned_data['data_encaminhamento'] @@ -295,9 +313,6 @@ class TramitacaoForm(ModelForm): if 'data_tramitacao' in cleaned_data: data_tram_form = cleaned_data['data_tramitacao'] - if self.errors: - return self.errors - ultima_tramitacao = Tramitacao.objects.filter( materia_id=self.instance.materia_id).exclude( id=self.instance.id).order_by( @@ -366,6 +381,11 @@ class TramitacaoUpdateForm(TramitacaoForm): } def clean(self): + super(TramitacaoUpdateForm, self).clean() + + if not self.is_valid(): + return self.cleaned_data + ultima_tramitacao = Tramitacao.objects.filter( materia_id=self.instance.materia_id).order_by( '-data_tramitacao', @@ -423,8 +443,8 @@ class LegislacaoCitadaForm(ModelForm): def clean(self): super(LegislacaoCitadaForm, self).clean() - if self.errors: - return self.errors + if not self.is_valid(): + return self.cleaned_data cleaned_data = self.cleaned_data @@ -486,8 +506,8 @@ class NumeracaoForm(ModelForm): def clean(self): super(NumeracaoForm, self).clean() - if self.errors: - return self.errors + if not self.is_valid(): + return self.cleaned_data try: MateriaLegislativa.objects.get( @@ -531,8 +551,8 @@ class AnexadaForm(ModelForm): def clean(self): super(AnexadaForm, self).clean() - if self.errors: - return self.errors + if not self.is_valid(): + return self.cleaned_data cleaned_data = self.cleaned_data @@ -724,8 +744,8 @@ class DespachoInicialForm(ModelForm): def clean(self): super(DespachoInicialForm, self).clean() - if self.errors: - return self.errors + if not self.is_valid(): + return self.cleaned_data if DespachoInicial.objects.filter( materia=self.instance.materia, @@ -769,8 +789,8 @@ class AutoriaForm(ModelForm): def clean(self): cd = super(AutoriaForm, self).clean() - if self.errors: - return self.errors + if not self.is_valid(): + return self.cleaned_data autorias = Autoria.objects.filter( materia=self.instance.materia, autor=cd['autor']) @@ -992,6 +1012,9 @@ class TipoProposicaoForm(ModelForm): def clean(self): super(TipoProposicaoForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + cd = self.cleaned_data content_type = cd['content_type'] @@ -1136,7 +1159,7 @@ class ProposicaoForm(forms.ModelForm): widgets = { 'descricao': widgets.Textarea(attrs={'rows': 4}), 'tipo': TipoProposicaoSelect(), - 'hash_code': forms.HiddenInput(),} + 'hash_code': forms.HiddenInput(), } def __init__(self, *args, **kwargs): self.texto_articulado_proposicao = sapl.base.models.AppConfig.attr( @@ -1209,10 +1232,26 @@ class ProposicaoForm(forms.ModelForm): "Arquivo muito grande. ( > {0}MB )".format(max_size)) return texto_original + def gerar_hash(self, inst, receber_recibo): + + inst.save() + if receber_recibo == True: + inst.hash_code = '' + else: + if inst.texto_original: + inst.hash_code = gerar_hash_arquivo( + inst.texto_original.path, str(inst.pk)) + elif inst.texto_articulado.exists(): + ta = inst.texto_articulado.first() + # FIXME hash para textos articulados + inst.hash_code = 'P' + ta.hash() + '/' + str(inst.pk) def clean(self): super(ProposicaoForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + cd = self.cleaned_data tm, am, nm = (cd.get('tipo_materia', ''), @@ -1235,6 +1274,7 @@ class ProposicaoForm(forms.ModelForm): def save(self, commit=True): cd = self.cleaned_data inst = self.instance + receber_recibo = AppConfig.objects.last().receber_recibo_proposicao if inst.pk: if 'tipo_texto' in cd: @@ -1249,6 +1289,8 @@ class ProposicaoForm(forms.ModelForm): not cd['texto_original'] and \ inst.texto_original: inst.texto_original.delete() + self.gerar_hash(inst, receber_recibo) + return super().save(commit) @@ -1260,13 +1302,7 @@ class ProposicaoForm(forms.ModelForm): inst.numero_proposicao = ( numero__max + 1) if numero__max else 1 - inst.save() - if cd['receber_recibo'] == 'True': - inst.hash_code = '' - else: - _hash = gerar_hash_arquivo(inst.texto_original.path, str(inst.pk)) - - inst.hash_code = _hash + self.gerar_hash(inst, receber_recibo) inst.save() @@ -1308,6 +1344,9 @@ class DevolverProposicaoForm(forms.ModelForm): def clean(self): super(DevolverProposicaoForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + cd = self.cleaned_data if 'justificativa_devolucao' not in cd or\ @@ -1490,6 +1529,9 @@ class ConfirmarProposicaoForm(ProposicaoForm): def clean(self): super(ConfirmarProposicaoForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + numeracao = sapl.base.models.AppConfig.attr('sequencia_numeracao') if not numeracao: @@ -1565,23 +1607,26 @@ class ConfirmarProposicaoForm(ProposicaoForm): except AttributeError: pass - tipo = proposicao.tipo.tipo_conteudo_related + tipo = self.instance.tipo.tipo_conteudo_related if tipo.sequencia_numeracao: numeracao = tipo.sequencia_numeracao - + ano = timezone.now().year if numeracao == 'A': numero = MateriaLegislativa.objects.filter( - ano=timezone.now().year).aggregate(Max('numero')) + ano=ano, tipo=tipo).aggregate(Max('numero')) elif numeracao == 'L': - legislatura = Legislatura.objects.first() + legislatura = Legislatura.objects.filter( + data_inicio__year__lte=ano, + data_fim__year__gte=ano).first() data_inicio = legislatura.data_inicio data_fim = legislatura.data_fim numero = MateriaLegislativa.objects.filter( data_apresentacao__gte=data_inicio, - data_apresentacao__lte=data_fim).aggregate( + data_apresentacao__lte=data_fim, + tipo=tipo).aggregate( Max('numero')) elif numeracao == 'U': - numero = MateriaLegislativa.objects.all().aggregate(Max('numero')) + numero = MateriaLegislativa.objects.filter(tipo=tipo).aggregate(Max('numero')) if numeracao is None: numero['numero__max'] = 0 @@ -1593,7 +1638,7 @@ class ConfirmarProposicaoForm(ProposicaoForm): materia.numero = max_numero materia.tipo = tipo materia.ementa = proposicao.descricao - materia.ano = timezone.now().year + materia.ano = ano materia.data_apresentacao = timezone.now() materia.em_tramitacao = True materia.regime_tramitacao = cd['regime_tramitacao'] @@ -1811,6 +1856,11 @@ class EtiquetaPesquisaForm(forms.Form): ) def clean(self): + super(EtiquetaPesquisaForm, self).clean() + + if not self.is_valid(): + return self.cleaned_data + cleaned_data = self.cleaned_data # Verifica se algum campo de data foi preenchido @@ -1879,7 +1929,12 @@ class FichaPesquisaForm(forms.Form): ) def clean(self): - cleaned_data = super(FichaPesquisaForm, self).clean() + super(FichaPesquisaForm, self).clean() + + if not self.is_valid(): + return self.cleaned_data + + cleaned_data = self.cleaned_data if not self.is_valid(): return cleaned_data diff --git a/sapl/materia/migrations/0026_auto_20180302_1411.py b/sapl/materia/migrations/0026_auto_20180302_1411.py new file mode 100644 index 000000000..9d401710e --- /dev/null +++ b/sapl/materia/migrations/0026_auto_20180302_1411.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-02 17:11 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('materia', '0025_auto_20180221_1649'), + ] + + operations = [ + migrations.AlterField( + model_name='numeracao', + name='data_materia', + field=models.DateField(null=True, verbose_name='Data'), + ), + ] diff --git a/sapl/materia/models.py b/sapl/materia/models.py index 66fd94900..34fd44d55 100644 --- a/sapl/materia/models.py +++ b/sapl/materia/models.py @@ -10,7 +10,7 @@ from django.utils import formats, timezone from django.utils.translation import ugettext_lazy as _ from model_utils import Choices -from sapl.base.models import Autor, SEQUENCIA_NUMERACAO +from sapl.base.models import SEQUENCIA_NUMERACAO, Autor from sapl.comissoes.models import Comissao from sapl.compilacao.models import (PerfilEstruturalTextoArticulado, TextoArticulado) @@ -187,7 +187,7 @@ class MateriaLegislativa(models.Model): em_tramitacao = models.BooleanField( verbose_name=_('Em Tramitação?'), default=False, - choices=EM_TRAMITACAO) + choices=YES_NO_CHOICES) polemica = models.NullBooleanField( blank=True, verbose_name=_('Matéria Polêmica?')) objeto = models.CharField( @@ -515,7 +515,7 @@ class Numeracao(models.Model): verbose_name=_('Número')) ano_materia = models.PositiveSmallIntegerField(verbose_name=_('Ano'), choices=RANGE_ANOS) - data_materia = models.DateField(verbose_name=_('Data')) + data_materia = models.DateField(verbose_name=_('Data'), null=True) class Meta: verbose_name = _('Numeração') @@ -529,7 +529,7 @@ class Numeracao(models.Model): def __str__(self): return _('%(numero)s/%(ano)s') % { 'numero': self.numero_materia, - 'ano': self.data_materia.year} + 'ano': self.ano_materia} @reversion.register() diff --git a/sapl/materia/receivers.py b/sapl/materia/receivers.py index 6e4ee737e..945c6636e 100644 --- a/sapl/materia/receivers.py +++ b/sapl/materia/receivers.py @@ -1,5 +1,6 @@ from django.db.models.signals import post_delete, post_save from django.dispatch import receiver + from sapl.materia.models import Tramitacao from sapl.materia.signals import tramitacao_signal from sapl.utils import get_base_url diff --git a/sapl/materia/tests/test_email_templates.py b/sapl/materia/tests/test_email_templates.py index 35b5f4ef2..aac13cbb7 100644 --- a/sapl/materia/tests/test_email_templates.py +++ b/sapl/materia/tests/test_email_templates.py @@ -1,4 +1,5 @@ from django.core import mail + from sapl.materia.email_utils import enviar_emails, load_email_templates diff --git a/sapl/materia/tests/test_materia.py b/sapl/materia/tests/test_materia.py index c0a0358d6..4af9df7ea 100644 --- a/sapl/materia/tests/test_materia.py +++ b/sapl/materia/tests/test_materia.py @@ -3,7 +3,9 @@ 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 django.db.models import Max from model_mommy import mommy + from sapl.base.models import Autor, TipoAutor from sapl.comissoes.models import Comissao, TipoComissao from sapl.materia.models import (Anexada, Autoria, DespachoInicial, @@ -14,6 +16,7 @@ from sapl.materia.models import (Anexada, Autoria, DespachoInicial, Tramitacao, UnidadeTramitacao) from sapl.norma.models import (LegislacaoCitada, NormaJuridica, TipoNormaJuridica) +from sapl.parlamentares.models import Legislatura from sapl.utils import models_with_gr_for_model @@ -505,3 +508,73 @@ def test_form_errors_proposicao(admin_client): ['Este campo é obrigatório.']) assert (response.context_data['form'].errors['descricao'] == ['Este campo é obrigatório.']) + + +@pytest.mark.django_db(transaction=False) +def test_numeracao_materia_legislativa_por_legislatura(admin_client): + + # Criar Legislaturas + legislatura1 = mommy.make(Legislatura, + data_inicio='2014-01-01', + data_fim='2018-12-31', + numero=20, + data_eleicao='2013-10-15' + ) + legislatura2 = mommy.make(Legislatura, + data_inicio='2009-01-01', + data_fim='2013-12-31', + numero=21, + data_eleicao='2018-10-15' + ) + + # Cria uma materia na legislatura1 + tipo_materia = mommy.make(TipoMateriaLegislativa, id=1, sequencia_numeracao='L') + materia = mommy.make(MateriaLegislativa, + tipo=tipo_materia, + ano=2017, + numero=1 + ) + + url = reverse('sapl.materia:recuperar_materia') + + # Testa numeração do Materia Legislativa na Legislatura1 + query_params = '?tipo={}&ano={}'.format(materia.tipo.id, materia.ano) + response = admin_client.get(url + query_params, follow=True) + response_content = eval(response.content.decode('ascii')) + esperado_legislatura1 = eval('{"numero": 2, "ano": "2017"}') + assert response_content['numero'] == esperado_legislatura1['numero'] + + # Testa numeração do Materia Legislativa na Legislatura2 + query_params = '?tipo={}&ano={}'.format(1, '2010') + response = admin_client.get(url + query_params, follow=True) + response_content = eval(response.content.decode('ascii')) + esperado_legislatura2 = eval('{"ano": "2010", "numero": 1}') + assert response_content['numero'] == esperado_legislatura2['numero'] + + +@pytest.mark.django_db(transaction=False) +def test_numeracao_materia_legislativa_por_ano(admin_client): + + # Cria uma materia + tipo_materia = mommy.make(TipoMateriaLegislativa, id=1, sequencia_numeracao='A') + materia = mommy.make(MateriaLegislativa, + tipo=tipo_materia, + ano=2017, + numero=1 + ) + + url = reverse('sapl.materia:recuperar_materia') + + # Testa numeração da Materia Legislativa no ano da materia criada + query_params = '?tipo={}&ano={}'.format(materia.tipo.id, materia.ano) + response = admin_client.get(url + query_params, follow=True) + response_content = eval(response.content.decode('ascii')) + esperado_ano = eval('{"numero": 2, "ano": "2017"}') + assert response_content['numero'] == esperado_ano['numero'] + + # Testa numeração da Materia Legislativa de outro ano + query_params = '?tipo={}&ano={}'.format(1, '2010') + response = admin_client.get(url + query_params, follow=True) + response_content = eval(response.content.decode('ascii')) + esperado_outro_ano = eval('{"ano": "2010", "numero": 1}') + assert response_content['numero'] == esperado_outro_ano['numero'] diff --git a/sapl/materia/tests/test_materia_form.py b/sapl/materia/tests/test_materia_form.py index f94a6cb8d..41ad04837 100644 --- a/sapl/materia/tests/test_materia_form.py +++ b/sapl/materia/tests/test_materia_form.py @@ -1,6 +1,7 @@ import pytest from django.utils.translation import ugettext as _ from model_mommy import mommy + from sapl.materia import forms from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa diff --git a/sapl/materia/urls.py b/sapl/materia/urls.py index 67ecf010f..934c4dbac 100644 --- a/sapl/materia/urls.py +++ b/sapl/materia/urls.py @@ -1,4 +1,5 @@ from django.conf.urls import include, url + from sapl.materia.views import (AcompanhamentoConfirmarView, AcompanhamentoExcluirView, AcompanhamentoMateriaView, AnexadaCrud, @@ -62,7 +63,7 @@ urlpatterns_materia = [ 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/recuperar-materia', recuperar_materia, name='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 7ca7c0b7a..08a6a50ef 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -2,7 +2,6 @@ from datetime import datetime from random import choice from string import ascii_letters, digits -import sapl import weasyprint from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML @@ -12,8 +11,6 @@ from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.exceptions import ObjectDoesNotExist from django.core.urlresolvers import reverse from django.db.models import Max -from django.forms.forms import Form -from django.forms.utils import ErrorDict from django.http import HttpResponse, JsonResponse from django.http.response import Http404, HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect @@ -24,15 +21,16 @@ from django.views.generic import CreateView, ListView, TemplateView, UpdateView from django.views.generic.base import RedirectView from django.views.generic.edit import FormView from django_filters.views import FilterView + +import sapl from sapl.base.models import Autor, CasaLegislativa from sapl.comissoes.models import Comissao, Participacao from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_RESTRICT, STATUS_TA_PRIVATE) from sapl.compilacao.views import IntegracaoTaView from sapl.crispy_layout_mixin import SaplFormLayout, form_actions -from sapl.crud.base import (ACTION_CREATE, ACTION_DELETE, ACTION_DETAIL, - ACTION_LIST, ACTION_UPDATE, RP_DETAIL, RP_LIST, - Crud, CrudAux, MasterDetailCrud, +from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux, + MasterDetailCrud, PermissionRequiredForAppCrudMixin, make_pagination) from sapl.materia.forms import (AnexadaForm, AutoriaForm, AutoriaMultiCreateForm, @@ -43,8 +41,8 @@ from sapl.materia.forms import (AnexadaForm, AutoriaForm, from sapl.norma.models import LegislacaoCitada from sapl.parlamentares.models import Legislatura 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, +from sapl.utils import (YES_NO_CHOICES, autor_label, autor_modal, + gerar_hash_arquivo, get_base_url, get_mime_type_from_file_extension, montar_row_autor, show_results_filter_set) @@ -56,7 +54,8 @@ from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, MateriaLegislativaFilterSet, MateriaLegislativaForm, MateriaSimplificadaForm, PrimeiraTramitacaoEmLoteFilterSet, ReceberProposicaoForm, RelatoriaForm, - TramitacaoEmLoteFilterSet, filtra_tramitacao_destino, + TramitacaoEmLoteFilterSet, UnidadeTramitacaoForm, + filtra_tramitacao_destino, filtra_tramitacao_destino_and_status, filtra_tramitacao_status) from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria, @@ -67,9 +66,9 @@ from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria, TipoProposicao, Tramitacao, UnidadeTramitacao) from .signals import tramitacao_signal -AssuntoMateriaCrud = Crud.build(AssuntoMateria, 'assunto_materia') +AssuntoMateriaCrud = CrudAux.build(AssuntoMateria, 'assunto_materia') -OrigemCrud = Crud.build(Origem, '') +OrigemCrud = CrudAux.build(Origem, '') TipoMateriaCrud = CrudAux.build( TipoMateriaLegislativa, 'tipo_materia_legislativa') @@ -295,17 +294,21 @@ def recuperar_materia(request): if numeracao == 'A': numero = MateriaLegislativa.objects.filter( - ano=timezone.now().year).aggregate(Max('numero')) + ano=ano, tipo=tipo).aggregate(Max('numero')) elif numeracao == 'L': - legislatura = Legislatura.objects.first() + legislatura = Legislatura.objects.filter( + data_inicio__year__lte=ano, + data_fim__year__gte=ano).first() data_inicio = legislatura.data_inicio data_fim = legislatura.data_fim numero = MateriaLegislativa.objects.filter( data_apresentacao__gte=data_inicio, - data_apresentacao__lte=data_fim).aggregate( + data_apresentacao__lte=data_fim, + tipo=tipo).aggregate( Max('numero')) elif numeracao == 'U': - numero = MateriaLegislativa.objects.all().aggregate(Max('numero')) + numero = MateriaLegislativa.objects.filter( + tipo=tipo).aggregate(Max('numero')) if numeracao is None: numero['numero__max'] = 0 @@ -320,10 +323,10 @@ def recuperar_materia(request): StatusTramitacaoCrud = CrudAux.build(StatusTramitacao, 'status_tramitacao') -class OrgaoCrud(Crud): +class OrgaoCrud(CrudAux): model = Orgao - class CreateView(Crud.CreateView): + class CreateView(CrudAux.CreateView): form_class = OrgaoForm @@ -588,11 +591,10 @@ class UnidadeTramitacaoCrud(CrudAux): model = UnidadeTramitacao help_topic = 'unidade_tramitacao' - class BaseMixin(Crud.BaseMixin): + class BaseMixin(CrudAux.BaseMixin): list_field_names = ['comissao', 'orgao', 'parlamentar'] - class ListView(Crud.ListView): - template_name = "crud/list.html" + class ListView(CrudAux.ListView): def get_headers(self): return [_('Unidade de Tramitação')] @@ -609,6 +611,12 @@ class UnidadeTramitacaoCrud(CrudAux): row[1], row[2] = ('', ''), ('', '') return context + class UpdateView(Crud.UpdateView): + form_class = UnidadeTramitacaoForm + + class CreateView(Crud.CreateView): + form_class = UnidadeTramitacaoForm + class ProposicaoCrud(Crud): model = Proposicao @@ -689,6 +697,12 @@ class ProposicaoCrud(Crud): messages.success(request, _( 'Proposição enviada com sucesso.')) + Numero = MateriaLegislativa.objects.filter(tipo=p.tipo.tipo_conteudo_related, + ano=p.ano).last().numero + 1 + messages.success(request, _( + '%s : nº %s de %s
Atenção! Este número é apenas um provável ' + 'número que pode não corresponder com a realidade' + % (p.tipo, Numero, p.ano))) elif action == 'return': if not p.data_envio: @@ -838,14 +852,17 @@ class ProposicaoCrud(Crud): obj.data_recebimento = 'Não recebida'\ if obj.data_envio else 'Não enviada' else: - obj.data_recebimento = timezone.localtime(obj.data_recebimento) - obj.data_recebimento = obj.data_recebimento = formats.date_format(obj.data_recebimento, "DATETIME_FORMAT") + obj.data_recebimento = timezone.localtime( + obj.data_recebimento) + obj.data_recebimento = obj.data_recebimento = formats.date_format( + obj.data_recebimento, "DATETIME_FORMAT") if obj.data_envio is None: obj.data_envio = 'Em elaboração...' else: obj.data_envio = timezone.localtime(obj.data_envio) - obj.data_envio = formats.date_format(obj.data_envio, "DATETIME_FORMAT") + obj.data_envio = formats.date_format( + obj.data_envio, "DATETIME_FORMAT") return [self._as_row(obj) for obj in object_list] @@ -1048,9 +1065,7 @@ class TramitacaoCrud(MasterDetailCrud): class UpdateView(MasterDetailCrud.UpdateView): form_class = TramitacaoUpdateForm - @property - def layout_key(self): - return 'TramitacaoUpdate' + layout_key = 'TramitacaoUpdate' def form_valid(self, form): self.object = form.save() @@ -1274,9 +1289,7 @@ class LegislacaoCitadaCrud(MasterDetailCrud): class DetailView(MasterDetailCrud.DetailView): - @property - def layout_key(self): - return 'LegislacaoCitadaDetail' + layout_key = 'LegislacaoCitadaDetail' class DeleteView(MasterDetailCrud.DeleteView): pass @@ -1350,9 +1363,7 @@ class MateriaLegislativaCrud(Crud): class BaseMixin(Crud.BaseMixin): list_field_names = ['tipo', 'numero', 'ano', 'data_apresentacao'] - @property - def list_url(self): - return '' + list_url = '' @property def search_url(self): @@ -1382,9 +1393,7 @@ class MateriaLegislativaCrud(Crud): class DetailView(Crud.DetailView): - @property - def layout_key(self): - return 'MateriaLegislativaDetail' + layout_key = 'MateriaLegislativaDetail' class ListView(Crud.ListView, RedirectView): @@ -1488,7 +1497,7 @@ class AcompanhamentoExcluirView(TemplateView): class MateriaLegislativaPesquisaView(FilterView): model = MateriaLegislativa filterset_class = MateriaLegislativaFilterSet - paginate_by = 10 + paginate_by = 50 def get_filterset_kwargs(self, filterset_class): super(MateriaLegislativaPesquisaView, @@ -1518,6 +1527,20 @@ class MateriaLegislativaPesquisaView(FilterView): if 'o' in self.request.GET and not self.request.GET['o']: qs = qs.order_by('-ano', 'tipo__sigla', '-numero') + qs = qs.prefetch_related("autoria_set", + "autoria_set__autor", + "numeracao_set", + "anexadas", + "tipo", + "texto_articulado", + "tramitacao_set", + "tramitacao_set__status", + "tramitacao_set__unidade_tramitacao_local", + "tramitacao_set__unidade_tramitacao_destino", + "normajuridica_set", + "registrovotacao_set", + "documentoacessorio_set") + kwargs.update({ 'queryset': qs, }) @@ -1684,6 +1707,8 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView): template_name = 'materia/em_lote/tramitacao.html' permission_required = ('materia.add_tramitacao', ) + primeira_tramitacao = True + def get_context_data(self, **kwargs): context = super(PrimeiraTramitacaoEmLoteView, self).get_context_data(**kwargs) @@ -1699,7 +1724,7 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView): qr = self.request.GET.copy() context['unidade_destino'] = UnidadeTramitacao.objects.all() context['status_tramitacao'] = StatusTramitacao.objects.all() - context['turnos_tramitacao'] = TURNO_TRAMITACAO_CHOICES + context['turnos_tramitacao'] = Tramitacao.TURNO_CHOICES context['urgente_tramitacao'] = YES_NO_CHOICES context['unidade_local'] = UnidadeTramitacao.objects.all() @@ -1770,10 +1795,12 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView): status = StatusTramitacao.objects.get(id=request.POST['status']) - if status.indicador == 'F': - for materia in MateriaLegislativa.objects.filter(id__in=marcadas): + for materia in MateriaLegislativa.objects.filter(id__in=marcadas): + if status.indicador == 'F': materia.em_tramitacao = False - materia.save() + elif self.primeira_tramitacao: + materia.em_tramitacao = True + materia.save() msg = _('Tramitação completa.') messages.add_message(request, messages.SUCCESS, msg) @@ -1783,6 +1810,8 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView): class TramitacaoEmLoteView(PrimeiraTramitacaoEmLoteView): filterset_class = TramitacaoEmLoteFilterSet + primeira_tramitacao = False + def get_context_data(self, **kwargs): context = super(TramitacaoEmLoteView, self).get_context_data(**kwargs) diff --git a/sapl/norma/forms.py b/sapl/norma/forms.py index c9daaec9f..affe07b87 100644 --- a/sapl/norma/forms.py +++ b/sapl/norma/forms.py @@ -8,6 +8,7 @@ from django.db import models from django.forms import ModelForm, widgets from django.utils import timezone from django.utils.translation import ugettext_lazy as _ + from sapl.crispy_layout_mixin import form_actions, to_row from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.settings import MAX_DOC_UPLOAD_SIZE @@ -192,8 +193,8 @@ class NormaRelacionadaForm(ModelForm): def clean(self): super(NormaRelacionadaForm, self).clean() - if self.errors: - return self.errors + if not self.is_valid(): + return self.cleaned_data cleaned_data = self.cleaned_data try: @@ -262,6 +263,10 @@ class NormaPesquisaSimplesForm(forms.Form): def clean(self): super(NormaPesquisaSimplesForm, self).clean() + + if not self.is_valid(): + return self.cleaned_data + cleaned_data = self.cleaned_data data_inicial = cleaned_data['data_inicial'] diff --git a/sapl/norma/tests/test_norma.py b/sapl/norma/tests/test_norma.py index 8d40b7202..6603d7167 100644 --- a/sapl/norma/tests/test_norma.py +++ b/sapl/norma/tests/test_norma.py @@ -2,6 +2,7 @@ import pytest from django.core.urlresolvers import reverse from django.utils.translation import ugettext_lazy as _ from model_mommy import mommy + from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.norma.forms import (NormaJuridicaForm, NormaPesquisaSimplesForm, NormaRelacionadaForm) diff --git a/sapl/norma/urls.py b/sapl/norma/urls.py index 12ba05cba..93081c4fc 100644 --- a/sapl/norma/urls.py +++ b/sapl/norma/urls.py @@ -1,4 +1,5 @@ from django.conf.urls import include, url + from sapl.norma.views import (AssuntoNormaCrud, NormaCrud, NormaPesquisaView, NormaRelacionadaCrud, NormaTaView, TipoNormaCrud, TipoVinculoNormaJuridicaCrud, recuperar_norma, diff --git a/sapl/norma/views.py b/sapl/norma/views.py index 249d480b0..d74bc8857 100644 --- a/sapl/norma/views.py +++ b/sapl/norma/views.py @@ -11,6 +11,7 @@ from django.views.generic import CreateView, ListView, TemplateView, UpdateView from django.views.generic.base import RedirectView from django.views.generic.edit import FormView from django_filters.views import FilterView + from sapl.base.models import AppConfig from sapl.compilacao.views import IntegracaoTaView from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux, @@ -59,9 +60,7 @@ class NormaRelacionadaCrud(MasterDetailCrud): class DetailView(MasterDetailCrud.DetailView): - @property - def layout_key(self): - return 'NormaRelacionadaDetail' + layout_key = 'NormaRelacionadaDetail' class NormaPesquisaView(FilterView): @@ -134,9 +133,7 @@ class NormaCrud(Crud): class BaseMixin(Crud.BaseMixin): list_field_names = ['tipo', 'numero', 'ano', 'ementa'] - @property - def list_url(self): - return '' + list_url = '' @property def search_url(self): @@ -158,9 +155,7 @@ class NormaCrud(Crud): def cancel_url(self): return self.search_url - @property - def layout_key(self): - return 'NormaJuridicaCreate' + layout_key = 'NormaJuridicaCreate' class ListView(Crud.ListView, RedirectView): @@ -174,9 +169,7 @@ class NormaCrud(Crud): class UpdateView(Crud.UpdateView): form_class = NormaJuridicaForm - @property - def layout_key(self): - return 'NormaJuridicaCreate' + layout_key = 'NormaJuridicaCreate' def get_initial(self): norma = NormaJuridica.objects.get(id=self.kwargs['pk']) diff --git a/sapl/painel/views.py b/sapl/painel/views.py index 5acb06195..454b61d8e 100644 --- a/sapl/painel/views.py +++ b/sapl/painel/views.py @@ -11,6 +11,7 @@ from django.http.response import Http404, HttpResponseRedirect from django.shortcuts import render from django.utils import timezone from django.utils.translation import ugettext_lazy as _ + from sapl.base.models import AppConfig as ConfiguracoesAplicacao from sapl.base.models import CasaLegislativa from sapl.crud.base import Crud diff --git a/sapl/parlamentares/forms.py b/sapl/parlamentares/forms.py index 80c78cf82..acee7d7e1 100644 --- a/sapl/parlamentares/forms.py +++ b/sapl/parlamentares/forms.py @@ -14,6 +14,7 @@ from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from floppyforms.widgets import ClearableFileInput from image_cropping.widgets import CropWidget, ImageCropWidget + from sapl.base.models import Autor, TipoAutor from sapl.crispy_layout_mixin import form_actions, to_row from sapl.rules import SAPL_GROUP_VOTANTE @@ -257,8 +258,8 @@ class FiliacaoForm(ModelForm): def clean(self): super(FiliacaoForm, self).clean() - if self.errors: - return self.errors + if not self.is_valid(): + return self.cleaned_data filiacao = super(FiliacaoForm, self).save(commit=False) validacao = validar_datas(self.cleaned_data['data'], @@ -281,6 +282,9 @@ class ComposicaoColigacaoForm(ModelForm): def clean(self): super(ComposicaoColigacaoForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + cleaned_data = self.cleaned_data pk = self.initial['coligacao_id'] if (ComposicaoColigacao.objects.filter( @@ -288,10 +292,8 @@ class ComposicaoColigacaoForm(ModelForm): partido=cleaned_data.get('partido')).exists()): msg = _('Esse partido já foi cadastrado nesta coligação.') raise ValidationError(msg) - else: - if self.errors: - return self.errors - return self.cleaned_data + + return self.cleaned_data class FrenteForm(ModelForm): @@ -351,6 +353,9 @@ class VotanteForm(ModelForm): def clean(self): super(VotanteForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + cd = self.cleaned_data username = cd['username'] diff --git a/sapl/parlamentares/migrations/0021_clear_thumbnails_cache.py b/sapl/parlamentares/migrations/0021_clear_thumbnails_cache.py new file mode 100644 index 000000000..b3581816f --- /dev/null +++ b/sapl/parlamentares/migrations/0021_clear_thumbnails_cache.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + + +from django.db import migrations +from sapl.utils import clear_thumbnails_cache + + +def clear_thumbnails_cache_migrate(apps, schema_editor): + Parlamentar = apps.get_model("parlamentares", "Parlamentar") + parlamentares = Parlamentar.objects.all() + clear_thumbnails_cache(parlamentares, 'fotografia') + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0020_fix_inicio_mandato'), + ] + + operations = [ + migrations.RunPython(clear_thumbnails_cache_migrate), + ] diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index 86e174f09..769093046 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -5,10 +5,11 @@ from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from image_cropping.fields import ImageCropField, ImageRatioField from model_utils import Choices + from sapl.base.models import Autor from sapl.decorators import vigencia_atual -from sapl.utils import (INDICADOR_AFASTAMENTO, LISTA_DE_UFS, YES_NO_CHOICES, - SaplGenericRelation, get_settings_auth_user_model, +from sapl.utils import (LISTA_DE_UFS, YES_NO_CHOICES, SaplGenericRelation, + get_settings_auth_user_model, intervalos_tem_intersecao, restringe_tipos_de_arquivo_img, texto_upload_path) @@ -260,12 +261,9 @@ class Parlamentar(models.Model): verbose_name=_('Ativo na Casa?')) biografia = models.TextField( blank=True, verbose_name=_('Biografia')) - # XXX Esse atribuito foi colocado aqui para não atrapalhar a migração - fotografia = ImageCropField( verbose_name=_('Fotografia'), upload_to=foto_upload_path, validators=[restringe_tipos_de_arquivo_img], null=True, blank=True) - cropping = ImageRatioField( 'fotografia', '128x128', verbose_name=_('Avatar'), size_warning=True, help_text=_('A configuração do Avatar ' @@ -402,7 +400,8 @@ class TipoAfastamento(models.Model): descricao = models.CharField(max_length=50, verbose_name=_('Descrição')) indicador = models.CharField( max_length=1, verbose_name=_('Indicador'), default='F', - choices=INDICADOR_AFASTAMENTO) + choices=[('A', _('Afastamento')), + ('F', _('Fim de Mandato')), ]) dispositivo = models.CharField( max_length=50, blank=True, verbose_name=_('Dispositivo')) diff --git a/sapl/parlamentares/tests/test_mandato.py b/sapl/parlamentares/tests/test_mandato.py index df0aabc7f..cc027da1f 100644 --- a/sapl/parlamentares/tests/test_mandato.py +++ b/sapl/parlamentares/tests/test_mandato.py @@ -2,6 +2,7 @@ from datetime import datetime import pytest from model_mommy import mommy + from sapl.parlamentares.models import Filiacao, Legislatura, Mandato pytestmark = pytest.mark.django_db diff --git a/sapl/parlamentares/tests/test_parlamentares.py b/sapl/parlamentares/tests/test_parlamentares.py index 4605f7af6..3bd7abf47 100644 --- a/sapl/parlamentares/tests/test_parlamentares.py +++ b/sapl/parlamentares/tests/test_parlamentares.py @@ -2,6 +2,7 @@ import pytest from django.core.urlresolvers import reverse from django.utils.translation import ugettext_lazy as _ from model_mommy import mommy + from sapl.parlamentares.forms import FrenteForm, LegislaturaForm, MandatoForm from sapl.parlamentares.models import (Dependente, Filiacao, Legislatura, Mandato, Parlamentar, Partido, diff --git a/sapl/parlamentares/urls.py b/sapl/parlamentares/urls.py index 4f7cd4c91..e383421c4 100644 --- a/sapl/parlamentares/urls.py +++ b/sapl/parlamentares/urls.py @@ -1,4 +1,5 @@ from django.conf.urls import include, url + from sapl.parlamentares.views import (CargoMesaCrud, ColigacaoCrud, ComposicaoColigacaoCrud, DependenteCrud, FiliacaoCrud, FrenteCrud, FrenteList, diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index 7654dde6c..078483f35 100644 --- a/sapl/parlamentares/views.py +++ b/sapl/parlamentares/views.py @@ -1,5 +1,5 @@ -from datetime import datetime import json +from datetime import datetime from django.contrib import messages from django.contrib.contenttypes.models import ContentType @@ -33,7 +33,6 @@ from .models import (CargoMesa, Coligacao, ComposicaoColigacao, ComposicaoMesa, NivelInstrucao, Parlamentar, Partido, SessaoLegislativa, SituacaoMilitar, TipoAfastamento, TipoDependente, Votante) - CargoMesaCrud = CrudAux.build(CargoMesa, 'cargo_mesa') PartidoCrud = CrudAux.build(Partido, 'partidos') SessaoLegislativaCrud = CrudAux.build(SessaoLegislativa, 'sessao_legislativa') @@ -421,16 +420,12 @@ class ParlamentarCrud(Crud): class UpdateView(Crud.UpdateView): form_class = ParlamentarForm - @property - def layout_key(self): - return 'ParlamentarUpdate' + layout_key = 'ParlamentarUpdate' class CreateView(Crud.CreateView): form_class = ParlamentarCreateForm - @property - def layout_key(self): - return 'ParlamentarCreate' + layout_key = 'ParlamentarCreate' def form_valid(self, form): """ diff --git a/sapl/protocoloadm/forms.py b/sapl/protocoloadm/forms.py index 15f4850b0..37d888ec2 100644 --- a/sapl/protocoloadm/forms.py +++ b/sapl/protocoloadm/forms.py @@ -10,21 +10,22 @@ from django.db import models from django.forms import ModelForm from django.utils import timezone from django.utils.translation import ugettext_lazy as _ + from sapl.base.models import Autor, TipoAutor from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row from sapl.materia.models import (MateriaLegislativa, TipoMateriaLegislativa, UnidadeTramitacao) -from sapl.utils import (RANGE_ANOS, AnoNumeroOrderingFilter, +from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, AnoNumeroOrderingFilter, RangeWidgetOverride, autor_label, autor_modal) from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo, Protocolo, TipoDocumentoAdministrativo, TramitacaoAdministrativo) -TIPOS_PROTOCOLO = [('0', 'Recebido'), ('1', 'Enviado'), ('', 'Ambos')] -TIPOS_PROTOCOLO_CREATE = [('0', 'Recebido'), ('1', 'Enviado')] +TIPOS_PROTOCOLO = [('0', 'Recebido'), ('1', 'Enviado'), ('2', 'Interno'), ('', '---------')] +TIPOS_PROTOCOLO_CREATE = [('0', 'Recebido'), ('1', 'Enviado'), ('2', 'Interno')] -NATUREZA_PROCESSO = [('', 'Ambos'), +NATUREZA_PROCESSO = [('', '---------'), ('0', 'Administrativo'), ('1', 'Legislativo')] @@ -33,7 +34,7 @@ def ANO_CHOICES(): return [('', '---------')] + RANGE_ANOS -EM_TRAMITACAO = [('', 'Tanto Faz'), +EM_TRAMITACAO = [('', '---------'), (0, 'Sim'), (1, 'Não')] @@ -216,7 +217,7 @@ class AnularProcoloAdmForm(ModelForm): def clean(self): super(AnularProcoloAdmForm, self).clean() - cleaned_data = super(AnularProcoloAdmForm, self).clean() + cleaned_data = self.cleaned_data if not self.is_valid(): return cleaned_data @@ -360,9 +361,20 @@ class ProtocoloMateriaForm(ModelForm): label=_('Tipo de Matéria'), required=True, queryset=TipoMateriaLegislativa.objects.all(), - empty_label='Selecione', + empty_label='------', ) + numero_materia = forms.CharField( + label=_('Número matéria'), required=False) + + ano_materia = forms.CharField( + label=_('Ano matéria'), required=False) + + vincular_materia = forms.ChoiceField(label=_('Vincular a matéria existente?'), + widget=forms.RadioSelect(), + choices=YES_NO_CHOICES, + initial=False) + numero_paginas = forms.CharField(label=_('Núm. Páginas'), required=True) observacao = forms.CharField(required=False, @@ -378,7 +390,11 @@ class ProtocoloMateriaForm(ModelForm): 'autor', 'tipo_autor', 'assunto_ementa', - 'observacao'] + 'observacao', + 'numero_materia', + 'ano_materia', + 'vincular_materia' + ] def clean_autor(self): autor_field = self.cleaned_data['autor'] @@ -390,6 +406,30 @@ class ProtocoloMateriaForm(ModelForm): autor_field = autor return autor_field + def clean(self): + super(ProtocoloMateriaForm, self).clean() + + if not self.is_valid(): + return self.cleaned_data + + data = self.cleaned_data + if self.is_valid(): + if data['vincular_materia'] == 'True': + try: + if not data['ano_materia'] or not data['numero_materia']: + raise ValidationError( + 'Favor informar o número e ano da matéria a ser vinculada') + self.materia = MateriaLegislativa.objects.get(ano=data['ano_materia'], + numero=data['numero_materia'], + tipo=data['tipo_materia']) + if self.materia.numero_protocolo: + raise ValidationError(_('Matéria Legislativa informada já possui o protocolo {}/{} vinculado.' + .format(self.materia.numero_protocolo, self.materia.ano))) + except ObjectDoesNotExist: + raise ValidationError(_('Matéria Legislativa informada não existente.')) + + return data + def __init__(self, *args, **kwargs): row1 = to_row( @@ -397,6 +437,10 @@ class ProtocoloMateriaForm(ModelForm): ('numero_paginas', 2), ('tipo_autor', 3), ('autor', 3)]) + row2 = to_row( + [(InlineRadios('vincular_materia'), 4), + ('numero_materia', 4), + ('ano_materia', 4), ]) row3 = to_row( [('assunto_ementa', 12)]) row4 = to_row( @@ -405,7 +449,7 @@ class ProtocoloMateriaForm(ModelForm): self.helper = FormHelper() self.helper.layout = Layout( Fieldset(_('Identificação da Matéria'), - row1, row3, + row1, row2, row3, row4, form_actions(label='Protocolar Matéria'))) super(ProtocoloMateriaForm, self).__init__( @@ -444,6 +488,9 @@ class TramitacaoAdmForm(ModelForm): def clean(self): cleaned_data = super(TramitacaoAdmForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + if 'data_encaminhamento' in cleaned_data: data_enc_form = cleaned_data['data_encaminhamento'] if 'data_fim_prazo' in cleaned_data: @@ -515,6 +562,11 @@ class TramitacaoAdmEditForm(TramitacaoAdmForm): ] def clean(self): + super(TramitacaoAdmEditForm, self).clean() + + if not self.is_valid(): + return self.cleaned_data + ultima_tramitacao = TramitacaoAdministrativo.objects.filter( documento_id=self.instance.documento_id).order_by( '-data_tramitacao', @@ -576,6 +628,9 @@ class DocumentoAdministrativoForm(ModelForm): def clean(self): super(DocumentoAdministrativoForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + cleaned_data = self.cleaned_data if not self.is_valid(): diff --git a/sapl/protocoloadm/models.py b/sapl/protocoloadm/models.py index 43e18ffd5..d4d45b3ad 100644 --- a/sapl/protocoloadm/models.py +++ b/sapl/protocoloadm/models.py @@ -2,6 +2,7 @@ import reversion from django.db import models from django.utils.translation import ugettext_lazy as _ from model_utils import Choices + from sapl.base.models import Autor from sapl.materia.models import TipoMateriaLegislativa, UnidadeTramitacao from sapl.utils import RANGE_ANOS, YES_NO_CHOICES, texto_upload_path diff --git a/sapl/protocoloadm/tests/test_protocoloadm.py b/sapl/protocoloadm/tests/test_protocoloadm.py index 31e957513..f74c4dd7b 100644 --- a/sapl/protocoloadm/tests/test_protocoloadm.py +++ b/sapl/protocoloadm/tests/test_protocoloadm.py @@ -5,6 +5,7 @@ from django.core.urlresolvers import reverse from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ from model_mommy import mommy + from sapl.materia.models import UnidadeTramitacao from sapl.protocoloadm.forms import (AnularProcoloAdmForm, DocumentoAdministrativoForm, @@ -411,5 +412,6 @@ def test_protocolo_materia_invalido(): assert errors['tipo_materia'] == [_('Este campo é obrigatório.')] assert errors['numero_paginas'] == [_('Este campo é obrigatório.')] assert errors['autor'] == [_('Este campo é obrigatório.')] + assert errors['vincular_materia'] == [_('Este campo é obrigatório.')] - assert len(errors) == 5 + assert len(errors) == 6 diff --git a/sapl/protocoloadm/urls.py b/sapl/protocoloadm/urls.py index 77512ff01..46fec9980 100644 --- a/sapl/protocoloadm/urls.py +++ b/sapl/protocoloadm/urls.py @@ -1,4 +1,5 @@ from django.conf.urls import include, url + from sapl.protocoloadm.views import (AnularProtocoloAdmView, ComprovanteProtocoloView, CriarDocumentoProtocolo, diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index a10aa30f8..ee03daaff 100644 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -1,5 +1,4 @@ -import sapl from braces.views import FormValidMessageMixin from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin @@ -15,6 +14,8 @@ from django.utils.translation import ugettext_lazy as _ from django.views.generic import CreateView, ListView from django.views.generic.base import RedirectView, TemplateView from django_filters.views import FilterView + +import sapl from sapl.base.models import Autor from sapl.comissoes.models import Comissao from sapl.crud.base import Crud, CrudAux, MasterDetailCrud, make_pagination @@ -92,9 +93,7 @@ class DocumentoAdministrativoCrud(Crud): namespace = self.model._meta.app_config.name return reverse('%s:%s' % (namespace, 'pesq_doc_adm')) - @property - def list_url(self): - return '' + list_url = '' class ListView(RedirectView, DocumentoAdministrativoMixin, Crud.ListView): @@ -277,8 +276,7 @@ class ProtocoloDocumentoView(PermissionRequiredMixin, kwargs={'pk': self.object.id}) def form_valid(self, form): - f = form.save(commit=False) - + protocolo = form.save(commit=False) try: numeracao = sapl.base.models.AppConfig.objects.last( ).sequencia_numeracao @@ -292,26 +290,29 @@ class ProtocoloDocumentoView(PermissionRequiredMixin, numero = Protocolo.objects.filter( ano=timezone.now().year).aggregate(Max('numero')) elif numeracao == 'L': - legislatura = Legislatura.objects.first() + legislatura = Legislatura.objects.filter( + data_inicio__year__lte=timezone.now().year, + data_fim__year__gte=timezone.now().year).first() data_inicio = legislatura.data_inicio data_fim = legislatura.data_fim numero = Protocolo.objects.filter( - data__gte=data_inicio, data__lte=data_fim).aggregate( - Max('numero')) + data__gte=data_inicio, + data__lte=data_fim).aggregate( + Max('numero')) elif numeracao == 'U': numero = Protocolo.objects.all().aggregate(Max('numero')) - f.tipo_processo = '0' # TODO validar o significado - f.anulado = False - f.numero = (numero['numero__max'] + 1) if numero['numero__max'] else 1 - f.ano = timezone.now().year - f.data = timezone.now() - f.hora = timezone.now().time() - f.timestamp = timezone.now() - f.assunto_ementa = self.request.POST['assunto'] - - f.save() - self.object = f + protocolo.tipo_processo = '0' # TODO validar o significado + protocolo.anulado = False + protocolo.numero = (numero['numero__max'] + 1) if numero['numero__max'] else 1 + protocolo.ano = timezone.now().year + protocolo.data = timezone.now() + protocolo.hora = timezone.now().time() + protocolo.timestamp = timezone.now() + protocolo.assunto_ementa = self.request.POST['assunto'] + + protocolo.save() + self.object = protocolo return redirect(self.get_success_url()) @@ -414,7 +415,6 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView): 'pk': protocolo.pk}) def form_valid(self, form): - try: numeracao = sapl.base.models.AppConfig.objects.last( ).sequencia_numeracao @@ -424,22 +424,19 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView): messages.add_message(self.request, messages.ERROR, msg) return self.render_to_response(self.get_context_data()) - # Se TipoMateriaLegislativa tem sequencia própria, - # então sobreescreve a sequência global - tipo = form.cleaned_data['tipo_materia'] - if tipo.sequencia_numeracao: - numeracao = tipo.sequencia_numeracao - if numeracao == 'A': numero = Protocolo.objects.filter( ano=timezone.now().year).aggregate(Max('numero')) elif numeracao == 'L': - legislatura = Legislatura.objects.first() + legislatura = Legislatura.objects.filter( + data_inicio__year__lte=timezone.now().year, + data_fim__year__gte=timezone.now().year).first() data_inicio = legislatura.data_inicio data_fim = legislatura.data_fim numero = Protocolo.objects.filter( - data__gte=data_inicio, data__lte=data_fim).aggregate( - Max('numero')) + data__gte=data_inicio, + data__lte=data_fim).aggregate( + Max('numero')) elif numeracao == 'U': numero = Protocolo.objects.all().aggregate(Max('numero')) @@ -468,6 +465,14 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView): protocolo.assunto_ementa = self.request.POST['assunto_ementa'] protocolo.save() + data = form.cleaned_data + if data['vincular_materia'] == 'True': + materia = MateriaLegislativa.objects.get(ano=data['ano_materia'], + numero=data['numero_materia'], + tipo=data['tipo_materia']) + materia.numero_protocolo = protocolo.numero + materia.save() + return redirect(self.get_success_url(protocolo)) def get_context_data(self, **kwargs): diff --git a/sapl/redireciona_urls/views.py b/sapl/redireciona_urls/views.py index 2981b206f..369ff7518 100644 --- a/sapl/redireciona_urls/views.py +++ b/sapl/redireciona_urls/views.py @@ -1,5 +1,6 @@ from django.core.urlresolvers import NoReverseMatch, reverse from django.views.generic import RedirectView + from sapl.base.apps import AppConfig as atasConfig from sapl.comissoes.apps import AppConfig as comissoesConfig from sapl.materia.apps import AppConfig as materiaConfig @@ -34,6 +35,7 @@ comissao_list = (app_comissoes + ':comissao_list') comissao_detail = (app_comissoes + ':comissao_detail') audiencia = (app_audiencia + ':audiencia') +reuniao_detail = (app_comissoes + ':reuniao_detail') materialegislativa_detail = (app_materia + ':materialegislativa_detail') materialegislativa_list = (app_materia + ':pesquisar_materia') @@ -636,3 +638,52 @@ class RedirecionaMateriasPorAnoAutorTipo(RedirectView): url = has_iframe(url, self.request) return url + + +class RedirecionaReuniao(RedirectView): + permanent = True + + def get_redirect_url(self): + pk_reuniao = self.request.GET.get( + 'cod_comissao', + EMPTY_STRING) + url = EMPTY_STRING + if pk_reuniao: + kwargs = {'pk': pk_reuniao} + try: + url = reverse(reuniao_detail, kwargs=kwargs) + except NoReverseMatch: + raise UnknownUrlNameError(reuniao_detail) + + else: + try: + url = reverse(reuniao_list) + except NoReverseMatch: + raise UnknownUrlNameError(reuniao_list) + + year = self.request.GET.get( + 'ano_reuniao', + EMPTY_STRING) + month = self.request.GET.get( + 'mes_reuniao', + EMPTY_STRING) + day = self.request.GET.get( + 'dia_reuniao', + EMPTY_STRING) + tipo_reuniao = self.request.GET.get( + 'tip_reuniao', + EMPTY_STRING) + + # Remove zeros à esquerda + day = day.lstrip("0") + month = month.lstrip("0") + args = EMPTY_STRING + args += "?data_inicio__year=%s" % (year) + args += "&data_inicio__month=%s" % (month) + args += "&data_inicio__day=%s" % (day) + args += "&tipo=%s&salvar=Pesquisar" % (tipo_reuniao) + url = "%s%s" % (url, args) + + url = has_iframe(url, self.request) + + return url diff --git a/sapl/relatorios/views.py b/sapl/relatorios/views.py index 30da38f8d..00f33e44e 100644 --- a/sapl/relatorios/views.py +++ b/sapl/relatorios/views.py @@ -6,6 +6,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.http import Http404, HttpResponse from django.utils import timezone from django.utils.translation import ugettext_lazy as _ + from sapl.base.models import Autor, CasaLegislativa from sapl.comissoes.models import Comissao from sapl.materia.models import (Autoria, MateriaLegislativa, Numeracao, @@ -944,16 +945,20 @@ def get_etiqueta_protocolos(prots): dic['nom_autor'] = str(p.autor or ' ') + dic['num_materia'] = '' + for materia in MateriaLegislativa.objects.filter( + numero_protocolo=p.numero, ano=p.ano): + dic['num_materia'] = materia.tipo.sigla + ' ' + \ + str(materia.numero) + '/' + str(materia.ano) + dic['natureza'] = '' if p.tipo_processo == 0: dic['natureza'] = 'Administrativo' if p.tipo_processo == 1: - dic['natureza'] = 'Legislativo' - - dic['num_materia'] = '' - for materia in MateriaLegislativa.objects.filter( - numero_protocolo=p.numero, ano=p.ano): - dic['num_materia'] = str(materia) + if dic['num_materia']: + dic['natureza'] = dic['num_materia'] + else: + dic['natureza'] = 'Legislativo' dic['num_documento'] = '' for documento in DocumentoAdministrativo.objects.filter( diff --git a/sapl/rules/apps.py b/sapl/rules/apps.py index 2b03bf239..606190110 100644 --- a/sapl/rules/apps.py +++ b/sapl/rules/apps.py @@ -10,6 +10,7 @@ from django.db import models, router from django.db.utils import DEFAULT_DB_ALIAS from django.utils.translation import ugettext_lazy as _ from django.utils.translation import string_concat + from sapl.rules import (SAPL_GROUP_ADMINISTRATIVO, SAPL_GROUP_COMISSOES, SAPL_GROUP_GERAL, SAPL_GROUP_MATERIA, SAPL_GROUP_NORMA, SAPL_GROUP_PAINEL, SAPL_GROUP_PROTOCOLO, diff --git a/sapl/rules/map_rules.py b/sapl/rules/map_rules.py index 9ab42eb1f..f98da8c0e 100644 --- a/sapl/rules/map_rules.py +++ b/sapl/rules/map_rules.py @@ -99,6 +99,8 @@ rules_group_comissoes = { (comissoes.Composicao, __base__), (comissoes.Participacao, __base__), (materia.Relatoria, __base__), + (comissoes.Reuniao, __base__), + (comissoes.DocumentoAcessorio, __base__), ] } @@ -206,9 +208,6 @@ rules_group_geral = { ]), (base.CasaLegislativa, __listdetailchange__ + [RP_ADD]), - (base.ProblemaMigracao, []), - (base.Argumento, []), - (base.Constraint, []), (base.TipoAutor, __base__), (base.Autor, __base__), diff --git a/sapl/rules/tests/test_rules.py b/sapl/rules/tests/test_rules.py index 8657290b8..07302bae5 100644 --- a/sapl/rules/tests/test_rules.py +++ b/sapl/rules/tests/test_rules.py @@ -5,8 +5,8 @@ from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType from django.utils import six from django.utils.translation import ugettext_lazy as _ -from sapl.base.models import (Argumento, CasaLegislativa, Constraint, - ProblemaMigracao) + +from sapl.base.models import CasaLegislativa from sapl.compilacao.models import (PerfilEstruturalTextoArticulado, TipoDispositivo, TipoDispositivoRelationship) @@ -56,41 +56,26 @@ def test_models_in_rules_patterns(model_item): # __falsos_positivos__ __fp__in__test_permission_of_models_in_rules_patterns = { map_rules.RP_ADD: [CasaLegislativa, - ProblemaMigracao, - Argumento, - Constraint, TipoDispositivo, TipoDispositivoRelationship, PerfilEstruturalTextoArticulado], - map_rules.RP_CHANGE: [ProblemaMigracao, - Argumento, - Constraint, - AcompanhamentoMateria, + map_rules.RP_CHANGE: [AcompanhamentoMateria, TipoDispositivo, TipoDispositivoRelationship, PerfilEstruturalTextoArticulado], map_rules.RP_DELETE: [CasaLegislativa, - ProblemaMigracao, - Argumento, - Constraint, TipoDispositivo, TipoDispositivoRelationship, PerfilEstruturalTextoArticulado], - map_rules.RP_LIST: [ProblemaMigracao, - Argumento, - Constraint, - AcompanhamentoMateria, + map_rules.RP_LIST: [AcompanhamentoMateria, TipoDispositivo, TipoDispositivoRelationship, PerfilEstruturalTextoArticulado], - map_rules.RP_DETAIL: [ProblemaMigracao, - Argumento, - Constraint, - AcompanhamentoMateria, + map_rules.RP_DETAIL: [AcompanhamentoMateria, TipoDispositivo, TipoDispositivoRelationship, PerfilEstruturalTextoArticulado] diff --git a/sapl/sessao/forms.py b/sapl/sessao/forms.py index 59fd3391c..470d27573 100644 --- a/sapl/sessao/forms.py +++ b/sapl/sessao/forms.py @@ -9,6 +9,7 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.db import transaction from django.forms import ModelForm from django.utils.translation import ugettext_lazy as _ + from sapl.base.models import Autor, TipoAutor from sapl.crispy_layout_mixin import form_actions, to_row from sapl.materia.forms import MateriaLegislativaFilterSet @@ -108,6 +109,9 @@ class BancadaForm(ModelForm): def clean(self): super(BancadaForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + if self.cleaned_data['data_extincao']: if (self.cleaned_data['data_extincao'] < self.cleaned_data['data_criacao']): @@ -140,6 +144,9 @@ class BlocoForm(ModelForm): def clean(self): super(BlocoForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + if self.cleaned_data['data_extincao']: if (self.cleaned_data['data_extincao'] < self.cleaned_data['data_criacao']): @@ -522,6 +529,9 @@ class ResumoOrdenacaoForm(forms.Form): def clean(self): super(ResumoOrdenacaoForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + cleaned_data = self.cleaned_data for c1 in cleaned_data: diff --git a/sapl/sessao/migrations/0017_auto_20180316_0731.py b/sapl/sessao/migrations/0017_auto_20180316_0731.py new file mode 100644 index 000000000..a81456161 --- /dev/null +++ b/sapl/sessao/migrations/0017_auto_20180316_0731.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-16 10:31 +from __future__ import unicode_literals + +from django.db import migrations, models +import sapl.sessao.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sessao', '0016_auto_20180131_1708'), + ] + + operations = [ + migrations.AddField( + model_name='orador', + name='upload_anexo', + field=models.FileField(blank=True, null=True, upload_to=sapl.sessao.models.anexo_upload_path, verbose_name='Anexo do Orador'), + ), + migrations.AddField( + model_name='oradorexpediente', + name='upload_anexo', + field=models.FileField(blank=True, null=True, upload_to=sapl.sessao.models.anexo_upload_path, verbose_name='Anexo do Orador'), + ), + ] diff --git a/sapl/sessao/migrations/0018_auto_20180327_1433.py b/sapl/sessao/migrations/0018_auto_20180327_1433.py new file mode 100644 index 000000000..692debf28 --- /dev/null +++ b/sapl/sessao/migrations/0018_auto_20180327_1433.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2018-03-27 17:33 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('sessao', '0017_auto_20180316_0731'), + ] + + operations = [ + migrations.RemoveField( + model_name='registrovotacao', + name='data_hora_atualizacao', + ), + migrations.RemoveField( + model_name='registrovotacao', + name='data_hora_criacao', + ), + ] diff --git a/sapl/sessao/models.py b/sapl/sessao/models.py index db7c50d9b..90736d6e0 100644 --- a/sapl/sessao/models.py +++ b/sapl/sessao/models.py @@ -1,7 +1,11 @@ +from operator import xor + import reversion +from django.core.exceptions import ValidationError from django.db import models from django.utils.translation import ugettext_lazy as _ from model_utils import Choices + from sapl.base.models import Autor from sapl.materia.models import MateriaLegislativa from sapl.parlamentares.models import (CargoMesa, Legislatura, Parlamentar, @@ -316,6 +320,11 @@ class AbstractOrador(models.Model): # Oradores max_length=150, blank=True, verbose_name=_('URL Vídeo')) observacao = models.CharField( max_length=150, blank=True, verbose_name=_('Observação')) + upload_anexo = models.FileField( + blank=True, + null=True, + upload_to=anexo_upload_path, + verbose_name=_('Anexo do Orador')) class Meta: abstract = True @@ -408,16 +417,6 @@ class RegistroVotacao(models.Model): observacao = models.TextField( blank=True, verbose_name=_('Observações')) - data_hora_criacao = models.DateTimeField( - blank=True, null=True, - auto_now_add=True, - verbose_name=_('Data Criação')) - - data_hora_atualizacao = models.DateTimeField( - blank=True, null=True, - auto_now=True, - verbose_name=_('Data')) - class Meta: verbose_name = _('Votação') verbose_name_plural = _('Votações') @@ -429,6 +428,16 @@ class RegistroVotacao(models.Model): 'votacao': self.tipo_resultado_votacao, 'materia': self.materia} + def clean(self): + """Exatamente um dos campos ordem ou expediente deve estar preenchido. + """ + # TODO remover esse método quando OrdemDia e ExpedienteMateria + # forem reestruturados e os campos ordem e expediente forem unificados + if not xor(bool(self.ordem), bool(self.expediente)): + raise ValidationError( + 'RegistroVotacao deve ter exatamente um dos campos ' + 'ordem ou expediente deve estar preenchido') + @reversion.register() class VotoParlamentar(models.Model): # RegistroVotacaoParlamentar diff --git a/sapl/sessao/tests/test_sessao.py b/sapl/sessao/tests/test_sessao.py index 37a17d1ed..d83c56e86 100644 --- a/sapl/sessao/tests/test_sessao.py +++ b/sapl/sessao/tests/test_sessao.py @@ -1,11 +1,13 @@ import pytest +from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ from model_mommy import mommy + from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.parlamentares.models import Legislatura, Partido, SessaoLegislativa from sapl.sessao import forms -from sapl.sessao.models import (ExpedienteMateria, SessaoPlenaria, - TipoSessaoPlenaria) +from sapl.sessao.models import (ExpedienteMateria, OrdemDia, RegistroVotacao, + SessaoPlenaria, TipoSessaoPlenaria) def test_valida_campos_obrigatorios_sessao_plenaria_form(): @@ -138,3 +140,25 @@ def test_expediente_materia_form_valido(): }, instance=instance) assert form.is_valid() + + +@pytest.mark.django_db(transaction=False) +def test_registro_votacao_tem_ordem_xor_expediente(): + + def registro_votacao_com(ordem, expediente): + return mommy.make(RegistroVotacao, ordem=ordem, expediente=expediente) + + ordem = mommy.make(OrdemDia) + expediente = mommy.make(ExpedienteMateria) + + # a validação funciona com exatamente um dos campos preenchido + registro_votacao_com(ordem, None).full_clean() + registro_votacao_com(None, expediente).full_clean() + + # a validação NÃO funciona quando nenhum deles é preenchido + with pytest.raises(ValidationError): + registro_votacao_com(None, None).full_clean() + + # a validação NÃO funciona quando ambos são preenchidos + with pytest.raises(ValidationError): + registro_votacao_com(ordem, expediente).full_clean() diff --git a/sapl/sessao/tests/test_sessao_view.py b/sapl/sessao/tests/test_sessao_view.py index 29e27c169..082652c19 100644 --- a/sapl/sessao/tests/test_sessao_view.py +++ b/sapl/sessao/tests/test_sessao_view.py @@ -2,6 +2,7 @@ import pytest from django.core.urlresolvers import reverse from django.utils.translation import ugettext_lazy as _ from model_mommy import mommy + from sapl.parlamentares.models import Legislatura, SessaoLegislativa from sapl.sessao.models import SessaoPlenaria, TipoSessaoPlenaria diff --git a/sapl/sessao/urls.py b/sapl/sessao/urls.py index 729c43d58..6e42afbaa 100644 --- a/sapl/sessao/urls.py +++ b/sapl/sessao/urls.py @@ -1,4 +1,5 @@ from django.conf.urls import include, url + from sapl.sessao.views import (AdicionarVariasMateriasExpediente, AdicionarVariasMateriasOrdemDia, BancadaCrud, BlocoCrud, CargoBancadaCrud, @@ -18,6 +19,7 @@ from sapl.sessao.views import (AdicionarVariasMateriasExpediente, VotacaoNominalExpedienteEditView, VotacaoNominalExpedienteView, VotacaoNominalTransparenciaDetailView, + VotacaoSimbolicaTransparenciaDetailView, VotacaoNominalView, VotacaoView, abrir_votacao, atualizar_mesa, insere_parlamentar_composicao, mudar_ordem_materia_sessao, recuperar_materia, @@ -143,6 +145,9 @@ urlpatterns = [ url(r'^sessao/(?P\d+)/votacao-nominal-transparencia/(?P\d+)/(?P\d+)$', VotacaoNominalTransparenciaDetailView.as_view(), name='votacao_nominal_transparencia'), + url(r'^sessao/(?P\d+)/votacao-simbolica-transparencia/(?P\d+)/(?P\d+)$', + VotacaoSimbolicaTransparenciaDetailView.as_view(), + name='votacao_simbolica_transparencia'), url(r'^sessao/mudar-ordem-materia-sessao/', mudar_ordem_materia_sessao, diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index 2b66849a7..c8070ee94 100644 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -20,6 +20,7 @@ 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 + from sapl.base.models import AppConfig as AppsAppConfig from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux, MasterDetailCrud, @@ -153,9 +154,10 @@ def abrir_votacao(request, pk, spk): reverse('sapl.sessao:' + redirect_url, kwargs={'pk': spk})) -def customize_link_materia(context, pk): +def customize_link_materia(context, pk, has_permission, is_expediente): for i, row in enumerate(context['rows']): materia = context['object_list'][i].materia + obj = context['object_list'][i] url_materia = reverse('sapl.materia:materialegislativa_detail', kwargs={'pk': materia.id}) numeracao = materia.numeracao_set.first() @@ -198,6 +200,189 @@ def customize_link_materia(context, pk): # Na linha abaixo, o segundo argumento é None para não colocar # url em toda a string de title_materia context['rows'][i][1] = (title_materia, None) + + exist_resultado = obj.registrovotacao_set.filter( + materia=obj.materia).exists() + if not exist_resultado: + if obj.votacao_aberta: + url = '' + if is_expediente: + if obj.tipo_votacao == 1: + url = reverse('sapl.sessao:votacaosimbolicaexp', + kwargs={ + 'pk': obj.sessao_plenaria_id, + 'oid': obj.pk, + 'mid': obj.materia_id}) + elif obj.tipo_votacao == 2: + url = reverse('sapl.sessao:votacaonominalexp', + kwargs={ + 'pk': obj.sessao_plenaria_id, + 'oid': obj.pk, + 'mid': obj.materia_id}) + elif obj.tipo_votacao == 3: + url = reverse('sapl.sessao:votacaosecretaexp', + kwargs={ + 'pk': obj.sessao_plenaria_id, + 'oid': obj.pk, + 'mid': obj.materia_id}) + else: + if obj.tipo_votacao == 1: + url = reverse('sapl.sessao:votacaosimbolica', + kwargs={ + 'pk': obj.sessao_plenaria_id, + 'oid': obj.pk, + 'mid': obj.materia_id}) + elif obj.tipo_votacao == 2: + url = reverse('sapl.sessao:votacaonominal', + kwargs={ + 'pk': obj.sessao_plenaria_id, + 'oid': obj.pk, + 'mid': obj.materia_id}) + elif obj.tipo_votacao == 3: + url = reverse('sapl.sessao:votacaosecreta', + kwargs={ + 'pk': obj.sessao_plenaria_id, + 'oid': obj.pk, + 'mid': obj.materia_id}) + if has_permission: + btn_registrar = ''' +
+ +
''' % ( + url) + + resultado = btn_registrar + else: + resultado = '''Não há resultado''' + else: + if is_expediente: + url = reverse('sapl.sessao:abrir_votacao', kwargs={ + 'pk': obj.pk, + 'spk': obj.sessao_plenaria_id + }) + '?tipo_materia=expediente' + else: + url = reverse('sapl.sessao:abrir_votacao', kwargs={ + 'pk': obj.pk, + 'spk': obj.sessao_plenaria_id + }) + '?tipo_materia=ordem' + + if has_permission: + btn_abrir = ''' + Matéria não votada
+ Abrir Votação''' % (url) + resultado = btn_abrir + else: + resultado = '''Não há resultado''' + else: + resultado = obj.registrovotacao_set.get( + materia_id=obj.materia_id) + resultado_descricao = resultado.tipo_resultado_votacao.nome + resultado_observacao = resultado.observacao + + if has_permission: + url = '' + if is_expediente: + if obj.tipo_votacao == 1: + url = reverse( + 'sapl.sessao:votacaosimbolicaexpedit', + kwargs={ + 'pk': obj.sessao_plenaria_id, + 'oid': obj.pk, + 'mid': obj.materia_id}) + elif obj.tipo_votacao == 2: + url = reverse('sapl.sessao:votacaonominalexpedit', + kwargs={ + 'pk': obj.sessao_plenaria_id, + 'oid': obj.pk, + 'mid': obj.materia_id}) + elif obj.tipo_votacao == 3: + url = reverse('sapl.sessao:votacaosecretaexpedit', + kwargs={ + 'pk': obj.sessao_plenaria_id, + 'oid': obj.pk, + 'mid': obj.materia_id}) + else: + if obj.tipo_votacao == 1: + url = reverse('sapl.sessao:votacaosimbolicaedit', + kwargs={ + 'pk': obj.sessao_plenaria_id, + 'oid': obj.pk, + 'mid': obj.materia_id}) + elif obj.tipo_votacao == 2: + url = reverse('sapl.sessao:votacaonominaledit', + kwargs={ + 'pk': obj.sessao_plenaria_id, + 'oid': obj.pk, + 'mid': obj.materia_id}) + elif obj.tipo_votacao == 3: + url = reverse('sapl.sessao:votacaosecretaedit', + kwargs={ + 'pk': obj.sessao_plenaria_id, + 'oid': obj.pk, + 'mid': obj.materia_id}) + + resultado = ('%s
%s
' % + (url, + resultado_descricao, + resultado_observacao)) + else: + if obj.tipo_votacao == 2: + if is_expediente: + url = reverse( + 'sapl.sessao:votacao_nominal_transparencia', + kwargs={ + 'pk': obj.sessao_plenaria_id, + 'oid': obj.pk, + 'mid': obj.materia_id}) + \ + '?&materia=expediente' + else: + url = reverse( + 'sapl.sessao:votacao_nominal_transparencia', + kwargs={ + 'pk': obj.sessao_plenaria_id, + 'oid': obj.pk, + 'mid': obj.materia_id}) + \ + '?&materia=ordem' + + resultado = ('%s
%s
' % + (url, + resultado_descricao, + resultado_observacao)) + else: + resultado = ('%s
%s' % + (resultado_descricao, + resultado_observacao)) + + if obj.tipo_votacao == 1: + if is_expediente: + url = reverse( + 'sapl.sessao:votacao_simbolica_transparencia', + kwargs={ + 'pk': obj.sessao_plenaria_id, + 'oid': obj.pk, + 'mid': obj.materia_id}) + \ + '?&materia=expediente' + else: + url = reverse( + 'sapl.sessao:votacao_simbolica_transparencia', + kwargs={ + 'pk': obj.sessao_plenaria_id, + 'oid': obj.pk, + 'mid': obj.materia_id}) + \ + '?&materia=ordem' + + resultado = ('%s
%s
' % + (url, + resultado_descricao, + resultado_observacao)) + else: + resultado = ('%s
%s' % + (resultado_descricao, + resultado_observacao)) + context['rows'][i][3] = (resultado, None) return context @@ -255,117 +440,15 @@ class MateriaOrdemDiaCrud(MasterDetailCrud): class DetailView(MasterDetailCrud.DetailView): - @property - def layout_key(self): - return 'OrdemDiaDetail' + layout_key = 'OrdemDiaDetail' class ListView(MasterDetailCrud.ListView): paginate_by = None ordering = ['numero_ordem', 'materia', 'resultado'] - def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - return customize_link_materia(context, self.kwargs['pk']) - - def get_rows(self, object_list): - for obj in object_list: - exist_resultado = obj.registrovotacao_set.filter( - materia=obj.materia).exists() - if not exist_resultado: - if obj.votacao_aberta: - url = '' - if obj.tipo_votacao == 1: - url = reverse('sapl.sessao:votacaosimbolica', - kwargs={ - 'pk': obj.sessao_plenaria_id, - 'oid': obj.pk, - 'mid': obj.materia_id}) - elif obj.tipo_votacao == 2: - url = reverse('sapl.sessao:votacaonominal', - kwargs={ - 'pk': obj.sessao_plenaria_id, - 'oid': obj.pk, - 'mid': obj.materia_id}) - elif obj.tipo_votacao == 3: - url = reverse('sapl.sessao:votacaosecreta', - kwargs={ - 'pk': obj.sessao_plenaria_id, - 'oid': obj.pk, - 'mid': obj.materia_id}) - if self.request.user.has_module_perms(AppConfig.label): - btn_registrar = ''' - Registrar Votação''' % ( - url) - obj.resultado = btn_registrar - else: - obj.resultado = '''Não há resultado''' - else: - url = reverse('sapl.sessao:abrir_votacao', kwargs={ - 'pk': obj.pk, - 'spk': obj.sessao_plenaria_id - }) + '?tipo_materia=ordem' - - if self.request.user.has_module_perms(AppConfig.label): - btn_abrir = ''' - Matéria não votada
- Abrir Votação''' % (url) - obj.resultado = btn_abrir - else: - obj.resultado = '''Não há resultado''' - else: - resultado = obj.registrovotacao_set.get( - materia_id=obj.materia_id) - resultado_descricao = resultado.tipo_resultado_votacao.nome - resultado_observacao = resultado.observacao - - if self.request.user.has_module_perms(AppConfig.label): - url = '' - if obj.tipo_votacao == 1: - url = reverse('sapl.sessao:votacaosimbolicaedit', - kwargs={ - 'pk': obj.sessao_plenaria_id, - 'oid': obj.pk, - 'mid': obj.materia_id}) - elif obj.tipo_votacao == 2: - url = reverse('sapl.sessao:votacaonominaledit', - kwargs={ - 'pk': obj.sessao_plenaria_id, - 'oid': obj.pk, - 'mid': obj.materia_id}) - elif obj.tipo_votacao == 3: - url = reverse('sapl.sessao:votacaosecretaedit', - kwargs={ - 'pk': obj.sessao_plenaria_id, - 'oid': obj.pk, - 'mid': obj.materia_id}) - obj.resultado = ('%s
%s
' % - (url, - resultado_descricao, - resultado_observacao)) - else: - if obj.tipo_votacao == 2: - url = reverse( - 'sapl.sessao:votacao_nominal_transparencia', - kwargs={ - 'pk': obj.sessao_plenaria_id, - 'oid': obj.pk, - 'mid': obj.materia_id}) +\ - '?&materia=ordem' - obj.resultado = ('%s
%s
' % - (url, - resultado_descricao, - resultado_observacao)) - else: - obj.resultado = ('%s
%s' % - (resultado_descricao, - resultado_observacao)) - - return [self._as_row(obj) for obj in object_list] - + has_permition = self.request.user.has_module_perms(AppConfig.label) + return customize_link_materia(context, self.kwargs['pk'], has_permition, False) def recuperar_materia(request): tipo = TipoMateriaLegislativa.objects.get(pk=request.GET['tipo_materia']) @@ -400,106 +483,8 @@ class ExpedienteMateriaCrud(MasterDetailCrud): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - - return customize_link_materia(context, self.kwargs['pk']) - - def get_rows(self, object_list): - for obj in object_list: - exist_resultado = obj.registrovotacao_set.filter( - materia=obj.materia).exists() - - if not exist_resultado: - if obj.votacao_aberta: - url = '' - if obj.tipo_votacao == 1: - url = reverse('sapl.sessao:votacaosimbolicaexp', - kwargs={ - 'pk': obj.sessao_plenaria_id, - 'oid': obj.pk, - 'mid': obj.materia_id}) - elif obj.tipo_votacao == 2: - url = reverse('sapl.sessao:votacaonominalexp', - kwargs={ - 'pk': obj.sessao_plenaria_id, - 'oid': obj.pk, - 'mid': obj.materia_id}) - elif obj.tipo_votacao == 3: - url = reverse('sapl.sessao:votacaosecretaexp', - kwargs={ - 'pk': obj.sessao_plenaria_id, - 'oid': obj.pk, - 'mid': obj.materia_id}) - - if self.request.user.has_module_perms(AppConfig.label): - btn_registrar = ''' -
- -
- ''' % (url) - obj.resultado = btn_registrar - else: - url = reverse('sapl.sessao:abrir_votacao', kwargs={ - 'pk': obj.pk, - 'spk': obj.sessao_plenaria_id - }) + '?tipo_materia=expediente' - btn_abrir = '''Matéria não votada
''' - - if self.request.user.has_module_perms(AppConfig.label): - btn_abrir += ''' - Abrir Votação''' % (url) - - obj.resultado = btn_abrir - else: - url = '' - resultado = obj.registrovotacao_set.get( - materia_id=obj.materia_id) - resultado_descricao = resultado.tipo_resultado_votacao.nome - resultado_observacao = resultado.observacao - if self.request.user.has_module_perms(AppConfig.label): - if obj.tipo_votacao == 1: - url = reverse( - 'sapl.sessao:votacaosimbolicaexpedit', - kwargs={ - 'pk': obj.sessao_plenaria_id, - 'oid': obj.pk, - 'mid': obj.materia_id}) - elif obj.tipo_votacao == 2: - url = reverse('sapl.sessao:votacaonominalexpedit', - kwargs={ - 'pk': obj.sessao_plenaria_id, - 'oid': obj.pk, - 'mid': obj.materia_id}) - elif obj.tipo_votacao == 3: - url = reverse('sapl.sessao:votacaosecretaexpedit', - kwargs={ - 'pk': obj.sessao_plenaria_id, - 'oid': obj.pk, - 'mid': obj.materia_id}) - obj.resultado = ('%s
%s
' % - (url, - resultado_descricao, - resultado_observacao)) - else: - if obj.tipo_votacao == 2: - url = reverse( - 'sapl.sessao:votacao_nominal_transparencia', - kwargs={ - 'pk': obj.sessao_plenaria_id, - 'oid': obj.pk, - 'mid': obj.materia_id}) +\ - '?&materia=expediente' - obj.resultado = ('%s
%s
' % - (url, - resultado_descricao, - resultado_observacao)) - else: - obj.resultado = ('%s
%s' % - (resultado_descricao, - resultado_observacao)) - return [self._as_row(obj) for obj in object_list] + has_permition = self.request.user.has_module_perms(AppConfig.label) + return customize_link_materia(context, self.kwargs['pk'], has_permition, True) class CreateView(MasterDetailCrud.CreateView): form_class = ExpedienteMateriaForm @@ -529,9 +514,7 @@ class ExpedienteMateriaCrud(MasterDetailCrud): class DetailView(MasterDetailCrud.DetailView): - @property - def layout_key(self): - return 'ExpedienteMateriaDetail' + layout_key = 'ExpedienteMateriaDetail' class OradorCrud(MasterDetailCrud): @@ -577,6 +560,9 @@ class OradorCrud(OradorCrud): class BancadaCrud(Crud): model = Bancada + class ListView(Crud.ListView): + template_name = 'crud/list_tabaux.html' + class CreateView(Crud.CreateView): form_class = BancadaForm @@ -587,6 +573,9 @@ class BancadaCrud(Crud): class BlocoCrud(Crud): model = Bloco + class ListView(Crud.ListView): + template_name = 'crud/list_tabaux.html' + class CreateView(Crud.CreateView): form_class = BlocoForm @@ -631,9 +620,7 @@ class SessaoCrud(Crud): list_field_names = ['data_inicio', 'legislatura', 'sessao_legislativa', 'tipo'] - @property - def list_url(self): - return '' + list_url = '' @property def search_url(self): @@ -2169,6 +2156,43 @@ class VotacaoNominalExpedienteDetailView(DetailView): kwargs={'pk': pk}) +class VotacaoSimbolicaTransparenciaDetailView(TemplateView): + template_name = 'sessao/votacao/simbolica_transparencia.html' + + def get_context_data(self, **kwargs): + context = super(VotacaoSimbolicaTransparenciaDetailView, + self).get_context_data(**kwargs) + + materia_votacao = self.request.GET.get('materia', None) + + if materia_votacao == 'ordem': + votacao = RegistroVotacao.objects.get(ordem=self.kwargs['oid']) + elif materia_votacao == 'expediente': + votacao = RegistroVotacao.objects.get(expediente=self.kwargs['oid']) + else: + raise Http404() + + context['votacao'] = votacao + + registro_votacao = {'numero_votos_sim': votacao.numero_votos_sim, + 'numero_votos_nao': votacao.numero_votos_nao, + 'numero_abstencoes': votacao.numero_abstencoes} + context.update({'registro_votacao':registro_votacao}) + + votacao_existente = {'observacao': sub( + ' ', ' ', strip_tags(votacao.observacao)), + 'resultado': votacao.tipo_resultado_votacao.nome, + 'tipo_resultado': + votacao.tipo_resultado_votacao_id} + context.update({'resultado_votacao': votacao_existente, + 'tipos': self.get_tipos_votacao()}) + + return context + + def get_tipos_votacao(self): + for tipo in TipoResultadoVotacao.objects.all(): + yield tipo + class VotacaoExpedienteView(SessaoPermissionMixin): """ diff --git a/sapl/settings.py b/sapl/settings.py index e8c032a32..41e7015ae 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -189,6 +189,9 @@ THUMBNAIL_PROCESSORS = ( 'image_cropping.thumbnail_processors.crop_corners', ) + thumbnail_settings.THUMBNAIL_PROCESSORS +THUMBNAIL_SOURCE_GENERATORS = ( + 'sapl.utils.pil_image', +) # troque no caso de reimplementação da classe User conforme # https://docs.djangoproject.com/en/1.9/topics/auth/customizing/#substituting-a-custom-user-model diff --git a/sapl/static/styles/app.scss b/sapl/static/styles/app.scss index 2edac98fc..262b3f45e 100644 --- a/sapl/static/styles/app.scss +++ b/sapl/static/styles/app.scss @@ -481,6 +481,80 @@ p { } /* FIM TEMPLATE AJUDA */ +.container-tabaux { + .sidebar-tabaux { + background: #fafafa; + margin-top: -70px; + padding: 10px; + border: 1px solid #eee; + .navbar-right { + margin: 0; + } + .nav-pills > li + li { + margin-left: 0px; + } + li { + width: 100%; + } + span { + display: none; + } + .dropdown-menu { + padding: 0px; + right: 10px; + margin-top: -5px; + overflow: hidden; + a { + border: 0px; + } + } + } + + ul { + list-style: none; + padding: 0; + } + + .list { + font-family: "SourceSansProSemiBold", Helvetica, Arial, sans-serif; + font-size: 0px; + display: table; + width: 100%; + margin: 0; + + ul { + display: table; + width: 100%; + margin: 0; + } + + li { + width: calc(50%); + display: inline-block; + position: relative; + } + & > li { + width: 100%; + border-bottom: 1px solid #eee; + padding-bottom: 20px; + margin-bottom: 20px; + } + + .head_title { + color: #364347; + font-size: 2.4rem; + text-transform: none; + } + + a { + span { + display: none; + } + } + } +} + + @media (max-width: 1199px) { .masthead { .navbar-brand { diff --git a/sapl/templates/auth/user_form.html b/sapl/templates/auth/user_form.html new file mode 100644 index 000000000..3b19ee162 --- /dev/null +++ b/sapl/templates/auth/user_form.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} +{% load i18n crispy_forms_tags %} + +{% block base_content %} + +
+ {% csrf_token %} + {% crispy form %} + {% if object.pk %} + Remover usuário + {% endif %} +
+ +{% endblock base_content %} \ No newline at end of file diff --git a/sapl/templates/auth/user_list.html b/sapl/templates/auth/user_list.html new file mode 100644 index 000000000..b24cb764e --- /dev/null +++ b/sapl/templates/auth/user_list.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} +{% load i18n %} +{% load tz %} +{% load common_tags %} +{% block base_content %} +
+

Lista de usuários

+ {% if not user_list %} +

{{ NO_ENTRIES_MSG }}

+ {% else %} + + + + + + + + + + {% for user in user_list %} + + + + + + {% endfor %} + +
Nome de LoginNomeE-mail do Usuário
+ {{ user.username }} + {{ user.first_name }} {{ user.last_name }}{{ user.email }}
+ {% endif %} + Criar Usuário +
+ {% include 'paginacao.html'%} +{% endblock base_content %} diff --git a/sapl/templates/base/RelatorioDataFimPrazoTramitacao_filter.html b/sapl/templates/base/RelatorioDataFimPrazoTramitacao_filter.html new file mode 100644 index 000000000..1fccf1268 --- /dev/null +++ b/sapl/templates/base/RelatorioDataFimPrazoTramitacao_filter.html @@ -0,0 +1,34 @@ +{% extends "crud/list.html" %} +{% load i18n %} +{% load crispy_forms_tags %} + +{% block base_content %} + {% if not show_results %} + {% crispy filter.form %} + {% endif %} + + {% if show_results %} + +



+ + + + + + + + + {% for materia in object_list %} + + + + + {% endfor %} + +
MatériaEmenta
+ {{materia.tipo.descricao}} - {{materia.tipo.sigla}} {{materia.numero}}/{{materia.ano}} + {{materia.ementa}}
+ {% endif %} +{% endblock base_content %} \ No newline at end of file diff --git a/sapl/templates/base/relatorios_list.html b/sapl/templates/base/relatorios_list.html index c7e47bd69..1bd0f5eda 100644 --- a/sapl/templates/base/relatorios_list.html +++ b/sapl/templates/base/relatorios_list.html @@ -36,6 +36,10 @@ Histórico de tramitações Histórico de tramitações por período e local informados. + + Tramitações por fim de prazo + Tramitações com fim de prazo no intervalo informado. + @@ -13,6 +15,7 @@ {% if user.is_superuser %}
  • {%model_verbose_name_plural 'sapl.compilacao.models.TipoDispositivo'%}
  • -
  • TODO: Perfil Estrutural de Textos Articulados
  • +
  • Relacionamento entre Dispositivos
  • {% endif %} +{% endif %} diff --git a/sapl/templates/compilacao/tipotextoarticulado_list.html b/sapl/templates/compilacao/tipotextoarticulado_list.html index e89e8afd4..509c9c0d3 100644 --- a/sapl/templates/compilacao/tipotextoarticulado_list.html +++ b/sapl/templates/compilacao/tipotextoarticulado_list.html @@ -1,9 +1,9 @@ {% extends "base.html" %} -{% load i18n %} -{% load compilacao_filters %} -{% load common_tags %} +{% load i18n compilacao_filters common_tags menus%} {% block base_content %} + + {% block actions %} {% if perms.compilacao.add_tipotextoarticulado %}
    @@ -14,27 +14,37 @@ {% endif %} {% endblock actions %} - {% if not object_list %} -

    {{ NO_ENTRIES_MSG }}

    - {% else %} - - - - - - - - - - {% for tipo_ta in object_list %} - - - - - - {% endfor %} - -
    {% fieldclass_verbose_name 'sapl.compilacao.models.TipoTextoArticulado' 'sigla' %}{% fieldclass_verbose_name 'sapl.compilacao.models.TipoTextoArticulado' 'descricao' %}{% fieldclass_verbose_name 'sapl.compilacao.models.TipoTextoArticulado' 'content_type' %}
    {{ tipo_ta.sigla }}{{ tipo_ta.descricao }}{{ tipo_ta.content_type }}
    - {%endif%} - {% include 'paginacao.html'%} +
    +
    + {% if not object_list %} +

    {{ NO_ENTRIES_MSG }}

    + {% else %} + + + + + + + + + + {% for tipo_ta in object_list %} + + + + + + {% endfor %} + +
    {% fieldclass_verbose_name 'sapl.compilacao.models.TipoTextoArticulado' 'sigla' %}{% fieldclass_verbose_name 'sapl.compilacao.models.TipoTextoArticulado' 'descricao' %}{% fieldclass_verbose_name 'sapl.compilacao.models.TipoTextoArticulado' 'content_type' %}
    {{ tipo_ta.sigla }}{{ tipo_ta.descricao }}{{ tipo_ta.content_type }}
    + {%endif%} + {% include 'paginacao.html'%} +
    + {% if perms.base.menu_tabelas_auxiliares %} + + {% endif %} +
    {% endblock %} diff --git a/sapl/templates/crud/detail.html b/sapl/templates/crud/detail.html index 8087b458b..4a1cad276 100644 --- a/sapl/templates/crud/detail.html +++ b/sapl/templates/crud/detail.html @@ -23,8 +23,8 @@
    {% if view.extras_url %}
    - {% for url, css_class, text in view.extras_url %} - + {% for href, css_class, text in view.extras_url %} + {{text}} {% endfor %} diff --git a/sapl/templates/crud/detail_detail.html b/sapl/templates/crud/detail_detail.html index cb0b103ec..b2312ec51 100644 --- a/sapl/templates/crud/detail_detail.html +++ b/sapl/templates/crud/detail_detail.html @@ -20,8 +20,8 @@
    {% if view.extras_url %}
    - {% for url, css_class, text in view.extras_url %} - + {% for href, css_class, text in view.extras_url %} + {{text}} {% endfor %} diff --git a/sapl/templates/crud/list.html b/sapl/templates/crud/list.html index 3789761fc..b1743516d 100644 --- a/sapl/templates/crud/list.html +++ b/sapl/templates/crud/list.html @@ -20,7 +20,22 @@ {% block more_buttons %}{% endblock more_buttons %}
    {% endblock actions %} + + {% block extra_actions %}{% endblock extra_actions %} + + {% comment %} + {% if view.extras_url %} +
    + {% for href, css_class, text in view.extras_url %} + + {{text}} + + {% endfor %} +
    + {% endif %} + {% endcomment %} + {% block extra_content %} {% endblock %} {% block container_table_list %} diff --git a/sapl/templates/crud/list_tabaux.html b/sapl/templates/crud/list_tabaux.html new file mode 100644 index 000000000..77c81f651 --- /dev/null +++ b/sapl/templates/crud/list_tabaux.html @@ -0,0 +1,15 @@ +{% extends "crud/list.html" %} +{% load i18n menus%} +{% block base_content %} +
    +
    + {{block.super}} +
    + {% if perms.base.menu_tabelas_auxiliares %} + + {% endif %} +
    +{% endblock base_content %} diff --git a/sapl/templates/materia/em_lote/tramitacao.html b/sapl/templates/materia/em_lote/tramitacao.html index 333050875..08483d88d 100644 --- a/sapl/templates/materia/em_lote/tramitacao.html +++ b/sapl/templates/materia/em_lote/tramitacao.html @@ -143,7 +143,7 @@ $('input[type=submit]').click(function() { $('#id_unidade_tramitacao_local').attr('disabled', false); $('#id_unidade_tramitacao_local').parents('form').submit(); - }) + }); }); {% endblock %} diff --git a/sapl/templates/materia/materialegislativa_filter.html b/sapl/templates/materia/materialegislativa_filter.html index 96a7eb55a..2d54016ba 100644 --- a/sapl/templates/materia/materialegislativa_filter.html +++ b/sapl/templates/materia/materialegislativa_filter.html @@ -137,7 +137,7 @@

    Acompanhar Matéria - + {% endfor %} {% else %} diff --git a/sapl/templates/materia/proposicao_detail.html b/sapl/templates/materia/proposicao_detail.html index 23500e739..53d6e8620 100644 --- a/sapl/templates/materia/proposicao_detail.html +++ b/sapl/templates/materia/proposicao_detail.html @@ -136,7 +136,7 @@ {% endif %} - + {% if not AppConfig.receber_recibo_proposicao %} {% if object.hash_code %} diff --git a/sapl/templates/materia/tipoproposicao_form.html b/sapl/templates/materia/tipoproposicao_form.html index ec8facf7f..11493e6fe 100644 --- a/sapl/templates/materia/tipoproposicao_form.html +++ b/sapl/templates/materia/tipoproposicao_form.html @@ -3,6 +3,17 @@ {% block extra_js %} + + + {% endblock %} diff --git a/sapl/templates/menu_tabelas_auxiliares.yaml b/sapl/templates/menu_tabelas_auxiliares.yaml new file mode 100644 index 000000000..c1ae1767f --- /dev/null +++ b/sapl/templates/menu_tabelas_auxiliares.yaml @@ -0,0 +1,175 @@ +{% load i18n common_tags %} +- title: {% trans 'Configurações Gerais' %} + css_class: head_title + children: + - title: {% trans 'Casa Legislativa' %} + url: sapl.base:casalegislativa_list + css_class: btn btn-link + - title: {% trans 'Configurações da Aplicação' %} + url: sapl.base:appconfig_list + css_class: btn btn-link' + - title: {% trans 'Autor' %} + url: sapl.base:autor_list + css_class: btn btn-link + - title: {% trans 'Tipo de Autor' %} + url: sapl.base:tipoautor_list + css_class: btn btn-link +- title: {% trans 'Módulo Parlamentares' %} + css_class: head_title + children: + - title: {% trans 'Legislatura' %} + url: sapl.parlamentares:legislatura_list + css_class: btn btn-link + - title: {% trans 'Tipo de Afastamento' %} + url: sapl.parlamentares:tipoafastamento_list + css_class: btn btn-link + - title: {% trans 'Tipo de Dependente' %} + url: sapl.parlamentares:tipodependente_list + css_class: btn btn-link + - title: {% trans 'Tipo de Situação Militar' %} + url: sapl.parlamentares:situacaomilitar_list + css_class: btn btn-link + - title: {% trans 'Nível de Instrução' %} + url: sapl.parlamentares:nivelinstrucao_list + css_class: btn btn-link + - title: {% trans 'Partido' %} + url: sapl.parlamentares:partido_list + css_class: btn btn-link + - title: {% trans 'Coligação' %} + url: sapl.parlamentares:coligacao_list + css_class: btn btn-link +- title: {% trans 'Módulo Mesa Diretora' %} + css_class: head_title + children: + - title: {% trans 'Sessão Legislativa' %} + url: sapl.parlamentares:sessaolegislativa_list + css_class: btn btn-link + - title: {% trans 'Cargo da Mesa' %} + url: sapl.parlamentares:cargomesa_list + css_class: btn btn-link +- title: {% trans 'Módulo Comissões' %} + css_class: head_title + children: + - title: {% trans 'Cargo de Comissão' %} + url: sapl.comissoes:cargocomissao_list + css_class: btn btn-link + - title: {% trans 'Período de Composição' %} + url: sapl.comissoes:periodo_list + css_class: btn btn-link + - title: {% trans 'Tipo de Comissão' %} + url: sapl.comissoes:tipocomissao_list + css_class: btn btn-link +- title: {% trans 'Módulo Bancadas Parlamentares' %} + css_class: head_title + children: + - title: {% trans 'Bancadas Parlamentares' %} + url: sapl.sessao:bancada_list + css_class: btn btn-link + - title: {% trans 'Cargo de Bancada Parlamentar' %} + url: sapl.sessao:cargobancada_list + css_class: btn btn-link + - title: {% trans 'Frente Parlamentar' %} + url: sapl.parlamentares:frente_list + css_class: btn btn-link + - title: {% trans 'Bloco Parlamentar' %} + url: sapl.sessao:bloco_list + css_class: btn btn-link +- title: {% trans 'Módulo Proposições' %} + css_class: head_title + children: + - title: {% trans 'Tipo de Proposição' %} + url: sapl.materia:tipoproposicao_list + css_class: btn btn-link +- title: {% trans 'Módulo Matéria Legislativa' %} + css_class: head_title + children: + - title: {% trans 'Tipo de Matéria Legislativa' %} + url: sapl.materia:tipomaterialegislativa_list + css_class: btn btn-link + - title: {% trans 'Regime de Tramitação' %} + url: sapl.materia:regimetramitacao_list + css_class: btn btn-link + - title: {% trans 'Tipo de Documento' %} + url: sapl.materia:tipodocumento_list + css_class: btn btn-link + - title: {% trans 'Tipo de fim de Relatoria' %} + url: sapl.materia:tipofimrelatoria_list + css_class: btn btn-link + - title: {% trans 'Unidade de Tramitação' %} + url: sapl.materia:unidadetramitacao_list + css_class: btn btn-link + - title: {% trans 'Origem' %} + url: sapl.materia:origem_list + css_class: btn btn-link + - title: {% trans 'Status da Tramitação' %} + url: sapl.materia:statustramitacao_list + css_class: btn btn-link + - title: {% trans 'Órgão' %} + url: sapl.materia:orgao_list + css_class: btn btn-link + - title: {% trans 'Assunto Matéria' %} + url: sapl.materia:assuntomateria_list + css_class: btn btn-link +- title: {% trans 'Módulo Normas Jurídicas' %} + css_class: head_title + children: + - title: {% trans 'Tipo de Norma Jurídica' %} + url: sapl.norma:tiponormajuridica_list + css_class: btn btn-link + - title: {% trans 'Assunto de Norma Jurídica' %} + url: sapl.norma:assuntonorma_list + css_class: btn btn-link + - title: {% trans 'Tipo de Vínculo' %} + url: sapl.norma:tipovinculonormajuridica_list + css_class: btn btn-link +- title: {% trans 'Módulo Textos Articulados' %} + css_class: head_title + children: + - title: {% trans 'Tipos de Textos Articulados' %} + url: sapl.compilacao:tipo_ta_list + css_class: btn btn-link + - title: {% trans 'Tipos de Publicação' %} + url: sapl.compilacao:tipopublicacao_list + css_class: btn btn-link + - title: {% trans 'Veículos de Publicação' %} + url: sapl.compilacao:veiculopublicacao_list + css_class: btn btn-link + - title: {% trans 'Tipos de Notas' %} + url: sapl.compilacao:tiponota_list + css_class: btn btn-link + - title: {% trans 'Tipos de Vides' %} + url: sapl.compilacao:tipovide_list + css_class: btn btn-link +- title: {% trans 'Módulo Sessão Plenária' %} + css_class: head_title + children: + - title: {% trans 'Tipo de Sessão Plenária' %} + url: sapl.sessao:tiposessaoplenaria_list + css_class: btn btn-link + - title: {% trans 'Tipo de Resultado da Votação' %} + url: sapl.sessao:tiporesultadovotacao_list + css_class: btn btn-link + - title: {% trans 'Tipo de Expediente' %} + url: sapl.sessao:tipoexpediente_list + css_class: btn btn-link + - title: {% trans 'Ordenação do Resumo' %} + url: sapl.sessao:resumo_ordenacao + css_class: btn btn-link +- title: {% trans 'Módulo LexML' %} + css_class: head_title + children: + - title: {% trans 'Provedor' %} + url: sapl.lexml:lexmlprovedor_list + css_class: btn btn-link + - title: {% trans 'Publicador' %} + url: sapl.lexml:lexmlpublicador_list + css_class: btn btn-link +- title: {% trans 'Módulo Administrativo' %} + css_class: head_title + children: + - title: {% trans 'Tipo de Documento' %} + url: sapl.protocoloadm:tipodocumentoadministrativo_list + css_class: btn btn-link + - title: {% trans 'Status de Tramitação' %} + url: sapl.protocoloadm:statustramitacaoadministrativo_list + css_class: btn btn-link diff --git a/sapl/templates/menus/menu.html b/sapl/templates/menus/menu.html new file mode 100644 index 000000000..9b0a086f4 --- /dev/null +++ b/sapl/templates/menus/menu.html @@ -0,0 +1,36 @@ +{% load i18n %} +{% if menu %} + {% for item in menu %} + {% if item.children %} +
  • + {% if item.url %} + + {{ item.title|safe }} + + + {% else %} + + {{ item.title|safe }} + + {% endif %} +
      + {% with item.children as menu %} + {% include "menus/menu.html" %} + {% endwith %} +
    +
  • + {% else %} +
  • + {% if item.url %} + + {{ item.title|safe }} + + {% else %} + + {{ item.title|safe }} + + {% endif %} +
  • + {% endif %} + {% endfor %} +{% endif %} diff --git a/sapl/templates/navbar.yaml b/sapl/templates/navbar.yaml index 6f4eeb84f..80d220fb3 100644 --- a/sapl/templates/navbar.yaml +++ b/sapl/templates/navbar.yaml @@ -70,7 +70,7 @@ url: '/sistema' check_permission: base.view_tabelas_auxiliares - title: {% trans 'Administração de Usuários' %} - url: '/admin' + url: {% url 'sapl.base:user_list' %} check_permission: user.is_superuser {% comment %} diff --git a/sapl/templates/parlamentares/frente_form.html b/sapl/templates/parlamentares/frente_form.html index 5d9ae8fc1..bbab780ae 100644 --- a/sapl/templates/parlamentares/frente_form.html +++ b/sapl/templates/parlamentares/frente_form.html @@ -22,7 +22,7 @@ -