diff --git a/Dockerfile b/Dockerfile index 838257c66..85724ca11 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 vim +py3-magic postgresql-client poppler-utils vim RUN apk add --no-cache python3 nginx && \ python3 -m ensurepip && \ @@ -35,9 +35,12 @@ COPY config/env_dockerfile /var/interlegis/sapl/sapl/.env # compilescss - Precompile all occurrences of your SASS/SCSS files for the whole project into css files RUN python3 manage.py bower_install -- --allow-root --no-input && \ - python3 manage.py compilescss && \ - python3 manage.py collectstatic --no-input && \ - rm -rf /var/interlegis/sapl/sapl/.env && \ + python3 manage.py compilescss + +RUN python3 manage.py collectstatic --noinput --clear + +# Remove .env(fake) e sapl.db da imagem +RUN rm -rf /var/interlegis/sapl/sapl/.env && \ rm -rf /var/interlegis/sapl/sapl.db RUN chmod +x /var/interlegis/sapl/start.sh && \ diff --git a/MANIFEST.in b/MANIFEST.in index 9a70ef5bd..3fe372d5d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,5 +2,6 @@ include README.rst LICENSE.txt recursive-include sapl *.html *.yaml recursive-include sapl/static * recursive-include sapl/relatorios/templates *.py +recursive-include sapl/compilacao *.sql global-exclude __pycache__ global-exclude *.py[co] diff --git a/README.rst b/README.rst index b149ad906..3cc73b28b 100644 --- a/README.rst +++ b/README.rst @@ -17,7 +17,7 @@ atual do sistema (2.5), visite a página do `projeto na Interlegis wiki `_ + `Instalação do Ambiente de Desenvolvimento `_ Instruções para Importação da base mysql 2.5 diff --git a/codeclimate.yml b/codeclimate.yml new file mode 100644 index 000000000..2cc90a96e --- /dev/null +++ b/codeclimate.yml @@ -0,0 +1,24 @@ +engines: + eslint: + enabled: false + csslint: + enabled: false + duplication: + enabled: true + config: + languages: + - python + - javascript + fixme: + enabled: true + radon: + enabled: true +ratings: + paths: + - "**.py" + - "**.js" +exclude_paths: +- sapl/**/migrations/* +- sapl/**/legacy/* +- sapl/relatorios/ +- sapl/templates/* diff --git a/create_admin.py b/create_admin.py index 7fe03a65c..eb60ac940 100644 --- a/create_admin.py +++ b/create_admin.py @@ -1,13 +1,13 @@ -import sys import os +import sys import django +from sapl import settings os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sapl.settings") -django.setup() - -from django.contrib.auth.models import User def create_superuser(): + from django.contrib.auth.models import User + username = "admin" password = os.environ['ADMIN_PASSWORD'] if 'ADMIN_PASSWORD' in os.environ else None email = os.environ['ADMIN_EMAIL'] if 'ADMIN_EMAIL' in os.environ else '' @@ -30,4 +30,5 @@ def create_superuser(): sys.exit(0) if __name__ == '__main__': + django.setup() create_superuser() diff --git a/docker-compose.yml b/docker-compose.yml index 4fbbd4bea..a06f2c81c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,7 @@ sapldb: ports: - "5532:5432" sapl: - image: interlegis/sapl:3.1.2-BETA + image: interlegis/sapl:3.1.12-BETA volumes: - sapl_data:/var/interlegis/sapl/data - sapl_media:/var/interlegis/sapl/media diff --git a/docs/deploy.rst b/docs/deploy.rst index ac81ac5d3..101b7bb43 100644 --- a/docs/deploy.rst +++ b/docs/deploy.rst @@ -17,16 +17,20 @@ alterando o variável DEBUG para false:: DEBUG = False +Entrar no ambiente virtual:: + + workon sapl + Arquivos Estáticos ------------------ Com o ambiente em produção, os arquivos estáticos devem ser servidos pelo web service, em nosso caso o `NGINX`, logo para ter acesso aos arquivos primeiro devemos rodar o seguinte comando:: - python3 manage.py compilescss + ./manage.py compilescss para que os arquivos SASS/SCSS sejam compilados em arquivos .css em ambiente de produção, e em seguida rode:: - pyhton3 manage.py collectstatic --no-input + ./manage.py collectstatic --no-input para coletar todos os arquivos estáticos do projeto e guarda-los no diretório definido em `STATIC_ROOT`, que será também o diretório no qual o `NGINX` irá referenciar para a aplicação. @@ -40,7 +44,7 @@ Instalar o NGINX:: Instalar o Gunicorn:: - sudo pip install gunicorn + sudo pip3 install gunicorn Preparando o NGINX @@ -91,7 +95,7 @@ Criar link simbólico para ativar o site:: sudo ln -s /etc/nginx/sites-available/sapl31.conf /etc/nginx/sites-enabled/sapl3 -Reiniciar o nginx +Reiniciar o nginx:: sudo service nginx restart diff --git a/docs/importacao_25_31.rst b/docs/importacao_25_31.rst index ba6f48a34..44819091b 100644 --- a/docs/importacao_25_31.rst +++ b/docs/importacao_25_31.rst @@ -80,3 +80,32 @@ Vincular os documentos ao novo banco do sapl 3.1 %run sapl/legacy/migracao_documentos.py migrar_documentos() + + +Para indexar os arquivos para pesquisa textual +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +1. workon sapl +2. ./manage.py rebuild_index + + +Dependendo da quantidade de arquivos a serem indexados, pode ser listado o seguinte erro 'Too many open files' + +Isto está ligado a quantidade máxima de aquivos que podem ser abertos ao mesmo tempo pelo sistema operacional + +Para aumentar este limite:: + + sudo nano /etc/security/limits.conf + * soft nofile 9000 + * hard nofile 65000 + + + sudo nano /etc/pam.d/common-session + session required pam_limits.so + +Após reiniciar, verificar se foram carregados os novos parâmetros com o comando:: + ulimit -a + +deve ser apresentado o seguinte:: + open files (-n) 9000 + + diff --git a/docs/instacao31.rst b/docs/instalacao31.rst similarity index 87% rename from docs/instacao31.rst rename to docs/instalacao31.rst index 3d6a8b3eb..60c30df56 100644 --- a/docs/instacao31.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 + python3-pip curl poppler-utils default-jre sudo -i curl -sL https://deb.nodesource.com/setup_5.x | bash - @@ -145,13 +145,15 @@ Criação da `SECRET_KEY campos lookup a serem filtrados em qualquer Model + que implemente SaplGenericRelation + q_1 -> o valor que será pesquisado no lookup de q_0 + + q_0 e q_1 podem ser separados por ","... isso dará a + possibilidade de filtrar mais de um campo. + + + http://localhost:8000 + /api/autor?tr=1&q_0=parlamentar_set__ativo&q_1=False + /api/autor?tr=1&q_0=parlamentar_set__ativo&q_1=True + /api/autor?tr=3&q_0=parlamentar_set__ativo&q_1=False + /api/autor?tr=3&q_0=parlamentar_set__ativo&q_1=True + + http://localhost:8000 + /api/autor?tr=1 + &q_0=parlamentar_set__nome_parlamentar__icontains, + parlamentar_set__ativo + &q_1=Carvalho,False + /api/autor?tr=1 + &q_0=parlamentar_set__nome_parlamentar__icontains, + parlamentar_set__ativo + &q_1=Carvalho,True + /api/autor?tr=3 + &q_0=parlamentar_set__nome_parlamentar__icontains, + parlamentar_set__ativo + &q_1=Carvalho,False + /api/autor?tr=3 + &q_0=parlamentar_set__nome_parlamentar__icontains, + parlamentar_set__ativo + &q_1=Carvalho,True + + + não importa o campo que vc passe de qualquer dos Models + ligados... é possível ver que models são esses, + na ocasião do commit deste texto, executando: + In [6]: from sapl.utils import models_with_gr_for_model + + In [7]: models_with_gr_for_model(Autor) + Out[7]: + [sapl.parlamentares.models.Parlamentar, + sapl.parlamentares.models.Frente, + sapl.comissoes.models.Comissao, + sapl.materia.models.Orgao, + sapl.sessao.models.Bancada, + sapl.sessao.models.Bloco] + + qualquer atributo destes models podem ser passados + para busca """ TR_AUTOR_CHOICE_SERIALIZER = 1 @@ -125,6 +174,9 @@ class AutorListView(ListAPIView): self.serializer_class = AutorSerializer self.permission_classes = (IsAuthenticated,) + if self.filter_class and 'q_0' in request.GET: + self.filter_class = AutorSearchForFieldFilterSet + return ListAPIView.get(self, request, *args, **kwargs) def get_queryset(self): @@ -186,9 +238,12 @@ class AutorListView(ListAPIView): q_filter = q_filter & q_fs qs = qs.filter(q_filter).distinct( - fields[0].fields_search[0][0]) + fields[0].fields_search[0][0]).order_by( + fields[0].fields_search[0][0]) + else: + qs = qs.order_by(fields[0].fields_search[0][0]) - qs = qs.order_by(fields[0].fields_search[0][0]).values_list( + qs = qs.values_list( 'id', fields[0].fields_search[0][0]) r += list(qs) @@ -207,6 +262,7 @@ class MateriaLegislativaViewSet(ListModelMixin, filter_backends = (DjangoFilterBackend,) filter_fields = ('numero', 'ano', 'tipo', ) + class SessaoPlenariaViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): diff --git a/sapl/base/forms.py b/sapl/base/forms.py index 11d93a13f..b245470a1 100644 --- a/sapl/base/forms.py +++ b/sapl/base/forms.py @@ -227,6 +227,8 @@ class AutorForm(ModelForm): return True def clean(self): + super(AutorForm, self).clean() + User = get_user_model() cd = self.cleaned_data @@ -505,6 +507,11 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet): 'widget': RangeWidgetOverride} }} + @property + def qs(self): + parent = super(RelatorioHistoricoTramitacaoFilterSet, self).qs + return parent.distinct().order_by('-ano', 'tipo', 'numero') + class Meta: model = MateriaLegislativa fields = ['tipo', 'tramitacao__unidade_tramitacao_local', @@ -534,7 +541,7 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet): class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet): ano = django_filters.ChoiceFilter(required=True, - label=u'Ano da Matéria', + label='Ano da Matéria', choices=RANGE_ANOS) class Meta: @@ -565,7 +572,7 @@ class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet): class RelatorioMateriasPorAnoAutorTipoFilterSet(django_filters.FilterSet): ano = django_filters.ChoiceFilter(required=True, - label=u'Ano da Matéria', + label='Ano da Matéria', choices=RANGE_ANOS) class Meta: @@ -720,6 +727,8 @@ class RecuperarSenhaForm(PasswordResetForm): super(RecuperarSenhaForm, self).__init__(*args, **kwargs) def clean(self): + super(RecuperarSenhaForm, self).clean() + email_existente = User.objects.filter( email=self.data['email']).exists() diff --git a/sapl/base/migrations/0003_auto_20170519_1106.py b/sapl/base/migrations/0003_auto_20170519_1106.py new file mode 100644 index 000000000..f16a20172 --- /dev/null +++ b/sapl/base/migrations/0003_auto_20170519_1106.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-05-19 11:06 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0002_auto_20170331_1900'), + ] + + operations = [ + migrations.AlterField( + model_name='autor', + name='nome', + field=models.CharField(blank=True, max_length=60, verbose_name='Nome do Autor'), + ), + ] diff --git a/sapl/base/migrations/0004_auto_20170714_1838.py b/sapl/base/migrations/0004_auto_20170714_1838.py new file mode 100644 index 000000000..f675e2e67 --- /dev/null +++ b/sapl/base/migrations/0004_auto_20170714_1838.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-07-14 18:38 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0003_auto_20170519_1106'), + ] + + operations = [ + migrations.RemoveField( + model_name='problemamigracao', + name='eh_importante', + ), + migrations.AddField( + model_name='problemamigracao', + name='critico', + field=models.BooleanField(default=False, verbose_name='Crítico'), + ), + ] diff --git a/sapl/base/models.py b/sapl/base/models.py index 9dd0f5ec3..f2f81ab15 100644 --- a/sapl/base/models.py +++ b/sapl/base/models.py @@ -65,8 +65,8 @@ class ProblemaMigracao(models.Model): 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?')) - eh_importante = models.BooleanField( - default=False, verbose_name=_('É importante?')) + critico = models.BooleanField( + default=False, verbose_name=_('Crítico')) class Meta: verbose_name = _('Problema na Migração') @@ -211,7 +211,7 @@ class Autor(models.Model): autor_related = GenericForeignKey('content_type', 'object_id') nome = models.CharField( - max_length=50, blank=True, verbose_name=_('Nome do Autor')) + max_length=60, blank=True, verbose_name=_('Nome do Autor')) cargo = models.CharField(max_length=50, blank=True) diff --git a/sapl/base/search_indexes.py b/sapl/base/search_indexes.py index c85be523b..e614a2f79 100644 --- a/sapl/base/search_indexes.py +++ b/sapl/base/search_indexes.py @@ -1,11 +1,18 @@ +import logging import os.path +import re +import string import textract -from django.template import Context, loader +from django.template import loader from haystack import indexes +from textract.exceptions import ExtensionNotSupported from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa from sapl.norma.models import NormaJuridica +from sapl.settings import BASE_DIR, SOLR_URL + +logger = logging.getLogger(BASE_DIR.name) class DocumentoAcessorioIndex(indexes.SearchIndex, indexes.Indexable): @@ -21,6 +28,37 @@ class DocumentoAcessorioIndex(indexes.SearchIndex, indexes.Indexable): def index_queryset(self, using=None): return self.get_model().objects.all() + def get_updated_field(self): + return 'data_ultima_atualizacao' + + def solr_extraction(self, arquivo): + extracted_data = self._get_backend(None).extract_file_contents( + arquivo)['contents'] + # Remove as tags xml + extracted_data = re.sub('<[^>]*>', '', extracted_data) + # Remove tags \t e \n + extracted_data = extracted_data.replace( + '\n', ' ').replace('\t', ' ') + # Remove sinais de pontuação + extracted_data = re.sub('[' + string.punctuation + ']', + ' ', extracted_data) + # Remove espaços múltiplos + extracted_data = " ".join(extracted_data.split()) + + return extracted_data + + def whoosh_extraction(self, arquivo): + return textract.process( + arquivo.path, + language='pt-br').decode('utf-8').replace('\n', ' ').replace( + '\t', ' ') + + def print_error(self, arquivo): + msg = 'Erro inesperado processando arquivo: %s' % ( + arquivo.path) + print(msg) + logger.error(msg) + def prepare(self, obj): if not self.filename or not self.model or not self.template_name: raise Exception @@ -30,26 +68,39 @@ class DocumentoAcessorioIndex(indexes.SearchIndex, indexes.Indexable): arquivo = getattr(obj, self.filename) if arquivo: - try: - arquivo.open() - except OSError: + if not os.path.exists(arquivo.path): return self.prepared_data if not os.path.splitext(arquivo.path)[1][:1]: return self.prepared_data - extracted_data = textract.process( - arquivo.path).decode( - 'utf-8').replace('\n', ' ') - - extracted_data = extracted_data.replace('\t', ' ') + # Em ambiente de produção utiliza-se o SOLR + if SOLR_URL: + try: + extracted_data = self.solr_extraction(arquivo) + except Exception: + self.print_error(arquivo) + return self.prepared_data + + # Em ambiente de DEV utiliza-se o Whoosh + # Como ele não possui extração, faz-se uso do textract + else: + try: + extracted_data = self.whoosh_extraction(arquivo) + except ExtensionNotSupported as e: + print(str(e)) + logger.error(str(e)) + return self.prepared_data + except Exception: + self.print_error(arquivo) + return self.prepared_data # Now we'll finally perform the template processing to render the # text field with *all* of our metadata visible for templating: t = loader.select_template(( 'search/indexes/' + self.template_name, )) - data['text'] = t.render(Context({'object': obj, - 'extracted': extracted_data})) + data['text'] = t.render({'object': obj, + 'extracted': extracted_data}) return data @@ -63,6 +114,9 @@ class MateriaLegislativaIndex(DocumentoAcessorioIndex): model = MateriaLegislativa template_name = 'materia/materialegislativa_text.txt' + def get_updated_field(self): + return 'data_ultima_atualizacao' + class NormaJuridicaIndex(DocumentoAcessorioIndex): text = indexes.CharField(document=True, use_template=True) @@ -70,3 +124,6 @@ class NormaJuridicaIndex(DocumentoAcessorioIndex): filename = 'texto_integral' model = NormaJuridica template_name = 'norma/normajuridica_text.txt' + + def get_updated_field(self): + return 'data_ultima_atualizacao' diff --git a/sapl/base/templatetags/common_tags.py b/sapl/base/templatetags/common_tags.py index 419899632..0f274ab29 100644 --- a/sapl/base/templatetags/common_tags.py +++ b/sapl/base/templatetags/common_tags.py @@ -117,6 +117,23 @@ def str2intabs(value): except: return '' +@register.filter +def has_iframe(request): + + iframe = request.session.get('iframe', False) + if not iframe and 'iframe' in request.GET: + ival = request.GET['iframe'] + if ival and int(ival) == 1: + request.session['iframe'] = True + return True + elif 'iframe' in request.GET: + ival = request.GET['iframe'] + if ival and int(ival) == 0: + del request.session['iframe'] + return False + + return iframe + @register.filter def url(value): @@ -151,3 +168,15 @@ def search_get_model(object): return 'n' return None + + +@register.filter +def urldetail_content_type(obj, value): + return '%s:%s_detail' % ( + value._meta.app_config.name, obj.content_type.model) + + +@register.filter +def urldetail(obj): + return '%s:%s_detail' % ( + obj._meta.app_config.name, obj._meta.model_name) diff --git a/sapl/base/urls.py b/sapl/base/urls.py index d5e4ae98a..2b4e52d62 100644 --- a/sapl/base/urls.py +++ b/sapl/base/urls.py @@ -66,7 +66,7 @@ urlpatterns = [ # TODO mover estas telas para a app 'relatorios' url(r'^sistema/relatorios/$', TemplateView.as_view( - template_name='base/relatorios_list.html')), + template_name='base/relatorios_list.html'), name='relatorios_list'), url(r'^sistema/relatorios/materia-por-autor$', RelatorioMateriasPorAutorView.as_view(), name='materia_por_autor'), url(r'^sistema/relatorios/materia-por-ano-autor-tipo$', @@ -92,7 +92,8 @@ urlpatterns = [ # todos os sublinks de sistema devem vir acima deste url(r'^sistema/$', permission_required('base.view_tabelas_auxiliares') - (TemplateView.as_view(template_name='sistema.html'))), + (TemplateView.as_view(template_name='sistema.html')), + name='sistema'), url(r'^login/$', views.login, { 'template_name': 'base/login.html', 'authentication_form': LoginForm}, diff --git a/sapl/comissoes/views.py b/sapl/comissoes/views.py index c7c7bd117..56151d414 100644 --- a/sapl/comissoes/views.py +++ b/sapl/comissoes/views.py @@ -1,6 +1,7 @@ from django.core.urlresolvers import reverse from django.db.models import F +from django.views.decorators.clickjacking import xframe_options_exempt from django.views.generic import ListView from sapl.crud.base import RP_DETAIL, RP_LIST, Crud, CrudAux, MasterDetailCrud @@ -54,9 +55,21 @@ class ComposicaoCrud(MasterDetailCrud): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['composicao_pk'] = context['composicao_list'].last( - ).pk if self.take_composicao_pk( - ) == 0 else self.take_composicao_pk() +# context['composicao_pk'] = context['composicao_list'].last( +# ).pk if self.take_composicao_pk( +# ) == 0 else self.take_composicao_pk() + + composicao_pk = self.take_composicao_pk() + + if composicao_pk == 0: + ultima_composicao = context['composicao_list'].last() + if ultima_composicao: + context['composicao_pk'] = ultima_composicao.pk + else: + context['composicao_pk'] = 0 + else: + context['composicao_pk'] = composicao_pk + context['participacao_set'] = Participacao.objects.filter( composicao__pk=context['composicao_pk'] ).order_by('parlamentar') @@ -69,9 +82,20 @@ class ComissaoCrud(Crud): public = [RP_LIST, RP_DETAIL, ] class BaseMixin(Crud.BaseMixin): - list_field_names = ['nome', 'sigla', 'tipo', 'data_criacao', 'ativa'] + list_field_names = ['nome', 'sigla', 'tipo', 'data_criacao', 'data_extincao', 'ativa'] ordering = '-ativa', 'sigla' + class ListView(Crud.ListView): + @xframe_options_exempt + def get(self, request, *args, **kwargs): + return super().get(request, *args, **kwargs) + + class DetailView(Crud.DetailView): + @xframe_options_exempt + def get(self, request, *args, **kwargs): + return super().get(request, *args, **kwargs) + + class MateriasTramitacaoListView(ListView): template_name = "comissoes/materias_em_tramitacao.html" diff --git a/sapl/compilacao/templatetags/compilacao_filters.py b/sapl/compilacao/templatetags/compilacao_filters.py index 941ec5fe1..d1e957eff 100644 --- a/sapl/compilacao/templatetags/compilacao_filters.py +++ b/sapl/compilacao/templatetags/compilacao_filters.py @@ -286,11 +286,6 @@ def nomenclatura_heranca(d, ignore_ultimo=0, ignore_primeiro=0): return result -@register.filter -def urldetail_content_type(obj): - return '%s:%s_detail' % ( - obj.content_object._meta.app_config.name, obj.content_type.model) - @register.filter def list(obj): diff --git a/sapl/crispy_layout_mixin.py b/sapl/crispy_layout_mixin.py index 369b6fe9e..29cd7849f 100644 --- a/sapl/crispy_layout_mixin.py +++ b/sapl/crispy_layout_mixin.py @@ -1,12 +1,13 @@ 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 from django import template +from django.core.urlresolvers import reverse from django.utils import formats from django.utils.translation import ugettext as _ +import rtyaml def heads_and_tails(list_of_lists): @@ -77,29 +78,30 @@ def get_field_display(obj, fieldname): else: value = getattr(obj, fieldname) - str_type = str(type(value)) + str_type_from_value = str(type(value)) + str_type_from_field = str(type(field)) if value is None: display = '' - elif 'date' in str_type: + elif 'date' in str_type_from_value: display = formats.date_format(value, "SHORT_DATE_FORMAT") - elif 'bool' in str(type(value)): + elif 'bool' in str_type_from_value: display = _('Sim') if value else _('Não') elif 'ImageFieldFile' in str(type(value)): if value: display = ''.format(value.url) else: display = '' - elif 'FieldFile' in str(type(value)): + elif 'FieldFile' in str_type_from_value: if value: display = '{}'.format( value.url, value.name.split('/')[-1:][0]) else: display = '' - elif 'ManyRelatedManager' in str(type(value))\ - or 'RelatedManager' in str(type(value))\ - or 'GenericRelatedObjectManager' in str(type(value)): + elif 'ManyRelatedManager' in str_type_from_value\ + or 'RelatedManager' in str_type_from_value\ + or 'GenericRelatedObjectManager' in str_type_from_value: display = '
    ' for v in value.all(): display += '
  • %s
  • ' % str(v) @@ -110,6 +112,13 @@ def get_field_display(obj, fieldname): field.related_model._meta.verbose_name_plural) elif hasattr(field, 'model'): verbose_name = str(field.model._meta.verbose_name_plural) + elif 'GenericForeignKey' in str_type_from_field: + display = '{}'.format( + reverse( + '%s:%s_detail' % ( + value._meta.app_config.name, obj.content_type.model), + args=(value.id,)), + value) else: display = str(value) return verbose_name, display diff --git a/sapl/crud/base.py b/sapl/crud/base.py index 1a1225cfc..a00336fc7 100644 --- a/sapl/crud/base.py +++ b/sapl/crud/base.py @@ -9,6 +9,7 @@ from django import forms from django.conf.urls import url from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin +from django.core.exceptions import ObjectDoesNotExist from django.core.urlresolvers import reverse from django.db import models from django.db.models.fields.related import ForeignKey @@ -1133,7 +1134,11 @@ class MasterDetailCrud(Crud): parent_field = obj.parent_field.split('__') if not obj.is_m2m or len(parent_field) > 1: field = self.model._meta.get_field(parent_field[0]) - parent = field.related_model.objects.get(pk=self.kwargs['pk']) + try: + parent = field.related_model.objects.get( + pk=self.kwargs['pk']) + except ObjectDoesNotExist: + raise Http404() setattr(form.instance, parent_field[0], parent) return form @@ -1155,7 +1160,7 @@ class MasterDetailCrud(Crud): try: parent_object = parent_model.objects.get(**params) - except: + except Exception: raise Http404() else: parent_model = self.model diff --git a/sapl/legacy/management/commands/migracao_25_31.py b/sapl/legacy/management/commands/migracao_25_31.py index 5334ac7c0..7e8671e38 100644 --- a/sapl/legacy/management/commands/migracao_25_31.py +++ b/sapl/legacy/management/commands/migracao_25_31.py @@ -5,7 +5,7 @@ from sapl.legacy import migration class Command(BaseCommand): - help = u'Migração de dados do SAPL 2.5 para o SAPL 3.1' + help ='Migração de dados do SAPL 2.5 para o SAPL 3.1' def add_arguments(self, parser): parser.add_argument( diff --git a/sapl/legacy/management/commands/migracao_documentos.py b/sapl/legacy/management/commands/migracao_documentos.py index c096e2905..0af503b39 100644 --- a/sapl/legacy/management/commands/migracao_documentos.py +++ b/sapl/legacy/management/commands/migracao_documentos.py @@ -5,7 +5,7 @@ from sapl.legacy.migracao_documentos import migrar_documentos class Command(BaseCommand): - help = u'Migração documentos do SAPL 2.5 para o SAPL 3.1' + help ='Migração documentos do SAPL 2.5 para o SAPL 3.1' def handle(self, *args, **options): migrar_documentos() diff --git a/sapl/legacy/migracao_documentos.py b/sapl/legacy/migracao_documentos.py index 21697dc4f..bd770a06e 100644 --- a/sapl/legacy/migracao_documentos.py +++ b/sapl/legacy/migracao_documentos.py @@ -3,19 +3,19 @@ import os import re import magic - from django.db.models.signals import post_delete, post_save + from sapl.base.models import CasaLegislativa from sapl.materia.models import (DocumentoAcessorio, MateriaLegislativa, Proposicao) from sapl.norma.models import NormaJuridica from sapl.parlamentares.models import Parlamentar -from sapl.protocoloadm.models import DocumentoAdministrativo +from sapl.protocoloadm.models import (DocumentoAcessorioAdministrativo, + DocumentoAdministrativo) from sapl.sessao.models import SessaoPlenaria from sapl.settings import MEDIA_ROOT from sapl.utils import delete_texto, save_texto - # MIGRAÇÃO DE DOCUMENTOS ################################################### EXTENSOES = { 'application/msword': '.doc', @@ -29,6 +29,17 @@ EXTENSOES = { 'text/html': '.html', 'text/rtf': '.rtf', 'text/x-python': '.py', + 'text/plain': '.ksh', + 'text/plain': '.c', + 'text/plain': '.h', + 'text/plain': '.txt', + 'text/plain': '.bat', + 'text/plain': '.pl', + 'text/plain': '.asc', + 'text/plain': '.text', + 'text/plain': '.pot', + 'text/plain': '.brf', + 'text/plain': '.srt', # sem extensao 'application/octet-stream': '', # binário @@ -43,35 +54,41 @@ DOCS = { Parlamentar: [( 'fotografia', 'parlamentar/fotos/{}_foto_parlamentar', - 'parlamentar/{0}/{0}_foto_parlamentar{1}')], + 'public/parlamentar/{0}/{0}_foto_parlamentar{1}')], MateriaLegislativa: [( 'texto_original', 'materia/{}_texto_integral', - 'materialegislativa/{0}/{0}_texto_integral{1}')], + 'public/materialegislativa/{2}/{0}/{0}_texto_integral{1}')], DocumentoAcessorio: [( 'arquivo', 'materia/{}', - 'documentoacessorio/{0}/{0}{1}')], + 'public/documentoacessorio/{2}/{0}/{0}{1}')], NormaJuridica: [( - 'texto_original', + 'texto_integral', 'norma_juridica/{}_texto_integral', - 'normajuridica/{0}/{0}_texto_integral{1}')], + 'public/normajuridica/{2}/{0}/{0}_texto_integral{1}')], SessaoPlenaria: [ ('upload_ata', 'ata_sessao/{}_ata_sessao', - 'sessaoplenaria/{0}/ata/{0}_ata_sessao{1}'), + 'public/sessaoplenaria/{0}/ata/{0}_ata_sessao{1}'), ('upload_anexo', 'anexo_sessao/{}_texto_anexado', - 'sessaoplenaria/{0}/anexo/{0}_texto_anexado{1}') + 'public/sessaoplenaria/{0}/anexo/{0}_texto_anexado{1}') ], Proposicao: [( 'texto_original', 'proposicao/{}', - 'proposicao/{0}/{0}{1}')], + 'private/proposicao/{0}/{0}{1}')], DocumentoAdministrativo: [( 'texto_integral', 'administrativo/{}_texto_integral', - 'documentoadministrativo/{0}/{0}_texto_integral{1}')], + 'private/documentoadministrativo/{0}/{0}_texto_integral{1}') + ], + DocumentoAcessorioAdministrativo: [( + 'arquivo', + 'administrativo/{}', + 'private/documentoacessorioadministrativo/{0}/{0}_acessorio_administrativo{1}') + ], } DOCS = {tipo: [(campo, @@ -105,9 +122,14 @@ def migrar_docs_logo(): print('#### Migrando logotipo da casa ####') [(_, origem, destino)] = DOCS[CasaLegislativa] props_sapl = os.path.dirname(origem) + # a pasta props_sapl deve conter apenas o origem e metadatas! + # Edit: Aparentemente há diretório que contém properties ao invés de + # metadata. O assert foi modificado para essa situação. assert set(os.listdir(em_media(props_sapl))) < { - 'logo_casa.gif', '.metadata', 'logo_casa.gif.metadata'} + 'logo_casa.gif', '.metadata', 'logo_casa.gif.metadata', + '.properties', 'logo_casa.gif.properties', '.objects'} + mover_documento(origem, destino) casa = get_casa_legislativa() casa.logotipo = destino @@ -146,15 +168,22 @@ def migrar_docs_por_ids(tipo): for arq in os.listdir(dir_origem): match = pat.match(arq) if match: - origem = os.path.join(dir_origem, match.group(0)) - id = match.group(1) - extensao = get_extensao(origem) - destino = base_destino.format(id, extensao) - mover_documento(origem, destino) - # associa documento ao objeto try: + origem = os.path.join(dir_origem, match.group(0)) + id = match.group(1) obj = tipo.objects.get(pk=id) + + extensao = get_extensao(origem) + if hasattr(obj, "ano"): + destino = base_destino.format(id, extensao, obj.ano) + elif isinstance(obj, DocumentoAcessorio): + destino = base_destino.format( + id, extensao, obj.materia.ano) + else: + destino = base_destino.format(id, extensao) + mover_documento(origem, destino) + setattr(obj, campo, destino) obj.save() except tipo.DoesNotExist: @@ -199,6 +228,7 @@ def migrar_documentos(): SessaoPlenaria, Proposicao, DocumentoAdministrativo, + DocumentoAcessorioAdministrativo, ]: migrar_docs_por_ids(tipo) diff --git a/sapl/legacy/migration.py b/sapl/legacy/migration.py index baf105e74..8f56f91f1 100644 --- a/sapl/legacy/migration.py +++ b/sapl/legacy/migration.py @@ -8,10 +8,11 @@ import yaml from django.apps import apps from django.apps.config import AppConfig from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist from django.db import OperationalError, ProgrammingError, connections, models -from django.db.models import CharField, Max, ProtectedError, TextField +from django.db.models import CharField, Max, ProtectedError, TextField, Count from django.db.models.base import ModelBase from django.db.models.signals import post_delete, post_save from model_mommy import mommy @@ -20,17 +21,20 @@ from model_mommy.mommy import foreign_key_required, make from sapl.base.models import Argumento, Autor, Constraint, ProblemaMigracao from sapl.comissoes.models import Comissao, Composicao, Participacao from sapl.legacy.models import Protocolo as ProtocoloLegado -from sapl.materia.models import (DocumentoAcessorio, MateriaLegislativa, +from sapl.materia.models import (AcompanhamentoMateria, DocumentoAcessorio, + MateriaLegislativa, Proposicao, StatusTramitacao, TipoDocumento, TipoMateriaLegislativa, TipoProposicao, Tramitacao) from sapl.norma.models import (AssuntoNorma, NormaJuridica, TipoVinculoNormaJuridica, NormaRelacionada) -from sapl.parlamentares.models import Parlamentar -from sapl.protocoloadm.models import Protocolo, StatusTramitacaoAdministrativo +from sapl.parlamentares.models import (Legislatura,Mandato, Parlamentar, + TipoAfastamento) +from sapl.protocoloadm.models import (DocumentoAdministrativo,Protocolo, + StatusTramitacaoAdministrativo) from sapl.sessao.models import ExpedienteMateria, OrdemDia, RegistroVotacao from sapl.settings import PROJECT_DIR -from sapl.utils import delete_texto, normalize, save_texto +from sapl.utils import normalize # BASE ###################################################################### # apps to be migrated, in app dependency order (very important) @@ -157,6 +161,8 @@ def get_fk_related(field, value, label=None): ct = ContentType.objects.get(pk=13) value = TipoProposicao.objects.create( id=value, descricao='Erro', content_type=ct) + ultimo_valor = get_last_value(type(value)) + alter_sequence(type(value), ultimo_valor+1) else: value = tipo[0] else: @@ -240,6 +246,25 @@ def delete_constraints(model): (table, r[0])) +def problema_duplicatas(model, lista_duplicatas, argumentos): + for obj in lista_duplicatas: + pks = [] + string_pks = "" + problema = "%s de PK %s não é único." % (model.__name__, obj.pk) + args_dict = {k: obj.__dict__[k] + for k in set(argumentos) & set(obj.__dict__.keys())} + for dup in model.objects.filter(**args_dict): + pks.append(dup.pk) + string_pks = "(" + ", ".join(map(str, pks)) + ")" + descricao = "As entradas de PK %s são idênticas, mas " \ + "apenas uma deve existir" % string_pks + with reversion.create_revision(): + warn(problema + ' => ' + descricao) + save_relation(obj=obj, problema=problema, + descricao=descricao, eh_stub=False, critico=True) + reversion.set_comment('%s não é único.' % model.__name__) + + def recria_constraints(): constraints = Constraint.objects.all() for con in constraints: @@ -249,6 +274,8 @@ def recria_constraints(): args = [a.argumento for a in con.argumento_set.all()] args_string = '' args_string = "(" + "_".join(map(str, args[2:-1])) + ")" + model = ContentType.objects.filter( + model=con.nome_model.lower())[0].model_class() try: exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" % (nome_tabela, nome_constraint, args_string)) @@ -268,18 +295,30 @@ def recria_constraints(): args[i] = args[i] + '_id' args_string = '' args_string += "(" + ', '.join(map(str, args)) + ")" - try: - exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" % - (nome_tabela, nome_constraint, args_string)) - except ProgrammingError: - info('A constraint %s já foi recriada!' % nome_constraint) - except Exception as err: - problema = re.findall('\(.*?\)', err.args[0]) - erro('A constraint [%s] da tabela [%s] não pode ser recriada' % - (nome_constraint, nome_tabela)) - erro('Os dados %s = %s estão duplicados. ' - 'Arrume antes de recriar as constraints!' % - (problema[0], problema[1])) + + distintos = model.objects.distinct(*args) + todos = model.objects.all() + if hasattr(model, "content_type"): + distintos = distintos.exclude(content_type_id=None, + object_id=None) + todos = todos.exclude(content_type_id=None, object_id=None) + + lista_duplicatas = list(set(todos).difference(set(distintos))) + if lista_duplicatas: + problema_duplicatas(model, lista_duplicatas, args) + else: + try: + exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" % + (nome_tabela, nome_constraint, args_string)) + except ProgrammingError: + info('A constraint %s já foi recriada!' % nome_constraint) + except Exception as err: + problema = re.findall('\(.*?\)', err.args[0]) + erro('A constraint [%s] da tabela [%s] não pode ser" \ + recriada' % (nome_constraint, nome_tabela)) + erro('Os dados %s = %s estão duplicados. ' + 'Arrume antes de recriar as constraints!' % + (problema[0], problema[1])) def obj_desnecessario(obj): @@ -311,10 +350,10 @@ def save_with_id(new, id): def save_relation(obj, nome_campo='', problema='', descricao='', - eh_stub=False): + eh_stub=False, critico=False): link = ProblemaMigracao( content_object=obj, nome_campo=nome_campo, problema=problema, - descricao=descricao, eh_stub=eh_stub,) + descricao=descricao, eh_stub=eh_stub, critico=critico) link.save() @@ -369,6 +408,34 @@ def fill_vinculo_norma_juridica(): TipoVinculoNormaJuridica.objects.bulk_create(lista_objs) +# 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 + + class DataMigrator: def __init__(self): @@ -445,8 +512,6 @@ class DataMigrator: call([PROJECT_DIR.child('manage.py'), 'flush', '--database=default', '--no-input'], stdout=PIPE) - desconecta_sinais_indexacao() - fill_vinculo_norma_juridica() info('Começando migração: %s...' % obj) self._do_migrate(obj) @@ -464,11 +529,15 @@ class DataMigrator: save_relation(obj=obj, problema=msg, descricao=descricao, eh_stub=False) + info('Excluindo possíveis duplicações em RegistroVotacao...') + excluir_registrovotacao_duplicados() + info('Deletando stubs desnecessários...') while self.delete_stubs(): pass - conecta_sinais_indexacao() + info('Recriando constraints...') + recria_constraints() def _do_migrate(self, obj): if isinstance(obj, AppConfig): @@ -580,11 +649,46 @@ def migrate(obj=appconfs, interativo=True): # MIGRATION_ADJUSTMENTS ##################################################### -def adjust_ordemdia(new, old): - # Prestar atenção +def adjust_acompanhamentomateria(new, old): + new.confirmado = True + + +def adjust_documentoadministrativo(new, old): + if new.numero_protocolo: + protocolo = Protocolo.objects.get(numero=new.numero_protocolo, + ano=new.ano) + new.protocolo = protocolo + + +def adjust_mandato(new, old): + if not new.data_fim_mandato: + legislatura = Legislatura.objects.latest('data_fim') + new.data_fim_mandato = legislatura.data_fim + new.data_expedicao_diploma = legislatura.data_inicio + + +def adjust_ordemdia_antes_salvar(new, old): + new.votacao_aberta = False + if not old.tip_votacao: new.tipo_votacao = 1 + 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.') + pass + def adjust_parlamentar(new, old): if old.ind_unid_deliberativa: @@ -616,6 +720,25 @@ def adjust_participacao(new, old): new.composicao = composicao +def adjust_proposicao_antes_salvar(new, old): + if new.data_envio: + new.ano = new.data_envio.year + + +def adjust_proposicao_depois_salvar(new, old): + if not hasattr(old.dat_envio, 'year') or old.dat_envio.year == 1800: + msg = "O valor do campo data_envio (DateField) da model Proposicao" + "era inválido" + descricao = 'A data 1111-11-11 foi colocada no lugar' + problema = 'O valor da data era nulo ou inválido' + warn(msg + ' => ' + descricao) + new.data_envio = date(1111, 11, 11) + with reversion.create_revision(): + save_relation(obj=new, problema=problema, + descricao=descricao, eh_stub=False) + reversion.set_comment('Ajuste de data pela migração') + + def adjust_normarelacionada(new, old): tipo = TipoVinculoNormaJuridica.objects.filter(sigla=old.tip_vinculo) assert len(tipo) == 1 @@ -660,6 +783,12 @@ def adjust_registrovotacao_depois_salvar(new, old): reversion.set_comment('RegistroVotacao sem ordem ou expediente') +def adjust_tipoafastamento(new, old): + if old.ind_afastamento == 1: + new.indicador = 'A' + + + def adjust_tipoproposicao(new, old): if old.ind_mat_ou_doc == 'M': new.tipo_conteudo_related = TipoMateriaLegislativa.objects.get( @@ -723,6 +852,7 @@ def adjust_autor(new, old): new.nome = new.autor_related.nome_parlamentar elif old.cod_comissao: new.autor_related = Comissao.objects.get(pk=old.cod_comissao) + new.nome = new.autor_related.nome if old.col_username: if not get_user_model().objects.filter( @@ -732,6 +862,13 @@ def adjust_autor(new, old): with reversion.create_revision(): user.save() reversion.set_comment('Objeto criado pela migração') + + grupo_autor = Group.objects.get(name="Autor") + user.groups.add(grupo_autor) + if old.cod_parlamentar: + grupo_parlamentar = Group.objects.get(name="Parlamentar") + user.groups.add(grupo_parlamentar) + new.user = user else: new.user = get_user_model().objects.filter( @@ -750,14 +887,19 @@ def adjust_comissao(new, old): AJUSTE_ANTES_SALVAR = { Autor: adjust_autor, + AcompanhamentoMateria: adjust_acompanhamentomateria, Comissao: adjust_comissao, + DocumentoAdministrativo: adjust_documentoadministrativo, + Mandato: adjust_mandato, NormaJuridica: adjust_normajuridica_antes_salvar, NormaRelacionada: adjust_normarelacionada, - OrdemDia: adjust_ordemdia, + OrdemDia: adjust_ordemdia_antes_salvar, Parlamentar: adjust_parlamentar, Participacao: adjust_participacao, + Proposicao: adjust_proposicao_antes_salvar, Protocolo: adjust_protocolo, RegistroVotacao: adjust_registrovotacao_antes_salvar, + TipoAfastamento: adjust_tipoafastamento, TipoProposicao: adjust_tipoproposicao, StatusTramitacao: adjust_statustramitacao, StatusTramitacaoAdministrativo: adjust_statustramitacaoadm, @@ -766,6 +908,8 @@ AJUSTE_ANTES_SALVAR = { AJUSTE_DEPOIS_SALVAR = { NormaJuridica: adjust_normajuridica_depois_salvar, + OrdemDia: adjust_ordemdia_depois_salvar, + Proposicao: adjust_proposicao_depois_salvar, Protocolo: adjust_protocolo_depois_salvar, RegistroVotacao: adjust_registrovotacao_depois_salvar, } @@ -801,23 +945,3 @@ def make_with_log(model, _quantity=None, make_m2m=False, **attrs): return stub make_with_log.required = foreign_key_required - -# DISCONNECT SIGNAL ######################################################## - - -def desconecta_sinais_indexacao(): - post_save.disconnect(save_texto, NormaJuridica) - post_save.disconnect(save_texto, DocumentoAcessorio) - post_save.disconnect(save_texto, MateriaLegislativa) - post_delete.disconnect(delete_texto, NormaJuridica) - post_delete.disconnect(delete_texto, DocumentoAcessorio) - post_delete.disconnect(delete_texto, MateriaLegislativa) - - -def conecta_sinais_indexacao(): - post_save.connect(save_texto, NormaJuridica) - post_save.connect(save_texto, DocumentoAcessorio) - post_save.connect(save_texto, MateriaLegislativa) - post_delete.connect(delete_texto, NormaJuridica) - post_delete.connect(delete_texto, DocumentoAcessorio) - post_delete.connect(delete_texto, MateriaLegislativa) diff --git a/sapl/legacy/scripts/street_sweeper.py b/sapl/legacy/scripts/street_sweeper.py new file mode 100644 index 000000000..d600e900e --- /dev/null +++ b/sapl/legacy/scripts/street_sweeper.py @@ -0,0 +1,146 @@ +#!/usr/bin/python + +# requisito: pip install PyMySQL + +import pymysql.cursors + +HOST = 'localhost' +USER = 'root' +PASSWORD = '' +DB = '' + + +SELECT_EXCLUIDOS = "SELECT %s FROM %s WHERE ind_excluido = 1 ORDER BY %s" + +REGISTROS_INCONSISTENTES = "DELETE FROM %s WHERE %s in (%s) AND ind_excluido = 0 " + +EXCLUI_REGISTRO = "DELETE FROM %s WHERE ind_excluido=1" + +NORMA_DEP = "DELETE FROM vinculo_norma_juridica WHERE cod_norma_referente in (%s) OR \ + cod_norma_referida in (%s) AND ind_excluido = 0 " + +mapa = {} # mapa com tabela principal -> tabelas dependentes + +mapa['tipo_autor'] = ['autor'] +mapa['materia_legislativa'] = ['acomp_materia', 'autoria', 'despacho_inicial', + 'documento_acessorio', 'expediente_materia', + 'legislacao_citada', 'materia_assunto', + 'numeracao', 'ordem_dia', 'parecer', + 'proposicao', 'registro_votacao', + 'relatoria', 'tramitacao'] +mapa['norma_juridica'] = ['vinculo_norma_juridica'] +mapa['comissao'] = ['composicao_comissao'] +mapa['sessao_legislativa'] = ['composicao_mesa'] +mapa['tipo_expediente'] = ['expediente_sessao_plenaria'] + +""" +mapa['autor'] = ['tipo_autor', 'partido', 'comissao', 'parlamentar'] +mapa['parlamentar'] = ['autor', 'autoria', 'composicao_comissao', + 'composicao_mesa', 'dependente', 'filiacao', + 'mandato', 'mesa_sessao_plenaria', 'oradores', + 'oradores_expediente', 'ordem_dia_presenca', + 'registro_votacao_parlamentar', 'relatoria', + 'sessao_plenaria_presenca', 'unidade_tramitacao'] +""" + +def get_ids_excluidos(cursor, query): + """ + recupera as PKs de registros com ind_excluido = 1 da tabela principal + """ + cursor.execute(query) + excluidos = cursor.fetchall() + # flat tuple of tuples with map transformation into string + excluidos = [str(val) for sublist in excluidos for val in sublist] + return excluidos + + +def remove_tabelas(cursor, tabela_principal, pk, query_dependentes=None): + + QUERY = SELECT_EXCLUIDOS % (pk, tabela_principal, pk) + ids_excluidos = get_ids_excluidos(cursor, QUERY) + print("\nRegistros da tabela '%s' com ind_excluido = 1: %s" % (tabela_principal.upper(), len(ids_excluidos))) + + """ + Remove registros de tabelas que dependem da tabela principal, + e que se encontram com ind_excluido = 0 (nao excluidas), se + tais registros existirem. + """ + if ids_excluidos: + print("Dependencias inconsistentes") + for tabela in mapa[tabela_principal]: + + QUERY_DEP = REGISTROS_INCONSISTENTES % (tabela, pk, ','.join(ids_excluidos)) + + # Trata caso especifico de norma_juridica + if query_dependentes: + QUERY_DEP = query_dependentes % (','.join(ids_excluidos), + ','.join(ids_excluidos)) + + print(tabela.upper(), cursor.execute(QUERY_DEP)) + + """ + Remove todos os registros com ind_excluido = 1 das tabelas + dependentes e da tabela principal, nesta ordem. + """ + print("\n\nRegistros com ind_excluido = 1") + for tabela in mapa[tabela_principal] + [tabela_principal]: + QUERY = EXCLUI_REGISTRO % tabela + print(tabela.upper(), cursor.execute(QUERY)) + + +def remove_excluidas(cursor): + cursor.execute("SHOW_TABLES") + for row in cursor.fetchall(): + print(row) + + +def remove_proposicao_invalida(cursor): + return cursor.execute( + "DELETE FROM proposicao WHERE cod_mat_ou_doc is null") + + +def remove_materia_assunto_invalida(cursor): + return cursor.execute( + "DELETE FROM materia_assunto WHERE cod_assunto = 0") + + +def shotgun_remove(cursor): + for tabela in get_ids_excluidos(cursor, "SHOW TABLES"): + try: + cursor.execute("DELETE FROM %s WHERE ind_excluido = 1" % tabela) + except: + pass + + +if __name__ == '__main__': + connection = pymysql.connect(host=HOST, + user=USER, + password=PASSWORD, + db=DB) + cursor = connection.cursor() + # TIPO AUTOR + remove_tabelas(cursor, 'tipo_autor', 'tip_autor') + # MATERIA LEGISLATIVA + remove_tabelas(cursor, 'materia_legislativa', 'cod_materia') + # NORMA JURIDICA + remove_tabelas(cursor, 'norma_juridica', 'cod_norma', NORMA_DEP) + # COMISSAO + remove_tabelas(cursor, 'comissao', 'cod_comissao') + # SESSAO LEGISLATIVA + remove_tabelas(cursor, 'sessao_legislativa', 'cod_sessao_leg') + # EXPEDIENTE SESSAO + remove_tabelas(cursor, 'tipo_expediente', 'cod_expediente') + # AUTOR + remove_tabelas(cursor, 'autor', 'cod_autor') + # PARLAMENTAR + remove_tabelas(cursor, 'parlamentar', 'cod_parlamentar') + + # PROPOSICAO + remove_proposicao_invalida(cursor) + + # MATERIA_ASSUNTO + remove_materia_assunto_invalida(cursor) + + # shotgun_remove(cursor) + + cursor.close() diff --git a/sapl/materia/email_utils.py b/sapl/materia/email_utils.py new file mode 100644 index 000000000..78c0483b5 --- /dev/null +++ b/sapl/materia/email_utils.py @@ -0,0 +1,211 @@ +from datetime import datetime + +from django.core.mail import EmailMultiAlternatives, get_connection, send_mail +from django.core.urlresolvers import reverse +from django.template import Context, loader + +from sapl.base.models import CasaLegislativa +from sapl.settings import EMAIL_SEND_USER + +from .models import AcompanhamentoMateria + + +def load_email_templates(templates, context={}): + + emails = [] + for t in templates: + tpl = loader.get_template(t) + email = tpl.render(Context(context)) + if t.endswith(".html"): + email = email.replace('\n', '').replace('\r', '') + emails.append(email) + return emails + + +def enviar_emails(sender, recipients, messages): + ''' + Recipients is a string list of email addresses + + Messages is an array of dicts of the form: + {'recipient': 'address', # useless???? + 'subject': 'subject text', + 'txt_message': 'text message', + 'html_message': 'html message' + } + ''' + + if len(messages) == 1: + # sends an email simultaneously to all recipients + send_mail(messages[0]['subject'], + messages[0]['txt_message'], + sender, + recipients, + html_message=messages[0]['html_message'], + fail_silently=False) + + elif len(recipients) > len(messages): + raise ValueError("Message list should have size 1 \ + or equal recipient list size. \ + recipients: %s, messages: %s" % (recipients, messages) + ) + + else: + # sends an email simultaneously to all reciepients + for (d, m) in zip(recipients, messages): + send_mail(m['subject'], + m['txt_message'], + sender, + [d], + html_message=m['html_message'], + fail_silently=False) + + +def criar_email_confirmacao(base_url, casa_legislativa, materia, hash_txt=''): + + if not casa_legislativa: + raise ValueError("Casa Legislativa é obrigatória") + + if not materia: + raise ValueError("Matéria é obrigatória") + + # FIXME i18n + casa_nome = (casa_legislativa.nome + ' de ' + + casa_legislativa.municipio + '-' + + casa_legislativa.uf) + + materia_url = reverse('sapl.materia:materialegislativa_detail', + kwargs={'pk': materia.id}) + confirmacao_url = reverse('sapl.materia:acompanhar_confirmar', + kwargs={'pk': materia.id}) + + autores = [] + for autoria in materia.autoria_set.all(): + autores.append(autoria.autor.nome) + + templates = load_email_templates(['email/acompanhar.txt', + 'email/acompanhar.html'], + {"casa_legislativa": casa_nome, + "logotipo": casa_legislativa.logotipo, + "descricao_materia": materia.ementa, + "autoria": autores, + "hash_txt": hash_txt, + "base_url": base_url, + "materia": str(materia), + "materia_url": materia_url, + "confirmacao_url": confirmacao_url, }) + return templates + + +def do_envia_email_confirmacao(base_url, casa, materia, destinatario): + # + # Envia email de confirmacao para atualizações de tramitação + # + + sender = EMAIL_SEND_USER + # FIXME i18n + subject = "[SAPL] " + str(materia) + " - Ative o Acompanhamento da Materia" + messages = [] + recipients = [] + + email_texts = criar_email_confirmacao(base_url, + casa, + materia, + destinatario.hash,) + recipients.append(destinatario.email) + messages.append({ + 'recipient': destinatario.email, + 'subject': subject, + 'txt_message': email_texts[0], + 'html_message': email_texts[1] + }) + + enviar_emails(sender, recipients, messages) + + +def criar_email_tramitacao(base_url, casa_legislativa, materia, status, + unidade_destino, hash_txt=''): + + if not casa_legislativa: + raise ValueError("Casa Legislativa é obrigatória") + + if not materia: + raise ValueError("Matéria é obrigatória") + + # FIXME i18n + casa_nome = (casa_legislativa.nome + ' de ' + + casa_legislativa.municipio + '-' + + casa_legislativa.uf) + + url_materia = reverse('sapl.materia:tramitacao_list', + kwargs={'pk': materia.id}) + url_excluir = reverse('sapl.materia:acompanhar_excluir', + kwargs={'pk': materia.id}) + + autores = [] + for autoria in materia.autoria_set.all(): + autores.append(autoria.autor.nome) + + tramitacao = materia.tramitacao_set.last() + + templates = load_email_templates(['email/tramitacao.txt', + 'email/tramitacao.html'], + {"casa_legislativa": casa_nome, + "data_registro": datetime.now().strftime( + "%d/%m/%Y"), + "cod_materia": materia.id, + "logotipo": casa_legislativa.logotipo, + "descricao_materia": materia.ementa, + "autoria": autores, + "data": tramitacao.data_tramitacao, + "status": status, + "localizacao": unidade_destino, + "texto_acao": tramitacao.texto, + "hash_txt": hash_txt, + "materia": str(materia), + "base_url": base_url, + "materia_url": url_materia, + "excluir_url": url_excluir}) + return templates + + +def do_envia_email_tramitacao(base_url, materia, status, unidade_destino): + # + # Envia email de tramitacao para usuarios cadastrados + # + destinatarios = AcompanhamentoMateria.objects.filter(materia=materia, + confirmado=True) + casa = CasaLegislativa.objects.first() + + sender = EMAIL_SEND_USER + # FIXME i18n + subject = "[SAPL] " + str(materia) + \ + " - Acompanhamento de Materia Legislativa" + + connection = get_connection() + connection.open() + + for destinatario in destinatarios: + try: + email_texts = criar_email_tramitacao(base_url, + casa, + materia, + status, + unidade_destino, + destinatario.hash,) + + email = EmailMultiAlternatives( + subject, + email_texts[0], + sender, + [destinatario.email], + connection=connection) + email.attach_alternative(email_texts[1], "text/html") + email.send() + + # Garantia de que, mesmo com o lançamento de qualquer exceção, + # a conexão será fechada + except Exception: + connection.close() + raise Exception('Erro ao enviar e-mail de acompanhamento de matéria.') + + connection.close() diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index 7c0b6d8c8..47eabe43b 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -1,8 +1,7 @@ -import os from datetime import date, datetime +import os -import django_filters 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, @@ -21,8 +20,8 @@ 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 _ +import django_filters -import sapl from sapl.base.models import Autor from sapl.comissoes.models import Comissao from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC, @@ -40,6 +39,7 @@ from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, ChoiceWithoutValidationField, MateriaPesquisaOrderingFilter, RangeWidgetOverride, autor_label, autor_modal, models_with_gr_for_model) +import sapl from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial, DocumentoAcessorio, Numeracao, Proposicao, Relatoria, @@ -52,8 +52,8 @@ def ANO_CHOICES(): def em_tramitacao(): return [('', 'Tanto Faz'), - (True, 'Sim'), - (False, 'Não')] + (1, 'Sim'), + (0, 'Não')] class AdicionarVariasAutoriasFilterSet(django_filters.FilterSet): @@ -124,6 +124,8 @@ class UnidadeTramitacaoForm(ModelForm): fields = ['comissao', 'orgao', 'parlamentar'] def clean(self): + super(UnidadeTramitacaoForm, self).clean() + cleaned_data = self.cleaned_data for key in list(cleaned_data.keys()): @@ -164,17 +166,6 @@ class DocumentoAcessorioForm(ModelForm): class Meta: model = DocumentoAcessorio fields = ['tipo', 'nome', 'data', 'autor', 'ementa', 'arquivo'] - widgets = {'autor': forms.HiddenInput()} - - def clean_autor(self): - autor_field = self.cleaned_data['autor'] - try: - int(autor_field) - except ValueError: - return autor_field - else: - if autor_field: - return str(Autor.objects.get(id=autor_field)) class RelatoriaForm(ModelForm): @@ -190,6 +181,8 @@ class RelatoriaForm(ModelForm): super(RelatoriaForm, self).__init__(*args, **kwargs) def clean(self): + super(RelatoriaForm, self).clean() + cleaned_data = self.cleaned_data try: @@ -222,6 +215,7 @@ class TramitacaoForm(ModelForm): self.fields['data_tramitacao'].initial = datetime.now() def clean(self): + super(TramitacaoForm, self).clean() if 'data_encaminhamento' in self.data: data_enc_form = self.cleaned_data['data_encaminhamento'] @@ -299,6 +293,8 @@ class TramitacaoUpdateForm(TramitacaoForm): } def clean(self): + super(TramitacaoUpdateForm, self).clean() + local = self.instance.unidade_tramitacao_local data_tram = self.instance.data_tramitacao @@ -339,6 +335,8 @@ class LegislacaoCitadaForm(ModelForm): 'item'] def clean(self): + super(LegislacaoCitadaForm, self).clean() + if self.errors: return self.errors @@ -400,6 +398,8 @@ class NumeracaoForm(ModelForm): 'data_materia'] def clean(self): + super(NumeracaoForm, self).clean() + if self.errors: return self.errors @@ -443,6 +443,8 @@ class AnexadaForm(ModelForm): return super(AnexadaForm, self).__init__(*args, **kwargs) def clean(self): + super(AnexadaForm, self).clean() + if self.errors: return self.errors @@ -483,32 +485,42 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet): }} ano = django_filters.ChoiceFilter(required=False, - label=u'Ano da Matéria', + label='Ano da Matéria', choices=ANO_CHOICES) autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput()) + autoria__primeiro_autor = django_filters.BooleanFilter(required=False, + label='Primeiro Autor', + widget=forms.HiddenInput()) + ementa = django_filters.CharFilter(lookup_expr='icontains') em_tramitacao = django_filters.ChoiceFilter(required=False, - label=u'Em tramitação', + label='Em tramitação', choices=em_tramitacao) materiaassunto__assunto = django_filters.ModelChoiceFilter( queryset=AssuntoMateria.objects.all(), label=_('Assunto da Matéria')) + numeracao__numero_materia = django_filters.NumberFilter( + required=False, + label=_('Número do Processo')) + o = MateriaPesquisaOrderingFilter() class Meta: model = MateriaLegislativa fields = ['numero', 'numero_protocolo', + 'numeracao__numero_materia', 'ano', 'tipo', 'data_apresentacao', 'data_publicacao', 'autoria__autor__tipo', + 'autoria__primeiro_autor', # FIXME 'autoria__autor__partido', 'relatoria__parlamentar_id', 'local_origem_externa', @@ -529,14 +541,16 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet): row1 = to_row( [('tipo', 12)]) row2 = to_row( - [('numero', 4), - ('ano', 4), - ('numero_protocolo', 4)]) + [('numero', 3), + ('numeracao__numero_materia', 3), + ('numero_protocolo', 3), + ('ano', 3)]) row3 = to_row( [('data_apresentacao', 6), ('data_publicacao', 6)]) row4 = to_row( [('autoria__autor', 0), + ('autoria__primeiro_autor', 0), (Button('pesquisar', 'Pesquisar Autor', css_class='btn btn-primary btn-sm'), 2), @@ -617,6 +631,8 @@ class DespachoInicialForm(ModelForm): fields = ['comissao'] def clean(self): + super(DespachoInicialForm, self).clean() + if self.errors: return self.errors @@ -637,6 +653,8 @@ class AutoriaForm(ModelForm): fields = ['autor', 'primeiro_autor'] def clean(self): + super(AutoriaForm, self).clean() + if self.errors: return self.errors @@ -808,6 +826,8 @@ class TipoProposicaoForm(ModelForm): 'tipo_conteudo_related'].initial = self.instance.object_id def clean(self): + super(TipoProposicaoForm, self).clean() + cd = self.cleaned_data content_type = cd['content_type'] @@ -821,9 +841,25 @@ class TipoProposicaoForm(ModelForm): pk=cd['tipo_conteudo_related']).exists(): raise ValidationError( _('O Registro definido (%s) não está na base de %s.' - ) % (cd['tipo_conteudo_related'], cd['q'], content_type)) + ) % (cd['tipo_conteudo_related'], content_type)) - return self.cleaned_data + unique_value = self._meta.model.objects.filter( + content_type=content_type, object_id=cd['tipo_conteudo_related']) + + if self.instance.pk: + unique_value = unique_value.exclude(pk=self.instance.pk) + + unique_value = unique_value.first() + + if unique_value: + raise ValidationError( + _('Já existe um Tipo de Proposição (%s) ' + 'que foi defindo como (%s) para (%s)' + ) % (unique_value, + content_type, + unique_value.tipo_conteudo_related)) + + return super().clean() @transaction.atomic def save(self, commit=False): @@ -854,11 +890,11 @@ class TipoProposicaoSelect(Select): else: selected_html = '' return format_html( - '', - option_value, - selected_html, - str(data_has_perfil), - force_text(option_label)) + '', + option_value, + selected_html, + str(data_has_perfil), + force_text(option_label)) def render_options(self, choices, selected_choices): # Normalize to strings. @@ -986,10 +1022,14 @@ class ProposicaoForm(forms.ModelForm): texto_original = self.cleaned_data.get('texto_original', False) if texto_original: if texto_original.size > MAX_DOC_UPLOAD_SIZE: - raise ValidationError("Arquivo muito grande. ( > 5mb )") + max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024)) + raise ValidationError( + "Arquivo muito grande. ( > {0}MB )".format(max_size)) return texto_original def clean(self): + super(ProposicaoForm, self).clean() + cd = self.cleaned_data tm, am, nm = (cd.get('tipo_materia', ''), @@ -1106,6 +1146,18 @@ class ConfirmarProposicaoForm(ProposicaoForm): if 'numero_de_paginas' not in self._meta.fields: self._meta.fields.append('numero_de_paginas') + self.instance = kwargs.get('instance', None) + if not self.instance: + raise ValueError(_('Erro na Busca por proposição a incorporar')) + + if self.instance.tipo.content_type.model_class() == TipoDocumento: + if 'numero_de_paginas' in self._meta.fields: + self._meta.fields.remove('numero_de_paginas') + if 'gerar_protocolo' in self._meta.fields: + self._meta.fields.remove('gerar_protocolo') + if 'regime_tramitacao' in self._meta.fields: + self._meta.fields.remove('regime_tramitacao') + # esta chamada isola o __init__ de ProposicaoForm super(ProposicaoForm, self).__init__(*args, **kwargs) @@ -1137,13 +1189,17 @@ class ConfirmarProposicaoForm(ProposicaoForm): css_class="ementa_materia hidden alert-info", dismiss=False), 12)))) - itens_incorporacao = [to_column(('regime_tramitacao', 4))] - if self.proposicao_incorporacao_obrigatoria == 'C': - itens_incorporacao.append(to_column((InlineRadios( - 'gerar_protocolo'), 4))) + itens_incorporacao = [] + if self.instance.tipo.content_type.model_class() == \ + TipoMateriaLegislativa: + itens_incorporacao = [to_column(('regime_tramitacao', 4))] + + if self.proposicao_incorporacao_obrigatoria == 'C': + itens_incorporacao.append(to_column((InlineRadios( + 'gerar_protocolo'), 4))) - if self.proposicao_incorporacao_obrigatoria != 'N': - itens_incorporacao.append(to_column(('numero_de_paginas', 4))) + if self.proposicao_incorporacao_obrigatoria != 'N': + itens_incorporacao.append(to_column(('numero_de_paginas', 4))) itens_incorporacao.append(to_column((FormActions(Submit( 'incorporar', _('Incorporar'), css_class='pull-right')), 12))) @@ -1180,6 +1236,14 @@ class ConfirmarProposicaoForm(ProposicaoForm): self.fields['gerar_protocolo'].initial = True def clean(self): + super(ConfirmarProposicaoForm, self).clean() + + numeracao = sapl.base.models.AppConfig.attr('sequencia_numeracao') + + if not numeracao: + raise ValidationError("A sequência de numeração (por ano ou geral)" + " não foi configurada para a aplicação em " + "tabelas auxiliares") if 'incorporar' in self.data: cd = ProposicaoForm.clean(self) @@ -1367,6 +1431,9 @@ class ConfirmarProposicaoForm(ProposicaoForm): proposicao.conteudo_gerado_related = conteudo_gerado proposicao.save() + if self.instance.tipo.content_type.model_class() == TipoDocumento: + return self.instance + # Nunca gerar protocolo if self.proposicao_incorporacao_obrigatoria == 'N': return self.instance @@ -1402,7 +1469,7 @@ class ConfirmarProposicaoForm(ProposicaoForm): protocolo.data = date.today() protocolo.hora = datetime.now().time() - # TODO transformar campo timestamp em auto_now_add + # TODO transformar campo timestamp em auto_now_add protocolo.timestamp = datetime.now() protocolo.tipo_protocolo = '1' diff --git a/sapl/materia/migrations/0005_auto_20170522_1051.py b/sapl/materia/migrations/0005_auto_20170522_1051.py new file mode 100644 index 000000000..5904fa7d1 --- /dev/null +++ b/sapl/materia/migrations/0005_auto_20170522_1051.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2017-05-22 10:51 +from __future__ import unicode_literals + +from django.db import migrations, models +import sapl.materia.models +import sapl.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('materia', '0004_auto_20170504_1751'), + ] + + operations = [ + migrations.AlterField( + model_name='documentoacessorio', + name='arquivo', + field=models.FileField(blank=True, null=True, upload_to=sapl.materia.models.anexo_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Integral'), + ), + migrations.AlterField( + model_name='materialegislativa', + name='texto_original', + field=models.FileField(blank=True, null=True, upload_to=sapl.materia.models.materia_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Original'), + ), + migrations.AlterField( + model_name='proposicao', + name='texto_original', + field=models.FileField(blank=True, null=True, upload_to=sapl.materia.models.materia_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Original'), + ), + ] diff --git a/sapl/materia/migrations/0005_auto_20170522_1904.py b/sapl/materia/migrations/0005_auto_20170522_1904.py new file mode 100644 index 000000000..7789aec96 --- /dev/null +++ b/sapl/materia/migrations/0005_auto_20170522_1904.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-05-22 19:04 +from __future__ import unicode_literals + +from django.db import migrations, models +import sapl.materia.models +import sapl.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('materia', '0004_auto_20170504_1751'), + ] + + operations = [ + migrations.AlterField( + model_name='documentoacessorio', + name='arquivo', + field=models.FileField(blank=True, null=True, upload_to=sapl.materia.models.anexo_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Integral'), + ), + migrations.AlterField( + model_name='materialegislativa', + name='texto_original', + field=models.FileField(blank=True, null=True, upload_to=sapl.materia.models.materia_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Original'), + ), + migrations.AlterField( + model_name='proposicao', + name='texto_original', + field=models.FileField(blank=True, null=True, upload_to=sapl.materia.models.materia_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Original'), + ), + ] diff --git a/sapl/materia/migrations/0006_merge.py b/sapl/materia/migrations/0006_merge.py new file mode 100644 index 000000000..c3ffdd30d --- /dev/null +++ b/sapl/materia/migrations/0006_merge.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2017-05-23 18:20 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('materia', '0005_auto_20170522_1051'), + ('materia', '0005_auto_20170522_1904'), + ] + + operations = [ + ] diff --git a/sapl/materia/migrations/0007_auto_20170620_1252.py b/sapl/materia/migrations/0007_auto_20170620_1252.py new file mode 100644 index 000000000..6d19ee4da --- /dev/null +++ b/sapl/materia/migrations/0007_auto_20170620_1252.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2017-06-20 12:52 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('materia', '0006_merge'), + ] + + operations = [ + migrations.AlterField( + model_name='tipoproposicao', + name='descricao', + field=models.CharField(error_messages={'unique': 'Já existe um Tipo de Proposição com esta descrição.'}, max_length=50, unique=True, verbose_name='Descrição'), + ), + ] diff --git a/sapl/materia/migrations/0008_auto_20170622_1527.py b/sapl/materia/migrations/0008_auto_20170622_1527.py new file mode 100644 index 000000000..089d385c8 --- /dev/null +++ b/sapl/materia/migrations/0008_auto_20170622_1527.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-06-22 15:27 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('materia', '0007_auto_20170620_1252'), + ] + + operations = [ + migrations.AlterField( + model_name='acompanhamentomateria', + name='materia', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='materia.MateriaLegislativa'), + ), + ] diff --git a/sapl/materia/migrations/0009_auto_20170712_0951.py b/sapl/materia/migrations/0009_auto_20170712_0951.py new file mode 100644 index 000000000..ecec298a9 --- /dev/null +++ b/sapl/materia/migrations/0009_auto_20170712_0951.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-07-12 09:51 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('materia', '0008_auto_20170622_1527'), + ] + + operations = [ + migrations.AddField( + model_name='documentoacessorio', + name='data_ultima_atualizacao', + field=models.DateTimeField(auto_now=True, null=True, verbose_name='Data'), + ), + migrations.AddField( + model_name='materialegislativa', + name='data_ultima_atualizacao', + field=models.DateTimeField(auto_now=True, null=True, verbose_name='Data'), + ), + ] diff --git a/sapl/materia/models.py b/sapl/materia/models.py index b5390f3b4..8d9eae4f5 100644 --- a/sapl/materia/models.py +++ b/sapl/materia/models.py @@ -1,6 +1,5 @@ from datetime import datetime -import reversion from django.contrib.auth.models import Group from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.models import ContentType @@ -9,6 +8,7 @@ from django.db import models from django.utils import formats from django.utils.translation import ugettext_lazy as _ from model_utils import Choices +import reversion from sapl.base.models import Autor from sapl.comissoes.models import Comissao @@ -19,6 +19,7 @@ from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, SaplGenericForeignKey, SaplGenericRelation, restringe_tipos_de_arquivo_txt, texto_upload_path) + EM_TRAMITACAO = [(1, 'Sim'), (0, 'Não')] @@ -33,7 +34,13 @@ def grupo_autor(): @reversion.register() class TipoProposicao(models.Model): - descricao = models.CharField(max_length=50, verbose_name=_('Descrição')) + descricao = models.CharField( + max_length=50, + verbose_name=_('Descrição'), + unique=True, + error_messages={ + 'unique': _('Já existe um Tipo de Proposição com esta descrição.') + }) # FIXME - para a rotina de migração - estes campos mudaram # retire o comentário quando resolver @@ -121,6 +128,14 @@ TIPO_APRESENTACAO_CHOICES = Choices(('O', 'oral', _('Oral')), ('E', 'escrita', _('Escrita'))) +def materia_upload_path(instance, filename): + return texto_upload_path(instance, filename, subpath=instance.ano) + + +def anexo_upload_path(instance, filename): + return texto_upload_path(instance, filename, subpath=instance.materia.ano) + + @reversion.register() class MateriaLegislativa(models.Model): @@ -194,19 +209,27 @@ class MateriaLegislativa(models.Model): texto_original = models.FileField( blank=True, null=True, - upload_to=texto_upload_path, + upload_to=materia_upload_path, verbose_name=_('Texto Original'), validators=[restringe_tipos_de_arquivo_txt]) texto_articulado = GenericRelation( TextoArticulado, related_query_name='texto_articulado') + proposicao = GenericRelation( + 'Proposicao', related_query_name='proposicao') + autores = models.ManyToManyField( Autor, through='Autoria', through_fields=('materia', 'autor'), symmetrical=False,) + data_ultima_atualizacao = models.DateTimeField( + blank=True, null=True, + auto_now=True, + verbose_name=_('Data')) + class Meta: verbose_name = _('Matéria Legislativa') verbose_name_plural = _('Matérias Legislativas') @@ -236,6 +259,10 @@ class MateriaLegislativa(models.Model): if self.texto_original: self.texto_original.delete() + for p in self.proposicao.all(): + p.conteudo_gerado_related = None + p.save() + return models.Model.delete( self, using=using, keep_parents=keep_parents) @@ -281,7 +308,7 @@ class Autoria(models.Model): @reversion.register() class AcompanhamentoMateria(models.Model): usuario = models.CharField(max_length=50) - materia = models.ForeignKey(MateriaLegislativa, on_delete=models.PROTECT) + materia = models.ForeignKey(MateriaLegislativa) email = models.EmailField( max_length=100, verbose_name=_('E-mail')) data_cadastro = models.DateField(auto_now_add=True) @@ -392,10 +419,18 @@ class DocumentoAcessorio(models.Model): arquivo = models.FileField( blank=True, null=True, - upload_to=texto_upload_path, + upload_to=anexo_upload_path, verbose_name=_('Texto Integral'), validators=[restringe_tipos_de_arquivo_txt]) + proposicao = GenericRelation( + 'Proposicao', related_query_name='proposicao') + + 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') @@ -411,6 +446,10 @@ class DocumentoAcessorio(models.Model): if self.arquivo: self.arquivo.delete() + for p in self.proposicao.all(): + p.conteudo_gerado_related = None + p.save() + return models.Model.delete( self, using=using, keep_parents=keep_parents) @@ -625,7 +664,7 @@ class Proposicao(models.Model): ('I', 'Incorporada')), verbose_name=_('Status Proposição')) texto_original = models.FileField( - upload_to=texto_upload_path, + upload_to=materia_upload_path, blank=True, null=True, verbose_name=_('Texto Original'), @@ -834,4 +873,4 @@ class Tramitacao(models.Model): return _('%(materia)s | %(status)s | %(data)s') % { 'materia': self.materia, 'status': self.status, - 'data': self.data_tramitacao} + 'data': self.data_tramitacao.strftime("%d/%m/%Y")} diff --git a/sapl/materia/receivers.py b/sapl/materia/receivers.py new file mode 100644 index 000000000..31f353fe0 --- /dev/null +++ b/sapl/materia/receivers.py @@ -0,0 +1,19 @@ +from django.dispatch import receiver + +from sapl.materia.signals import tramitacao_signal +from sapl.utils import get_base_url + +from .email_utils import do_envia_email_tramitacao + + +@receiver(tramitacao_signal) +def handle_tramitacao_signal(sender, **kwargs): + tramitacao = kwargs.get("post") + request = kwargs.get("request") + materia = tramitacao.materia + + do_envia_email_tramitacao( + get_base_url(request), + materia, + tramitacao.status, + tramitacao.unidade_tramitacao_destino) diff --git a/sapl/materia/signals.py b/sapl/materia/signals.py index 9f08b104c..f1cf66e03 100644 --- a/sapl/materia/signals.py +++ b/sapl/materia/signals.py @@ -1,10 +1,8 @@ from django.db.models.signals import post_delete, post_save -from sapl.utils import save_texto, delete_texto + +import django.dispatch from .models import DocumentoAcessorio, MateriaLegislativa -post_save.connect(save_texto, sender=MateriaLegislativa) -post_save.connect(save_texto, sender=DocumentoAcessorio) -post_delete.connect(delete_texto, sender=MateriaLegislativa) -post_delete.connect(delete_texto, sender=DocumentoAcessorio) +tramitacao_signal = django.dispatch.Signal(providing_args=['post', 'request']) diff --git a/sapl/materia/tests/test_email_templates.py b/sapl/materia/tests/test_email_templates.py index 03982b150..aac13cbb7 100644 --- a/sapl/materia/tests/test_email_templates.py +++ b/sapl/materia/tests/test_email_templates.py @@ -1,6 +1,6 @@ from django.core import mail -from sapl.materia.views import enviar_emails, load_email_templates +from sapl.materia.email_utils import enviar_emails, load_email_templates def test_email_template_loading(): diff --git a/sapl/materia/urls.py b/sapl/materia/urls.py index 7eac42e5c..600fadfc6 100644 --- a/sapl/materia/urls.py +++ b/sapl/materia/urls.py @@ -25,6 +25,8 @@ from sapl.materia.views import (AcompanhamentoConfirmarView, from .apps import AppConfig +from . import receivers + app_name = AppConfig.name urlpatterns_materia = [ diff --git a/sapl/materia/views.py b/sapl/materia/views.py index b93d81289..f4b6c4e35 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -1,28 +1,47 @@ -from datetime import datetime +from datetime import datetime, date from random import choice from string import ascii_letters, digits +from .email_utils import do_envia_email_confirmacao +from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, + AdicionarVariasAutoriasFilterSet, DespachoInicialForm, + DocumentoAcessorioForm, MateriaAssuntoForm, + MateriaLegislativaFilterSet, MateriaSimplificadaForm, + PrimeiraTramitacaoEmLoteFilterSet, ReceberProposicaoForm, + RelatoriaForm, TramitacaoEmLoteFilterSet, + filtra_tramitacao_destino, + filtra_tramitacao_destino_and_status, + filtra_tramitacao_status) +from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria, + DespachoInicial, DocumentoAcessorio, MateriaAssunto, + MateriaLegislativa, Numeracao, Orgao, Origem, Proposicao, + RegimeTramitacao, Relatoria, StatusTramitacao, + TipoDocumento, TipoFimRelatoria, TipoMateriaLegislativa, + TipoProposicao, Tramitacao, UnidadeTramitacao) +from .signals import tramitacao_signal from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML +from django import forms from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.contrib.auth.mixins import PermissionRequiredMixin -from django.core.exceptions import ObjectDoesNotExist -from django.core.mail import send_mail +from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import (ObjectDoesNotExist, + MultipleObjectsReturned) from django.core.urlresolvers import reverse +from django.db.models import Q from django.http import HttpResponse, JsonResponse from django.http.response import Http404, HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect -from django.template import Context, loader from django.utils import formats from django.utils.translation import ugettext_lazy as _ from django.views.generic import CreateView, ListView, TemplateView, UpdateView from django.views.generic.base import RedirectView from django.views.generic.edit import FormView from django_filters.views import FilterView - import sapl from sapl.base.models import Autor, CasaLegislativa +from sapl.comissoes.models import Comissao from sapl.comissoes.models import Comissao, Participacao from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_RESTRICT, STATUS_TA_PRIVATE) @@ -33,29 +52,18 @@ from sapl.crud.base import (ACTION_CREATE, ACTION_DELETE, ACTION_DETAIL, Crud, CrudAux, MasterDetailCrud, PermissionRequiredForAppCrudMixin, make_pagination) from sapl.materia.forms import (AnexadaForm, ConfirmarProposicaoForm, - LegislacaoCitadaForm, ProposicaoForm, - TipoProposicaoForm) + LegislacaoCitadaForm, AutoriaForm, ProposicaoForm, + TipoProposicaoForm, TramitacaoForm, + TramitacaoUpdateForm) +from sapl.materia.models import Autor from sapl.norma.models import LegislacaoCitada +from sapl.parlamentares.models import Parlamentar from sapl.protocoloadm.models import Protocolo from sapl.utils import (TURNO_TRAMITACAO_CHOICES, YES_NO_CHOICES, autor_label, autor_modal, gerar_hash_arquivo, get_base_url, montar_row_autor) -from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, - AdicionarVariasAutoriasFilterSet, DespachoInicialForm, - DocumentoAcessorioForm, MateriaAssuntoForm, - MateriaLegislativaFilterSet, MateriaSimplificadaForm, - PrimeiraTramitacaoEmLoteFilterSet, ReceberProposicaoForm, - RelatoriaForm, TramitacaoEmLoteFilterSet, - filtra_tramitacao_destino, - filtra_tramitacao_destino_and_status, - filtra_tramitacao_status) -from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria, - DespachoInicial, DocumentoAcessorio, MateriaAssunto, - MateriaLegislativa, Numeracao, Orgao, Origem, Proposicao, - RegimeTramitacao, Relatoria, StatusTramitacao, - TipoDocumento, TipoFimRelatoria, TipoMateriaLegislativa, - TipoProposicao, Tramitacao, UnidadeTramitacao) + AssuntoMateriaCrud = Crud.build(AssuntoMateria, 'assunto_materia') @@ -84,8 +92,8 @@ def proposicao_texto(request, pk): proposicao = Proposicao.objects.get(pk=pk) if proposicao.texto_original: - if not proposicao.data_recebimento: - if proposicao.autor.user_id != request.user.id: + if (not proposicao.data_recebimento and + proposicao.autor.user_id != request.user.id): raise Http404 arquivo = proposicao.texto_original @@ -162,19 +170,43 @@ class CriarProtocoloMateriaView(CreateView): context = super( CriarProtocoloMateriaView, self).get_context_data(**kwargs) - protocolo = Protocolo.objects.get(pk=self.kwargs['pk']) + try: + protocolo = Protocolo.objects.get(pk=self.kwargs['pk']) + except ObjectDoesNotExist: + raise Http404() + + materias_ano = MateriaLegislativa.objects.filter( + ano=protocolo.ano, + tipo=protocolo.tipo_materia).order_by('-numero') + + if materias_ano: + numero = materias_ano.first().numero + 1 + else: + numero = 1 context['form'].fields['tipo'].initial = protocolo.tipo_materia - context['form'].fields['numero'].initial = protocolo.numero + context['form'].fields['numero'].initial = numero context['form'].fields['ano'].initial = protocolo.ano context['form'].fields['data_apresentacao'].initial = protocolo.data context['form'].fields['numero_protocolo'].initial = protocolo.numero - context['form'].fields['ementa'].initial = protocolo.observacao + context['form'].fields['ementa'].initial = protocolo.assunto_ementa return context def form_valid(self, form): materia = form.save() + + try: + protocolo = Protocolo.objects.get(pk=self.kwargs['pk']) + except ObjectDoesNotExist: + raise Http404() + + if protocolo.autor: + Autoria.objects.create( + materia=materia, + autor=protocolo.autor, + primeiro_autor=True) + return redirect(self.get_success_url(materia)) @@ -419,7 +451,7 @@ class ReceberProposicao(PermissionRequiredForAppCrudMixin, FormView): hasher = gerar_hash_arquivo( proposicao.texto_original.path, str(proposicao.pk)) \ - if proposicao.texto_original else None + if proposicao.texto_original else None if hasher == form.cleaned_data['cod_hash']: return HttpResponseRedirect( reverse('sapl.materia:proposicao-confirmar', @@ -524,7 +556,7 @@ class ProposicaoCrud(Crud): class BaseMixin(Crud.BaseMixin): list_field_names = ['data_envio', 'data_recebimento', 'descricao', - 'tipo'] + 'tipo', 'conteudo_gerado_related'] class BaseLocalMixin: form_class = ProposicaoForm @@ -826,6 +858,7 @@ class RelatoriaCrud(MasterDetailCrud): composicao=composicao) parlamentares = [] + parlamentares.append(['', '---------']) for p in participacao: if p.titular: parlamentares.append( @@ -854,6 +887,28 @@ class RelatoriaCrud(MasterDetailCrud): class UpdateView(MasterDetailCrud.UpdateView): form_class = RelatoriaForm + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + try: + comissao = Comissao.objects.get( + pk=context['form'].initial['comissao']) + except ObjectDoesNotExist: + pass + else: + composicao = comissao.composicao_set.last() + participacao = Participacao.objects.filter( + composicao=composicao) + + parlamentares = [] + for p in participacao: + if p.titular: + parlamentares.append( + [p.parlamentar.id, p.parlamentar.nome_parlamentar]) + context['form'].fields['parlamentar'].choices = parlamentares + + return context + class TramitacaoCrud(MasterDetailCrud): model = Tramitacao @@ -868,33 +923,77 @@ class TramitacaoCrud(MasterDetailCrud): ordering = '-data_tramitacao', class CreateView(MasterDetailCrud.CreateView): + form_class = TramitacaoForm + + def get_success_url(self): + return reverse('sapl.materia:tramitacao_list', kwargs={ + 'pk': self.kwargs['pk']}) def get_initial(self): local = MateriaLegislativa.objects.get( - pk=self.kwargs['pk']).tramitacao_set.last() + pk=self.kwargs['pk']).tramitacao_set.order_by( + '-data_tramitacao').first() + if local: self.initial['unidade_tramitacao_local' ] = local.unidade_tramitacao_destino.pk + else: + self.initial['unidade_tramitacao_local'] = '' self.initial['data_tramitacao'] = datetime.now() return self.initial - def post(self, request, *args, **kwargs): - materia = MateriaLegislativa.objects.get(id=kwargs['pk']) - do_envia_email_tramitacao(request, materia) - return super(CreateView, self).post(request, *args, **kwargs) + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) - class UpdateView(MasterDetailCrud.UpdateView): + primeira_tramitacao = not(Tramitacao.objects.filter( + materia_id=int(kwargs['root_pk'])).exists()) - def post(self, request, *args, **kwargs): - materia = MateriaLegislativa.objects.get( - tramitacao__id=kwargs['pk']) - do_envia_email_tramitacao(request, materia) - return super(UpdateView, self).post(request, *args, **kwargs) + # Se não for a primeira tramitação daquela matéria, o campo + # não pode ser modificado + if not primeira_tramitacao: + context['form'].fields[ + 'unidade_tramitacao_local'].widget.attrs['disabled'] = True + return context + + def form_valid(self, form): + self.object = form.save() + + try: + tramitacao_signal.send(sender=Tramitacao, + post=self.object, + request=self.request) + except Exception: + # TODO log error + msg = _('Tramitação criada, mas e-mail de acompanhamento ' + 'de matéria não enviado. Há problemas na configuração ' + 'do e-mail.') + messages.add_message(self.request, messages.ERROR, msg) + return HttpResponseRedirect(self.get_success_url()) + return super().form_valid(form) + + class UpdateView(MasterDetailCrud.UpdateView): + form_class = TramitacaoUpdateForm @property def layout_key(self): return 'TramitacaoUpdate' + def form_valid(self, form): + self.object = form.save() + + try: + tramitacao_signal.send(sender=Tramitacao, + post=self.object, + request=self.request) + except Exception: + # TODO log error + msg = _('Tramitação atualizada, mas e-mail de acompanhamento ' + 'de matéria não enviado. Há problemas na configuração ' + 'do e-mail.') + messages.add_message(self.request, messages.ERROR, msg) + return HttpResponseRedirect(self.get_success_url()) + return super().form_valid(form) + class ListView(MasterDetailCrud.ListView): def get_queryset(self): @@ -951,25 +1050,21 @@ class DocumentoAcessorioCrud(MasterDetailCrud): form_class = DocumentoAcessorioForm def __init__(self, **kwargs): - montar_helper_documento_acessorio(self) super(MasterDetailCrud.CreateView, self).__init__(**kwargs) def get_context_data(self, **kwargs): context = super( MasterDetailCrud.CreateView, self).get_context_data(**kwargs) - context['helper'] = self.helper return context class UpdateView(MasterDetailCrud.UpdateView): form_class = DocumentoAcessorioForm def __init__(self, **kwargs): - montar_helper_documento_acessorio(self) super(MasterDetailCrud.UpdateView, self).__init__(**kwargs) def get_context_data(self, **kwargs): context = super(UpdateView, self).get_context_data(**kwargs) - context['helper'] = self.helper return context @@ -979,6 +1074,32 @@ class AutoriaCrud(MasterDetailCrud): help_path = '' public = [RP_LIST, RP_DETAIL] + class CreateView(MasterDetailCrud.CreateView): + form_class = AutoriaForm + + def get_context_data(self, **kwargs): + context = super(CreateView, self).get_context_data(**kwargs) + autores_ativos = self.autores_ativos() + + autores = [] + for a in autores_ativos: + autores.append([a.id, a.__str__()]) + + context['form'].fields['autor'].choices = autores + return context + + def autores_ativos(self): + lista_parlamentares = Parlamentar.objects.filter(ativo=True).values_list('id', flat=True) + model_parlamentar = ContentType.objects.get_for_model(Parlamentar) + autor_parlamentar = Autor.objects.filter(content_type=model_parlamentar, object_id__in=lista_parlamentares) + + lista_comissoes = Comissao.objects.filter(Q(data_extincao__isnull=True)|Q(data_extincao__gt=date.today())).values_list('id', flat=True) + model_comissao = ContentType.objects.get_for_model(Comissao) + autor_comissoes = Autor.objects.filter(content_type=model_comissao, object_id__in=lista_comissoes) + autores_outros = Autor.objects.exclude(content_type__in=[model_parlamentar, model_comissao]) + q = autor_parlamentar | autor_comissoes | autores_outros + return q + class DespachoInicialCrud(MasterDetailCrud): model = DespachoInicial @@ -1222,25 +1343,41 @@ class DocumentoAcessorioView(PermissionRequiredMixin, CreateView): class AcompanhamentoConfirmarView(TemplateView): - def get_redirect_url(self): - return reverse('sapl.sessao:list_pauta_sessao') + def get_redirect_url(self, email): + msg = _('Esta matéria está sendo acompanhada pelo e-mail: %s') % ( + email) + messages.add_message(self.request, messages.SUCCESS, msg) + return reverse('sapl.materia:materialegislativa_detail', + kwargs={'pk': self.kwargs['pk']}) def get(self, request, *args, **kwargs): materia_id = kwargs['pk'] hash_txt = request.GET.get('hash_txt', '') - acompanhar = AcompanhamentoMateria.objects.get(materia_id=materia_id, - hash=hash_txt) + try: + acompanhar = AcompanhamentoMateria.objects.get( + materia_id=materia_id, + hash=hash_txt) + except ObjectDoesNotExist: + raise Http404() + # except MultipleObjectsReturned: + # A melhor solução deve ser permitir que a exceção + # (MultipleObjectsReturned) seja lançada e vá para o log, + # pois só poderá ser causada por um erro de desenvolvimente + acompanhar.confirmado = True acompanhar.save() - return HttpResponseRedirect(self.get_redirect_url()) + return HttpResponseRedirect(self.get_redirect_url(acompanhar.email)) class AcompanhamentoExcluirView(TemplateView): - def get_redirect_url(self): - return reverse('sapl.sessao:list_pauta_sessao') + def get_success_url(self): + msg = _('Você parou de acompanhar esta matéria.') + messages.add_message(self.request, messages.INFO, msg) + return reverse('sapl.materia:materialegislativa_detail', + kwargs={'pk': self.kwargs['pk']}) def get(self, request, *args, **kwargs): materia_id = kwargs['pk'] @@ -1252,7 +1389,7 @@ class AcompanhamentoExcluirView(TemplateView): except ObjectDoesNotExist: pass - return HttpResponseRedirect(self.get_redirect_url()) + return HttpResponseRedirect(self.get_success_url()) class MateriaLegislativaPesquisaView(FilterView): @@ -1270,7 +1407,7 @@ class MateriaLegislativaPesquisaView(FilterView): unidade_destino = self.request.GET.get( 'tramitacao__unidade_tramitacao_destino') - qs = self.get_queryset() + qs = self.get_queryset().distinct() if status_tramitacao and unidade_destino: lista = filtra_tramitacao_destino_and_status(status_tramitacao, @@ -1337,237 +1474,62 @@ class AcompanhamentoMateriaView(CreateView): materia = MateriaLegislativa.objects.get(id=pk) if form.is_valid(): - email = form.cleaned_data['email'] usuario = request.user hash_txt = self.get_random_chars() - try: - AcompanhamentoMateria.objects.get( - email=email, - materia=materia, - hash=hash_txt) - except ObjectDoesNotExist: - acompanhar = form.save(commit=False) + acompanhar = AcompanhamentoMateria.objects.get_or_create( + materia=materia, + email=form.data['email']) + + # Se o segundo elemento do retorno do get_or_create for True + # quer dizer que o elemento não existia + if acompanhar[1]: + acompanhar = acompanhar[0] acompanhar.hash = hash_txt - acompanhar.materia = materia acompanhar.usuario = usuario.username acompanhar.confirmado = False acompanhar.save() - do_envia_email_confirmacao(request, materia, email) + base_url = get_base_url(request) + + destinatario = AcompanhamentoMateria.objects.get( + materia=materia, + email=email, + confirmado=False) + casa = CasaLegislativa.objects.first() + + do_envia_email_confirmacao(base_url, + casa, + materia, + destinatario) + + msg = _('Foi enviado um e-mail de confirmação. Confira sua caixa \ + de mensagens e clique no link que nós enviamos para \ + confirmar o acompanhamento desta matéria.') + messages.add_message(request, messages.SUCCESS, msg) + # Caso esse Acompanhamento já exista + # avisa ao usuário que essa matéria já está sendo acompanhada else: + msg = _('Este e-mail já está acompanhando essa matéria.') + messages.add_message(request, messages.INFO, msg) + return self.render_to_response( {'form': form, 'materia': materia, 'error': _('Essa matéria já está\ sendo acompanhada por este e-mail.')}) - return self.form_valid(form) + return HttpResponseRedirect(self.get_success_url()) else: return self.render_to_response( {'form': form, 'materia': materia}) def get_success_url(self): - return reverse('sapl.sessao:list_pauta_sessao') - - -def load_email_templates(templates, context={}): - - emails = [] - for t in templates: - tpl = loader.get_template(t) - email = tpl.render(Context(context)) - if t.endswith(".html"): - email = email.replace('\n', '').replace('\r', '') - emails.append(email) - return emails - - -def criar_email_confirmacao(request, casa_legislativa, materia, hash_txt=''): - - if not casa_legislativa: - raise ValueError("Casa Legislativa é obrigatória") - - if not materia: - raise ValueError("Matéria é obrigatória") - - # FIXME i18n - casa_nome = (casa_legislativa.nome + ' de ' + - casa_legislativa.municipio + '-' + - casa_legislativa.uf) - - base_url = get_base_url(request) - materia_url = reverse('sapl.materia:acompanhar_materia', - kwargs={'pk': materia.id}) - confirmacao_url = reverse('sapl.materia:acompanhar_confirmar', - kwargs={'pk': materia.id}) - - autores = [] - for autoria in materia.autoria_set.all(): - autores.append(autoria.autor.nome) - - templates = load_email_templates(['email/acompanhar.txt', - 'email/acompanhar.html'], - {"casa_legislativa": casa_nome, - "logotipo": casa_legislativa.logotipo, - "descricao_materia": materia.ementa, - "autoria": autores, - "hash_txt": hash_txt, - "base_url": base_url, - "materia": str(materia), - "materia_url": materia_url, - "confirmacao_url": confirmacao_url, }) - return templates - - -def criar_email_tramitacao(request, casa_legislativa, materia, hash_txt=''): - - if not casa_legislativa: - raise ValueError("Casa Legislativa é obrigatória") - - if not materia: - raise ValueError("Matéria é obrigatória") - - # FIXME i18n - casa_nome = (casa_legislativa.nome + ' de ' + - casa_legislativa.municipio + '-' + - casa_legislativa.uf) - - base_url = get_base_url(request) - url_materia = reverse('sapl.materia:acompanhar_materia', - kwargs={'pk': materia.id}) - url_excluir = reverse('sapl.materia:acompanhar_excluir', - kwargs={'pk': materia.id}) - - autores = [] - for autoria in materia.autoria_set.all(): - autores.append(autoria.autor.nome) - - templates = load_email_templates(['email/tramitacao.txt', - 'email/tramitacao.html'], - {"casa_legislativa": casa_nome, - "data_registro": datetime.now().strftime( - "%d/%m/%Y"), - "cod_materia": materia.id, - "logotipo": casa_legislativa.logotipo, - "descricao_materia": materia.ementa, - "autoria": autores, - "data": materia.tramitacao_set.last( - ).data_tramitacao, - "status": materia.tramitacao_set.last( - ).status, - "texto_acao": - materia.tramitacao_set.last().texto, - "hash_txt": hash_txt, - "materia": str(materia), - "base_url": base_url, - "materia_url": url_materia, - "excluir_url": url_excluir}) - return templates - - -def enviar_emails(sender, recipients, messages): - ''' - Recipients is a string list of email addresses - - Messages is an array of dicts of the form: - {'recipient': 'address', # useless???? - 'subject': 'subject text', - 'txt_message': 'text message', - 'html_message': 'html message' - } - ''' - - if len(messages) == 1: - # sends an email simultaneously to all recipients - send_mail(messages[0]['subject'], - messages[0]['txt_message'], - sender, - recipients, - html_message=messages[0]['html_message'], - fail_silently=False) - - elif len(recipients) > len(messages): - raise ValueError("Message list should have size 1 \ - or equal recipient list size. \ - recipients: %s, messages: %s" % (recipients, messages) - ) - - else: - # sends an email simultaneously to all reciepients - for (d, m) in zip(recipients, messages): - send_mail(m['subject'], - m['txt_message'], - sender, - [d], - html_message=m['html_message'], - fail_silently=False) - return None - - -def do_envia_email_confirmacao(request, materia, email): - # - # Envia email de confirmacao para atualizações de tramitação - # - destinatario = AcompanhamentoMateria.objects.get(materia=materia, - email=email, - confirmado=False) - casa = CasaLegislativa.objects.first() - - sender = 'sapl-test@interlegis.leg.br' - # FIXME i18n - subject = "[SAPL] " + str(materia) + " - Ative o Acompanhamento da Materia" - messages = [] - recipients = [] - - email_texts = criar_email_confirmacao(request, - casa, - materia, - destinatario.hash,) - recipients.append(destinatario.email) - messages.append({ - 'recipient': destinatario.email, - 'subject': subject, - 'txt_message': email_texts[0], - 'html_message': email_texts[1] - }) - - enviar_emails(sender, recipients, messages) - return None - - -def do_envia_email_tramitacao(request, materia): - # - # Envia email de tramitacao para usuarios cadastrados - # - destinatarios = AcompanhamentoMateria.objects.filter(materia=materia, - confirmado=True) - casa = CasaLegislativa.objects.first() - - sender = 'sapl-test@interlegis.leg.br' - # FIXME i18n - subject = "[SAPL] " + str(materia) + \ - " - Acompanhamento de Materia Legislativa" - messages = [] - recipients = [] - for destinatario in destinatarios: - email_texts = criar_email_tramitacao(request, - casa, - materia, - destinatario.hash,) - recipients.append(destinatario.email) - messages.append({ - 'recipient': destinatario.email, - 'subject': subject, - 'txt_message': email_texts[0], - 'html_message': email_texts[1] - }) - - enviar_emails(sender, recipients, messages) - return None + return reverse('sapl.materia:materialegislativa_detail', + kwargs={'pk': self.kwargs['pk']}) class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView): @@ -1586,6 +1548,8 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView): qr = self.request.GET.copy() context['tipos_docs'] = TipoDocumento.objects.all() + context['object_list'] = context['object_list'].order_by( + 'ano', 'numero') context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' return context @@ -1600,15 +1564,15 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView): tipo = TipoDocumento.objects.get(descricao=request.POST['tipo']) for materia_id in marcadas: - DocumentoAcessorio.objects.create( - materia_id=materia_id, - tipo=tipo, - arquivo=request.POST['arquivo'], - nome=request.POST['nome'], - data=datetime.strptime(request.POST['data'], "%d/%m/%Y"), - autor=Autor.objects.get(id=request.POST['autor']), - ementa=request.POST['ementa'] - ) + doc = DocumentoAcessorio() + doc.materia_id = materia_id + doc.tipo = tipo + doc.arquivo = request.FILES['arquivo'] + doc.nome = request.POST['nome'] + doc.data = datetime.strptime(request.POST['data'], "%d/%m/%Y") + doc.autor = request.POST['autor'] + doc.ementa = request.POST['ementa'] + doc.save() msg = _('Documento(s) criado(s).') messages.add_message(request, messages.SUCCESS, msg) return self.get(request, self.kwargs) @@ -1638,6 +1602,8 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView): context['urgente_tramitacao'] = YES_NO_CHOICES context['unidade_local'] = UnidadeTramitacao.objects.all() + context['primeira_tramitacao'] = True + # Pega somente matéria que não possuem tramitação if (type(self.__dict__['filterset']).__name__ == 'PrimeiraTramitacaoEmLoteFilterSet'): @@ -1674,8 +1640,12 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView): else: data_fim_prazo = None + # issue https://github.com/interlegis/sapl/issues/1123 + # TODO: usar Form + urgente = request.POST['urgente'] == 'True' + for materia_id in marcadas: - Tramitacao.objects.create( + t = Tramitacao( materia_id=materia_id, data_tramitacao=datetime.strptime( request.POST['data_tramitacao'], "%d/%m/%Y"), @@ -1685,11 +1655,12 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView): 'unidade_tramitacao_local'], unidade_tramitacao_destino_id=request.POST[ 'unidade_tramitacao_destino'], - urgente=request.POST['urgente'], + urgente=urgente, status_id=request.POST['status'], turno=request.POST['turno'], texto=request.POST['texto'] ) + t.save() msg = _('Tramitação completa.') messages.add_message(request, messages.SUCCESS, msg) return self.get(request, self.kwargs) @@ -1697,3 +1668,24 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView): class TramitacaoEmLoteView(PrimeiraTramitacaoEmLoteView): filterset_class = TramitacaoEmLoteFilterSet + + def get_context_data(self, **kwargs): + context = super(TramitacaoEmLoteView, + self).get_context_data(**kwargs) + + qr = self.request.GET.copy() + + context['primeira_tramitacao'] = False + + if ('tramitacao__status' in qr and + 'tramitacao__unidade_tramitacao_destino' in qr and + qr['tramitacao__status'] and + qr['tramitacao__unidade_tramitacao_destino'] + ): + lista = filtra_tramitacao_destino_and_status( + qr['tramitacao__status'], + qr['tramitacao__unidade_tramitacao_destino']) + context['object_list'] = context['object_list'].filter( + id__in=lista).distinct() + + return context diff --git a/sapl/norma/forms.py b/sapl/norma/forms.py index af2b8b38b..5ba72e161 100644 --- a/sapl/norma/forms.py +++ b/sapl/norma/forms.py @@ -47,7 +47,7 @@ class NormaFilterSet(django_filters.FilterSet): }} ano = django_filters.ChoiceFilter(required=False, - label=u'Ano', + label='Ano', choices=ANO_CHOICES) ementa = django_filters.CharFilter(lookup_expr='icontains') @@ -118,6 +118,8 @@ class NormaJuridicaForm(ModelForm): widgets = {'assuntos': widgets.CheckboxSelectMultiple} def clean(self): + super(NormaJuridicaForm, self).clean() + cleaned_data = self.cleaned_data if (cleaned_data['tipo_materia'] and @@ -142,7 +144,9 @@ class NormaJuridicaForm(ModelForm): texto_integral = self.cleaned_data.get('texto_integral', False) if texto_integral: if texto_integral.size > MAX_DOC_UPLOAD_SIZE: - raise ValidationError("Arquivo muito grande. ( > 5mb )") + max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024)) + raise ValidationError( + "Arquivo muito grande. ( > {0}MB )".format(max_size)) return texto_integral def save(self, commit=False): @@ -175,6 +179,8 @@ class NormaRelacionadaForm(ModelForm): super(NormaRelacionadaForm, self).__init__(*args, **kwargs) def clean(self): + super(NormaRelacionadaForm, self).clean() + if self.errors: return self.errors cleaned_data = self.cleaned_data diff --git a/sapl/norma/migrations/0004_auto_20170522_1051.py b/sapl/norma/migrations/0004_auto_20170522_1051.py new file mode 100644 index 000000000..d2a3b4a8c --- /dev/null +++ b/sapl/norma/migrations/0004_auto_20170522_1051.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2017-05-22 10:51 +from __future__ import unicode_literals + +from django.db import migrations, models +import sapl.norma.models +import sapl.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('norma', '0003_auto_20170510_1549'), + ] + + operations = [ + migrations.AlterField( + model_name='normajuridica', + name='texto_integral', + field=models.FileField(blank=True, null=True, upload_to=sapl.norma.models.norma_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Integral'), + ), + ] diff --git a/sapl/norma/migrations/0004_auto_20170522_1115.py b/sapl/norma/migrations/0004_auto_20170522_1115.py new file mode 100644 index 000000000..26e390bdb --- /dev/null +++ b/sapl/norma/migrations/0004_auto_20170522_1115.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-05-22 11:15 +from __future__ import unicode_literals + +from django.db import migrations, models +import sapl.norma.models +import sapl.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('norma', '0003_auto_20170510_1549'), + ] + + operations = [ + migrations.AlterField( + model_name='normajuridica', + name='texto_integral', + field=models.FileField(blank=True, null=True, upload_to=sapl.norma.models.norma_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Integral'), + ), + migrations.AlterField( + model_name='normajuridica', + name='timestamp', + field=models.DateTimeField(null=True), + ), + ] diff --git a/sapl/norma/migrations/0005_merge.py b/sapl/norma/migrations/0005_merge.py new file mode 100644 index 000000000..0e3cc25d0 --- /dev/null +++ b/sapl/norma/migrations/0005_merge.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2017-05-23 18:20 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('norma', '0004_auto_20170522_1115'), + ('norma', '0004_auto_20170522_1051'), + ] + + operations = [ + ] diff --git a/sapl/norma/migrations/0006_normajuridica_data_ultima_atualizacao.py b/sapl/norma/migrations/0006_normajuridica_data_ultima_atualizacao.py new file mode 100644 index 000000000..9f5d5faba --- /dev/null +++ b/sapl/norma/migrations/0006_normajuridica_data_ultima_atualizacao.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-07-12 09:51 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('norma', '0005_merge'), + ] + + operations = [ + migrations.AddField( + model_name='normajuridica', + name='data_ultima_atualizacao', + field=models.DateTimeField(auto_now=True, null=True, verbose_name='Data'), + ), + ] diff --git a/sapl/norma/models.py b/sapl/norma/models.py index 0d0b8ce6b..a80f5a564 100644 --- a/sapl/norma/models.py +++ b/sapl/norma/models.py @@ -59,6 +59,8 @@ class TipoNormaJuridica(models.Model): def __str__(self): return self.descricao +def norma_upload_path(instance, filename): + return texto_upload_path(instance, filename, subpath=instance.ano) @reversion.register() class NormaJuridica(models.Model): @@ -67,10 +69,11 @@ class NormaJuridica(models.Model): ('F', 'federal', _('Federal')), ('M', 'municipal', _('Municipal')), ) + texto_integral = models.FileField( blank=True, null=True, - upload_to=texto_upload_path, + upload_to=norma_upload_path, verbose_name=_('Texto Integral'), validators=[restringe_tipos_de_arquivo_txt]) tipo = models.ForeignKey( @@ -113,11 +116,16 @@ class NormaJuridica(models.Model): AssuntoNorma, blank=True, verbose_name=_('Assuntos')) data_vigencia = models.DateField(blank=True, null=True) - timestamp = models.DateTimeField() + timestamp = models.DateTimeField(null=True) texto_articulado = GenericRelation( TextoArticulado, related_query_name='texto_articulado') + data_ultima_atualizacao = models.DateTimeField( + blank=True, null=True, + auto_now=True, + verbose_name=_('Data')) + class Meta: verbose_name = _('Norma Jurídica') verbose_name_plural = _('Normas Jurídicas') diff --git a/sapl/norma/signals.py b/sapl/norma/signals.py index 3089e563e..4a5472715 100644 --- a/sapl/norma/signals.py +++ b/sapl/norma/signals.py @@ -1,8 +1,3 @@ from django.db.models.signals import post_delete, post_save -from sapl.utils import save_texto, delete_texto from .models import NormaJuridica - - -post_save.connect(save_texto, sender=NormaJuridica) -post_delete.connect(delete_texto, sender=NormaJuridica) diff --git a/sapl/painel/urls.py b/sapl/painel/urls.py index b617d8fec..29890f30f 100644 --- a/sapl/painel/urls.py +++ b/sapl/painel/urls.py @@ -1,8 +1,8 @@ from django.conf.urls import url from .apps import AppConfig -from .views import (controlador_painel, cronometro_painel, get_dados_painel, - painel_mensagem_view, painel_parlamentar_view, painel_view, +from .views import (cronometro_painel, get_dados_painel, painel_mensagem_view, + painel_parlamentar_view, painel_view, painel_votacao_view, votante_view) app_name = AppConfig.name @@ -11,8 +11,6 @@ urlpatterns = [ url(r'^painel-principal/(?P\d+)$', painel_view, name="painel_principal"), url(r'^painel/(?P\d+)/dados$', get_dados_painel, name='dados_painel'), - url(r'^painel/controlador$', - controlador_painel, name='painel_controlador'), url(r'^painel/mensagem$', painel_mensagem_view, name="painel_mensagem"), url(r'^painel/parlamentar$', painel_parlamentar_view, name='painel_parlamentar'), @@ -20,6 +18,6 @@ urlpatterns = [ url(r'^painel/cronometro$', cronometro_painel, name='cronometro_painel'), # url(r'^painel/cronometro$', include(CronometroPainelCrud.get_urls())), - url(r'^voto-individual/(?P\d+)$', votante_view, - name="voto_individual"), + url(r'^voto-individual/$', votante_view, + name='voto_individual'), ] diff --git a/sapl/painel/views.py b/sapl/painel/views.py index 299747f40..b6abd9cc5 100644 --- a/sapl/painel/views.py +++ b/sapl/painel/views.py @@ -1,7 +1,9 @@ from datetime import date +from django.contrib import messages from django.contrib.auth.decorators import user_passes_test -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned +from django.db.models import Q from django.core.urlresolvers import reverse from django.http import HttpResponse, JsonResponse from django.http.response import Http404, HttpResponseRedirect @@ -14,8 +16,7 @@ from sapl.painel.models import Painel from sapl.parlamentares.models import Filiacao, Votante from sapl.sessao.models import (ExpedienteMateria, OrdemDia, PresencaOrdemDia, RegistroVotacao, SessaoPlenaria, - SessaoPlenariaPresenca, VotoNominal, - VotoParlamentar) + SessaoPlenariaPresenca, VotoParlamentar) from sapl.utils import get_client_ip from .models import Cronometro @@ -31,127 +32,191 @@ def check_permission(user): return user.has_module_perms(AppConfig.label) -def votante_view(request, pk): - if not Votante.objects.filter(user=request.user).exists(): - raise Http404('Você não tem permissão para votar') - - context = {'head_title': str(_('Votação Individual')), 'sessao_id': pk} - - # Pega sessão - sessao = SessaoPlenaria.objects.get(pk=pk) - context.update({'sessao': sessao, - 'data': sessao.data_inicio, - 'hora': sessao.hora_inicio}) - - # Inicializa presentes - presentes = [] +def votacao_aberta(request): + ''' + Função que verifica se há somente 1 uma matéria aberta ou + nenhuma. É utilizada como uma função auxiliar para a view + votante_view. + ''' + votacoes_abertas = SessaoPlenaria.objects.filter( + Q(ordemdia__votacao_aberta=True) | + Q(expedientemateria__votacao_aberta=True)).distinct() + + if len(votacoes_abertas) > 1: + msg_abertas = [] + for v in votacoes_abertas: + msg_abertas.append('''
  • %s
  • ''' % ( + reverse('sapl.sessao:sessaoplenaria_detail', + kwargs={'pk': v.id}), + v.__str__())) + + msg = _('Existe mais de uma votações aberta. Elas se encontram ' + 'nas seguintes Sessões: ' + ', '.join(msg_abertas) + '. ' + 'Para votar, peça para que o Operador feche-as.') + messages.add_message(request, messages.INFO, msg) + return None, msg + + elif len(votacoes_abertas) == 1: + ordens = OrdemDia.objects.filter( + sessao_plenaria=votacoes_abertas.first(), + votacao_aberta=True) + expedientes = ExpedienteMateria.objects.filter( + sessao_plenaria=votacoes_abertas.first(), + votacao_aberta=True) + + numero_materias_abertas = len(ordens) + len(expedientes) + if numero_materias_abertas > 1: + msg = _('Existe mais de uma votação aberta na Sessão: ' + + ('''
  • %s
  • ''' % ( + reverse('sapl.sessao:sessaoplenaria_detail', + kwargs={'pk': votacoes_abertas.first().id}), + votacoes_abertas.first().__str__())) + + 'Para votar, peça para que o Operador as feche.') + messages.add_message(request, messages.INFO, msg) + return None, msg + + return votacoes_abertas.first(), None + + +def votante_view(request): + # Pega o votante relacionado ao usuário + try: + votante = Votante.objects.get(user=request.user) + except ObjectDoesNotExist: + raise Http404() - # Verifica votação aberta - # Se aberta, verifica se é nominal. ID nominal == 2 - ordem_dia = get_materia_aberta(pk) - expediente = get_materia_expediente_aberta(pk) - materia = None - - if ordem_dia: - materia = ordem_dia.materia - if ordem_dia.tipo_votacao == VOTACAO_NOMINAL: - context.update({'materia': materia, 'ementa': materia.ementa}) - presentes = PresencaOrdemDia.objects.filter(sessao_plenaria_id=pk) - else: - context.update( - {'materia': 'A matéria aberta não é votação nominal.'}) - elif expediente: - materia = expediente.materia - if expediente.tipo_votacao == VOTACAO_NOMINAL: - context.update({'materia': materia, 'ementa': materia.ementa}) - presentes = SessaoPlenariaPresenca.objects.filter( - sessao_plenaria_id=pk) - else: - context.update( - {'materia': 'A matéria aberta não é votação nominal.'}) - else: - context.update( - {'materia': 'Nenhuma matéria com votação nominal aberta.'}) + context = {'head_title': str(_('Votação Individual'))} # Verifica se usuário possui permissão para votar if 'parlamentares.can_vote' in request.user.get_all_permissions(): context.update({'permissao': True}) - else: - context.update({'permissao': False}) - # Verifica se usuário está presente na sessão - try: - votante = Votante.objects.get(user=request.user) - except ObjectDoesNotExist: - context.update({'error_message': - 'Erro ao recuperar parlamentar ligado ao usuário'}) - else: - parlamentar = votante.parlamentar - context.update({'presente': False}) - if len(presentes) > 0: - for p in presentes: - if p.parlamentar.id == parlamentar.id: - context.update({'presente': True}) - break + # Pega sessão + sessao, msg = votacao_aberta(request) + + if sessao and not msg: + pk = sessao.pk + context.update({'sessao_id': pk}) + context.update({'sessao': sessao, + 'data': sessao.data_inicio, + 'hora': sessao.hora_inicio}) + + # Inicializa presentes + presentes = [] + + # Verifica votação aberta + # Se aberta, verifica se é nominal. ID nominal == 2 + ordem_dia = get_materia_aberta(pk) + expediente = get_materia_expediente_aberta(pk) + + materia_aberta = None + if ordem_dia: + materia_aberta = ordem_dia + presentes = PresencaOrdemDia.objects.filter( + sessao_plenaria_id=pk).values_list( + 'parlamentar_id', flat=True).distinct() + elif expediente: + materia_aberta = expediente + presentes = SessaoPlenariaPresenca.objects.filter( + sessao_plenaria_id=pk).values_list( + 'parlamentar_id', flat=True).distinct() + + if materia_aberta: + if materia_aberta.tipo_votacao == VOTACAO_NOMINAL: + context.update({'materia': materia_aberta.materia, + 'ementa': materia_aberta.materia.ementa}) + + parlamentar = votante.parlamentar + parlamentar_presente = False + if parlamentar.id in presentes: + parlamentar_presente = True + else: + context.update({'error_message': + 'Não há presentes na Sessão com a ' + 'matéria em votação.'}) + + if parlamentar_presente: + voto = [] + if ordem_dia: + voto = VotoParlamentar.objects.filter( + ordem=ordem_dia) + elif expediente: + voto = VotoParlamentar.objects.filter( + expediente=expediente) + + if voto: + try: + voto = voto.get(parlamentar=parlamentar) + context.update({'voto_parlamentar': voto.voto}) + except ObjectDoesNotExist: + context.update( + {'voto_parlamentar': 'Voto não ' + 'computado.'}) + else: + context.update({'error_message': + 'Você não está presente na ' + 'Ordem do Dia/Expediente em votação.'}) + else: + context.update( + {'error_message': 'A matéria aberta não é do tipo ' + 'votação nominal.'}) + else: + context.update( + {'error_message': 'Não há nenhuma matéria aberta.'}) + + elif not sessao and msg: + return HttpResponseRedirect('/') + else: - context.update({'error_message': - 'Nenhuma matéria com votação nominal aberta.'}) + context.update( + {'error_message': 'Não há nenhuma sessão com matéria aberta.'}) - # Recupera o voto do parlamentar logado - try: - voto = VotoNominal.objects.get( - sessao=sessao, - parlamentar=parlamentar, - materia=materia) - except ObjectDoesNotExist: - context.update({'voto_parlamentar': 'Voto não computado.'}) else: - context.update({'voto_parlamentar': voto.voto}) + context.update({'permissao': False, + 'error_message': 'Usuário sem permissão para votar.'}) # Salva o voto if request.method == 'POST': - try: - voto = VotoNominal.objects.get( - sessao=sessao, - parlamentar=parlamentar, - materia=materia) - except ObjectDoesNotExist: - voto = VotoNominal.objects.create( - sessao=sessao, - parlamentar=parlamentar, - materia=materia, - voto=request.POST['voto'], - ip=get_client_ip(request), - user=request.user) - else: - voto.voto = request.POST['voto'] - voto.ip = get_client_ip(request) - voto.save() - return HttpResponseRedirect( - reverse('sapl.painel:voto_individual', kwargs={'pk': pk})) - - return render(request, 'painel/voto_nominal.html', context) - - -@user_passes_test(check_permission) -def controlador_painel(request): + if ordem_dia: + try: + voto = VotoParlamentar.objects.get( + parlamentar=parlamentar, + ordem=ordem_dia) + except ObjectDoesNotExist: + voto = VotoParlamentar.objects.create( + parlamentar=parlamentar, + voto=request.POST['voto'], + user=request.user, + ip=get_client_ip(request), + ordem=ordem_dia) + else: + voto.voto = request.POST['voto'] + voto.ip = get_client_ip(request) + voto.user = request.user + voto.save() - painel_created = Painel.objects.get_or_create(data_painel=date.today()) - painel = painel_created[0] + elif expediente: + try: + voto = VotoParlamentar.objects.get( + parlamentar=parlamentar, + expediente=expediente) + except ObjectDoesNotExist: + voto = VotoParlamentar.objects.create( + parlamentar=parlamentar, + voto=request.POST['voto'], + user=request.user, + ip=get_client_ip(request), + expediente=expediente) + else: + voto.voto = request.POST['voto'] + voto.ip = get_client_ip(request) + voto.user = request.user + voto.save() - if request.method == 'POST': - if 'start-painel' in request.POST: - painel.aberto = True - painel.save() - elif 'stop-painel' in request.POST: - painel.aberto = False - painel.save() - elif 'save-painel' in request.POST: - painel.mostrar = request.POST['tipo_painel'] - painel.save() + return HttpResponseRedirect( + reverse('sapl.painel:voto_individual')) - context = {'painel': painel, 'PAINEL_TYPES': Painel.PAINEL_TYPES} - return render(request, 'painel/controlador.html', context) + return render(request, 'painel/voto_nominal.html', context) @user_passes_test(check_permission) @@ -249,7 +314,8 @@ def get_presentes(pk, response, materia): 'num_presentes_sessao_plenaria': num_presentes_sessao_plen, 'status_painel': 'ABERTO', 'msg_painel': str(_('Votação aberta!')), - 'tipo_resultado': tipo_votacao, + 'tipo_resultado': materia.resultado, + 'tipo_votacao': tipo_votacao, 'observacao_materia': materia.observacao, 'materia_legislativa_texto': str(materia.materia)}) @@ -502,12 +568,12 @@ def get_dados_painel(request, pk): else: if ultimo_expediente_votado.tipo_votacao in [1, 3]: return JsonResponse( - get_votos(get_presentes( + get_votos(get_presentes_expediente( pk, response, ultimo_expediente_votado), ultimo_expediente_votado)) elif ultimo_expediente_votado.tipo_votacao == 2: return JsonResponse( - get_votos_nominal(get_presentes( + get_votos_nominal(get_presentes_expediente( pk, response, ultimo_expediente_votado), ultimo_expediente_votado)) @@ -518,8 +584,9 @@ def get_dados_painel(request, pk): pk, response, ultima_ordem_votada)) # Caso a Ordem do dia não tenha resultado, mostra o último expediente if last_expediente_voto: - return JsonResponse(get_presentes(pk, response, - ultimo_expediente_votado)) + return JsonResponse(get_presentes_expediente( + pk, response, + ultimo_expediente_votado)) # Retorna que não há nenhuma matéria já votada ou aberta return response_nenhuma_materia(response) diff --git a/sapl/parlamentares/forms.py b/sapl/parlamentares/forms.py index 9e2b26b59..d36b3ff73 100644 --- a/sapl/parlamentares/forms.py +++ b/sapl/parlamentares/forms.py @@ -54,11 +54,14 @@ class MandatoForm(ModelForm): class Meta: model = Mandato fields = ['legislatura', 'coligacao', 'votos_recebidos', - 'data_fim_mandato', 'data_expedicao_diploma', 'titular', + 'data_inicio_mandato', 'data_fim_mandato', + 'data_expedicao_diploma', 'titular', 'tipo_afastamento', 'observacao', 'parlamentar'] widgets = {'parlamentar': forms.HiddenInput()} def clean(self): + super(MandatoForm, self).clean() + data = self.cleaned_data try: if 'legislatura' in data and 'parlamentar' in data: @@ -82,6 +85,8 @@ class LegislaturaForm(ModelForm): class LegislaturaCreateForm(LegislaturaForm): def clean(self): + super(LegislaturaCreateForm, self).clean() + cleaned_data = self.cleaned_data eleicao = cleaned_data['data_eleicao'] inicio = cleaned_data['data_inicio'] @@ -96,6 +101,8 @@ class LegislaturaCreateForm(LegislaturaForm): class LegislaturaUpdateForm(LegislaturaCreateForm): def clean(self): + super(LegislaturaUpdateForm, self).clean() + cleaned_data = super(LegislaturaCreateForm, self).clean() eleicao = cleaned_data['data_eleicao'] inicio = cleaned_data['data_inicio'] @@ -214,6 +221,8 @@ class FiliacaoForm(ModelForm): 'data_desfiliacao'] def clean(self): + super(FiliacaoForm, self).clean() + if self.errors: return self.errors @@ -236,6 +245,8 @@ class ComposicaoColigacaoForm(ModelForm): fields = ['partido'] def clean(self): + super(ComposicaoColigacaoForm, self).clean() + cleaned_data = self.cleaned_data pk = self.initial['coligacao_id'] if (ComposicaoColigacao.objects.filter( @@ -311,6 +322,8 @@ class VotanteForm(ModelForm): return True def clean(self): + super(VotanteForm, self).clean() + cd = self.cleaned_data username = cd['username'] diff --git a/sapl/parlamentares/legacy.yaml b/sapl/parlamentares/legacy.yaml index 738656e4e..a4c6b9d12 100644 --- a/sapl/parlamentares/legacy.yaml +++ b/sapl/parlamentares/legacy.yaml @@ -94,7 +94,6 @@ TipoAfastamento: Mandato: coligacao: cod_coligacao data_expedicao_diploma: dat_expedicao_diploma - data_fim_mandato: dat_fim_mandato legislatura: num_legislatura observacao: txt_observacao parlamentar: cod_parlamentar diff --git a/sapl/parlamentares/migrations/0003_auto_20170707_1656.py b/sapl/parlamentares/migrations/0003_auto_20170707_1656.py new file mode 100644 index 000000000..e6212354d --- /dev/null +++ b/sapl/parlamentares/migrations/0003_auto_20170707_1656.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2017-07-07 16:56 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0002_auto_20170504_1751'), + ] + + operations = [ + migrations.AddField( + model_name='mandato', + name='data_inicio_mandato', + field=models.DateField(default=datetime.datetime(2017, 7, 7, 16, 56, 58, 525896), verbose_name='Início do Mandato'), + preserve_default=False, + ), + migrations.AlterField( + model_name='mandato', + name='data_fim_mandato', + field=models.DateField(blank=True, null=True, verbose_name='Fim do Mandato'), + ), + ] diff --git a/sapl/parlamentares/migrations/0004_auto_20170711_1305.py b/sapl/parlamentares/migrations/0004_auto_20170711_1305.py new file mode 100644 index 000000000..9ef1faef6 --- /dev/null +++ b/sapl/parlamentares/migrations/0004_auto_20170711_1305.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2017-07-11 13:05 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0003_auto_20170707_1656'), + ] + + operations = [ + migrations.AlterField( + model_name='mandato', + name='data_inicio_mandato', + field=models.DateField(blank=True, null=True, verbose_name='Início do Mandato'), + ), + ] diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index 4d15ab6fd..5039be89e 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -1,9 +1,9 @@ from datetime import datetime -import reversion from django.db import models from django.utils.translation import ugettext_lazy as _ from model_utils import Choices +import reversion from sapl.base.models import Autor from sapl.utils import (INDICADOR_AFASTAMENTO, UF, YES_NO_CHOICES, @@ -26,17 +26,10 @@ class Legislatura(models.Model): def atual(self): current_year = datetime.now().year - if(self.data_inicio.year <= current_year and - self.data_fim.year >= current_year): - return True - else: - return False + return self.data_inicio.year <= current_year <= self.data_fim.year def __str__(self): - if self.atual(): - current = ' (%s)' % _('Atual') - else: - current = '' + current = ' (%s)' % _('Atual') if self.atual() else '' return _('%(numero)sª (%(start)s - %(end)s)%(current)s') % { 'numero': self.numero, @@ -206,6 +199,15 @@ def foto_upload_path(instance, filename): return texto_upload_path(instance, filename, subpath='') +def true_false_none(x): + if x == 'True': + return True + elif x == 'False': + return False + else: + return None + + @reversion.register() class Parlamentar(models.Model): FEMININO = 'F' @@ -311,7 +313,7 @@ class Parlamentar(models.Model): ordering = ['nome_parlamentar'] def __str__(self): - return self.nome_completo + return self.nome_parlamentar @property def filiacao_atual(self): @@ -447,7 +449,12 @@ class Mandato(models.Model): on_delete=models.PROTECT, verbose_name=_('Coligação')) # TODO what is this field?????? tipo_causa_fim_mandato = models.PositiveIntegerField(blank=True, null=True) - data_fim_mandato = models.DateField(verbose_name=_('Fim do Mandato')) + data_inicio_mandato = models.DateField(verbose_name=_('Início do Mandato'), + blank=True, + null=True) + data_fim_mandato = models.DateField(verbose_name=_('Fim do Mandato'), + blank=True, + null=True) votos_recebidos = models.PositiveIntegerField( blank=True, null=True, verbose_name=_('Votos Recebidos (Mandato)')) data_expedicao_diploma = models.DateField( diff --git a/sapl/parlamentares/tests/test_parlamentares.py b/sapl/parlamentares/tests/test_parlamentares.py index b71816d5b..f210a4551 100644 --- a/sapl/parlamentares/tests/test_parlamentares.py +++ b/sapl/parlamentares/tests/test_parlamentares.py @@ -147,7 +147,5 @@ def test_form_errors_mandato(admin_client): assert (response.context_data['form'].errors['legislatura'] == ['Este campo é obrigatório.']) - assert (response.context_data['form'].errors['data_fim_mandato'] == - ['Este campo é obrigatório.']) assert (response.context_data['form'].errors['data_expedicao_diploma'] == ['Este campo é obrigatório.']) diff --git a/sapl/parlamentares/urls.py b/sapl/parlamentares/urls.py index 0d8d406da..7b0f8f031 100644 --- a/sapl/parlamentares/urls.py +++ b/sapl/parlamentares/urls.py @@ -1,22 +1,24 @@ from django.conf.urls import include, url -from sapl.parlamentares.views import (altera_field_mesa, - altera_field_mesa_public_view, - CargoMesaCrud, ColigacaoCrud, +from sapl.parlamentares.views import (CargoMesaCrud, ColigacaoCrud, ComposicaoColigacaoCrud, DependenteCrud, FiliacaoCrud, FrenteCrud, FrenteList, - LegislaturaCrud, - insere_parlamentar_composicao, - MandatoCrud, + LegislaturaCrud, MandatoCrud, MesaDiretoraView, NivelInstrucaoCrud, ParlamentarCrud, + ParlamentarMateriasView, ParticipacaoParlamentarCrud, PartidoCrud, ProposicaoParlamentarCrud, RelatoriaParlamentarCrud, - remove_parlamentar_composicao, SessaoLegislativaCrud, TipoAfastamentoCrud, TipoDependenteCrud, - TipoMilitarCrud, VotanteView) + TipoMilitarCrud, VotanteView, + altera_field_mesa, + altera_field_mesa_public_view, + frente_atualiza_lista_parlamentares, + insere_parlamentar_composicao, + parlamentares_frente_selected, + remove_parlamentar_composicao) from .apps import AppConfig @@ -32,11 +34,22 @@ urlpatterns = [ VotanteView.get_urls() )), + url(r'^parlamentar/(?P\d+)/materias$', + ParlamentarMateriasView.as_view(), name='parlamentar_materias'), + url(r'^sistema/coligacao/', include(ColigacaoCrud.get_urls() + ComposicaoColigacaoCrud.get_urls())), + url(r'^sistema/frente/', include(FrenteCrud.get_urls())), + url(r'^sistema/frente/atualiza-lista-parlamentares', + frente_atualiza_lista_parlamentares, + name='atualiza_lista_parlamentares'), + url(r'^sistema/frente/parlamentares-frente-selected', + parlamentares_frente_selected, + name='parlamentares_frente_selected'), + url(r'^sistema/parlamentar/legislatura/', include(LegislaturaCrud.get_urls())), url(r'^sistema/parlamentar/tipo-dependente/', diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index f9dc33f60..309c8b3cd 100644 --- a/sapl/parlamentares/views.py +++ b/sapl/parlamentares/views.py @@ -1,11 +1,13 @@ from django.contrib import messages -from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned +from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist from django.core.urlresolvers import reverse, reverse_lazy from django.db.models import F, Q from django.http import JsonResponse from django.http.response import HttpResponseRedirect +from django.templatetags.static import static from django.utils.datastructures import MultiValueDictKeyError from django.utils.translation import ugettext_lazy as _ +from django.views.decorators.clickjacking import xframe_options_exempt from django.views.generic import FormView from sapl.comissoes.models import Participacao @@ -23,6 +25,15 @@ from .models import (CargoMesa, Coligacao, ComposicaoColigacao, ComposicaoMesa, NivelInstrucao, Parlamentar, Partido, SessaoLegislativa, SituacaoMilitar, TipoAfastamento, TipoDependente, Votante) +from sapl.base.models import Autor +from sapl.materia.models import Autoria +from django.contrib.contenttypes.models import ContentType +from django.db.models.aggregates import Count + +import datetime +import json + + CargoMesaCrud = CrudAux.build(CargoMesa, 'cargo_mesa') PartidoCrud = CrudAux.build(Partido, 'partidos') SessaoLegislativaCrud = CrudAux.build(SessaoLegislativa, 'sessao_legislativa') @@ -31,9 +42,6 @@ NivelInstrucaoCrud = CrudAux.build(NivelInstrucao, 'nivel_instrucao') TipoAfastamentoCrud = CrudAux.build(TipoAfastamento, 'tipo_afastamento') TipoMilitarCrud = CrudAux.build(SituacaoMilitar, 'tipo_situa_militar') -FrenteCrud = CrudAux.build(Frente, 'tipo_situa_militar', list_field_names=[ - 'nome', 'data_criacao', 'parlamentares']) - DependenteCrud = MasterDetailCrud.build( Dependente, 'parlamentar', 'dependente') @@ -110,8 +118,6 @@ class ProposicaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView): def get_context_data(self, **kwargs): context = CrudBaseForListAndDetailExternalAppView\ .ListView.get_context_data(self, **kwargs) - context['title'] = context['title'].replace( - 'Proposições', 'Matérias') return context def get_queryset(self): @@ -182,10 +188,133 @@ class ColigacaoCrud(CrudAux): class ListView(CrudAux.ListView): ordering = ('-numero_votos', 'nome') + def get_context_data(self, **kwargs): + context = super(ColigacaoCrud.ListView, self).get_context_data(kwargs=kwargs) + rows = context['rows'] + coluna_votos_recebidos = 2 + for row in rows: + if not row[coluna_votos_recebidos][0]: + row[coluna_votos_recebidos] = ('0', None) + + return context + + class DetailView(CrudAux.DetailView): + + def get_context_data(self, **kwargs): + context = super(ColigacaoCrud.DetailView, self).get_context_data(kwargs=kwargs) + coligacao = context['coligacao'] + if not coligacao.numero_votos: + coligacao.numero_votos = '0' + + return context + class BaseMixin(CrudAux.BaseMixin): subnav_template_name = 'parlamentares/subnav_coligacao.yaml' +def json_date_convert(date): + ''' + :param date: recebe a data de uma chamada ajax no formato de + string "dd/mm/yyyy" + :return: + ''' + dia, mes, ano = date.split('/') + return datetime.date(day=int(dia), + month=int(mes), + year=int(ano)) + + +def parlamentares_ativos(data_inicio, data_fim=None): + ''' + :param data_inicio: define a data de inicial do período desejado + :param data_fim: define a data final do período desejado + :return: queryset dos parlamentares ativos naquele período + ''' + mandatos_ativos = Mandato.objects.filter(Q( + data_inicio_mandato__lte=data_inicio, + data_fim_mandato__isnull=True) | Q( + data_inicio_mandato__lte=data_inicio, + data_fim_mandato__gte=data_inicio)) + if data_fim: + mandatos_ativos = mandatos_ativos | Mandato.objects.filter( + data_inicio_mandato__gte=data_inicio, + data_inicio_mandato__lte=data_fim) + else: + mandatos_ativos = mandatos_ativos | Mandato.objects.filter( + data_inicio_mandato__gte=data_inicio) + + parlamentares_id = mandatos_ativos.values_list( + 'parlamentar_id', + flat=True).distinct('parlamentar_id') + + return Parlamentar.objects.filter(id__in=parlamentares_id) + + +def frente_atualiza_lista_parlamentares(request): + ''' + :param request: recebe os parâmetros do GET da chamada Ajax + :return: retorna a lista atualizada dos parlamentares + ''' + ativos = json.loads(request.GET['ativos']) + + parlamentares = Parlamentar.objects.all() + + if ativos: + if 'data_criacao' in request.GET and request.GET['data_criacao']: + data_criacao = json_date_convert(request.GET['data_criacao']) + + if 'data_extincao' in request.GET and request.GET['data_extincao']: + data_extincao = json_date_convert(request.GET['data_extincao']) + parlamentares = parlamentares_ativos(data_criacao, + data_extincao) + else: + parlamentares = parlamentares_ativos(data_criacao) + + parlamentares_list = [(p.id, p.__str__()) for p in parlamentares] + + return JsonResponse({'parlamentares_list': parlamentares_list}) + + +def parlamentares_frente_selected(request): + ''' + :return: Lista com o id dos parlamentares em uma frente + ''' + try: + frente = Frente.objects.get(id=int(request.GET['frente_id'])) + except ObjectDoesNotExist: + lista_parlamentar_id = [] + else: + lista_parlamentar_id = frente.parlamentares.all().values_list( + 'id', flat=True) + return JsonResponse({'id_list': list(lista_parlamentar_id)}) + + +class FrenteCrud(CrudAux): + model = Frente + help_path = 'tabelas_auxiliares#tipo_situa_militar' + list_field_names = ['nome', 'data_criacao', 'parlamentares'] + + class CreateView(CrudAux.CreateView): + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + # Update view é um indicador para o javascript + # de que esta não é uma tela de edição de frente + context['update_view'] = 0 + + return context + + class UpdateView(CrudAux.UpdateView): + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + # Update view é um indicador para o javascript + # de que esta não é uma tela de edição de frente + context['update_view'] = 1 + + return context + + class MandatoCrud(MasterDetailCrud): model = Mandato parent_field = 'parlamentar' @@ -199,6 +328,22 @@ class MandatoCrud(MasterDetailCrud): class ListView(MasterDetailCrud.ListView): ordering = ('-legislatura__numero') + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + rows = context['rows'] + + coluna_coligacao = 2 + coluna_votos_recebidos = 3 + for row in rows: + if not row[coluna_coligacao][0]: + row[coluna_coligacao] = (' ', None) + + if not row[coluna_votos_recebidos][0]: + row[coluna_votos_recebidos] = (' ', None) + + return context + + class CreateView(MasterDetailCrud.CreateView): form_class = MandatoForm @@ -242,6 +387,22 @@ class LegislaturaCrud(CrudAux): class UpdateView(CrudAux.UpdateView): form_class = LegislaturaUpdateForm + class DetailView(CrudAux.DetailView): + def has_permission(self): + return True + + @xframe_options_exempt + def get(self, request, *args, **kwargs): + return super().get(request, *args, **kwargs) + + class ListView(CrudAux.ListView): + def has_permission(self): + return True + + @xframe_options_exempt + def get(self, request, *args, **kwargs): + return super().get(request, *args, **kwargs) + class FiliacaoCrud(MasterDetailCrud): model = Filiacao @@ -275,9 +436,18 @@ class ParlamentarCrud(Crud): class DetailView(Crud.DetailView): def get_template_names(self): - return ['crud/detail.html']\ - if self.request.user.has_perm(self.permission(RP_CHANGE))\ - else ['parlamentares/parlamentar_perfil_publico.html'] + if self.request.user.has_perm(self.permission(RP_CHANGE)): + if 'iframe' not in self.request.GET: + if not self.request.session.get('iframe'): + return ['crud/detail.html'] + elif self.request.GET['iframe'] == '0': + return ['crud/detail.html'] + + return ['parlamentares/parlamentar_perfil_publico.html'] + + @xframe_options_exempt + def get(self, request, *args, **kwargs): + return super().get(request, *args, **kwargs) class UpdateView(Crud.UpdateView): form_class = ParlamentarForm @@ -302,6 +472,10 @@ class ParlamentarCrud(Crud): template_name = "parlamentares/parlamentares_list.html" paginate_by = None + @xframe_options_exempt + def get(self, request, *args, **kwargs): + return super().get(request, *args, **kwargs) + def take_legislatura_id(self): try: return int(self.request.GET['pk']) @@ -345,6 +519,14 @@ class ParlamentarCrud(Crud): # Tira Link do avatar_html e coloca no nome for row in context['rows']: + + # preenche coluna foto, se vazia + if not row[0][0]: + img = "
    " \ + % static('img/avatar.png') + row[0] = (img, row[0][1]) + # Coloca a filiação atual ao invés da última if row[0][1]: # Pega o Parlamentar por meio da pk @@ -387,15 +569,87 @@ class ParlamentarCrud(Crud): return context +class ParlamentarMateriasView(FormView): + template_name = "parlamentares/materias.html" + success_url = reverse_lazy('sapl.parlamentares:parlamentar_materia') + + def get_autoria(self, resultset): + autoria = {} + total_autoria = 0 + + for i in resultset: + row = autoria.get(i['materia__ano'], []) + columns = (i['materia__tipo__pk'], + i['materia__tipo__sigla'], + i['materia__tipo__descricao'], + int(i['total'])) + row.append(columns) + autoria[i['materia__ano']] = row + total_autoria += columns[3] + autoria = sorted(autoria.items(), reverse=True) + return autoria, total_autoria + + @xframe_options_exempt + def get(self, request, *args, **kwargs): + parlamentar_pk = kwargs['pk'] + + try: + autor = Autor.objects.get( + content_type=ContentType.objects.get_for_model(Parlamentar), + object_id=parlamentar_pk) + except ObjectDoesNotExist: + mensagem = _('Este Parlamentar não é autor de matéria.') + messages.add_message(request, messages.ERROR, mensagem) + return HttpResponseRedirect( + reverse( + 'sapl.parlamentares:parlamentar_detail', + kwargs={'pk': parlamentar_pk})) + + autoria = Autoria.objects.filter( + autor=autor, primeiro_autor=True).values( + 'materia__ano', + 'materia__tipo__pk', + 'materia__tipo__sigla', + 'materia__tipo__descricao').annotate( + total=Count('materia__tipo__pk')).order_by( + '-materia__ano', 'materia__tipo') + + coautoria = Autoria.objects.filter( + autor=autor, primeiro_autor=False).values( + 'materia__ano', + 'materia__tipo__pk', + 'materia__tipo__sigla', + 'materia__tipo__descricao').annotate( + total=Count('materia__tipo__pk')).order_by( + '-materia__ano', 'materia__tipo') + + autor_list = self.get_autoria(autoria) + coautor_list = self.get_autoria(coautoria) + + parlamentar_pk = autor.autor_related.pk + nome_parlamentar = autor.autor_related.nome_parlamentar + + return self.render_to_response({'autor_pk': autor.pk, + 'root_pk': parlamentar_pk, + 'autoria': autor_list, + 'coautoria': coautor_list, + 'nome_parlamentar': nome_parlamentar + }) + + class MesaDiretoraView(FormView): template_name = 'parlamentares/composicaomesa_form.html' success_url = reverse_lazy('sapl.parlamentares:mesa_diretora') def get_template_names(self): - return ['parlamentares/composicaomesa_form.html']\ - if self.request.user.has_perm( - 'parlamentares.change_composicaomesa')\ - else ['parlamentares/public_composicaomesa_form.html'] + if self.request.user.has_perm('parlamentares.change_composicaomesa'): + if 'iframe' not in self.request.GET: + if not self.request.session.get('iframe'): + return 'parlamentares/composicaomesa_form.html' + elif self.request.GET['iframe'] == '0': + return 'parlamentares/composicaomesa_form.html' + + return 'parlamentares/public_composicaomesa_form.html' # Essa função avisa quando se pode compor uma Mesa Legislativa def validation(self, request): @@ -410,6 +664,7 @@ class MesaDiretoraView(FormView): 'legislatura_selecionada': Legislatura.objects.last(), 'cargos_vagos': CargoMesa.objects.all()}) + @xframe_options_exempt def get(self, request, *args, **kwargs): if (not Legislatura.objects.exists() or diff --git a/sapl/protocoloadm/forms.py b/sapl/protocoloadm/forms.py index d8f996fe3..e513d4b1b 100644 --- a/sapl/protocoloadm/forms.py +++ b/sapl/protocoloadm/forms.py @@ -20,8 +20,8 @@ from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo, Protocolo, TipoDocumentoAdministrativo, TramitacaoAdministrativo) -TIPOS_PROTOCOLO = [('0', 'Enviado'), ('1', 'Recebido'), ('', 'Ambos')] -TIPOS_PROTOCOLO_CREATE = [('0', 'Enviado'), ('1', 'Recebido')] +TIPOS_PROTOCOLO = [('0', 'Recebido'), ('1', 'Enviado'), ('', 'Ambos')] +TIPOS_PROTOCOLO_CREATE = [('0', 'Recebido'), ('1', 'Enviado')] NATUREZA_PROCESSO = [('', 'Ambos'), ('0', 'Administrativo'), @@ -47,7 +47,7 @@ class ProtocoloFilterSet(django_filters.FilterSet): }} ano = django_filters.ChoiceFilter(required=False, - label=u'Ano', + label='Ano', choices=ANO_CHOICES) assunto_ementa = django_filters.CharFilter(lookup_expr='icontains') @@ -135,11 +135,11 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet): }} ano = django_filters.ChoiceFilter(required=False, - label=u'Ano', + label='Ano', choices=ANO_CHOICES) tramitacao = django_filters.ChoiceFilter(required=False, - label=u'Em Tramitação?', + label='Em Tramitação?', choices=EM_TRAMITACAO) assunto = django_filters.CharFilter(lookup_expr='icontains') @@ -213,6 +213,8 @@ class AnularProcoloAdmForm(ModelForm): widget=forms.Textarea) def clean(self): + super(AnularProcoloAdmForm, self).clean() + cleaned_data = super(AnularProcoloAdmForm, self).clean() numero = cleaned_data.get("numero") @@ -409,34 +411,6 @@ class DocumentoAcessorioAdministrativoForm(ModelForm): 'data': forms.DateInput(format='%d/%m/%Y') } - def __init__(self, excluir=False, *args, **kwargs): - - row1 = to_row( - [('tipo', 4), - ('nome', 4), - ('data', 4)]) - row2 = to_row( - [('autor', 12)]) - row3 = to_row( - [('arquivo', 12)]) - row4 = to_row( - [('assunto', 12)]) - - more = [] - if excluir: - more = [Submit('Excluir', 'Excluir')] - - self.helper = FormHelper() - self.helper.layout = Layout( - Fieldset( - _('Incluir Documento Acessório'), - row1, row2, row3, row4, - form_actions(more=more) - ) - ) - super(DocumentoAcessorioAdministrativoForm, self).__init__( - *args, **kwargs) - class TramitacaoAdmForm(ModelForm): @@ -458,6 +432,8 @@ class TramitacaoAdmForm(ModelForm): } def clean(self): + super(TramitacaoAdmForm, self).clean() + data_enc_form = self.cleaned_data['data_encaminhamento'] data_prazo_form = self.cleaned_data['data_fim_prazo'] data_tram_form = self.cleaned_data['data_tramitacao'] @@ -530,6 +506,8 @@ class TramitacaoAdmEditForm(TramitacaoAdmForm): } def clean(self): + super(TramitacaoAdmEditForm, self).clean() + local = self.instance.unidade_tramitacao_local data_tram = self.instance.data_tramitacao @@ -570,6 +548,8 @@ class DocumentoAdministrativoForm(ModelForm): widgets = {'protocolo': forms.HiddenInput()} def clean(self): + super(DocumentoAdministrativoForm, self).clean() + numero_protocolo = self.data['numero_protocolo'] ano_protocolo = self.data['ano_protocolo'] diff --git a/sapl/protocoloadm/urls.py b/sapl/protocoloadm/urls.py index 0c22d74d1..46fec9980 100644 --- a/sapl/protocoloadm/urls.py +++ b/sapl/protocoloadm/urls.py @@ -3,10 +3,7 @@ from django.conf.urls import include, url from sapl.protocoloadm.views import (AnularProtocoloAdmView, ComprovanteProtocoloView, CriarDocumentoProtocolo, - DetailDocumentoAdministrativo, DocumentoAcessorioAdministrativoCrud, - DocumentoAcessorioAdministrativoEditView, - DocumentoAcessorioAdministrativoView, DocumentoAdministrativoCrud, PesquisarDocumentoAdministrativoView, ProtocoloDocumentoView, @@ -26,20 +23,12 @@ app_name = AppConfig.name urlpatterns_documento_administrativo = [ url(r'^docadm/', - include(DocumentoAdministrativoCrud.get_urls())), - url(r'^docadm/doc-acessorio/', - include(DocumentoAcessorioAdministrativoCrud.get_urls())), - url(r'^docadm/tramitacao-doc-adm/', - include(TramitacaoAdmCrud.get_urls())), + include(DocumentoAdministrativoCrud.get_urls() + + TramitacaoAdmCrud.get_urls() + + DocumentoAcessorioAdministrativoCrud.get_urls())), + url(r'^docadm/pesq-doc-adm', PesquisarDocumentoAdministrativoView.as_view(), name='pesq_doc_adm'), - url(r'^docadm/doc-adm/(?P\d+)$', - DetailDocumentoAdministrativo.as_view(), name='detail_doc_adm'), - url(r'^docadm/doc-ace-adm/(?P\d+)', - DocumentoAcessorioAdministrativoView.as_view(), name='doc_ace_adm'), - url(r'^docadm/doc-ace-adm/edit/(?P\d+)/(?P\d+)', - DocumentoAcessorioAdministrativoEditView.as_view(), - name='doc_ace_adm_edit'), url(r'^docadm/texto_integral/(?P\d+)$', doc_texto_integral, name='doc_texto_integral'), diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index 9505f3bd9..c478b6f62 100644 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -37,10 +37,6 @@ TipoDocumentoAdministrativoCrud = CrudAux.build( # ProtocoloMateriaCrud = Crud.build(Protocolo, '') -DocumentoAcessorioAdministrativoCrud = Crud.build( - DocumentoAcessorioAdministrativo, '') - - def doc_texto_integral(request, pk): can_see = True @@ -87,9 +83,18 @@ class DocumentoAdministrativoCrud(Crud): class BaseMixin(Crud.BaseMixin): list_field_names = ['tipo', 'numero', 'ano', 'data', - 'numero_protocolo', 'ano_protocolo', 'assunto', + 'numero_protocolo', 'assunto', 'interessado', 'tramitacao', 'texto_integral'] + @property + def search_url(self): + namespace = self.model._meta.app_config.name + return reverse('%s:%s' % (namespace, 'pesq_doc_adm')) + + @property + def list_url(self): + return '' + class ListView(DocumentoAdministrativoMixin, Crud.ListView): pass @@ -116,6 +121,10 @@ class DocumentoAdministrativoCrud(Crud): kwargs={'pk': self.object.pk})) return context + class DeleteView(DocumentoAdministrativoMixin, Crud.DeleteView): + def get_success_url(self): + return reverse('sapl.protocoloadm:pesq_doc_adm', kwargs={}) + class StatusTramitacaoAdministrativoCrud(CrudAux): model = StatusTramitacaoAdministrativo @@ -416,6 +425,7 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView): '%H:%M') protocolo.timestamp = datetime.strptime( datetime.now().strftime("%Y-%m-%d %H:%M"), "%Y-%m-%d %H:%M") + protocolo.tipo_protocolo = 0 protocolo.tipo_processo = '1' # TODO validar o significado protocolo.anulado = False @@ -425,6 +435,7 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView): id=self.request.POST['tipo_materia']) protocolo.numero_paginas = self.request.POST['numero_paginas'] protocolo.observacao = self.request.POST['observacao'] + protocolo.assunto_ementa = self.request.POST['assunto_ementa'] protocolo.save() return redirect(self.get_success_url(protocolo)) @@ -508,159 +519,55 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin, return self.render_to_response(context) -class DetailDocumentoAdministrativo(PermissionRequiredMixin, DetailView): - template_name = "protocoloadm/detail_doc_adm.html" - permission_required = ('protocoloadm.detail_documentoadministrativo', ) - - def get(self, request, *args, **kwargs): - documento = DocumentoAdministrativo.objects.get( - id=self.kwargs['pk']) - - form = DocumentoAdministrativoForm( - instance=documento) - return self.render_to_response({ - 'form': form, - 'pk': kwargs['pk']}) - - def post(self, request, *args, **kwargs): - if 'Salvar' in request.POST: - form = DocumentoAdministrativoForm(request.POST) - - if form.is_valid(): - doc = form.save(commit=False) - if 'texto_integral' in request.FILES: - doc.texto_integral = request.FILES['texto_integral'] - doc.save() - return self.form_valid(form) - else: - return self.render_to_response({'form': form}) - elif 'Excluir' in request.POST: - DocumentoAdministrativo.objects.get( - id=kwargs['pk']).delete() - return HttpResponseRedirect(self.get_success_delete()) - - return HttpResponseRedirect(self.get_success_url()) - - def get_success_delete(self): - return reverse('sapl.protocoloadm:pesq_doc_adm') - - def get_success_url(self): - return reverse('sapl.protocoloadm:detail_doc_adm', kwargs={ - 'pk': self.kwargs['pk']}) - - -class DocumentoAcessorioAdministrativoEditView(PermissionRequiredMixin, - FormView): - template_name = "protocoloadm/documento_acessorio_administrativo_edit.html" - permission_required = ( - 'protocoloadm.change_documentoacessorioadministrativo', ) +class TramitacaoAdmCrud(MasterDetailCrud): + model = TramitacaoAdministrativo + parent_field = 'documento' + help_path = '' - def get(self, request, *args, **kwargs): - doc = DocumentoAdministrativo.objects.get( - id=kwargs['pk']) - doc_ace = DocumentoAcessorioAdministrativo.objects.get( - id=kwargs['ano']) - form = DocumentoAcessorioAdministrativoForm(instance=doc_ace, - excluir=True) - - return self.render_to_response({'pk': self.kwargs['pk'], - 'doc': doc, - 'doc_ace': doc_ace, - 'form': form}) - - def post(self, request, *args, **kwargs): - form = DocumentoAcessorioAdministrativoForm(request.POST, excluir=True) - doc_ace = DocumentoAcessorioAdministrativo.objects.get( - id=kwargs['ano']) - - if form.is_valid(): - if 'Salvar' in request.POST: - if 'arquivo' in request.FILES: - doc_ace.arquivo = request.FILES['arquivo'] - doc_ace.documento = DocumentoAdministrativo.objects.get( - id=kwargs['pk']) - doc_ace.tipo = TipoDocumentoAdministrativo.objects.get( - id=form.data['tipo']) - doc_ace.nome = form.data['nome'] - doc_ace.autor = form.data['autor'] - doc_ace.data = datetime.strptime( - form.data['data'], '%d/%m/%Y') - doc_ace.assunto = form.data['assunto'] - - doc_ace.save() - elif 'Excluir' in request.POST: - doc_ace.delete() - return self.form_valid(form) - else: - return self.form_invalid(form) + class BaseMixin(MasterDetailCrud.BaseMixin): + list_field_names = ['data_tramitacao', 'unidade_tramitacao_local', + 'unidade_tramitacao_destino', 'status'] - def get_success_url(self): - pk = self.kwargs['pk'] - return reverse('sapl.protocoloadm:doc_ace_adm', kwargs={'pk': pk}) + class CreateView(MasterDetailCrud.CreateView): + form_class = TramitacaoAdmForm + class UpdateView(MasterDetailCrud.UpdateView): + form_class = TramitacaoAdmEditForm -class DocumentoAcessorioAdministrativoView(PermissionRequiredMixin, FormView): - template_name = "protocoloadm/documento_acessorio_administrativo.html" - permission_required = ( - 'protocoloadm.add_documentoacessorioadministrativo', ) + class ListView(DocumentoAdministrativoMixin, MasterDetailCrud.ListView): - def get(self, request, *args, **kwargs): - form = DocumentoAcessorioAdministrativoForm() - doc = DocumentoAdministrativo.objects.get( - id=kwargs['pk']) - doc_ace_null = '' - doc_acessorio = DocumentoAcessorioAdministrativo.objects.filter( - documento_id=kwargs['pk']) - if not doc_acessorio: - doc_ace_null = _('Nenhum documento acessório' + - 'cadastrado para este processo.') - - return self.render_to_response({'pk': kwargs['pk'], - 'doc': doc, - 'doc_ace': doc_acessorio, - 'doc_ace_null': doc_ace_null, - 'form': form}) - - def post(self, request, *args, **kwargs): - form = DocumentoAcessorioAdministrativoForm(request.POST) - if form.is_valid(): - doc_ace = form.save(commit=False) - if 'arquivo' in request.FILES: - doc_ace.arquivo = request.FILES['arquivo'] - doc = DocumentoAdministrativo.objects.get( - id=kwargs['pk']) - doc_ace.documento = doc - doc_ace.save() - return self.form_valid(form) - else: - return self.form_invalid(form) + def get_queryset(self): + qs = super(MasterDetailCrud.ListView, self).get_queryset() + kwargs = {self.crud.parent_field: self.kwargs['pk']} + return qs.filter(**kwargs).order_by('-data_tramitacao', '-id') - def get_success_url(self): - pk = self.kwargs['pk'] - return reverse('sapl.protocoloadm:doc_ace_adm', kwargs={'pk': pk}) + class DetailView(DocumentoAdministrativoMixin, + MasterDetailCrud.DetailView): + pass -class TramitacaoAdmCrud(MasterDetailCrud): - model = TramitacaoAdministrativo +class DocumentoAcessorioAdministrativoCrud(MasterDetailCrud): + model = DocumentoAcessorioAdministrativo parent_field = 'documento' help_path = '' class BaseMixin(MasterDetailCrud.BaseMixin): - list_field_names = ['data_tramitacao', 'unidade_tramitacao_local', - 'unidade_tramitacao_destino', 'status'] + list_field_names = ['nome', 'tipo', + 'data', 'autor', + 'assunto'] class CreateView(MasterDetailCrud.CreateView): - form_class = TramitacaoAdmForm + form_class = DocumentoAcessorioAdministrativoForm class UpdateView(MasterDetailCrud.UpdateView): - form_class = TramitacaoAdmEditForm + form_class = DocumentoAcessorioAdministrativoForm class ListView(DocumentoAdministrativoMixin, MasterDetailCrud.ListView): def get_queryset(self): qs = super(MasterDetailCrud.ListView, self).get_queryset() kwargs = {self.crud.parent_field: self.kwargs['pk']} - return qs.filter(**kwargs).order_by('-data_tramitacao', '-id') + return qs.filter(**kwargs).order_by('-data', '-id') class DetailView(DocumentoAdministrativoMixin, MasterDetailCrud.DetailView): diff --git a/sapl/redireciona_urls/__init__.py b/sapl/redireciona_urls/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sapl/redireciona_urls/apps.py b/sapl/redireciona_urls/apps.py new file mode 100644 index 000000000..8ee8742bc --- /dev/null +++ b/sapl/redireciona_urls/apps.py @@ -0,0 +1,8 @@ +from django import apps +from django.utils.translation import ugettext_lazy as _ + + +class AppConfig(apps.AppConfig): + name = 'sapl.redireciona_urls' + label = 'redireciona_urls' + verbose_name = _('Redirecionador de URLs') diff --git a/sapl/redireciona_urls/exceptions.py b/sapl/redireciona_urls/exceptions.py new file mode 100644 index 000000000..e86aaefbb --- /dev/null +++ b/sapl/redireciona_urls/exceptions.py @@ -0,0 +1,13 @@ +from django.utils.translation import ugettext as _ + + +class UnknownUrlNameError(Exception): + + def __init__(self, url_name): + self.url_name = url_name + + def __str__(self): + return repr( + _("Funcionalidade") + + " '%s' " % (self.url_name) + + _("pode ter sido removida ou movida para outra url.")) diff --git a/sapl/redireciona_urls/tests.py b/sapl/redireciona_urls/tests.py new file mode 100644 index 000000000..f9ffa02ef --- /dev/null +++ b/sapl/redireciona_urls/tests.py @@ -0,0 +1,710 @@ +from django.core.urlresolvers import reverse +from django.test import TestCase + +MovedPermanentlyHTTPStatusCode = 301 +EMPTY_STRING = '' + + +class RedirecionaURLsTests(TestCase): + def test_redireciona_index_SAPL(self): + response = self.client.get(reverse( + 'sapl.redireciona_urls:redireciona_sapl_index') + ) + url_e = reverse('sapl_index') + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + +class RedirecionaParlamentarTests(TestCase): + url_pattern = 'sapl.redireciona_urls:redireciona_parlamentar' + + def test_redireciona_parlamentar_list(self): + url = reverse(self.url_pattern) + url_e = reverse('sapl.parlamentares:parlamentar_list') + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + def test_redireciona_parlamentar_list_por_legislatura(self): + numero_legislatura = 123 + + url = reverse(self.url_pattern) + url_e = reverse('sapl.parlamentares:parlamentar_list') + + url = "%s%s" % ( + url, + "?hdn_num_legislatura=%s" % (numero_legislatura) + ) + url_e = "%s%s" % (url_e, "?pk=%s" % numero_legislatura) + + response = self.client.get(url) + + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + def test_redireciona_parlamentar_detail(self): + url = reverse(self.url_pattern) + pk_parlamentar = 21 + url = "%s%s" % (url, "?cod_parlamentar=%s" % (pk_parlamentar)) + url_e = reverse( + 'sapl.parlamentares:parlamentar_detail', + kwargs={'pk': pk_parlamentar} + ) + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + +class RedirecionaComissaoTests(TestCase): + url_pattern = 'sapl.redireciona_urls:redireciona_comissao' + + def test_redireciona_comissao_detail(self): + url = reverse(self.url_pattern) + pk_comissao = 21 + url = "%s%s" % (url, "?cod_comissao=%s" % (pk_comissao)) + url_e = reverse( + 'sapl.comissoes:comissao_detail', + kwargs={'pk': pk_comissao} + ) + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + def test_redireciona_comissao_list(self): + url = reverse(self.url_pattern) + url_e = reverse( + 'sapl.comissoes:comissao_list') + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + +class RedirecionaPautaSessaoTests(TestCase): + url_pattern = 'sapl.redireciona_urls:redireciona_pauta_sessao_' + + def test_redireciona_pauta_sessao_detail(self): + url = reverse(self.url_pattern) + pk_pauta_sessao = 21 + url = "%s%s" % (url, "?cod_sessao_plen=%s" % (pk_pauta_sessao)) + url_e = reverse( + 'sapl.sessao:pauta_sessao_detail', + kwargs={'pk': pk_pauta_sessao} + ) + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + def test_redireciona_pauta_sessao_list(self): + url = reverse(self.url_pattern) + url_e = reverse('sapl.sessao:pesquisar_pauta') + + response = self.client.get(url) + + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + def test_redireciona_pauta_sessao_list_por_dat_sessao_sel(self): + + url = reverse(self.url_pattern) + + ano_s_p = "2016" + mes_s_p = "05" + dia_s_p = "14" + data_s_p = "%s/%s/%s" % (dia_s_p, mes_s_p, ano_s_p) + + url = "%s%s" % (url, "?dat_sessao_sel=%s" % data_s_p) + + url_e = reverse('sapl.sessao:pesquisar_pauta') + + args_e = EMPTY_STRING + args_e += "?data_inicio__year=%s" % (ano_s_p) + args_e += "&data_inicio__month=%s" % (mes_s_p.lstrip("0")) + args_e += "&data_inicio__day=%s" % (dia_s_p.lstrip("0")) + args_e += "&tipo=&salvar=Pesquisar" + + url_e = "%s%s" % (url_e, args_e) + + response = self.client.get(url) + + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + +class RedirecionaMesaDiretoraTests(TestCase): + url_pattern = 'sapl.redireciona_urls:redireciona_mesa_diretora' + + def test_redireciona_mesa_diretora(self): + url = reverse(self.url_pattern) + url_e = reverse('sapl.parlamentares:mesa_diretora') + + response = self.client.get(url) + + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + +class RedirecionaMesaDiretoraParlamentarTests(TestCase): + url_pattern = 'sapl.redireciona_urls:redireciona_mesa_diretora_parlamentar' + + def test_redireciona_mesa_diretora_parlamentar(self): + url = reverse(self.url_pattern) + pk_parlamentar = 21 + url = "%s%s" % (url, "?cod_parlamentar=%s" % (pk_parlamentar)) + url_e = reverse( + 'sapl.parlamentares:parlamentar_detail', + kwargs={'pk': pk_parlamentar} + ) + + response = self.client.get(url) + + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + +class RedirecionaNormasJuridicasListTests(TestCase): + url_pattern = 'sapl.redireciona_urls:redireciona_norma_juridica_pesquisa' + + def test_redireciona_norma_juridica_pesquisa_sem_parametros(self): + url = reverse(self.url_pattern) + url_e = reverse('sapl.norma:norma_pesquisa') + + tipo_norma = EMPTY_STRING + numero_norma = EMPTY_STRING + ano_norma = EMPTY_STRING + periodo_inicial_aprovacao = EMPTY_STRING + periodo_final_aprovacao = EMPTY_STRING + periodo_inicial_publicacao = EMPTY_STRING + periodo_final_publicacao = EMPTY_STRING + ementa_norma = EMPTY_STRING + assuntos_norma = EMPTY_STRING + + args = EMPTY_STRING + args += "?lst_tip_norma=%s" % (tipo_norma) + args += "&txt_numero=%s" % (numero_norma) + args += "&txt_ano=%s" % (ano_norma) + args += "&dt_norma=%s" % (periodo_inicial_aprovacao) + args += "&dt_norma2=%s" % (periodo_final_aprovacao) + args += "&dt_public=%s" % (periodo_inicial_publicacao) + args += "&dt_public2=%s" % (periodo_final_publicacao) + args += "&txt_assunto=%s" % (ementa_norma) + args += "&lst_assunto_norma=%s" % (assuntos_norma) + args += "&salvar=%s" % ('Pesquisar') + url = "%s%s" % (url, args) + + args_e = EMPTY_STRING + args_e += "?tipo=%s" % (tipo_norma) + args_e += "&numero=%s" % (numero_norma) + args_e += "&ano=%s" % (ano_norma) + args_e += "&data_0=%s" % (periodo_inicial_aprovacao) + args_e += "&data_1=%s" % (periodo_final_aprovacao) + args_e += "&data_publicacao_0=%s" % (periodo_inicial_publicacao) + args_e += "&data_publicacao_1=%s" % (periodo_final_publicacao) + args_e += "&ementa=%s" % (ementa_norma) + args_e += "&assuntos=%s" % (assuntos_norma) + args_e += "&salvar=%s" % ('Pesquisar') + url_e = "%s%s" % (url_e, args_e) + + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + def test_redireciona_norma_juridica_pesquisa_por_tipo(self): + url = reverse(self.url_pattern) + url_e = reverse('sapl.norma:norma_pesquisa') + + tipo_norma = '4' + numero_norma = EMPTY_STRING + ano_norma = EMPTY_STRING + periodo_inicial_aprovacao = EMPTY_STRING + periodo_final_aprovacao = EMPTY_STRING + periodo_inicial_publicacao = EMPTY_STRING + periodo_final_publicacao = EMPTY_STRING + ementa_norma = EMPTY_STRING + assuntos_norma = EMPTY_STRING + + args = EMPTY_STRING + args += "?lst_tip_norma=%s" % (tipo_norma) + args += "&txt_numero=%s" % (numero_norma) + args += "&txt_ano=%s" % (ano_norma) + args += "&dt_norma=%s" % (periodo_inicial_aprovacao) + args += "&dt_norma2=%s" % (periodo_final_aprovacao) + args += "&dt_public=%s" % (periodo_inicial_publicacao) + args += "&dt_public2=%s" % (periodo_final_publicacao) + args += "&txt_assunto=%s" % (ementa_norma) + args += "&lst_assunto_norma=%s" % (assuntos_norma) + args += "&salvar=%s" % ('Pesquisar') + url = "%s%s" % (url, args) + + args_e = EMPTY_STRING + args_e += "?tipo=%s" % (tipo_norma) + args_e += "&numero=%s" % (numero_norma) + args_e += "&ano=%s" % (ano_norma) + args_e += "&data_0=%s" % (periodo_inicial_aprovacao) + args_e += "&data_1=%s" % (periodo_final_aprovacao) + args_e += "&data_publicacao_0=%s" % (periodo_inicial_publicacao) + args_e += "&data_publicacao_1=%s" % (periodo_final_publicacao) + args_e += "&ementa=%s" % (ementa_norma) + args_e += "&assuntos=%s" % (assuntos_norma) + args_e += "&salvar=%s" % ('Pesquisar') + url_e = "%s%s" % (url_e, args_e) + + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + def test_redireciona_norma_juridica_pesquisa_por_ano(self): + url = reverse(self.url_pattern) + url_e = reverse('sapl.norma:norma_pesquisa') + + tipo_norma = EMPTY_STRING + numero_norma = EMPTY_STRING + ano_norma = '2010' + periodo_inicial_aprovacao = EMPTY_STRING + periodo_final_aprovacao = EMPTY_STRING + periodo_inicial_publicacao = EMPTY_STRING + periodo_final_publicacao = EMPTY_STRING + ementa_norma = EMPTY_STRING + assuntos_norma = EMPTY_STRING + + args = EMPTY_STRING + args += "?lst_tip_norma=%s" % (tipo_norma) + args += "&txt_numero=%s" % (numero_norma) + args += "&txt_ano=%s" % (ano_norma) + args += "&dt_norma=%s" % (periodo_inicial_aprovacao) + args += "&dt_norma2=%s" % (periodo_final_aprovacao) + args += "&dt_public=%s" % (periodo_inicial_publicacao) + args += "&dt_public2=%s" % (periodo_final_publicacao) + args += "&txt_assunto=%s" % (ementa_norma) + args += "&lst_assunto_norma=%s" % (assuntos_norma) + args += "&salvar=%s" % ('Pesquisar') + url = "%s%s" % (url, args) + + args_e = EMPTY_STRING + args_e += "?tipo=%s" % (tipo_norma) + args_e += "&numero=%s" % (numero_norma) + args_e += "&ano=%s" % (ano_norma) + args_e += "&data_0=%s" % (periodo_inicial_aprovacao) + args_e += "&data_1=%s" % (periodo_final_aprovacao) + args_e += "&data_publicacao_0=%s" % (periodo_inicial_publicacao) + args_e += "&data_publicacao_1=%s" % (periodo_final_publicacao) + args_e += "&ementa=%s" % (ementa_norma) + args_e += "&assuntos=%s" % (assuntos_norma) + args_e += "&salvar=%s" % ('Pesquisar') + url_e = "%s%s" % (url_e, args_e) + + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + +class RedirecionaNormasJuridicasDetailTests(TestCase): + url_pattern = 'sapl.redireciona_urls:redireciona_norma_juridica_detail' + + def test_redireciona_norma_juridica_detail(self): + url = reverse(self.url_pattern) + + pk_norma = 120 + + args = EMPTY_STRING + args += "?cod_norma=%s" % (pk_norma) + url = "%s%s" % (url, args) + + url_e = reverse( + 'sapl.norma:normajuridica_detail', + kwargs={ + 'pk': pk_norma} + ) + + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + def test_redireciona_norma_juridica_detail_sem_parametros(self): + url = reverse(self.url_pattern) + + pk_norma = EMPTY_STRING + + args = EMPTY_STRING + args += "?cod_norma=%s" % (pk_norma) + url = "%s%s" % (url, args) + + url_e = reverse('sapl.norma:norma_pesquisa') + + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + +class RedirecionaSessaoPlenariaTests(TestCase): + url_pattern = 'sapl.redireciona_urls:redireciona_sessao_plenaria_' + + def test_redireciona_sessao_plenaria_detail(self): + url = reverse(self.url_pattern) + pk_sessao_plenaria = 258 + url = "%s%s" % (url, "?cod_sessao_plen=%s" % (pk_sessao_plenaria)) + url_e = reverse( + 'sapl.sessao:sessaoplenaria_detail', + kwargs={'pk': pk_sessao_plenaria} + ) + + response = self.client.get(url) + + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + def test_redireciona_sessao_plenaria_list_sem_parametro(self): + url = reverse(self.url_pattern) + url_e = reverse('sapl.sessao:pesquisar_sessao') + + year = EMPTY_STRING + month = EMPTY_STRING + day = EMPTY_STRING + tipo_sessao = EMPTY_STRING + + args = EMPTY_STRING + args += "?ano_sessao_sel=%s" % (year) + args += "&mes_sessao_sel=%s" % (month) + args += "&dia_sessao_sel=%s" % (day) + args += "&tip_sessao_sel=%s" % (tipo_sessao) + url = "%s%s" % (url, args) + + # Remove zeros à esquerda + day = day.lstrip("0") + month = month.lstrip("0") + args_e = EMPTY_STRING + args_e += "?data_inicio__year=%s" % (year) + args_e += "&data_inicio__month=%s" % (month) + args_e += "&data_inicio__day=%s" % (day) + args_e += "&tipo=%s&salvar=Pesquisar" % (tipo_sessao) + url_e = "%s%s" % (url_e, args_e) + + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + def test_redireciona_sessao_plenaria_list_sem_tipo(self): + url = reverse(self.url_pattern) + url_e = reverse('sapl.sessao:pesquisar_sessao') + + year = '2015' + month = '04' + day = '06' + tipo_sessao = EMPTY_STRING + + args = EMPTY_STRING + args += "?ano_sessao_sel=%s" % (year) + args += "&mes_sessao_sel=%s" % (month) + args += "&dia_sessao_sel=%s" % (day) + args += "&tip_sessao_sel=%s" % (tipo_sessao) + url = "%s%s" % (url, args) + + # Remove zeros à esquerda + day = day.lstrip("0") + month = month.lstrip("0") + args_e = EMPTY_STRING + args_e += "?data_inicio__year=%s" % (year) + args_e += "&data_inicio__month=%s" % (month) + args_e += "&data_inicio__day=%s" % (day) + args_e += "&tipo=%s&salvar=Pesquisar" % (tipo_sessao) + url_e = "%s%s" % (url_e, args_e) + + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + def test_redireciona_sessao_plenaria_list_sem_tipo_e_ano(self): + url = reverse(self.url_pattern) + url_e = reverse('sapl.sessao:pesquisar_sessao') + + year = EMPTY_STRING + month = '04' + day = '06' + tipo_sessao = EMPTY_STRING + + args = EMPTY_STRING + args += "?ano_sessao_sel=%s" % (year) + args += "&mes_sessao_sel=%s" % (month) + args += "&dia_sessao_sel=%s" % (day) + args += "&tip_sessao_sel=%s" % (tipo_sessao) + url = "%s%s" % (url, args) + + # Remove zeros à esquerda + day = day.lstrip("0") + month = month.lstrip("0") + args_e = EMPTY_STRING + args_e += "?data_inicio__year=%s" % (year) + args_e += "&data_inicio__month=%s" % (month) + args_e += "&data_inicio__day=%s" % (day) + args_e += "&tipo=%s&salvar=Pesquisar" % (tipo_sessao) + url_e = "%s%s" % (url_e, args_e) + + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + def test_redireciona_sessao_plenaria_list_sem_ano(self): + url = reverse(self.url_pattern) + url_e = reverse('sapl.sessao:pesquisar_sessao') + + year = EMPTY_STRING + month = '04' + day = '06' + tipo_sessao = '4' + + args = EMPTY_STRING + args += "?ano_sessao_sel=%s" % (year) + args += "&mes_sessao_sel=%s" % (month) + args += "&dia_sessao_sel=%s" % (day) + args += "&tip_sessao_sel=%s" % (tipo_sessao) + url = "%s%s" % (url, args) + + # Remove zeros à esquerda + day = day.lstrip("0") + month = month.lstrip("0") + args_e = EMPTY_STRING + args_e += "?data_inicio__year=%s" % (year) + args_e += "&data_inicio__month=%s" % (month) + args_e += "&data_inicio__day=%s" % (day) + args_e += "&tipo=%s&salvar=Pesquisar" % (tipo_sessao) + url_e = "%s%s" % (url_e, args_e) + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + def test_redireciona_sessao_plenaria_list_sem_mes_dia(self): + url = reverse(self.url_pattern) + url_e = reverse('sapl.sessao:pesquisar_sessao') + + year = '2015' + month = EMPTY_STRING + day = EMPTY_STRING + tipo_sessao = '4' + + args = EMPTY_STRING + args += "?ano_sessao_sel=%s" % (year) + args += "&mes_sessao_sel=%s" % (month) + args += "&dia_sessao_sel=%s" % (day) + args += "&tip_sessao_sel=%s" % (tipo_sessao) + url = "%s%s" % (url, args) + + # Remove zeros à esquerda + day = day.lstrip("0") + month = month.lstrip("0") + args_e = EMPTY_STRING + args_e += "?data_inicio__year=%s" % (year) + args_e += "&data_inicio__month=%s" % (month) + args_e += "&data_inicio__day=%s" % (day) + args_e += "&tipo=%s&salvar=Pesquisar" % (tipo_sessao) + url_e = "%s%s" % (url_e, args_e) + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + +class RedirecionaHistoricoTramitacoesListTests(TestCase): + url_pattern = 'sapl.redireciona_urls:redireciona_historico_tramitacoes' + + def test_redireciona_historico_tramitacoes_sem_parametros(self): + args_e = EMPTY_STRING + args = EMPTY_STRING + url = reverse(self.url_pattern) + url_e = reverse('sapl.base:historico_tramitacoes') + + inicio_dt_tramitacao = EMPTY_STRING + fim_dt_tramitacao = EMPTY_STRING + tipo_materia = EMPTY_STRING + unidade_local_tramitacao = EMPTY_STRING + status_tramitacao = EMPTY_STRING + + args += "?txt_dat_inicio_periodo=%s" % (inicio_dt_tramitacao) + args += "&txt_dat_fim_periodo=%s" % (fim_dt_tramitacao) + args += "&lst_tip_materia=%s" % (tipo_materia) + args += "&lst_cod_unid_tram_dest=%s" % (unidade_local_tramitacao) + args += "&lst_status=%s" % (status_tramitacao) + args += "&btn_materia_pesquisar=%s" % ('Pesquisar') + url = "%s%s" % (url, args) + + # Remove zeros à esquerda + inicio_dt_tramitacao = inicio_dt_tramitacao.lstrip("0") + fim_dt_tramitacao = fim_dt_tramitacao.lstrip("0") + tipo_materia = tipo_materia.lstrip("0") + unidade_local_tramitacao = unidade_local_tramitacao.lstrip("0") + status_tramitacao = status_tramitacao.lstrip("0") + + if ( + (inicio_dt_tramitacao != EMPTY_STRING) or + (fim_dt_tramitacao != EMPTY_STRING) or + (tipo_materia != EMPTY_STRING) or + (unidade_local_tramitacao != EMPTY_STRING) or + (status_tramitacao != EMPTY_STRING)): + args_e += "?tramitacao__data_tramitacao_0=%s" % ( + inicio_dt_tramitacao) + args_e += "&tramitacao__data_tramitacao_1=%s" % ( + fim_dt_tramitacao) + args_e += "&tipo=%s" % (tipo_materia) + args_e += "&tramitacao__unidade_tramitacao_local=%s" % ( + unidade_local_tramitacao) + args_e += "&tramitacao__status=%s" % (status_tramitacao) + args_e += "&salvar=%s" % ('Pesquisar') + + url_e = "%s%s" % (url_e, args_e) + + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + def test_redireciona_historico_tramitacoes(self): + args = EMPTY_STRING + args_e = EMPTY_STRING + url = reverse(self.url_pattern) + url_e = reverse('sapl.base:historico_tramitacoes') + + inicio_dt_tramitacao = '12/07/2000' + fim_dt_tramitacao = '26/05/2017' + unidade_local_tramitacao = '0' + tipo_materia = '0' + status_tramitacao = '0' + + args += "?txt_dat_inicio_periodo=%s" % (inicio_dt_tramitacao) + args += "&txt_dat_fim_periodo=%s" % (fim_dt_tramitacao) + args += "&lst_tip_materia=%s" % (tipo_materia) + args += "&lst_cod_unid_tram_dest=%s" % (unidade_local_tramitacao) + args += "&lst_status=%s" % (status_tramitacao) + args += "&btn_materia_pesquisar=%s" % ('Pesquisar') + url = "%s%s" % (url, args) + + # Remove zeros à esquerda + inicio_dt_tramitacao = inicio_dt_tramitacao.lstrip("0") + fim_dt_tramitacao = fim_dt_tramitacao.lstrip("0") + tipo_materia = tipo_materia.lstrip("0") + unidade_local_tramitacao = unidade_local_tramitacao.lstrip("0") + status_tramitacao = status_tramitacao.lstrip("0") + + if ( + (inicio_dt_tramitacao != EMPTY_STRING) or + (fim_dt_tramitacao != EMPTY_STRING) or + (tipo_materia != EMPTY_STRING) or + (unidade_local_tramitacao != EMPTY_STRING) or + (status_tramitacao != EMPTY_STRING)): + args_e += "?tramitacao__data_tramitacao_0=%s" % ( + inicio_dt_tramitacao) + args_e += "&tramitacao__data_tramitacao_1=%s" % ( + fim_dt_tramitacao) + args_e += "&tipo=%s" % (tipo_materia) + args_e += "&tramitacao__unidade_tramitacao_local=%s" % ( + unidade_local_tramitacao) + args_e += "&tramitacao__status=%s" % (status_tramitacao) + args_e += "&salvar=%s" % ('Pesquisar') + + url_e = "%s%s" % (url_e, args_e) + + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + +class RedirecionaPresencaParlamentaresTests(TestCase): + url_pattern = 'sapl.redireciona_urls:redireciona_presencaparlamentar_list' + + def test_redireciona_presenca_list_sem_parametros(self): + args_e = EMPTY_STRING + args = EMPTY_STRING + url = reverse(self.url_pattern) + url_e = reverse('sapl.base:presenca_sessao') + + inicio_intervalo_presenca = EMPTY_STRING + fim_intervalo_presenca = EMPTY_STRING + + args += "?txt_dat_inicio=%s" % ( + inicio_intervalo_presenca) + args += "&txt_dat_fim=%s" % ( + fim_intervalo_presenca) + url = "%s%s" % (url, args) + + # Remove zeros à esquerda + inicio_intervalo_presenca = inicio_intervalo_presenca.lstrip("0") + fim_intervalo_presenca = fim_intervalo_presenca.lstrip("0") + + args_e += "?data_inicio_0=%s" % ( + inicio_intervalo_presenca) + args_e += "&data_inicio_1=%s" % ( + fim_intervalo_presenca) + args_e += "&salvar=%s" % ('Pesquisar') + + url_e = "%s%s" % (url_e, args_e) + + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + def test_redireciona_presenca_list(self): + args_e = EMPTY_STRING + args = EMPTY_STRING + url = reverse(self.url_pattern) + url_e = reverse('sapl.base:presenca_sessao') + + inicio_intervalo_presenca = '01/02/2015' + fim_intervalo_presenca = '01/02/2017' + + args += "?txt_dat_inicio=%s" % ( + inicio_intervalo_presenca) + args += "&txt_dat_fim=%s" % ( + fim_intervalo_presenca) + url = "%s%s" % (url, args) + + # Remove zeros à esquerda + inicio_intervalo_presenca = inicio_intervalo_presenca.lstrip("0") + fim_intervalo_presenca = fim_intervalo_presenca.lstrip("0") + + args_e += "?data_inicio_0=%s" % ( + inicio_intervalo_presenca) + args_e += "&data_inicio_1=%s" % ( + fim_intervalo_presenca) + args_e += "&salvar=%s" % ('Pesquisar') + + url_e = "%s%s" % (url_e, args_e) + + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + +class RedirecionaMateriasPorAutorTests(TestCase): + url_pattern = 'sapl.redireciona_urls:redireciona_materias_por_autor_list' + + def test_redireciona_materias_por_autor_list_sem_parametros(self): + url = reverse(self.url_pattern) + url_e = reverse('sapl.base:materia_por_autor') + + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + +class RedirecionaMateriasPorAnoAutorTipoTests(TestCase): + url_pattern = ( + 'sapl.redireciona_urls:redireciona_materia_por_ano_autor_tipo_list') + + def test_redireciona_materias_por_ano_autor_tipo_list_sem_parametros(self): + url = reverse(self.url_pattern) + url_e = reverse('sapl.base:materia_por_ano_autor_tipo') + + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) + + def test_redireciona_materias_por_ano_autor_tipo_list(self): + url = reverse(self.url_pattern) + url_e = reverse('sapl.base:materia_por_ano_autor_tipo') + + ano = 2017 + + args = "?ano=%s" % (ano) + url = "%s%s" % (url, args) + + args_e = "?ano=%s&salvar=Pesquisar" % (ano) + url_e = "%s%s" % (url_e, args_e) + + response = self.client.get(url) + self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode) + self.assertEqual(response.url, url_e) diff --git a/sapl/redireciona_urls/urls.py b/sapl/redireciona_urls/urls.py new file mode 100644 index 000000000..8e8ac7d99 --- /dev/null +++ b/sapl/redireciona_urls/urls.py @@ -0,0 +1,80 @@ +from .apps import AppConfig +from .views import ( + RedirecionaAtasList, + RedirecionaComissao, + RedirecionaHistoricoTramitacoesList, + RedirecionaMateriaLegislativaDetail, + RedirecionaMateriaLegislativaList, + RedirecionaMateriasPorAnoAutorTipo, + RedirecionaMateriasPorAutor, + RedirecionaMesaDiretoraView, + RedirecionaNormasJuridicasDetail, + RedirecionaNormasJuridicasList, + RedirecionaParlamentar, + RedirecionaPautaSessao, + RedirecionaPresencaParlamentares, + RedirecionaRelatoriosList, + RedirecionaRelatoriosMateriasEmTramitacaoList, + RedirecionaSessaoPlenaria, + RedirecionaSAPLIndex) +from django.conf.urls import url + + +app_name = AppConfig.name + +urlpatterns = [ + url(r'^default_index_html$', + RedirecionaSAPLIndex.as_view(), + name='redireciona_sapl_index'), + url(r'^consultas/parlamentar/parlamentar_', + RedirecionaParlamentar.as_view(), + name='redireciona_parlamentar'), + url(r'^consultas/comissao/comissao_', + RedirecionaComissao.as_view(), + name='redireciona_comissao'), + url(r'^consultas/pauta_sessao/pauta_sessao_', + RedirecionaPautaSessao.as_view(), + name='redireciona_pauta_sessao_'), + url(r'^consultas/mesa_diretora/mesa_diretora_index_html', + RedirecionaMesaDiretoraView.as_view(), + name='redireciona_mesa_diretora'), + url(r'^consultas/mesa_diretora/parlamentar/parlamentar_', + RedirecionaParlamentar.as_view(), + name='redireciona_mesa_diretora_parlamentar'), + url(r'^consultas/sessao_plenaria/', + RedirecionaSessaoPlenaria.as_view(), + name='redireciona_sessao_plenaria_'), + url(r'^generico/norma_juridica_pesquisar_', + RedirecionaNormasJuridicasList.as_view(), + name='redireciona_norma_juridica_pesquisa'), + url(r'^consultas/norma_juridica/norma_juridica_mostrar_proc', + RedirecionaNormasJuridicasDetail.as_view(), + name='redireciona_norma_juridica_detail'), + url(r'^relatorios_administrativos/relatorios_administrativos_index_html$', + RedirecionaRelatoriosList.as_view(), + name='redireciona_relatorios_list'), + url(r'tramitacaoMaterias/tramitacaoMaterias', + RedirecionaRelatoriosMateriasEmTramitacaoList.as_view(), + name='redireciona_relatorio_materia_por_tramitacao'), + url(r'tramitacaoMaterias/materia_mostrar_proc$', + RedirecionaMateriaLegislativaDetail.as_view(), + name='redireciona_materialegislativa_detail'), + url(r'^generico/materia_pesquisar_', + RedirecionaMateriaLegislativaList.as_view(), + name='redireciona_materialegislativa_list'), + url(r'historicoTramitacoes/historicoTramitacoes', + RedirecionaHistoricoTramitacoesList.as_view(), + name='redireciona_historico_tramitacoes'), + url(r'atasSessao', + RedirecionaAtasList.as_view(), + name='redireciona_atas_list'), + url(r'presencaSessao', + RedirecionaPresencaParlamentares.as_view(), + name='redireciona_presencaparlamentar_list'), + url(r'resumoPropositurasAutor', + RedirecionaMateriasPorAutor.as_view(), + name='redireciona_materias_por_autor_list'), + url(r'propositurasAnoAutorTipo', + RedirecionaMateriasPorAnoAutorTipo.as_view(), + name='redireciona_materia_por_ano_autor_tipo_list'), +] diff --git a/sapl/redireciona_urls/views.py b/sapl/redireciona_urls/views.py new file mode 100644 index 000000000..8e9d5afbe --- /dev/null +++ b/sapl/redireciona_urls/views.py @@ -0,0 +1,577 @@ +from .exceptions import UnknownUrlNameError +from django.core.urlresolvers import NoReverseMatch, reverse +from django.views.generic import RedirectView +from sapl.base.apps import AppConfig as atasConfig +from sapl.base.apps import AppConfig as presenca_sessaoConfig +from sapl.base.apps import AppConfig as relatoriosConfig +from sapl.comissoes.apps import AppConfig as comissoesConfig +from sapl.materia.apps import AppConfig as materiaConfig +from sapl.norma.apps import AppConfig as normaConfig +from sapl.parlamentares.apps import AppConfig as parlamentaresConfig +from sapl.sessao.apps import AppConfig as sessaoConfig + +EMPTY_STRING = '' + +app_parlamentares = parlamentaresConfig.name +app_atas = atasConfig.name +app_presenca_sessao = presenca_sessaoConfig.name +app_comissoes = comissoesConfig.name +app_materia = materiaConfig.name +app_sessao = sessaoConfig.name +app_norma = normaConfig.name +app_relatorios = relatoriosConfig.name + +pesquisar_atas = (app_atas + ':atas') +presenca_sessao = (app_presenca_sessao + ':presenca_sessao') +parlamentar_list = (app_parlamentares + ':parlamentar_list') +parlamentar_detail = (app_parlamentares + ':parlamentar_detail') +parlamentar_mesa_diretora = (app_parlamentares + ':mesa_diretora') + +comissao_list = (app_comissoes + ':comissao_list') +comissao_detail = (app_comissoes + ':comissao_detail') + + +materialegislativa_detail = (app_materia + ':materialegislativa_detail') +materialegislativa_list = (app_materia + ':pesquisar_materia') + +pauta_sessao_list = (app_sessao + ':pesquisar_pauta') +pauta_sessao_detail = (app_sessao + ':pauta_sessao_detail') +sessao_plenaria_list = (app_sessao + ':pesquisar_sessao') +sessao_plenaria_detail = (app_sessao + ':sessaoplenaria_detail') + +norma_juridica_detail = (app_norma + ':normajuridica_detail') +norma_juridica_pesquisa = (app_norma + ':norma_pesquisa') + +relatorios_list = (app_relatorios + ':relatorios_list') +relatorio_materia_por_tramitacao = (app_relatorios + ':materia_por_tramitacao') +relatorio_materia_por_autor = (app_relatorios + ':materia_por_autor') +relatorio_materia_por_ano_autor_tipo = ( + app_relatorios + ':materia_por_ano_autor_tipo') +historico_tramitacoes = (app_relatorios + ':historico_tramitacoes') + + +class RedirecionaSAPLIndex(RedirectView): + permanent = True + + def get_redirect_url(self): + url_pattern = 'sapl_index' + try: + url = reverse(url_pattern) + except NoReverseMatch: + raise UnknownUrlNameError(url_pattern) + return url + + +class RedirecionaParlamentar(RedirectView): + permanent = True + + def get_redirect_url(self): + url = EMPTY_STRING + pk_parlamentar = self.request.GET.get( + 'cod_parlamentar', + EMPTY_STRING) + + if pk_parlamentar: + try: + kwargs = {'pk': pk_parlamentar} + url = reverse(parlamentar_detail, kwargs=kwargs) + except NoReverseMatch: + raise UnknownUrlNameError(parlamentar_detail, kwargs=kwargs) + else: + try: + url = reverse(parlamentar_list) + except NoReverseMatch: + raise UnknownUrlNameError(parlamentar_list) + + numero_legislatura = self.request.GET.get( + 'hdn_num_legislatura', + EMPTY_STRING) + if numero_legislatura: + args = '?pk=' + numero_legislatura + url = "%s%s" % (url, args) + + return url + + +class RedirecionaComissao(RedirectView): + permanent = True + + def get_redirect_url(self): + url = EMPTY_STRING + pk_comissao = self.request.GET.get('cod_comissao', EMPTY_STRING) + + if pk_comissao: + kwargs = {'pk': pk_comissao} + + try: + url = reverse(comissao_detail, kwargs=kwargs) + except NoReverseMatch: + raise UnknownUrlNameError(comissao_detail) + else: + try: + url = reverse(comissao_list) + except NoReverseMatch: + raise UnknownUrlNameError(comissao_list) + return url + + +class RedirecionaPautaSessao(RedirectView): + permanent = True + + def get_redirect_url(self): + pk_sessao_plenaria = self.request.GET.get( + 'cod_sessao_plen', + EMPTY_STRING) + + if pk_sessao_plenaria: + kwargs = {'pk': pk_sessao_plenaria} + try: + url = reverse(pauta_sessao_detail, kwargs=kwargs) + except NoReverseMatch: + raise UnknownUrlNameError(pauta_sessao_detail) + else: + try: + url = reverse(pauta_sessao_list) + except NoReverseMatch: + raise UnknownUrlNameError(pauta_sessao_list) + + data_sessao_plenaria = self.request.GET.get( + 'dat_sessao_sel', + EMPTY_STRING) + + if data_sessao_plenaria: + dia_s_p, mes_s_p, ano_s_p = data_sessao_plenaria.split('/') + # Remove zeros à esquerda de dia_s_p e mes_s_p + dia_s_p = dia_s_p.lstrip("0") + mes_s_p = mes_s_p.lstrip("0") + args = EMPTY_STRING + args += "?data_inicio__year=%s" % (ano_s_p) + args += "&data_inicio__month=%s" % (mes_s_p) + args += "&data_inicio__day=%s" % (dia_s_p) + args += "&tipo=&salvar=Pesquisar" + url = "%s%s" % (url, args) + + return url + + +class RedirecionaSessaoPlenaria(RedirectView): + permanent = True + + def get_redirect_url(self): + pk_sessao_plenaria = self.request.GET.get( + 'cod_sessao_plen', + EMPTY_STRING) + url = EMPTY_STRING + if pk_sessao_plenaria: + kwargs = {'pk': pk_sessao_plenaria} + try: + url = reverse(sessao_plenaria_detail, kwargs=kwargs) + except NoReverseMatch: + raise UnknownUrlNameError(sessao_plenaria_detail) + + else: + try: + url = reverse(sessao_plenaria_list) + except NoReverseMatch: + raise UnknownUrlNameError(sessao_plenaria_list) + + year = self.request.GET.get( + 'ano_sessao_sel', + EMPTY_STRING) + month = self.request.GET.get( + 'mes_sessao_sel', + EMPTY_STRING) + day = self.request.GET.get( + 'dia_sessao_sel', + EMPTY_STRING) + tipo_sessao = self.request.GET.get( + 'tip_sessao_sel', + 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_sessao) + url = "%s%s" % (url, args) + + return url + + +class RedirecionaRelatoriosList(RedirectView): + permanent = True + + def get_redirect_url(self): + url = EMPTY_STRING + try: + url = reverse(relatorios_list) + except NoReverseMatch: + raise UnknownUrlNameError(relatorios_list) + return url + + +class RedirecionaRelatoriosMateriasEmTramitacaoList(RedirectView): + permanent = True + + def get_redirect_url(self): + url = EMPTY_STRING + try: + url = reverse(relatorio_materia_por_tramitacao) + except NoReverseMatch: + raise UnknownUrlNameError(relatorio_materia_por_tramitacao) + + year = self.request.GET.get( + 'selAno', + EMPTY_STRING) + if year: + tramitacao_tipo = self.request.GET.get( + 'lst_tip_materia', + EMPTY_STRING) + tramitacao_unidade_local = self.request.GET.get( + 'lst_cod_unid_tram_dest', + EMPTY_STRING) + tramitacao_status = self.request.GET.get( + 'lst_status', + EMPTY_STRING) + salvar = self.request.GET.get( + 'btn_materia_pesquisar', + 'Pesquisar') + + tramitacao_tipo = tramitacao_tipo.lstrip("0") + tramitacao_unidade_local = tramitacao_unidade_local.lstrip("0") + tramitacao_status = tramitacao_status.lstrip("0") + + args = EMPTY_STRING + args += "?ano=%s" % (year) + args += "&tipo=%s" % (tramitacao_tipo) + args += "&tramitacao__unidade_tramitacao_local=%s" % ( + tramitacao_unidade_local) + args += "&tramitacao__status=%s" % (tramitacao_status) + args += "&salvar=%s" % (salvar) + url = "%s%s" % (url, args) + + return url + + +class RedirecionaMateriaLegislativaDetail(RedirectView): + permanent = True + + def get_redirect_url(self): + pk = self.request.GET.get('cod_materia', EMPTY_STRING) + + if pk: + kwargs = {'pk': pk} + return reverse(materialegislativa_detail, kwargs=kwargs) + else: + return reverse(materialegislativa_list) + + +class RedirecionaMateriaLegislativaList(RedirectView): + permanent = True + + def get_redirect_url(self): + url = EMPTY_STRING + args = EMPTY_STRING + try: + url = reverse(materialegislativa_list) + except NoReverseMatch: + raise UnknownUrlNameError(materialegislativa_list) + + tipo_materia = self.request.GET.get( + 'lst_tip_materia', + EMPTY_STRING) + numero_materia = self.request.GET.get( + 'txt_numero', + EMPTY_STRING) + ano_materia = self.request.GET.get( + 'txt_ano', + EMPTY_STRING) + num_protocolo_materia = self.request.GET.get( + 'txt_num_protocolo', + EMPTY_STRING) + periodo_inicial_apresentacao = self.request.GET.get( + 'dt_apres', + EMPTY_STRING) + periodo_final_apresentacao = self.request.GET.get( + 'dt_apres2', + EMPTY_STRING) + periodo_inicial_publicacao = self.request.GET.get( + 'dt_public', + EMPTY_STRING) + periodo_final_publicacao = self.request.GET.get( + 'dt_public2', + EMPTY_STRING) + tipo_autor = self.request.GET.get( + 'lst_tip_autor', + EMPTY_STRING) + ementa_materia = self.request.GET.get( + 'txt_assunto', + EMPTY_STRING) + tramitando = self.request.GET.get( + 'rad_tramitando', + EMPTY_STRING) + status_tramitacao = self.request.GET.get( + 'lst_status', + EMPTY_STRING) + + args += "?tipo=%s" % (tipo_materia) + args += "&numero=%s" % (numero_materia) + args += "&ano=%s" % (ano_materia) + args += "&numero_protocolo=%s" % (num_protocolo_materia) + args += "&data_apresentacao_0=%s" % (periodo_inicial_apresentacao) + args += "&data_apresentacao_1=%s" % (periodo_final_apresentacao) + args += "&data_publicacao_0=%s" % (periodo_inicial_publicacao) + args += "&data_publicacao_1=%s" % (periodo_final_publicacao) + args += "&autoria__autor=%s" % (EMPTY_STRING) + args += "&autoria__autor__tipo=%s" % (tipo_autor) + args += "&relatoria__parlamentar_id=%s" % (EMPTY_STRING) + args += "&local_origem_externa=%s" % (EMPTY_STRING) + args += "&tramitacao__unidade_tramitacao_destino=%s" % (EMPTY_STRING) + args += "&tramitacao__status=%s" % (status_tramitacao) + args += "&em_tramitacao=%s" % (tramitando) + args += "&o=%s" % (EMPTY_STRING) + args += "&materiaassunto__assunto=%s" % (EMPTY_STRING) + args += "&ementa=%s" % (ementa_materia) + args += "&salvar=%s" % ('Pesquisar') # Default in both SAPL version + + url = "%s%s" % (url, args) + + return url + + +class RedirecionaMesaDiretoraView(RedirectView): + permanent = True + + def get_redirect_url(self): + try: + url = reverse(parlamentar_mesa_diretora) + except NoReverseMatch: + raise UnknownUrlNameError(parlamentar_mesa_diretora) + + return url + + +class RedirecionaNormasJuridicasDetail(RedirectView): + permanent = True + + def get_redirect_url(self): + pk_norma = self.request.GET.get('cod_norma', EMPTY_STRING) + + if pk_norma: + kwargs = {'pk': pk_norma} + return reverse(norma_juridica_detail, kwargs=kwargs) + else: + return reverse(norma_juridica_pesquisa) + + +class RedirecionaNormasJuridicasList(RedirectView): + + permanent = True + + def get_redirect_url(self): + url = EMPTY_STRING + args = EMPTY_STRING + try: + url = reverse(norma_juridica_pesquisa) + except NoReverseMatch: + raise UnknownUrlNameError(norma_juridica_pesquisa) + + tipo_norma = self.request.GET.get( + 'lst_tip_norma', + EMPTY_STRING) + numero_norma = self.request.GET.get( + 'txt_numero', + EMPTY_STRING) + ano_norma = self.request.GET.get( + 'txt_ano', + EMPTY_STRING) + periodo_inicial_aprovacao = self.request.GET.get( + 'dt_norma', + EMPTY_STRING) + periodo_final_aprovacao = self.request.GET.get( + 'dt_norma2', + EMPTY_STRING) + periodo_inicial_publicacao = self.request.GET.get( + 'dt_public', + EMPTY_STRING) + periodo_final_publicacao = self.request.GET.get( + 'dt_public2', + EMPTY_STRING) + ementa_norma = self.request.GET.get( + 'txt_assunto', + EMPTY_STRING) + assuntos_norma = self.request.GET.get( + 'lst_assunto_norma', + EMPTY_STRING) + + args += "?tipo=%s" % (tipo_norma) + args += "&numero=%s" % (numero_norma) + args += "&ano=%s" % (ano_norma) + args += "&data_0=%s" % (periodo_inicial_aprovacao) + args += "&data_1=%s" % (periodo_final_aprovacao) + args += "&data_publicacao_0=%s" % (periodo_inicial_publicacao) + args += "&data_publicacao_1=%s" % (periodo_final_publicacao) + args += "&ementa=%s" % (ementa_norma) + args += "&assuntos=%s" % (assuntos_norma) + args += "&salvar=%s" % ('Pesquisar') # Default in both SAPL version + + url = "%s%s" % (url, args) + + return url + + +class RedirecionaHistoricoTramitacoesList(RedirectView): + + permanent = True + + def get_redirect_url(self): + url = EMPTY_STRING + args = EMPTY_STRING + try: + url = reverse(historico_tramitacoes) + except NoReverseMatch: + raise UnknownUrlNameError(historico_tramitacoes) + + inicio_intervalo_data_tramitacao = self.request.GET.get( + 'txt_dat_inicio_periodo', + EMPTY_STRING + ).lstrip("0") + fim_intervalo_data_tramitacao = self.request.GET.get( + 'txt_dat_fim_periodo', + EMPTY_STRING + ).lstrip("0") + tipo_materia = self.request.GET.get( + 'lst_tip_materia', + EMPTY_STRING + ).lstrip("0") + unidade_local_tramitacao = self.request.GET.get( + 'lst_cod_unid_tram_dest', + EMPTY_STRING + ).lstrip("0") + status_tramitacao = self.request.GET.get( + 'lst_status', + EMPTY_STRING + ).lstrip("0") + + if ( + (inicio_intervalo_data_tramitacao != EMPTY_STRING) or + (fim_intervalo_data_tramitacao != EMPTY_STRING) or + (tipo_materia != EMPTY_STRING) or + (unidade_local_tramitacao != EMPTY_STRING) or + (status_tramitacao != EMPTY_STRING)): + + args += "?tramitacao__data_tramitacao_0=%s" % ( + inicio_intervalo_data_tramitacao) + args += "&tramitacao__data_tramitacao_1=%s" % ( + fim_intervalo_data_tramitacao) + args += "&tipo=%s" % (tipo_materia) + args += "&tramitacao__unidade_tramitacao_local=%s" % ( + unidade_local_tramitacao) + args += "&tramitacao__status=%s" % (status_tramitacao) + args += "&salvar=%s" % ('Pesquisar') + + url = "%s%s" % (url, args) + + return url + + +class RedirecionaAtasList(RedirectView): + + permanent = True + + def get_redirect_url(self): + url = EMPTY_STRING + args = EMPTY_STRING + try: + url = reverse(pesquisar_atas) + except NoReverseMatch: + raise UnknownUrlNameError(pesquisar_atas) + + inicio_intervalo_data_ata = self.request.GET.get( + 'txt_dat_inicio', + EMPTY_STRING + ).lstrip("0") + fim_intervalo_data_ata = self.request.GET.get( + 'txt_dat_fim', + EMPTY_STRING + ).lstrip("0") + + args += "?data_inicio_0=%s" % ( + inicio_intervalo_data_ata) + args += "&data_inicio_1=%s" % ( + fim_intervalo_data_ata) + args += "&salvar=%s" % ('Pesquisar') + + url = "%s%s" % (url, args) + + return url + + +class RedirecionaPresencaParlamentares(RedirectView): + + permanent = True + + def get_redirect_url(self): + url = EMPTY_STRING + args = EMPTY_STRING + try: + url = reverse(presenca_sessao) + except NoReverseMatch: + raise UnknownUrlNameError(presenca_sessao) + + inicio_intervalo_data_presenca_parlamentar = self.request.GET.get( + 'txt_dat_inicio', + EMPTY_STRING + ).lstrip("0") + fim_intervalo_data_presenca_parlamentar = self.request.GET.get( + 'txt_dat_fim', + EMPTY_STRING + ).lstrip("0") + + args += "?data_inicio_0=%s" % ( + inicio_intervalo_data_presenca_parlamentar) + args += "&data_inicio_1=%s" % ( + fim_intervalo_data_presenca_parlamentar) + args += "&salvar=%s" % ('Pesquisar') + + url = "%s%s" % (url, args) + + return url + + +class RedirecionaMateriasPorAutor(RedirectView): + + permanent = True + + def get_redirect_url(self): + url = EMPTY_STRING + try: + url = reverse(relatorio_materia_por_autor) + except NoReverseMatch: + raise UnknownUrlNameError(relatorio_materia_por_autor) + + return url + + +class RedirecionaMateriasPorAnoAutorTipo(RedirectView): + + permanent = True + + def get_redirect_url(self): + url = EMPTY_STRING + ano = self.request.GET.get('ano', '') + + try: + url = reverse(relatorio_materia_por_ano_autor_tipo) + except NoReverseMatch: + raise UnknownUrlNameError(relatorio_materia_por_ano_autor_tipo) + + if ano: + args = "?ano=%s" % (ano) + args += "&salvar=%s" % ('Pesquisar') + url = "%s%s" % (url, args) + + return url diff --git a/sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py b/sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py index 8890b9dcf..96fb7d581 100644 --- a/sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py +++ b/sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py @@ -6,6 +6,8 @@ """ import time +from sapl.sessao.models import ResumoOrdenacao + from trml2pdf import parseString @@ -287,15 +289,43 @@ def principal(cabecalho_dic, rodape_dic, imagem, sessao, inf_basicas_dic, lst_me tmp += '\t\n' tmp += paraStyle() tmp += '\t\n' - tmp += inf_basicas(inf_basicas_dic) - tmp += mesa(lst_mesa) - tmp += presenca(lst_presenca_sessao) - tmp += expedientes(lst_expedientes) - tmp += expediente_materia(lst_expediente_materia) - tmp += oradores_expediente(lst_oradores_expediente) - tmp += presenca_ordem_dia(lst_presenca_ordem_dia) - tmp += votacao(lst_votacao) - tmp += oradores(lst_oradores) + + ordenacao = ResumoOrdenacao.objects.first() + dict_ord_template = { + 'cont_mult': '', + 'exp': expedientes(lst_expedientes), + 'id_basica': inf_basicas(inf_basicas_dic), + 'lista_p': presenca(lst_presenca_sessao), + 'lista_p_o_d': presenca_ordem_dia(lst_presenca_ordem_dia), + 'mat_exp': expediente_materia(lst_expediente_materia), + 'mat_o_d': votacao(lst_votacao), + 'mesa_d': mesa(lst_mesa), + 'oradores_exped': oradores_expediente(lst_oradores_expediente), + 'oradores_expli': oradores(lst_oradores) + } + + if ordenacao: + tmp += dict_ord_template[ordenacao.primeiro] + tmp += dict_ord_template[ordenacao.segundo] + tmp += dict_ord_template[ordenacao.terceiro] + tmp += dict_ord_template[ordenacao.quarto] + tmp += dict_ord_template[ordenacao.quinto] + tmp += dict_ord_template[ordenacao.sexto] + tmp += dict_ord_template[ordenacao.setimo] + tmp += dict_ord_template[ordenacao.oitavo] + tmp += dict_ord_template[ordenacao.nono] + tmp += dict_ord_template[ordenacao.decimo] + else: + tmp += inf_basicas(inf_basicas_dic) + tmp += mesa(lst_mesa) + tmp += presenca(lst_presenca_sessao) + tmp += expedientes(lst_expedientes) + tmp += expediente_materia(lst_expediente_materia) + tmp += oradores_expediente(lst_oradores_expediente) + tmp += presenca_ordem_dia(lst_presenca_ordem_dia) + tmp += votacao(lst_votacao) + tmp += oradores(lst_oradores) + tmp += '\t\n' tmp += '\n' diff --git a/sapl/relatorios/views.py b/sapl/relatorios/views.py index 4b5416c2c..c00f770a2 100644 --- a/sapl/relatorios/views.py +++ b/sapl/relatorios/views.py @@ -1,7 +1,6 @@ -from datetime import datetime - -import re import html +import re +from datetime import datetime from django.core.exceptions import ObjectDoesNotExist from django.http import Http404, HttpResponse @@ -494,20 +493,21 @@ def get_sessao_plenaria(sessao, casa): dic_mesa = {} dic_mesa['nom_parlamentar'] = parlamentar.nome_parlamentar partido_sigla = Filiacao.objects.filter( - parlamentar=parlamentar).first().partido.sigla + parlamentar=parlamentar).first() if not partido_sigla: - partido_sigla = '' - dic_mesa['sgl_partido'] = partido_sigla + sigla = '' + else: + sigla = partido_sigla.partido.sigla + dic_mesa['sgl_partido'] = sigla dic_mesa['des_cargo'] = cargo.descricao lst_mesa.append(dic_mesa) # Lista de presença na sessão lst_presenca_sessao = [] - for presenca in SessaoPlenariaPresenca.objects.filter( - sessao_plenaria=sessao): + presenca = SessaoPlenariaPresenca.objects.filter( + sessao_plenaria=sessao).order_by('parlamentar__nome_parlamentar') - for parlamentar in Parlamentar.objects.filter( - id=presenca.parlamentar.id): + for parlamentar in [p.parlamentar for p in presenca]: dic_presenca = {} dic_presenca["nom_parlamentar"] = parlamentar.nome_parlamentar partido = Filiacao.objects.filter( @@ -635,26 +635,29 @@ def get_sessao_plenaria(sessao, casa): dic_oradores_expediente["nom_parlamentar"] = ( parlamentar.nome_parlamentar) partido_sigla = Filiacao.objects.filter( - parlamentar=parlamentar).first().partido.sigla + parlamentar=parlamentar).first() if not partido_sigla: - partido_sigla = '' - dic_oradores_expediente['sgl_partido'] = partido_sigla + sigla = '' + else: + sigla = partido_sigla.partido.sigla + dic_oradores_expediente['sgl_partido'] = sigla lst_oradores_expediente.append(dic_oradores_expediente) # Lista presença na ordem do dia lst_presenca_ordem_dia = [] - for presenca_ordem_dia in PresencaOrdemDia.objects.filter( - sessao_plenaria=sessao): - for parlamentar in Parlamentar.objects.filter( - id=presenca_ordem_dia.parlamentar.id): + presenca_ordem_dia = PresencaOrdemDia.objects.filter( + sessao_plenaria=sessao).order_by('parlamentar__nome_parlamentar') + for parlamentar in [p.parlamentar for p in presenca_ordem_dia]: dic_presenca_ordem_dia = {} dic_presenca_ordem_dia['nom_parlamentar'] = ( parlamentar.nome_parlamentar) partido_sigla = Filiacao.objects.filter( - parlamentar=parlamentar).first().partido.sigla + parlamentar=parlamentar).first() if not partido_sigla: - partido_sigla = '' - dic_presenca_ordem_dia['sgl_partido'] = partido_sigla + sigla = '' + else: + sigla = partido_sigla.partido.sigla + dic_presenca_ordem_dia['sgl_partido'] = sigla lst_presenca_ordem_dia.append(dic_presenca_ordem_dia) # Lista das matérias da Ordem do Dia, incluindo o resultado das votacoes @@ -746,10 +749,12 @@ def get_sessao_plenaria(sessao, casa): dic_oradores["num_ordem"] = orador.numero_ordem dic_oradores["nom_parlamentar"] = parlamentar.nome_parlamentar partido_sigla = Filiacao.objects.filter( - parlamentar=parlamentar).first().partido.sigla + parlamentar=parlamentar).first() if not partido_sigla: - partido_sigla = '' - dic_oradores['sgl_partido'] = partido_sigla + sigla = '' + else: + sigla = partido_sigla.partido.sigla + dic_oradores['sgl_partido'] = sigla lst_oradores.append(dic_oradores) return (inf_basicas_dic, diff --git a/sapl/rules/map_rules.py b/sapl/rules/map_rules.py index 05d869fd8..34d7372f6 100644 --- a/sapl/rules/map_rules.py +++ b/sapl/rules/map_rules.py @@ -152,8 +152,6 @@ rules_group_sessao = { (sessao.PresencaOrdemDia, __base__), (sessao.RegistroVotacao, __base__), (sessao.VotoParlamentar, __base__), - - (sessao.VotoNominal, __base__), ] } @@ -251,6 +249,7 @@ rules_group_geral = { (sessao.TipoResultadoVotacao, __base__), (sessao.TipoExpediente, __base__), (sessao.Bloco, __base__), + (sessao.ResumoOrdenacao, __base__), (lexml.LexmlProvedor, __base__), (lexml.LexmlPublicador, __base__), diff --git a/sapl/rules/tests/test_rules.py b/sapl/rules/tests/test_rules.py index 6fc7eb3fe..099ca8bf7 100644 --- a/sapl/rules/tests/test_rules.py +++ b/sapl/rules/tests/test_rules.py @@ -6,8 +6,8 @@ from django.contrib.contenttypes.models import ContentType from django.utils import six from django.utils.translation import ugettext_lazy as _ -from sapl.base.models import (CasaLegislativa, ProblemaMigracao, Argumento, - Constraint) +from sapl.base.models import (Argumento, CasaLegislativa, Constraint, + ProblemaMigracao) from sapl.compilacao.models import (PerfilEstruturalTextoArticulado, TipoDispositivo, TipoDispositivoRelationship) diff --git a/sapl/sessao/forms.py b/sapl/sessao/forms.py index 4303dabcc..91881b32c 100644 --- a/sapl/sessao/forms.py +++ b/sapl/sessao/forms.py @@ -28,17 +28,30 @@ def recupera_anos(): # apos a adicao do .dates(), por isso o reversed() abaixo anos = [(k.year, k.year) for k in reversed(anos_list)] return anos - except: + except Exception: return [] def ANO_CHOICES(): return [('', '---------')] + recupera_anos() + MES_CHOICES = [('', '---------')] + RANGE_MESES DIA_CHOICES = [('', '---------')] + RANGE_DIAS_MES +ORDENACAO_RESUMO = [('cont_mult', 'Conteúdo Multimídia'), + ('exp', 'Expedientes'), + ('id_basica', 'Identificação Básica'), + ('lista_p', 'Lista de Presença'), + ('lista_p_o_d', 'Lista de Presença Ordem do Dia'), + ('mat_exp', 'Matérias do Expediente'), + ('mat_o_d', 'Matérias da Ordem do Dia'), + ('mesa_d', 'Mesa Diretora'), + ('oradores_exped', 'Oradores do Expediente'), + ('oradores_expli', 'Oradores das Explicações Pessoais')] + + class BancadaForm(ModelForm): class Meta: @@ -47,6 +60,8 @@ class BancadaForm(ModelForm): 'data_extincao', 'descricao'] def clean(self): + super(BancadaForm, self).clean() + if self.cleaned_data['data_extincao']: if (self.cleaned_data['data_extincao'] < self.cleaned_data['data_criacao']): @@ -57,6 +72,8 @@ class BancadaForm(ModelForm): class ExpedienteMateriaForm(ModelForm): + _model = ExpedienteMateria + tipo_materia = forms.ModelChoiceField( label=_('Tipo Matéria'), required=True, @@ -87,7 +104,7 @@ class ExpedienteMateriaForm(ModelForm): sessao_plenaria=sessao, numero_ordem=self.cleaned_data['numero_ordem']).exists() - if numero_ordem_exists: + if numero_ordem_exists and not self.instance.pk: msg = _('Esse número de ordem já existe.') raise ValidationError(msg) @@ -97,6 +114,8 @@ class ExpedienteMateriaForm(ModelForm): return self.instance.sessao_plenaria.data_inicio def clean(self): + super(ExpedienteMateriaForm, self).clean() + cleaned_data = self.cleaned_data sessao = self.instance.sessao_plenaria @@ -112,11 +131,11 @@ class ExpedienteMateriaForm(ModelForm): else: cleaned_data['materia'] = materia - ex = ExpedienteMateria.objects.filter( + exists = self._model.objects.filter( sessao_plenaria=sessao, - materia=materia).count() + materia=materia).exists() - if ex >= 1: + if exists and not self.instance.pk: msg = _('Essa matéria já foi cadastrada.') raise ValidationError(msg) @@ -131,6 +150,8 @@ class ExpedienteMateriaForm(ModelForm): class OrdemDiaForm(ExpedienteMateriaForm): + _model = OrdemDia + class Meta: model = OrdemDia fields = ['data_ordem', 'numero_ordem', 'tipo_materia', 'observacao', @@ -139,47 +160,22 @@ class OrdemDiaForm(ExpedienteMateriaForm): def clean_data_ordem(self): return self.instance.sessao_plenaria.data_inicio - def clean_numero_ordem(self): sessao = self.instance.sessao_plenaria numero_ordem_exists = OrdemDia.objects.filter( - sessao_plenaria=sessao, - numero_ordem=self.cleaned_data[ - 'numero_ordem']).exists() + sessao_plenaria=sessao, + numero_ordem=self.cleaned_data['numero_ordem']).exists() - if numero_ordem_exists: + if numero_ordem_exists and not self.instance.pk: msg = _('Esse número de ordem já existe.') raise ValidationError(msg) return self.cleaned_data['numero_ordem'] - def clean(self): - cleaned_data = self.cleaned_data - sessao = self.instance.sessao_plenaria - - try: - materia = MateriaLegislativa.objects.get( - numero=self.cleaned_data['numero_materia'], - ano=self.cleaned_data['ano_materia'], - tipo=self.cleaned_data['tipo_materia']) - except ObjectDoesNotExist: - msg = _('A matéria a ser inclusa não existe no cadastro' - ' de matérias legislativas.') - raise ValidationError(msg) - else: - cleaned_data['materia'] = materia - - ex = ExpedienteMateria.objects.filter( - sessao_plenaria=sessao, - materia=materia).count() - - if ex >= 1: - msg = _('Essa matéria já foi cadastrada.') - raise ValidationError(msg) - - return cleaned_data + super(OrdemDiaForm, self).clean() + return self.cleaned_data def save(self, commit=False): ordem = super(OrdemDiaForm, self).save(commit) @@ -224,13 +220,13 @@ class VotacaoEditForm(forms.Form): class SessaoPlenariaFilterSet(django_filters.FilterSet): data_inicio__year = django_filters.ChoiceFilter(required=False, - label=u'Ano', + label='Ano', choices=ANO_CHOICES) data_inicio__month = django_filters.ChoiceFilter(required=False, - label=u'Mês', + label='Mês', choices=MES_CHOICES) data_inicio__day = django_filters.ChoiceFilter(required=False, - label=u'Dia', + label='Dia', choices=DIA_CHOICES) titulo = _('Pesquisa de Sessão Plenária') @@ -359,3 +355,72 @@ class OradorExpedienteForm(ModelForm): class PautaSessaoFilterSet(SessaoPlenariaFilterSet): titulo = _('Pesquisa de Pauta de Sessão') + + +class ResumoOrdenacaoForm(forms.Form): + primeiro = forms.ChoiceField(label=_('1°'), + choices=ORDENACAO_RESUMO) + segundo = forms.ChoiceField(label=_('2°'), + choices=ORDENACAO_RESUMO) + terceiro = forms.ChoiceField(label='3°', + choices=ORDENACAO_RESUMO) + quarto = forms.ChoiceField(label=_('4°'), + choices=ORDENACAO_RESUMO) + quinto = forms.ChoiceField(label=_('5°'), + choices=ORDENACAO_RESUMO) + sexto = forms.ChoiceField(label=_('6°'), + choices=ORDENACAO_RESUMO) + setimo = forms.ChoiceField(label=_('7°'), + choices=ORDENACAO_RESUMO) + oitavo = forms.ChoiceField(label=_('8°'), + choices=ORDENACAO_RESUMO) + nono = forms.ChoiceField(label=_('9°'), + choices=ORDENACAO_RESUMO) + decimo = forms.ChoiceField(label='10°', + choices=ORDENACAO_RESUMO) + + def __init__(self, *args, **kwargs): + super(ResumoOrdenacaoForm, self).__init__(*args, **kwargs) + + row1 = to_row( + [('primeiro', 12)]) + row2 = to_row( + [('segundo', 12)]) + row3 = to_row( + [('terceiro', 12)]) + row4 = to_row( + [('quarto', 12)]) + row5 = to_row( + [('quinto', 12)]) + row6 = to_row( + [('sexto', 12)]) + row7 = to_row( + [('setimo', 12)]) + row8 = to_row( + [('oitavo', 12)]) + row9 = to_row( + [('nono', 12)]) + row10 = to_row( + [('decimo', 12)]) + + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset(_(''), + row1, row2, row3, row4, row5, + row6, row7, row8, row9, row10, + form_actions(save_label='Atualizar')) + ) + + def clean(self): + super(ResumoOrdenacaoForm, self).clean() + + cleaned_data = self.cleaned_data + + for c1 in cleaned_data: + i = 0 + for c2 in cleaned_data: + if cleaned_data[str(c1)] == cleaned_data[str(c2)]: + i = i + 1 + if i > 1: + raise ValidationError(_( + 'Não é possível ter campos repetidos')) diff --git a/sapl/sessao/migrations/0003_resumoordenacao.py b/sapl/sessao/migrations/0003_resumoordenacao.py new file mode 100644 index 000000000..ef6e990c5 --- /dev/null +++ b/sapl/sessao/migrations/0003_resumoordenacao.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2017-05-22 10:51 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sessao', '0002_sessaoplenaria_interativa'), + ] + + operations = [ + migrations.CreateModel( + name='ResumoOrdenacao', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('primeiro', models.CharField(max_length=30)), + ('segundo', models.CharField(max_length=30)), + ('terceiro', models.CharField(max_length=30)), + ('quarto', models.CharField(max_length=30)), + ('quinto', models.CharField(max_length=30)), + ('sexto', models.CharField(max_length=30)), + ('setimo', models.CharField(max_length=30)), + ('oitavo', models.CharField(max_length=30)), + ('nono', models.CharField(max_length=30)), + ('decimo', models.CharField(max_length=30)), + ], + options={ + 'verbose_name': 'Ordenação do Resumo de uma Sessão', + 'verbose_name_plural': 'Ordenação do Resumo de uma Sessão', + }, + ), + ] diff --git a/sapl/sessao/migrations/0004_votonominal_registro_votacao.py b/sapl/sessao/migrations/0004_votonominal_registro_votacao.py new file mode 100644 index 000000000..d3267d34f --- /dev/null +++ b/sapl/sessao/migrations/0004_votonominal_registro_votacao.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2017-06-01 11:06 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('sessao', '0003_resumoordenacao'), + ] + + operations = [ + migrations.AddField( + model_name='votonominal', + name='registro_votacao', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sessao.RegistroVotacao'), + ), + ] diff --git a/sapl/sessao/migrations/0005_auto_20170601_1246.py b/sapl/sessao/migrations/0005_auto_20170601_1246.py new file mode 100644 index 000000000..842f99319 --- /dev/null +++ b/sapl/sessao/migrations/0005_auto_20170601_1246.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2017-06-01 12:46 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +from datetime import datetime + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('sessao', '0004_votonominal_registro_votacao'), + ] + + operations = [ + migrations.RemoveField( + model_name='votonominal', + name='registro_votacao', + ), + migrations.AddField( + model_name='votoparlamentar', + name='data_hora', + field=models.DateTimeField(auto_now_add=True, blank=True, null=True, verbose_name='Data/Hora'), + preserve_default=False, + ), + migrations.AddField( + model_name='votoparlamentar', + name='ip', + field=models.CharField(blank=True, max_length=30, null=True, verbose_name='IP'), + ), + migrations.AddField( + model_name='votoparlamentar', + name='user', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/sapl/sessao/migrations/0006_auto_20170601_1257.py b/sapl/sessao/migrations/0006_auto_20170601_1257.py new file mode 100644 index 000000000..6d43c17d0 --- /dev/null +++ b/sapl/sessao/migrations/0006_auto_20170601_1257.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2017-06-01 12:57 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('sessao', '0005_auto_20170601_1246'), + ] + + operations = [ + migrations.AddField( + model_name='votonominal', + name='votacao', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sessao.RegistroVotacao'), + ), + migrations.AlterField( + model_name='votoparlamentar', + name='votacao', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sessao.RegistroVotacao'), + ), + ] diff --git a/sapl/sessao/migrations/0007_auto_20170606_1238.py b/sapl/sessao/migrations/0007_auto_20170606_1238.py new file mode 100644 index 000000000..33cc2be35 --- /dev/null +++ b/sapl/sessao/migrations/0007_auto_20170606_1238.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2017-06-06 12:38 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('sessao', '0006_auto_20170601_1257'), + ] + + operations = [ + migrations.AddField( + model_name='votoparlamentar', + name='expediente', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sessao.ExpedienteMateria'), + ), + migrations.AddField( + model_name='votoparlamentar', + name='ordem', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sessao.OrdemDia'), + ), + ] diff --git a/sapl/sessao/migrations/0008_auto_20170607_1220.py b/sapl/sessao/migrations/0008_auto_20170607_1220.py new file mode 100644 index 000000000..d07ac2e26 --- /dev/null +++ b/sapl/sessao/migrations/0008_auto_20170607_1220.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2017-06-07 12:20 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('sessao', '0007_auto_20170606_1238'), + ] + + operations = [ + migrations.RemoveField( + model_name='votonominal', + name='materia', + ), + migrations.RemoveField( + model_name='votonominal', + name='parlamentar', + ), + migrations.RemoveField( + model_name='votonominal', + name='sessao', + ), + migrations.RemoveField( + model_name='votonominal', + name='user', + ), + migrations.RemoveField( + model_name='votonominal', + name='votacao', + ), + migrations.DeleteModel( + name='VotoNominal', + ), + ] diff --git a/sapl/sessao/migrations/0009_auto_20170619_1441.py b/sapl/sessao/migrations/0009_auto_20170619_1441.py new file mode 100644 index 000000000..be2e74332 --- /dev/null +++ b/sapl/sessao/migrations/0009_auto_20170619_1441.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-06-19 14:41 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sessao', '0008_auto_20170607_1220'), + ] + + operations = [ + migrations.AlterField( + model_name='votoparlamentar', + name='ip', + field=models.CharField(blank=True, default='', max_length=30, verbose_name='IP'), + ), + ] diff --git a/sapl/sessao/models.py b/sapl/sessao/models.py index b76fac9f3..5cd6c6501 100644 --- a/sapl/sessao/models.py +++ b/sapl/sessao/models.py @@ -86,17 +86,19 @@ def get_sessao_media_path(instance, subpath, filename): def pauta_upload_path(instance, filename): - return texto_upload_path(instance, filename, subpath='pauta') + return texto_upload_path( + instance, filename, subpath='pauta', pk_first=True) # return get_sessao_media_path(instance, 'pauta', filename) def ata_upload_path(instance, filename): - return texto_upload_path(instance, filename, subpath='ata') + return texto_upload_path(instance, filename, subpath='ata', pk_first=True) # return get_sessao_media_path(instance, 'ata', filename) def anexo_upload_path(instance, filename): - return texto_upload_path(instance, filename, subpath='anexo') + return texto_upload_path( + instance, filename, subpath='anexo', pk_first=True) # return get_sessao_media_path(instance, 'anexo', filename) @@ -412,11 +414,39 @@ class RegistroVotacao(models.Model): @reversion.register() class VotoParlamentar(models.Model): # RegistroVotacaoParlamentar - votacao = models.ForeignKey(RegistroVotacao, on_delete=models.PROTECT) + ''' + As colunas ordem e expediente são redundantes, levando em consideração + que RegistroVotacao já possui ordem/expediente. Entretanto, para + viabilizar a votação interativa, uma vez que ela é feita antes de haver + um RegistroVotacao, é preciso identificar o voto por ordem/expediente. + ''' + votacao = models.ForeignKey(RegistroVotacao, + blank=True, + null=True) parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT) - # XXX change to restricted choices voto = models.CharField(max_length=10) + user = models.ForeignKey(get_settings_auth_user_model(), + on_delete=models.PROTECT, + null=True, + blank=True) + ip = models.CharField(verbose_name=_('IP'), + max_length=30, + blank=True, + default='') + data_hora = models.DateTimeField( + verbose_name=_('Data/Hora'), + auto_now_add=True, + blank=True, + null=True) + + ordem = models.ForeignKey(OrdemDia, + blank=True, + null=True) + expediente = models.ForeignKey(ExpedienteMateria, + blank=True, + null=True) + class Meta: verbose_name = _('Registro de Votação de Parlamentar') verbose_name_plural = _('Registros de Votações de Parlamentares') @@ -426,28 +456,6 @@ class VotoParlamentar(models.Model): # RegistroVotacaoParlamentar 'votacao': self.votacao, 'parlamentar': self.parlamentar} -@reversion.register() -class VotoNominal(models.Model): - parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT) - voto = models.CharField(verbose_name=_('Voto'), max_length=10) - - sessao = models.ForeignKey(SessaoPlenaria, on_delete=models.PROTECT) - materia = models.ForeignKey(MateriaLegislativa, on_delete=models.PROTECT) - - user = models.ForeignKey(get_settings_auth_user_model(), - on_delete=models.PROTECT) - ip = models.CharField(verbose_name=_('IP'), max_length=30) - data_hora = models.DateTimeField( - verbose_name=_('Data/Hora'), auto_now_add=True) - - class Meta: - verbose_name = _('Registro do Voto do Parlamentar') - verbose_name_plural = _('Registros dos Votos dos Parlamentares') - - def __str__(self): - return '%s - %s' % (self.parlamentar.nome_parlamentar, self.voto) - - @reversion.register() class SessaoPlenariaPresenca(models.Model): sessao_plenaria = models.ForeignKey(SessaoPlenaria, @@ -493,3 +501,28 @@ class Bloco(models.Model): def __str__(self): return self.nome + + +@reversion.register() +class ResumoOrdenacao(models.Model): + ''' + Tabela para registrar em qual ordem serão renderizados os componentes + da tela de resumo de uma sessão + ''' + primeiro = models.CharField(max_length=30) + segundo = models.CharField(max_length=30) + terceiro = models.CharField(max_length=30) + quarto = models.CharField(max_length=30) + quinto = models.CharField(max_length=30) + sexto = models.CharField(max_length=30) + setimo = models.CharField(max_length=30) + oitavo = models.CharField(max_length=30) + nono = models.CharField(max_length=30) + decimo = models.CharField(max_length=30) + + class Meta: + verbose_name = _('Ordenação do Resumo de uma Sessão') + verbose_name_plural = _('Ordenação do Resumo de uma Sessão') + + def __str__(self): + return 'Ordenação do Resumo de uma Sessão' diff --git a/sapl/sessao/serializers.py b/sapl/sessao/serializers.py index 6d6f4cd9b..c38509f93 100644 --- a/sapl/sessao/serializers.py +++ b/sapl/sessao/serializers.py @@ -2,6 +2,7 @@ from rest_framework import serializers from .models import SessaoPlenaria + class SessaoPlenariaSerializer(serializers.Serializer): class Meta: model = SessaoPlenaria diff --git a/sapl/sessao/urls.py b/sapl/sessao/urls.py index f237bf721..a28d5eb38 100644 --- a/sapl/sessao/urls.py +++ b/sapl/sessao/urls.py @@ -10,19 +10,22 @@ from sapl.sessao.views import (AdicionarVariasMateriasExpediente, PautaSessaoDetailView, PautaSessaoListView, PesquisarPautaSessaoView, PesquisarSessaoPlenariaView, - PresencaOrdemDiaView, PresencaView, ResumoView, - SessaoCrud, TipoExpedienteCrud, - TipoResultadoVotacaoCrud, TipoSessaoCrud, - VotacaoEditView, VotacaoExpedienteEditView, + PresencaOrdemDiaView, PresencaView, + ResumoOrdenacaoView, ResumoView, SessaoCrud, + TipoExpedienteCrud, TipoResultadoVotacaoCrud, + TipoSessaoCrud, VotacaoEditView, + VotacaoExpedienteEditView, VotacaoExpedienteView, VotacaoNominalEditView, - VotacaoNominalExpedienteEditView, VotacaoNominalExpedienteDetailView, + VotacaoNominalExpedienteEditView, VotacaoNominalExpedienteView, VotacaoNominalView, VotacaoView, abrir_votacao_expediente_view, - abrir_votacao_ordem_view, + abrir_votacao_ordem_view, atualizar_mesa, + insere_parlamentar_composicao, mudar_ordem_materia_sessao, recuperar_materia, recuperar_numero_sessao, + remove_parlamentar_composicao, reordernar_materias_expediente, reordernar_materias_ordem, sessao_legislativa_legislatura_ajax) @@ -40,6 +43,18 @@ urlpatterns = [ url(r'^sessao/(?P\d+)/mesa$', MesaView.as_view(), name='mesa'), + url(r'^sessao/mesa/atualizar-mesa/$', + atualizar_mesa, + name='atualizar_mesa'), + + url(r'^sessao/mesa/insere-parlamentar/composicao/$', + insere_parlamentar_composicao, + name='insere_parlamentar_composicao'), + + url(r'^sessao/mesa/remove-parlamentar-composicao/$', + remove_parlamentar_composicao, + name='remove_parlamentar_composicao'), + url(r'^sessao/recuperar-materia/', recuperar_materia), url(r'^sessao/recuperar-numero-sessao/', recuperar_numero_sessao), url(r'^sessao/sessao-legislativa-legislatura-ajax/', @@ -68,6 +83,9 @@ urlpatterns = [ include(BlocoCrud.get_urls())), url(r'^sistema/cargo-bancada/', include(CargoBancadaCrud.get_urls())), + url(r'^sistema/resumo-ordenacao/', + ResumoOrdenacaoView.as_view(), + name='resumo_ordenacao'), url(r'^sessao/(?P\d+)/adicionar-varias-materias-expediente/', AdicionarVariasMateriasExpediente.as_view(), name='adicionar_varias_materias_expediente'), diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index 980594784..16ca533e3 100644 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -3,17 +3,19 @@ from re import sub from django.contrib import messages from django.contrib.auth.decorators import permission_required +from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist from django.core.urlresolvers import reverse +from django.db.models import Max, Q from django.forms.utils import ErrorList from django.http import JsonResponse -from django.http.response import HttpResponseRedirect +from django.http.response import Http404, HttpResponseRedirect from django.utils.datastructures import MultiValueDictKeyError from django.utils.decorators import method_decorator from django.utils.html import strip_tags from django.utils.translation import ugettext_lazy as _ from django.views.decorators.csrf import csrf_exempt -from django.views.generic import ListView, TemplateView +from django.views.generic import FormView, ListView, TemplateView from django.views.generic.base import RedirectView from django.views.generic.detail import DetailView from django.views.generic.edit import FormMixin @@ -29,21 +31,21 @@ from sapl.materia.models import (Autoria, DocumentoAcessorio, from sapl.materia.views import MateriaLegislativaPesquisaView from sapl.norma.models import NormaJuridica from sapl.parlamentares.models import (Filiacao, Legislatura, Parlamentar, - SessaoLegislativa) + SessaoLegislativa, Mandato) from sapl.sessao.apps import AppConfig from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm from .forms import (AdicionarVariasMateriasFilterSet, ExpedienteForm, ListMateriaForm, MesaForm, OradorExpedienteForm, OradorForm, PautaSessaoFilterSet, PresencaForm, - SessaoPlenariaFilterSet, VotacaoEditForm, VotacaoForm, - VotacaoNominalForm) + ResumoOrdenacaoForm, SessaoPlenariaFilterSet, + VotacaoEditForm, VotacaoForm, VotacaoNominalForm) from .models import (Bancada, Bloco, CargoBancada, CargoMesa, ExpedienteMateria, ExpedienteSessao, IntegranteMesa, MateriaLegislativa, Orador, OradorExpediente, OrdemDia, - PresencaOrdemDia, RegistroVotacao, SessaoPlenaria, - SessaoPlenariaPresenca, TipoExpediente, - TipoResultadoVotacao, TipoSessaoPlenaria, VotoNominal, + PresencaOrdemDia, RegistroVotacao, ResumoOrdenacao, + SessaoPlenaria, SessaoPlenariaPresenca, TipoExpediente, + TipoResultadoVotacao, TipoSessaoPlenaria, VotoParlamentar) TipoSessaoCrud = CrudAux.build(TipoSessaoPlenaria, 'tipo_sessao_plenaria') @@ -79,47 +81,78 @@ def reordernar_materias_ordem(request, pk): return HttpResponseRedirect( reverse('sapl.sessao:ordemdia_list', kwargs={'pk': pk})) +def verifica_presenca(request, model, spk): + if not model.objects.filter(sessao_plenaria_id=spk).exists(): + msg = _('Votação não pode ser aberta sem presenças') + messages.add_message(request, messages.ERROR, msg) + return False + return True + + +def verifica_votacoes_abertas(request, model, pk): + votacoes_abertas = SessaoPlenaria.objects.filter( + Q(ordemdia__votacao_aberta=True) | + Q(expedientemateria__votacao_aberta=True)).distinct() + + if votacoes_abertas: + msg_abertas = [] + for v in votacoes_abertas: + msg_abertas.append('''
  • %s
  • ''' % ( + reverse('sapl.sessao:sessaoplenaria_detail', + kwargs={'pk': v.id}), + v.__str__())) + + msg = _('Já existem votações abertas nas seguintes Sessões: ' + + ', '.join(msg_abertas) + '. Para abrir ' + 'outra, termine ou feche as votações abertas.') + messages.add_message(request, messages.INFO, msg) + + else: + materia_votacao = model.objects.get(id=pk) + materia_votacao.votacao_aberta = True + materia_votacao.save() + @permission_required('sessao.change_expedientemateria') def abrir_votacao_expediente_view(request, pk, spk): - existe_expediente_aberto = ExpedienteMateria.objects.filter( - sessao_plenaria_id=spk, votacao_aberta=True - ).exists() - existe_ordem_aberta = OrdemDia.objects.filter( - sessao_plenaria_id=spk, votacao_aberta=True - ).exists() - - if existe_expediente_aberto or existe_ordem_aberta: - msg = _('Já existe uma matéria com votação aberta. Para abrir ' - 'outra, termine ou feche a votação existente.') - messages.add_message(request, messages.INFO, msg) - else: - expediente = ExpedienteMateria.objects.get(id=pk) - expediente.votacao_aberta = True - expediente.save() + if verifica_presenca(request, SessaoPlenariaPresenca, spk): + verifica_votacoes_abertas(request, ExpedienteMateria, pk) return HttpResponseRedirect( - reverse('sapl.sessao:expedientemateria_list', kwargs={'pk': spk})) + reverse('sapl.sessao:expedientemateria_list', kwargs={'pk': spk})) @permission_required('sessao.change_ordemdia') def abrir_votacao_ordem_view(request, pk, spk): - existe_ordem_aberta = OrdemDia.objects.filter( - sessao_plenaria_id=spk, votacao_aberta=True - ).exists() - existe_expediente_aberto = ExpedienteMateria.objects.filter( - sessao_plenaria_id=spk, votacao_aberta=True - ).exists() - - if existe_ordem_aberta or existe_expediente_aberto: - msg = _('Já existe uma matéria com votação aberta. Para abrir ' - 'outra, termine ou feche a votação existente.') - messages.add_message(request, messages.INFO, msg) - else: - ordem = OrdemDia.objects.get(id=pk) - ordem.votacao_aberta = True - ordem.save() + if verifica_presenca(request, PresencaOrdemDia, spk): + verifica_votacoes_abertas(request, OrdemDia, pk) return HttpResponseRedirect( - reverse('sapl.sessao:ordemdia_list', kwargs={'pk': spk})) + reverse('sapl.sessao:ordemdia_list', kwargs={'pk': spk})) + + +def put_link_materia(context): + for i, row in enumerate(context['rows']): + materia = context['object_list'][i].materia + url_materia = reverse('sapl.materia:materialegislativa_detail', + kwargs={'pk': materia.id}) + + context['rows'][i][1] = (row[1][0], url_materia) + return context + + +def get_presencas_generic(model, sessao, legislatura): + presencas = model.objects.filter( + sessao_plenaria=sessao) + + presentes = [p.parlamentar for p in presencas] + + mandato = Mandato.objects.filter( + legislatura=legislatura).order_by('parlamentar__nome_parlamentar') + + for m in mandato: + if m.parlamentar in presentes: + yield (m.parlamentar, True) + else: + yield (m.parlamentar, False) class MateriaOrdemDiaCrud(MasterDetailCrud): @@ -138,6 +171,9 @@ class MateriaOrdemDiaCrud(MasterDetailCrud): def get_initial(self): self.initial['data_ordem'] = SessaoPlenaria.objects.get( pk=self.kwargs['pk']).data_inicio.strftime('%d/%m/%Y') + max_numero_ordem = OrdemDia.objects.filter( + sessao_plenaria=self.kwargs['pk']).aggregate(Max('numero_ordem'))['numero_ordem__max'] + self.initial['numero_ordem'] = (max_numero_ordem if max_numero_ordem else 0) + 1 return self.initial def get_success_url(self): @@ -162,11 +198,15 @@ class MateriaOrdemDiaCrud(MasterDetailCrud): class ListView(MasterDetailCrud.ListView): ordering = ['numero_ordem', 'materia', 'resultado'] + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + return put_link_materia(context) + def get_rows(self, object_list): for obj in object_list: exist_resultado = obj.registrovotacao_set.filter( - materia=obj.materia - ).exists() + materia=obj.materia).exists() if not exist_resultado: if obj.votacao_aberta: url = '' @@ -174,20 +214,20 @@ class MateriaOrdemDiaCrud(MasterDetailCrud): url = reverse('sapl.sessao:votacaosimbolica', kwargs={ 'pk': obj.sessao_plenaria_id, - 'oid': obj.materia_id, - 'mid': obj.pk}) + '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.materia_id, - 'mid': obj.pk}) + '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.materia_id, - 'mid': obj.pk}) + 'oid': obj.pk, + 'mid': obj.materia_id}) if self.request.user.has_module_perms(AppConfig.label): btn_registrar = ''' %s
    %s' % - (url, - resultado_descricao, - resultado_observacao)) + (url, + resultado_descricao, + resultado_observacao)) else: obj.resultado = ('%s
    %s' % - (resultado_descricao, - resultado_observacao)) + (resultado_descricao, + resultado_observacao)) return [self._as_row(obj) for obj in object_list] @@ -278,11 +318,15 @@ class ExpedienteMateriaCrud(MasterDetailCrud): class ListView(MasterDetailCrud.ListView): ordering = ['numero_ordem', 'materia', 'resultado'] + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + return put_link_materia(context) + def get_rows(self, object_list): for obj in object_list: exist_resultado = obj.registrovotacao_set.filter( - materia=obj.materia - ).exists() + materia=obj.materia).exists() if not exist_resultado: if obj.votacao_aberta: url = '' @@ -290,20 +334,20 @@ class ExpedienteMateriaCrud(MasterDetailCrud): url = reverse('sapl.sessao:votacaosimbolicaexp', kwargs={ 'pk': obj.sessao_plenaria_id, - 'oid': obj.materia_id, - 'mid': obj.pk}) + '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.materia_id, - 'mid': obj.pk}) + '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.materia_id, - 'mid': obj.pk}) + 'oid': obj.pk, + 'mid': obj.materia_id}) if self.request.user.has_module_perms(AppConfig.label): btn_registrar = ''' @@ -335,39 +379,40 @@ class ExpedienteMateriaCrud(MasterDetailCrud): 'sapl.sessao:votacaosimbolicaexpedit', kwargs={ 'pk': obj.sessao_plenaria_id, - 'oid': obj.materia_id, - 'mid': obj.pk}) + '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.materia_id, - 'mid': obj.pk}) + '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.materia_id, - 'mid': obj.pk}) + 'oid': obj.pk, + 'mid': obj.materia_id}) obj.resultado = ('%s
    %s' % - (url, - resultado_descricao, - resultado_observacao)) + (url, + resultado_descricao, + resultado_observacao)) else: if obj.tipo_votacao == 2: - url = reverse('sapl.sessao:votacaonominalexpdetail', - kwargs={ - 'pk': obj.sessao_plenaria_id, - 'oid': obj.materia_id, - 'mid': obj.pk}) + url = reverse( + 'sapl.sessao:votacaonominalexpdetail', + kwargs={ + 'pk': obj.sessao_plenaria_id, + 'oid': obj.pk, + 'mid': obj.materia_id}) obj.resultado = ('%s
    %s' % - (url, - resultado_descricao, - resultado_observacao)) + (url, + resultado_descricao, + resultado_observacao)) else: obj.resultado = ('%s
    %s' % - (resultado_descricao, - resultado_observacao)) + (resultado_descricao, + resultado_observacao)) return [self._as_row(obj) for obj in object_list] class CreateView(MasterDetailCrud.CreateView): @@ -376,6 +421,9 @@ class ExpedienteMateriaCrud(MasterDetailCrud): def get_initial(self): self.initial['data_ordem'] = SessaoPlenaria.objects.get( pk=self.kwargs['pk']).data_inicio.strftime('%d/%m/%Y') + max_numero_ordem = ExpedienteMateria.objects.filter( + sessao_plenaria=self.kwargs['pk']).aggregate(Max('numero_ordem'))['numero_ordem__max'] + self.initial['numero_ordem'] = (max_numero_ordem if max_numero_ordem else 0) + 1 return self.initial def get_success_url(self): @@ -534,32 +582,16 @@ class SessaoPermissionMixin(PermissionRequiredForAppCrudMixin, class PresencaMixin: def get_presencas(self): - self.object = self.get_object() - - presencas = SessaoPlenariaPresenca.objects.filter( - sessao_plenaria_id=self.object.id - ) - presentes = [p.parlamentar for p in presencas] - - for parlamentar in Parlamentar.objects.filter(ativo=True): - if parlamentar in presentes: - yield (parlamentar, True) - else: - yield (parlamentar, False) + return get_presencas_generic( + SessaoPlenariaPresenca, + self.object, + self.object.legislatura) def get_presencas_ordem(self): - self.object = self.get_object() - - presencas = PresencaOrdemDia.objects.filter( - sessao_plenaria_id=self.object.id - ) - presentes = [p.parlamentar for p in presencas] - - for parlamentar in Parlamentar.objects.filter(ativo=True): - if parlamentar in presentes: - yield (parlamentar, True) - else: - yield (parlamentar, False) + return get_presencas_generic( + PresencaOrdemDia, + self.object, + self.object.legislatura) class PresencaView(FormMixin, PresencaMixin, DetailView): @@ -582,16 +614,18 @@ class PresencaView(FormMixin, PresencaMixin, DetailView): if form.is_valid(): # Pegar os presentes salvos no banco presentes_banco = SessaoPlenariaPresenca.objects.filter( - sessao_plenaria_id=self.object.id) + sessao_plenaria_id=self.object.id).values_list( + 'parlamentar_id', flat=True).distinct() # Id dos parlamentares presentes - marcados = request.POST.getlist('presenca') + marcados = request.POST.getlist('presenca_ativos') \ + + request.POST.getlist('presenca_inativos') - # Deletar os que foram desmarcadors - deletar = set(set(presentes_banco) - set(marcados)) - for d in deletar: - SessaoPlenariaPresenca.objects.filter( - parlamentar_id=d.parlamentar_id).delete() + # Deletar os que foram desmarcados + deletar = set(presentes_banco) - set(marcados) + SessaoPlenariaPresenca.objects.filter( + parlamentar_id__in=deletar, + sessao_plenaria_id=self.object.id).delete() for p in marcados: sessao = SessaoPlenariaPresenca() @@ -690,16 +724,18 @@ class PresencaOrdemDiaView(FormMixin, PresencaMixin, DetailView): if form.is_valid(): # Pegar os presentes salvos no banco presentes_banco = PresencaOrdemDia.objects.filter( - sessao_plenaria_id=pk) + sessao_plenaria_id=self.object.id).values_list( + 'parlamentar_id', flat=True).distinct() # Id dos parlamentares presentes - marcados = request.POST.getlist('presenca') + marcados = request.POST.getlist('presenca_ativos') \ + + request.POST.getlist('presenca_inativos') - # Deletar os que foram desmarcadors - deletar = set(set(presentes_banco) - set(marcados)) - for d in deletar: - PresencaOrdemDia.objects.filter( - parlamentar_id=d.parlamentar_id).delete() + # Deletar os que foram desmarcados + deletar = set(presentes_banco) - set(marcados) + PresencaOrdemDia.objects.filter( + parlamentar_id__in=deletar, + sessao_plenaria_id=self.object.id).delete() for p in marcados: ordem = PresencaOrdemDia() @@ -741,8 +777,8 @@ class ListMateriaOrdemDiaView(FormMixin, DetailView): autor = [str(a.autor) for a in autoria] mat = {'pk': pk, - 'oid': o.materia_id, - 'ordem_id': o.id, + 'oid': o.id, + 'ordem_id': o.materia_id, 'ementa': ementa, 'titulo': titulo, 'numero': numero, @@ -801,8 +837,8 @@ class ListMateriaOrdemDiaView(FormMixin, DetailView): autor = [str(a.autor) for a in autoria] mat = {'pk': pk, - 'oid': o.materia_id, - 'ordem_id': o.id, + 'oid': o.id, + 'ordem_id': o.materia_id, 'ementa': ementa, 'titulo': titulo, 'numero': numero, @@ -834,24 +870,40 @@ class MesaView(FormMixin, DetailView): model = SessaoPlenaria def get(self, request, *args, **kwargs): - self.object = self.get_object() context = self.get_context_data(object=self.object) - mesa = IntegranteMesa.objects.filter( - sessao_plenaria=self.object) + try: + sessao = SessaoPlenaria.objects.get( + id=kwargs['pk']) + except ObjectDoesNotExist: + mensagem = _('Esta Sessão Plenária não existe!') + messages.add_message(request, messages.INFO, mensagem) - integrantes = [] - for m in mesa: - parlamentar = Parlamentar.objects.get( - id=m.parlamentar_id) - cargo = CargoMesa.objects.get( - id=m.cargo_id) - integrante = {'parlamentar': parlamentar, 'cargo': cargo} - integrantes.append(integrante) + return self.render_to_response(context) + + mesa = sessao.integrantemesa_set.all() if sessao else [] + + cargos_ocupados = [m.cargo for m in mesa] + cargos = CargoMesa.objects.all() + cargos_vagos = list(set(cargos) - set(cargos_ocupados)) + + parlamentares = Legislatura.objects.first().mandato_set.all() + parlamentares_ocupados = [m.parlamentar for m in mesa] + parlamentares_vagos = list( + set( + [p.parlamentar for p in parlamentares]) - set( + parlamentares_ocupados)) + + # Se todos os cargos estiverem ocupados, a listagem de parlamentares + # deve ser renderizada vazia + if not cargos_vagos: + parlamentares_vagos = [] context.update( - {'integrantes': ordenar_integrantes_por_cargo(integrantes)}) + {'composicao_mesa': mesa, + 'parlamentares': parlamentares_vagos, + 'cargos_vagos': cargos_vagos}) return self.render_to_response(context) @@ -861,73 +913,166 @@ class MesaView(FormMixin, DetailView): _('Mesa Diretora'), self.object) return context - @method_decorator(permission_required('sessao.change_integrantemesa')) - def post(self, request, *args, **kwargs): - self.object = self.get_object() - form = MesaForm(request.POST) - - if 'Incluir' in request.POST: - if form.is_valid(): - integrante = IntegranteMesa() - integrante.sessao_plenaria_id = self.object.id - integrante.parlamentar_id = request.POST['parlamentar'] - integrante.cargo_id = request.POST['cargo'] - integrante.save() - return self.form_valid(form) + def get_success_url(self): + pk = self.kwargs['pk'] + return reverse('sapl.sessao:mesa', kwargs={'pk': pk}) - else: - form.clean() - return self.form_valid(form) - elif 'Excluir' in request.POST: - if 'composicao_mesa' in request.POST: - ids = request.POST['composicao_mesa'].split(':') - IntegranteMesa.objects.get( - sessao_plenaria_id=self.object.id, - parlamentar_id=ids[0], - cargo_id=ids[1] - ).delete() - else: - pass - # TODO display message asking to select a member of list - return self.form_valid(form) +def atualizar_mesa(request): + """ + Esta função lida com qualquer alteração nos campos + da Mesa Diretora, atualizando os campos após cada alteração + """ + try: + sessao = SessaoPlenaria.objects.get( + id=int(request.GET['sessao'])) + except ObjectDoesNotExist: + return JsonResponse({'msg': ('Sessão Inexistente!', 0)}) + + # Atualiza os componentes da view após a mudança + composicao_mesa = IntegranteMesa.objects.filter( + sessao_plenaria=sessao.id) + + cargos_ocupados = [m.cargo for m in composicao_mesa] + cargos = CargoMesa.objects.all() + cargos_vagos = list(set(cargos) - set(cargos_ocupados)) + + parlamentares = Legislatura.objects.get( + id=sessao.legislatura.id).mandato_set.all() + parlamentares_ocupados = [m.parlamentar for m in composicao_mesa] + parlamentares_vagos = list( + set( + [p.parlamentar for p in parlamentares]) - set( + parlamentares_ocupados)) + + lista_composicao = [(c.id, c.parlamentar.__str__(), + c.cargo.__str__()) for c in composicao_mesa] + lista_parlamentares = [( + p.id, p.__str__()) for p in parlamentares_vagos] + lista_cargos = [(c.id, c.__str__()) for c in cargos_vagos] + + return JsonResponse( + {'lista_composicao': lista_composicao, + 'lista_parlamentares': lista_parlamentares, + 'lista_cargos': lista_cargos, + 'msg': ('', 1)}) + + +def insere_parlamentar_composicao(request): + """ + Esta função lida com qualquer operação de inserção + na composição da Mesa Diretora + """ + if request.user.has_perm( + '%s.add_%s' % ( + AppConfig.label, IntegranteMesa._meta.model_name)): + + composicao = IntegranteMesa() - def get_candidatos_mesa(self): - self.object = self.get_object() - lista_parlamentares = [] - lista_integrantes = [] + try: + composicao.sessao_plenaria = SessaoPlenaria.objects.get( + id=int(request.POST['sessao'])) + except MultiValueDictKeyError: + return JsonResponse({'msg': ('A Sessão informada não existe!', 0)}) - for parlamentar in Parlamentar.objects.all(): - if parlamentar.ativo: - lista_parlamentares.append(parlamentar) + try: + composicao.parlamentar = Parlamentar.objects.get( + id=int(request.POST['parlamentar'])) + except MultiValueDictKeyError: + return JsonResponse({ + 'msg': ('Nenhum parlamentar foi inserido!', 0)}) - for integrante in IntegranteMesa.objects.filter( - sessao_plenaria=self.object): - parlamentar = Parlamentar.objects.get( - id=integrante.parlamentar_id) - lista_integrantes.append(parlamentar) + try: + composicao.cargo = CargoMesa.objects.get( + id=int(request.POST['cargo'])) + parlamentar_ja_inserido = IntegranteMesa.objects.filter( + sessao_plenaria_id=composicao.sessao_plenaria.id, + cargo_id=composicao.cargo.id).exists() - lista = list(set(lista_parlamentares) - set(lista_integrantes)) - lista.sort(key=lambda x: x.nome_parlamentar) - return lista + if parlamentar_ja_inserido: + return JsonResponse({'msg': ('Parlamentar já inserido!', 0)}) - def get_cargos_mesa(self): - self.object = self.get_object() - lista_cargos = CargoMesa.objects.all() - lista_cargos_ocupados = [] + composicao.save() + + except MultiValueDictKeyError: + return JsonResponse({'msg': ('Nenhum cargo foi inserido!', 0)}) + + return JsonResponse({'msg': ('Parlamentar inserido com sucesso!', 1)}) + + else: + return JsonResponse( + {'msg': ('Você não tem permissão para esta operação!', 0)}) + + +def remove_parlamentar_composicao(request): + """ + Essa função lida com qualquer operação de remoção + na composição da Mesa Diretora + """ + if request.POST and request.user.has_perm( + '%s.delete_%s' % ( + AppConfig.label, IntegranteMesa._meta.model_name)): + + if 'composicao_mesa' in request.POST: + try: + composicao = IntegranteMesa.objects.get( + id=int(request.POST['composicao_mesa'])) + except ObjectDoesNotExist: + return JsonResponse( + {'msg': ( + 'Composição da Mesa não pôde ser removida!', 0)}) + + composicao.delete() + + return JsonResponse( + {'msg': ( + 'Parlamentar excluido com sucesso!', 1)}) + else: + return JsonResponse( + {'msg': ( + 'Selecione algum parlamentar para ser excluido!', 0)}) - for integrante in IntegranteMesa.objects.filter( - sessao_plenaria=self.object): - cargo = CargoMesa.objects.get( - id=integrante.cargo_id) - lista_cargos_ocupados.append(cargo) - lista = list(set(lista_cargos) - set(lista_cargos_ocupados)) - return lista +class ResumoOrdenacaoView(PermissionRequiredMixin, FormView): + template_name = 'sessao/resumo_ordenacao.html' + form_class = ResumoOrdenacaoForm + permission_required = {'sessao.change_resumoordenacao'} def get_success_url(self): - pk = self.kwargs['pk'] - return reverse('sapl.sessao:mesa', kwargs={'pk': pk}) + return reverse('sapl.base:sistema') + + def get_initial(self): + ordenacao = ResumoOrdenacao.objects.first() + if ordenacao: + return {'primeiro': ordenacao.primeiro, + 'segundo': ordenacao.segundo, + 'terceiro': ordenacao.terceiro, + 'quarto': ordenacao.quarto, + 'quinto': ordenacao.quinto, + 'sexto': ordenacao.sexto, + 'setimo': ordenacao.setimo, + 'oitavo': ordenacao.oitavo, + 'nono': ordenacao.nono, + 'decimo': ordenacao.decimo} + return self.initial.copy() + + def form_valid(self, form): + ordenacao = ResumoOrdenacao.objects.get_or_create()[0] + + ordenacao.primeiro = form.cleaned_data['primeiro'] + ordenacao.segundo = form.cleaned_data['segundo'] + ordenacao.terceiro = form.cleaned_data['terceiro'] + ordenacao.quarto = form.cleaned_data['quarto'] + ordenacao.quinto = form.cleaned_data['quinto'] + ordenacao.sexto = form.cleaned_data['sexto'] + ordenacao.setimo = form.cleaned_data['setimo'] + ordenacao.oitavo = form.cleaned_data['oitavo'] + ordenacao.nono = form.cleaned_data['nono'] + ordenacao.decimo = form.cleaned_data['decimo'] + + ordenacao.save() + + return HttpResponseRedirect(self.get_success_url()) class ResumoView(DetailView): @@ -987,13 +1132,9 @@ class ResumoView(DetailView): # Presença Sessão presencas = SessaoPlenariaPresenca.objects.filter( sessao_plenaria_id=self.object.id - ) + ).order_by('parlamentar__nome_parlamentar') - parlamentares_sessao = [] - for p in presencas: - parlamentar = Parlamentar.objects.get( - id=p.parlamentar_id) - parlamentares_sessao.append(parlamentar) + parlamentares_sessao = [p.parlamentar for p in presencas] context.update({'presenca_sessao': parlamentares_sessao}) @@ -1066,13 +1207,9 @@ class ResumoView(DetailView): # Presença Ordem do Dia presencas = PresencaOrdemDia.objects.filter( sessao_plenaria_id=self.object.id - ) + ).order_by('parlamentar__nome_parlamentar') - parlamentares_ordem = [] - for p in presencas: - parlamentar = Parlamentar.objects.get( - id=p.parlamentar_id) - parlamentares_ordem.append(parlamentar) + parlamentares_ordem = [p.parlamentar for p in presencas] context.update({'presenca_ordem': parlamentares_ordem}) @@ -1117,17 +1254,60 @@ class ResumoView(DetailView): for parlamentar in Parlamentar.objects.filter( id=orador.parlamentar.id): partido_sigla = Filiacao.objects.filter( - parlamentar=parlamentar).last().partido.sigla + parlamentar=parlamentar).last() if not partido_sigla: - partido_sigla = '' + sigla = '' + else: + sigla = partido_sigla.partido.sigla oradores = { - 'numero_ordem': orador.numero_ordem, - 'parlamentar': parlamentar, - 'sgl_partido': partido_sigla - } + 'numero_ordem': orador.numero_ordem, + 'parlamentar': parlamentar, + 'sgl_partido': sigla + } oradores_explicacoes.append(oradores) context.update({'oradores_explicacoes': oradores_explicacoes}) + # ===================================================================== + # Indica a ordem com a qual o template será renderizado + ordenacao = ResumoOrdenacao.objects.first() + dict_ord_template = { + 'cont_mult': 'conteudo_multimidia.html', + 'exp': 'expedientes.html', + 'id_basica': 'identificacao_basica.html', + 'lista_p': 'lista_presenca.html', + 'lista_p_o_d': 'lista_presenca_ordem_dia.html', + 'mat_exp': 'materias_expediente.html', + 'mat_o_d': 'materias_ordem_dia.html', + 'mesa_d': 'mesa_diretora.html', + 'oradores_exped': 'oradores_expediente.html', + 'oradores_expli': 'oradores_explicacoes.html' + } + + if ordenacao: + context.update( + {'primeiro_ordenacao': dict_ord_template[ordenacao.primeiro], + 'segundo_ordenacao': dict_ord_template[ordenacao.segundo], + 'terceiro_ordenacao': dict_ord_template[ordenacao.terceiro], + 'quarto_ordenacao': dict_ord_template[ordenacao.quarto], + 'quinto_ordenacao': dict_ord_template[ordenacao.quinto], + 'sexto_ordenacao': dict_ord_template[ordenacao.sexto], + 'setimo_ordenacao': dict_ord_template[ordenacao.setimo], + 'oitavo_ordenacao': dict_ord_template[ordenacao.oitavo], + 'nono_ordenacao': dict_ord_template[ordenacao.nono], + 'decimo_ordenacao': dict_ord_template[ordenacao.decimo]}) + else: + context.update( + {'primeiro_ordenacao': dict_ord_template['id_basica'], + 'segundo_ordenacao': dict_ord_template['cont_mult'], + 'terceiro_ordenacao': dict_ord_template['mesa_d'], + 'quarto_ordenacao': dict_ord_template['lista_p'], + 'quinto_ordenacao': dict_ord_template['exp'], + 'sexto_ordenacao': dict_ord_template['mat_exp'], + 'setimo_ordenacao': dict_ord_template['oradores_exped'], + 'oitavo_ordenacao': dict_ord_template['lista_p_o_d'], + 'nono_ordenacao': dict_ord_template['mat_o_d'], + 'decimo_ordenacao': dict_ord_template['oradores_expli']}) + return self.render_to_response(context) @@ -1216,8 +1396,8 @@ class VotacaoEditView(SessaoPermissionMixin): self.object = self.get_object() form = VotacaoEditForm(request.POST) - materia_id = kwargs['oid'] - ordem_id = kwargs['mid'] + materia_id = kwargs['mid'] + ordem_id = kwargs['oid'] if(int(request.POST['anular_votacao']) == 1): RegistroVotacao.objects.filter( @@ -1245,8 +1425,8 @@ class VotacaoEditView(SessaoPermissionMixin): else: titulo = _("Não definida") - materia_id = kwargs['oid'] - ordem_id = kwargs['mid'] + materia_id = kwargs['mid'] + ordem_id = kwargs['oid'] ordem = OrdemDia.objects.get(id=ordem_id) @@ -1299,7 +1479,7 @@ class VotacaoView(SessaoPermissionMixin): else: titulo = _("Não definida") - ordem_id = kwargs['mid'] + ordem_id = kwargs['oid'] ordem = OrdemDia.objects.get(id=ordem_id) qtde_presentes = PresencaOrdemDia.objects.filter( sessao_plenaria_id=self.object.id).count() @@ -1325,7 +1505,7 @@ class VotacaoView(SessaoPermissionMixin): else: titulo = _("Não definida") - ordem_id = kwargs['mid'] + ordem_id = kwargs['oid'] ordem = OrdemDia.objects.get(id=ordem_id) qtde_presentes = PresencaOrdemDia.objects.filter( sessao_plenaria_id=self.object.id).count() @@ -1343,8 +1523,8 @@ class VotacaoView(SessaoPermissionMixin): return self.form_valid(form) if form.is_valid(): - materia_id = kwargs['oid'] - ordem_id = kwargs['mid'] + materia_id = kwargs['mid'] + ordem_id = kwargs['oid'] qtde_presentes = PresencaOrdemDia.objects.filter( sessao_plenaria_id=self.object.id).count() @@ -1396,20 +1576,80 @@ class VotacaoView(SessaoPermissionMixin): kwargs={'pk': pk}) -class VotacaoNominalView(SessaoPermissionMixin): +def fechar_votacao_materia(materia): + if type(materia) == OrdemDia: + registro_votacao = RegistroVotacao.objects.filter(ordem=materia) + voto_parlamentar = VotoParlamentar.objects.filter(ordem=materia) + + elif type(materia) == ExpedienteMateria: + registro_votacao = RegistroVotacao.objects.filter( + expediente=materia) + voto_parlamentar = VotoParlamentar.objects.filter(expediente=materia) + + for v in voto_parlamentar: + v.delete() + + for r in registro_votacao: + r.delete() + + if materia.resultado: + materia.resultado = '' + materia.votacao_aberta = False + materia.save() + + +class VotacaoNominalAbstract(SessaoPermissionMixin): template_name = 'sessao/votacao/nominal.html' + ordem = None + expediente = None def get(self, request, *args, **kwargs): - ordem_id = kwargs['mid'] - ordem = OrdemDia.objects.get(id=ordem_id) - total = PresencaOrdemDia.objects.filter( - sessao_plenaria_id=ordem.sessao_plenaria_id).count() + if self.ordem: + ordem_id = kwargs['oid'] + if RegistroVotacao.objects.filter(ordem_id=ordem_id).exists(): + msg = _('Esta matéria já foi votada!') + messages.add_message(request, messages.ERROR, msg) + return HttpResponseRedirect(reverse( + 'sapl.sessao:ordemdia_list', kwargs={'pk': kwargs['pk']})) - materia = {'materia': ordem.materia, + try: + ordem = OrdemDia.objects.get(id=ordem_id) + except ObjectDoesNotExist: + raise Http404() + + presentes = PresencaOrdemDia.objects.filter( + sessao_plenaria_id=ordem.sessao_plenaria_id) + total = presentes.count() + + materia_votacao = ordem + + elif self.expediente: + expediente_id = kwargs['oid'] + if (RegistroVotacao.objects.filter( + expediente_id=expediente_id).exists()): + msg = _('Esta matéria já foi votada!') + messages.add_message(request, messages.ERROR, msg) + return HttpResponseRedirect(reverse( + 'sapl.sessao:expedientemateria_list', + kwargs={'pk': kwargs['pk']})) + + try: + expediente = ExpedienteMateria.objects.get(id=expediente_id) + except ObjectDoesNotExist: + raise Http404() + + presentes = SessaoPlenariaPresenca.objects.filter( + sessao_plenaria_id=expediente.sessao_plenaria_id) + total = presentes.count() + + materia_votacao = expediente + + materia = {'materia': materia_votacao.materia, 'ementa': sub( - ' ', ' ', strip_tags(ordem.observacao))} + ' ', ' ', strip_tags( + materia_votacao.observacao))} context = {'materia': materia, 'object': self.get_object(), - 'parlamentares': self.get_parlamentares(ordem.materia), + 'parlamentares': self.get_parlamentares(presentes), 'tipos': self.get_tipos_votacao(), 'total': total} @@ -1418,35 +1658,31 @@ class VotacaoNominalView(SessaoPermissionMixin): def post(self, request, *args, **kwargs): self.object = self.get_object() - ordem_id = kwargs['mid'] - ordem = OrdemDia.objects.get(id=ordem_id) + if self.ordem: + ordem_id = kwargs['oid'] + try: + ordem = OrdemDia.objects.get(id=ordem_id) + except ObjectDoesNotExist: + raise Http404() + + materia_votacao = ordem + + elif self.expediente: + expediente_id = kwargs['oid'] + try: + expediente = ExpedienteMateria.objects.get(id=expediente_id) + except ObjectDoesNotExist: + raise Http404() + + materia_votacao = expediente form = VotacaoNominalForm(request.POST) if 'cancelar-votacao' in request.POST: - sessao = self.object - materia = ordem.materia - presentes = PresencaOrdemDia.objects.filter( - sessao_plenaria_id=expediente.sessao_plenaria_id) - for p in presentes: - try: - voto = VotoNominal.objects.get( - parlamentar=p.parlamentar, - sessao=self.object.pk, - materia=materia) - except ObjectDoesNotExist: - pass - else: - voto.delete() - - ordem.votacao_aberta = False - ordem.save() + fechar_votacao_materia(materia_votacao) return self.form_valid(form) if form.is_valid(): - materia_id = kwargs['oid'] - ordem_id = kwargs['mid'] - votos_sim = 0 votos_nao = 0 abstencoes = 0 @@ -1466,22 +1702,35 @@ class VotacaoNominalView(SessaoPermissionMixin): elif(voto == 'Não Votou'): nao_votou += 1 - try: - votacao = RegistroVotacao.objects.get( - materia_id=materia_id, + # Caso todas as opções sejam 'Não votou', fecha a votação + if nao_votou == len(request.POST.getlist('voto_parlamentar')): + fechar_votacao_materia(materia_votacao) + return self.form_valid(form) + + if self.ordem: + votacao = RegistroVotacao.objects.filter( ordem_id=ordem_id) - except ObjectDoesNotExist: - pass - else: - votacao.delete() + elif self.expediente: + votacao = RegistroVotacao.objects.filter( + expediente_id=expediente_id) + + # Remove todas as votação desta matéria, caso existam + for v in votacao: + v.delete() votacao = RegistroVotacao() votacao.numero_votos_sim = votos_sim votacao.numero_votos_nao = votos_nao votacao.numero_abstencoes = abstencoes votacao.observacao = request.POST['observacao'] - votacao.materia_id = materia_id - votacao.ordem_id = ordem_id + + if self.ordem: + votacao.materia_id = ordem.materia.id + votacao.ordem_id = ordem_id + elif self.expediente: + votacao.materia_id = expediente.materia.id + votacao.expediente_id = expediente_id + votacao.tipo_resultado_votacao_id = int( request.POST['resultado_votacao']) votacao.save() @@ -1491,40 +1740,60 @@ class VotacaoNominalView(SessaoPermissionMixin): voto = v[0] parlamentar_id = v[1] - voto_parlamentar = VotoParlamentar() + if self.ordem: + voto_parlamentar = VotoParlamentar.objects.get_or_create( + parlamentar_id=parlamentar_id, + ordem=ordem)[0] + elif self.expediente: + voto_parlamentar = VotoParlamentar.objects.get_or_create( + parlamentar_id=parlamentar_id, + expediente=expediente)[0] + voto_parlamentar.voto = voto voto_parlamentar.parlamentar_id = parlamentar_id voto_parlamentar.votacao_id = votacao.id voto_parlamentar.save() - ordem = OrdemDia.objects.get( - sessao_plenaria_id=self.object.id, - materia_id=materia_id) resultado = TipoResultadoVotacao.objects.get( id=request.POST['resultado_votacao']) - ordem.resultado = resultado.nome - ordem.votacao_aberta = False - ordem.save() + materia_votacao.resultado = resultado.nome + materia_votacao.votacao_aberta = False + materia_votacao.save() + + # Verifica se existe algum VotoParlamentar sem RegistroVotacao + # Por exemplo, se algum parlamentar votar e sua presença for + # removida da ordem do dia/expediente antes da conclusão da + # votação + if self.ordem: + VotoParlamentar.objects.filter( + ordem=ordem, + votacao__isnull=True).delete() + elif self.expediente: + VotoParlamentar.objects.filter( + expediente=expediente, + votacao__isnull=True).delete() return self.form_valid(form) else: return self.form_invalid(form) - def get_parlamentares(self, materia): + def get_parlamentares(self, presencas): self.object = self.get_object() - presencas = PresencaOrdemDia.objects.filter( - sessao_plenaria_id=self.object.id - ) presentes = [p.parlamentar for p in presencas] + if self.ordem: + voto_parlamentar = VotoParlamentar.objects.filter( + ordem=self.kwargs['oid']) + elif self.expediente: + voto_parlamentar = VotoParlamentar.objects.filter( + expediente=self.kwargs['oid']) + for parlamentar in Parlamentar.objects.filter(ativo=True): if parlamentar in presentes: try: - voto = VotoNominal.objects.get( - parlamentar=parlamentar, - sessao=self.object.pk, - materia=materia) + voto = voto_parlamentar.get( + parlamentar=parlamentar) except ObjectDoesNotExist: yield [parlamentar, None] else: @@ -1536,256 +1805,47 @@ class VotacaoNominalView(SessaoPermissionMixin): def get_success_url(self): pk = self.kwargs['pk'] - return reverse('sapl.sessao:ordemdia_list', - kwargs={'pk': pk}) + + if self.ordem: + return reverse('sapl.sessao:ordemdia_list', + kwargs={'pk': pk}) + elif self.expediente: + return reverse('sapl.sessao:expedientemateria_list', + kwargs={'pk': pk}) -class VotacaoNominalEditView(SessaoPermissionMixin): +class VotacaoNominalEditAbstract(SessaoPermissionMixin): template_name = 'sessao/votacao/nominal_edit.html' def get(self, request, *args, **kwargs): context = {} - materia_id = kwargs['oid'] - ordem_id = kwargs['mid'] - - votacao = RegistroVotacao.objects.get( - materia_id=materia_id, - ordem_id=ordem_id) - ordem = OrdemDia.objects.get(id=ordem_id) - votos = VotoParlamentar.objects.filter(votacao_id=votacao.id) - - list_votos = [] - for v in votos: - parlamentar = Parlamentar.objects.get(id=v.parlamentar_id) - list_votos.append({'parlamentar': parlamentar, 'voto': v.voto}) + if self.ordem: + ordem_id = kwargs['oid'] - context.update({'votos': list_votos}) - - materia = {'materia': ordem.materia, - 'ementa': sub( - ' ', ' ', strip_tags(ordem.observacao))} - context.update({'materia': materia}) - - votacao_existente = {'observacao': sub( - ' ', ' ', strip_tags(votacao.observacao)), - 'resultado': votacao.tipo_resultado_votacao.nome, - 'tipo_resultado': - votacao.tipo_resultado_votacao_id} - context.update({'votacao': votacao_existente, - 'tipos': self.get_tipos_votacao()}) - - return self.render_to_response(context) - - def post(self, request, *args, **kwargs): - self.object = self.get_object() - form = VotacaoEditForm(request.POST) - - materia_id = kwargs['oid'] - ordem_id = kwargs['mid'] - - sessao = self.object - ordem = ExpedienteMateria.objects.get(id=ordem_id) - presentes = PresencaOrdemDia.objects.filter( - sessao_plenaria_id=ordem.sessao_plenaria_id) - for p in presentes: try: - voto = VotoNominal.objects.get( - parlamentar=p.parlamentar, - sessao=self.object.pk, - materia=materia_id) + ordem = OrdemDia.objects.get(id=ordem_id) + votacao = RegistroVotacao.objects.get( + ordem_id=ordem_id) except ObjectDoesNotExist: - pass - else: - voto.delete() - - if(int(request.POST['anular_votacao']) == 1): - registro = RegistroVotacao.objects.get( - materia_id=materia_id, - ordem_id=ordem_id) - - ordem = OrdemDia.objects.get( - sessao_plenaria_id=self.object.id, - materia_id=materia_id) - ordem.resultado = '' - ordem.votacao_aberta = False - ordem.save() - - try: - votacao = VotoParlamentar.objects.filter( - votacao_id=registro.id) - for v in votacao: - v.delete() - except: - pass - - return self.form_valid(form) - - def get_tipos_votacao(self): - for tipo in TipoResultadoVotacao.objects.all(): - yield tipo - - def get_success_url(self): - pk = self.kwargs['pk'] - return reverse('sapl.sessao:ordemdia_list', - kwargs={'pk': pk}) - + raise Http404() -class VotacaoNominalExpedienteView(SessaoPermissionMixin): - template_name = 'sessao/votacao/nominal.html' - - def get(self, request, *args, **kwargs): - expediente_id = kwargs['mid'] - expediente = ExpedienteMateria.objects.get(id=expediente_id) - total = SessaoPlenariaPresenca.objects.filter( - sessao_plenaria_id=expediente.sessao_plenaria_id).count() - - materia = {'materia': expediente.materia, - 'ementa': sub( - ' ', ' ', strip_tags(expediente.observacao))} - context = {'materia': materia, 'object': self.get_object(), - 'parlamentares': self.get_parlamentares(expediente.materia), - 'tipos': self.get_tipos_votacao(), - 'total': total} - - return self.render_to_response(context) - - def post(self, request, *args, **kwargs): - self.object = self.get_object() - - expediente_id = kwargs['mid'] - expediente = ExpedienteMateria.objects.get(id=expediente_id) - - form = VotacaoNominalForm(request.POST) - - if 'cancelar-votacao' in request.POST: - sessao = self.object - expediente_id = kwargs['mid'] - expediente = ExpedienteMateria.objects.get(id=expediente_id) - materia = expediente.materia - presentes = SessaoPlenariaPresenca.objects.filter( - sessao_plenaria_id=expediente.sessao_plenaria_id) - for p in presentes: - try: - voto = VotoNominal.objects.get( - parlamentar=p.parlamentar, - sessao=self.object.pk, - materia=materia) - except ObjectDoesNotExist: - pass - else: - voto.delete() - - expediente.votacao_aberta = False - expediente.save() - return self.form_valid(form) - - if form.is_valid(): - materia_id = kwargs['oid'] - expediente_id = kwargs['mid'] - - votos_sim = 0 - votos_nao = 0 - abstencoes = 0 - nao_votou = 0 - - for votos in request.POST.getlist('voto_parlamentar'): - v = votos.split(':') - voto = v[0] - parlamentar_id = v[1] + materia = ordem.materia + observacao = ordem.observacao - if(voto == 'Sim'): - votos_sim += 1 - elif(voto == 'Não'): - votos_nao += 1 - elif(voto == 'Abstenção'): - abstencoes += 1 - elif(voto == 'Não Votou'): - nao_votou += 1 + elif self.expediente: + expediente_id = kwargs['oid'] try: - votacao = RegistroVotacao() - votacao.numero_votos_sim = votos_sim - votacao.numero_votos_nao = votos_nao - votacao.numero_abstencoes = abstencoes - votacao.observacao = request.POST['observacao'] - votacao.materia_id = materia_id - votacao.expediente = expediente - votacao.tipo_resultado_votacao_id = int( - request.POST['resultado_votacao']) - votacao.save() - except: - return self.form_invalid(form) - else: + expediente = ExpedienteMateria.objects.get(id=expediente_id) votacao = RegistroVotacao.objects.get( - materia_id=materia_id, - expediente_id=expediente) - - for votos in request.POST.getlist('voto_parlamentar'): - v = votos.split(':') - voto = v[0] - parlamentar_id = v[1] - - voto_parlamentar = VotoParlamentar() - voto_parlamentar.voto = voto - voto_parlamentar.parlamentar_id = parlamentar_id - voto_parlamentar.votacao_id = votacao.id - voto_parlamentar.save() - - expediente = ExpedienteMateria.objects.get( - sessao_plenaria_id=self.object.id, - materia_id=materia_id) - resultado = TipoResultadoVotacao.objects.get( - id=request.POST['resultado_votacao']) - expediente.resultado = resultado.nome - expediente.votacao_aberta = False - expediente.save() - - return self.form_valid(form) - else: - return self.form_invalid(form) - - def get_parlamentares(self, materia): - self.object = self.get_object() - presencas = SessaoPlenariaPresenca.objects.filter( - sessao_plenaria_id=self.object.id - ) - presentes = [p.parlamentar for p in presencas] - - for parlamentar in Parlamentar.objects.filter(ativo=True): - if parlamentar in presentes: - try: - voto = VotoNominal.objects.get( - parlamentar=parlamentar, - sessao=self.object.pk, - materia=materia) - except ObjectDoesNotExist: - yield [parlamentar, None] - else: - yield [parlamentar, voto.voto] - - def get_tipos_votacao(self): - for tipo in TipoResultadoVotacao.objects.all(): - yield tipo - - def get_success_url(self): - pk = self.kwargs['pk'] - return reverse('sapl.sessao:expedientemateria_list', - kwargs={'pk': pk}) - - -class VotacaoNominalExpedienteEditView(SessaoPermissionMixin): - template_name = 'sessao/votacao/nominal_edit.html' + expediente_id=expediente_id) + except ObjectDoesNotExist: + raise Http404() - def get(self, request, *args, **kwargs): - context = {} - materia_id = kwargs['oid'] - expediente_id = kwargs['mid'] + materia = expediente.materia + observacao = expediente.observacao - votacao = RegistroVotacao.objects.get( - materia_id=materia_id, - expediente_id=expediente_id) - expediente = ExpedienteMateria.objects.get(id=expediente_id) votos = VotoParlamentar.objects.filter(votacao_id=votacao.id) list_votos = [] @@ -1795,9 +1855,9 @@ class VotacaoNominalExpedienteEditView(SessaoPermissionMixin): context.update({'votos': list_votos}) - materia = {'materia': expediente.materia, + materia = {'materia': materia, 'ementa': sub( - ' ', ' ', strip_tags(expediente.observacao))} + ' ', ' ', strip_tags(observacao))} context.update({'materia': materia}) votacao_existente = {'observacao': sub( @@ -1814,45 +1874,25 @@ class VotacaoNominalExpedienteEditView(SessaoPermissionMixin): self.object = self.get_object() form = VotacaoEditForm(request.POST) - materia_id = kwargs['oid'] - expediente_id = kwargs['mid'] + if self.ordem: + ordem_id = kwargs['oid'] - sessao = self.object - expediente = ExpedienteMateria.objects.get(id=expediente_id) - presentes = SessaoPlenariaPresenca.objects.filter( - sessao_plenaria_id=expediente.sessao_plenaria_id) - for p in presentes: try: - voto = VotoNominal.objects.get( - parlamentar=p.parlamentar, - sessao=self.object.pk, - materia=materia_id) + materia_votacao = OrdemDia.objects.get(id=ordem_id) except ObjectDoesNotExist: - pass - else: - voto.delete() - - if(int(request.POST['anular_votacao']) == 1): - registro = RegistroVotacao.objects.get( - materia_id=materia_id, - expediente_id=expediente_id) + raise Http404() - expediente = ExpedienteMateria.objects.get( - sessao_plenaria_id=self.object.id, - materia_id=materia_id) - expediente.resultado = '' - expediente.votacao_aberta = False - expediente.save() + elif self.expediente: + expediente_id = kwargs['oid'] try: - votacao = VotoParlamentar.objects.filter( - votacao_id=registro.id) - for v in votacao: - v.delete() - except: - pass + materia_votacao = ExpedienteMateria.objects.get( + id=expediente_id) + except ObjectDoesNotExist: + raise Http404() - registro.delete() + if(int(request.POST['anular_votacao']) == 1): + fechar_votacao_materia(materia_votacao) return self.form_valid(form) @@ -1862,16 +1902,42 @@ class VotacaoNominalExpedienteEditView(SessaoPermissionMixin): def get_success_url(self): pk = self.kwargs['pk'] - return reverse('sapl.sessao:expedientemateria_list', - kwargs={'pk': pk}) + + if self.ordem: + return reverse('sapl.sessao:ordemdia_list', + kwargs={'pk': pk}) + elif self.expediente: + return reverse('sapl.sessao:expedientemateria_list', + kwargs={'pk': pk}) + + +class VotacaoNominalView(VotacaoNominalAbstract): + ordem = True + expediente = False + + +class VotacaoNominalExpedienteView(VotacaoNominalAbstract): + expediente = True + ordem = False + + +class VotacaoNominalEditView(VotacaoNominalEditAbstract): + ordem = True + expediente = False + + +class VotacaoNominalExpedienteEditView(VotacaoNominalEditAbstract): + expediente = True + ordem = False + class VotacaoNominalExpedienteDetailView(DetailView): template_name = 'sessao/votacao/nominal_detail.html' def get(self, request, *args, **kwargs): context = {} - materia_id = kwargs['oid'] - expediente_id = kwargs['mid'] + materia_id = kwargs['mid'] + expediente_id = kwargs['oid'] votacao = RegistroVotacao.objects.get( materia_id=materia_id, @@ -1933,7 +1999,7 @@ class VotacaoExpedienteView(SessaoPermissionMixin): else: titulo = _("Não definida") - expediente_id = kwargs['mid'] + expediente_id = kwargs['oid'] expediente = ExpedienteMateria.objects.get(id=expediente_id) qtde_presentes = SessaoPlenariaPresenca.objects.filter( sessao_plenaria_id=self.object.id).count() @@ -1960,7 +2026,7 @@ class VotacaoExpedienteView(SessaoPermissionMixin): else: titulo = _("Não definida") - expediente_id = kwargs['mid'] + expediente_id = kwargs['oid'] expediente = ExpedienteMateria.objects.get(id=expediente_id) qtde_presentes = SessaoPlenariaPresenca.objects.filter( sessao_plenaria_id=self.object.id).count() @@ -1979,8 +2045,8 @@ class VotacaoExpedienteView(SessaoPermissionMixin): return self.form_valid(form) if form.is_valid(): - materia_id = kwargs['oid'] - expediente_id = kwargs['mid'] + materia_id = kwargs['mid'] + expediente_id = kwargs['oid'] qtde_presentes = SessaoPlenariaPresenca.objects.filter( sessao_plenaria_id=self.object.id).count() @@ -2063,8 +2129,8 @@ class VotacaoExpedienteEditView(SessaoPermissionMixin): else: titulo = _("Não definida") - materia_id = kwargs['oid'] - expediente_id = kwargs['mid'] + materia_id = kwargs['mid'] + expediente_id = kwargs['oid'] expediente = ExpedienteMateria.objects.get(id=expediente_id) @@ -2095,8 +2161,8 @@ class VotacaoExpedienteEditView(SessaoPermissionMixin): self.object = self.get_object() form = VotacaoEditForm(request.POST) - materia_id = kwargs['oid'] - expediente_id = kwargs['mid'] + materia_id = kwargs['mid'] + expediente_id = kwargs['oid'] if(int(request.POST['anular_votacao']) == 1): try: diff --git a/sapl/settings.py b/sapl/settings.py index 694c12a95..9bb544a15 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -53,6 +53,7 @@ SAPL_APPS = ( 'sapl.lexml', 'sapl.painel', 'sapl.protocoloadm', + 'sapl.redireciona_urls', 'sapl.compilacao', 'sapl.api', @@ -85,6 +86,7 @@ INSTALLED_APPS = ( ) + SAPL_APPS # FTS = Full Text Search +HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' SEARCH_BACKEND = 'haystack.backends.whoosh_backend.WhooshEngine' SEARCH_URL = ('PATH', PROJECT_DIR.child('whoosh')) @@ -180,6 +182,8 @@ DATABASES = { # https://docs.djangoproject.com/en/1.9/topics/auth/customizing/#substituting-a-custom-user-model AUTH_USER_MODEL = 'auth.User' +X_FRAME_OPTIONS = 'ALLOWALL' + EMAIL_HOST = config('EMAIL_HOST', default='localhost') EMAIL_PORT = config('EMAIL_PORT', cast=int, default=587) EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='') @@ -189,14 +193,14 @@ EMAIL_SEND_USER = config('EMAIL_SEND_USER', cast=str, default='') DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', cast=str, default='') SERVER_EMAIL = config('SERVER_EMAIL', cast=str, default='') -MAX_DOC_UPLOAD_SIZE = 5 * 1024 * 1024 # 5MB +MAX_DOC_UPLOAD_SIZE = 20 * 1024 * 1024 # 20MB MAX_IMAGE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB # Internationalization # https://docs.djangoproject.com/en/1.8/topics/i18n/ LANGUAGE_CODE = 'pt-br' LANGUAGES = ( - ('pt-br', u'Português'), + ('pt-br','Português'), ) TIME_ZONE = 'America/Sao_Paulo' diff --git a/sapl/static/img/avatar.png b/sapl/static/img/avatar.png new file mode 100644 index 000000000..959c168a5 Binary files /dev/null and b/sapl/static/img/avatar.png differ diff --git a/sapl/static/img/beta.png b/sapl/static/img/beta.png new file mode 100644 index 000000000..fe23d0e6d Binary files /dev/null and b/sapl/static/img/beta.png differ diff --git a/sapl/static/js/app.js b/sapl/static/js/app.js index bbb54171a..24728945c 100644 --- a/sapl/static/js/app.js +++ b/sapl/static/js/app.js @@ -89,10 +89,12 @@ function autorModal() { }); $("#pesquisar").click(function() { - var query = $("#q").val() + var name_in_query = $("#q").val() + var q_0 = "q_0=parlamentar_set__ativo,parlamentar_set__nome_parlamentar__icontains" + var q_1 = "q_1=True," + name_in_query + query = q_0 + "&" + q_1 - - $.get("/api/autor?q=" + query, function(data, status) { + $.get("/api/autor?" + query, function(data, status) { $("#div-resultado").children().remove(); if (data.pagination.total_entries == 0) { $("#selecionar").attr("hidden", "hidden"); diff --git a/sapl/static/styles/app.scss b/sapl/static/styles/app.scss index b7d83a3df..f1a1ff684 100644 --- a/sapl/static/styles/app.scss +++ b/sapl/static/styles/app.scss @@ -235,6 +235,8 @@ fieldset { .avatar-parlamentar { height: 84px; width: 84px; + margin: 0 auto; + display: table; } /* INDEX */ diff --git a/sapl/templates/ajuda/sessao_plenaria_materias_ordem_dia.html b/sapl/templates/ajuda/sessao_plenaria_materias_ordem_dia.html index ea5bf4b6c..9505abd97 100644 --- a/sapl/templates/ajuda/sessao_plenaria_materias_ordem_dia.html +++ b/sapl/templates/ajuda/sessao_plenaria_materias_ordem_dia.html @@ -13,7 +13,7 @@
    Na identificação da matéria há um link que, quando acionado, permite o acesso aos meta dados da matéria propriamente.

    As matérias legislativas são inseridas na Ordem do Dia, por meio da função Ordem do Dia, integrada a Sessão Plenária.

    Esta função permite o acesso as funções inclusão individual ou de várias matérias na mesma transação, conforme o botão que for acionado, via clique do mouse.
    -
    Também, é possível reordenar as matérias na Ordem do Dia, de modo a restaurar o número de ordem sequencial, bastando para isso, clicar no botão 'Reordenar Matérias na Ordem do Dia', as quais serão renumeradas em ordem de tipo, ano e número.
    +
    Também, é possível ajustar as matérias na Ordem do Dia, de modo a restaurar o número de ordem sequencial, bastando para isso, clicar no botão 'Ajustar Ordenação na Ordem do Dia', as quais serão renumeradas em ordem de tipo, ano e número.

    O retorno a tela anterior é feito ao acionar o botão 'Retornar', que se encontra na parte inferior da tela.

    Anterior | diff --git a/sapl/templates/base.html b/sapl/templates/base.html index 51351e7a0..7c0a72ca2 100644 --- a/sapl/templates/base.html +++ b/sapl/templates/base.html @@ -28,8 +28,8 @@
    + {% if not request|has_iframe %} {% block navigation %} -
    + {{ value|safe|default:"" }} {% else %} - {{ value|safe }} + {{ value|safe|default:"" }} {% endif %} {% endif %} diff --git a/sapl/templates/email/acompanhar.html b/sapl/templates/email/acompanhar.html index c95092bf2..bd1a20263 100644 --- a/sapl/templates/email/acompanhar.html +++ b/sapl/templates/email/acompanhar.html @@ -1,14 +1,12 @@ {% load i18n %} {% load static %} -

    - Logo -

    -

    {{casa_legislativa}} -
    - Sistema de Apoio ao Processo Legislativo -

    + +

    {{casa_legislativa}} +
    + Sistema de Apoio ao Processo Legislativo +

    +

    Registramos seu pedido para acompanhamento por e-mail da matéria legislativa identificada a seguir:

    {{materia}} - {{descricao_materia}}
    {{ementa}}
    diff --git a/sapl/templates/email/tramitacao.html b/sapl/templates/email/tramitacao.html index 6ca454903..4b30a1dc5 100644 --- a/sapl/templates/email/tramitacao.html +++ b/sapl/templates/email/tramitacao.html @@ -1,38 +1,35 @@ {% load i18n %} {% load static %} - - -

    - Logo -

    -

    {{casa_legislativa}} -
    - Sistema de Apoio ao Processo Legislativo -

    -

    A seguinte matéria de seu interesse sofreu - tramitação registrada em {{data_registro}} -

    + + +

    {{casa_legislativa}} +
    + Sistema de Apoio ao Processo Legislativo +

    +

    A seguinte matéria, de seu interesse, sofreu + Tramitação registrada em {{data_registro}}. +

    - {{materia}} - {{descricao_materia}} -

    + {{materia}} - {{descricao_materia}} +

    Autoria:
    {% for autor in autoria %} - {{ autor }}
    + {{ autor }}
    {% endfor %}

    - Data da ação: {{data}}
    - Status: {{status}}
    - Texto da ação: {{texto_acao}}

    -
    -

    - - Clique aqui para excluir seu e-mail da lista de envio -

    -

    Esta é uma mensagem automática. - Por favor, não a responda.

    + Data da ação: {{data}}
    + Status: {{status}}
    + Localização Atual: {{localizacao}}
    + Texto da ação: {{texto_acao}}

    +
    +

    + + Clique aqui para excluir seu e-mail da lista de envio +

    +

    Esta é uma mensagem automática. + Por favor, não a responda.

    diff --git a/sapl/templates/email/tramitacao.txt b/sapl/templates/email/tramitacao.txt index 0e84653e8..f0a06687a 100644 --- a/sapl/templates/email/tramitacao.txt +++ b/sapl/templates/email/tramitacao.txt @@ -3,7 +3,7 @@ Sistema de Apoio ao Processo Legislativo ----------------------------------------- -A seguinte matéria de seu interesse sofreu tramitação registrada em {{data_registro}} +A seguinte matéria, de seu interesse, sofreu Tramitação registrada em {{data_registro}} Matéria: {{materia}} - {{descricao_materia}} @@ -18,6 +18,8 @@ Data da ação: {{data}} Status: {{status}} +Localização Atual: {{localizacao}} + Texto da ação: {{texto_acao}} Acesse o link abaixo para excluir seu e-mail da lista de envio diff --git a/sapl/templates/materia/documentoacessorio_form.html b/sapl/templates/materia/documentoacessorio_form.html index fb5810653..e25d5d3ab 100644 --- a/sapl/templates/materia/documentoacessorio_form.html +++ b/sapl/templates/materia/documentoacessorio_form.html @@ -2,5 +2,5 @@ {% load i18n crispy_forms_tags %} {% block base_content %} - {% crispy form helper %} + {% crispy form %} {% endblock %} diff --git a/sapl/templates/materia/em_lote/acessorio.html b/sapl/templates/materia/em_lote/acessorio.html index 5035d8956..9f5407a5f 100644 --- a/sapl/templates/materia/em_lote/acessorio.html +++ b/sapl/templates/materia/em_lote/acessorio.html @@ -14,7 +14,7 @@ {% else %}

    {% blocktrans with object_list.count as total_materias %}Foram encontradas {{total_materias}} matérias.{% endblocktrans %}

    {% endif %} -
    + {% csrf_token %}
    Documento Acessório @@ -40,22 +40,9 @@
    - +
    - - -
    -
    -
    -
    -
    -
    -
    @@ -76,6 +63,13 @@
    Matérias para inclusão do Documento Acessório +
    +
    + +
    +
    @@ -98,3 +92,13 @@ {% endif %} {% endif %} {% endblock detail_content %} +{% block extra_js %} + +{% endblock %} diff --git a/sapl/templates/materia/em_lote/tramitacao.html b/sapl/templates/materia/em_lote/tramitacao.html index 687dcddd2..0c3b11be9 100644 --- a/sapl/templates/materia/em_lote/tramitacao.html +++ b/sapl/templates/materia/em_lote/tramitacao.html @@ -39,8 +39,8 @@
    - {% if unidade_local|length > 1 %}{% endif %} - + {% if unidade_local|length > 1 %}{% endif %} {% for u in unidade_local %} {% endfor %}
    @@ -93,11 +93,19 @@
    2. Selecione as matérias para primeira tramitação:
    Matéria
    +
    +
    + +
    +
    {% for materia in object_list %} + {% endfor %} {% else %} diff --git a/sapl/templates/materia/proposicao_detail.html b/sapl/templates/materia/proposicao_detail.html index 007b63b6b..ab1939aa9 100644 --- a/sapl/templates/materia/proposicao_detail.html +++ b/sapl/templates/materia/proposicao_detail.html @@ -110,19 +110,30 @@ - {% if object.materia_de_vinculo %} -

    {% trans "Vínculo com a Matéria Legislativa" %}

    -
    -
    -
    -
    -
    {{object.materia_de_vinculo}}
    - +
    + {% if object.conteudo_gerado_related %} +
    +

    {% trans "Conteúdo Gerado" %}

    +
    -
    -
    - {% endif %} + {% endif %} + {% if object.materia_de_vinculo %} +
    +

    {% trans "Vínculo com a Matéria Legislativa" %}

    + +
    + {% endif %} +
    {% endblock detail_content %} diff --git a/sapl/templates/materia/tipoproposicao_form.html b/sapl/templates/materia/tipoproposicao_form.html index 25943b466..ec8facf7f 100644 --- a/sapl/templates/materia/tipoproposicao_form.html +++ b/sapl/templates/materia/tipoproposicao_form.html @@ -1,6 +1,6 @@ {% extends "crud/form.html" %} {% load i18n %} -aaa + {% block extra_js %} + +{% endblock %} diff --git a/sapl/templates/navbar.yaml b/sapl/templates/navbar.yaml index 80235ecb6..502fb09f1 100644 --- a/sapl/templates/navbar.yaml +++ b/sapl/templates/navbar.yaml @@ -5,10 +5,10 @@ - title: {% trans 'Institucional' %} children: - - title: {% trans 'Mesa Diretora' %} - url: sapl.parlamentares:mesa_diretora - title: {% trans 'Comissões' %} url: sapl.comissoes:comissao_list + - title: {% trans 'Mesa Diretora' %} + url: sapl.parlamentares:mesa_diretora - title: {% trans 'Parlamentares' %} url: sapl.parlamentares:parlamentar_list @@ -26,21 +26,21 @@ - title: {% trans 'Atividade Legislativa' %} children: + - title: {% trans 'Acessório em Lote' %} + url: sapl.materia:acessorio_em_lote + check_permission: materia.list_documentoacessorio {% comment %} FIXME transformar para checagens de menu_[funcionalidade]{% endcomment%} + - title: {% trans 'Matérias Legislativas' %} + url: sapl.materia:pesquisar_materia + - title: {% trans 'Pautas das Sessões' %} + url: sapl.sessao:pesquisar_pauta - title: {% trans 'Proposições' %} url: sapl.materia:proposicao_list check_permission: materia.add_proposicao - - title: {% trans 'Matérias Legislativas' %} - url: sapl.materia:pesquisar_materia - title: {% trans 'Sessões Plenárias' %} url: sapl.sessao:pesquisar_sessao - - title: {% trans 'Pautas das Sessões' %} - url: sapl.sessao:pesquisar_pauta - title: {% trans 'Tramitação em Lote' %} url: sapl.materia:primeira_tramitacao_em_lote check_permission: materia.list_tramitacao {% comment %} FIXME transformar para checagens de menu_[funcionalidade]{% endcomment%} - - title: {% trans 'Acessório em Lote' %} - url: sapl.materia:acessorio_em_lote - check_permission: materia.list_documentoacessorio {% comment %} FIXME transformar para checagens de menu_[funcionalidade]{% endcomment%} - title: {% trans 'Normas Jurídicas' %} children: diff --git a/sapl/templates/norma/normajuridica_detail.html b/sapl/templates/norma/normajuridica_detail.html index ab1df921e..ed52564ed 100644 --- a/sapl/templates/norma/normajuridica_detail.html +++ b/sapl/templates/norma/normajuridica_detail.html @@ -14,15 +14,15 @@ {% comment %}TODO Transformar os links em URLs diretamente no CRUD{% endcomment %} {% if column.text|url %} - + {% elif column.verbose_name == 'Matéria' %} {% if object.materia.id %} - + {% else %} -
    {{ column.text|safe }}
    +
    {{ column.text|safe|default:"" }}
    {% endif %} {% else %} -
    {{ column.text|safe }}
    +
    {{ column.text|safe|default:"" }}
    {% endif %}
    diff --git a/sapl/templates/painel/controlador.html b/sapl/templates/painel/controlador.html deleted file mode 100644 index 32ed27913..000000000 --- a/sapl/templates/painel/controlador.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block base_content %} - -STATUS: -{% if painel.aberto %} -ABERTO -{% else %} -FECHADO -{% endif %} -
    - - {% csrf_token %} - Tipo de painel: {{ painel.get_mostrar_display }}
    - {% for id, value in PAINEL_TYPES %} - -
    - {% endfor %} -
    - - - - -
    -
    -Voltar - -{% endblock %} diff --git a/sapl/templates/painel/voto_nominal.html b/sapl/templates/painel/voto_nominal.html index faa2e87b7..a9d8e96f4 100644 --- a/sapl/templates/painel/voto_nominal.html +++ b/sapl/templates/painel/voto_nominal.html @@ -21,7 +21,7 @@ } - {% if permissao and presente %} + {% if not error_message %}

    {{sessao}}

    Matéria
    @@ -115,3 +123,26 @@ {% endif %} {% endif %} {% endblock detail_content %} +{% block extra_js %} + +{% endblock %} diff --git a/sapl/templates/materia/materialegislativa_filter.html b/sapl/templates/materia/materialegislativa_filter.html index a36f6cceb..d7b0bb3ec 100644 --- a/sapl/templates/materia/materialegislativa_filter.html +++ b/sapl/templates/materia/materialegislativa_filter.html @@ -47,6 +47,10 @@ Apresentação: {{ m.data_apresentacao }}
    {% endif %} + {% if m.data_fim_prazo %} + Data Fim Prazo (Matéria): {{ m.data_fim_prazo|default_if_none:"" }} +
    + {% endif %} {% if m.numeracao_set.first %} Processo: {{ m.numeracao_set.first.numero_materia }} / {{ m.numeracao_set.first.ano_materia }}
    @@ -56,13 +60,11 @@ {% endif %} {% if m.autoria_set.all %} - Autores: + Autor: {% for a in m.autoria_set.all %} - {% if not forloop.first %} - ,    {{a.autor}} - {% else %} -  {{a.autor}} - {% endif %} + {% if a.primeiro_autor %} +  {{a.autor.nome}} + {% endif %} {% endfor %}
    {% endif %} @@ -70,7 +72,8 @@ Localização Atual:  {{m.tramitacao_set.last.unidade_tramitacao_destino}}
    {% endif %} {% if m.tramitacao_set.last.status %} - Status:  {{m.tramitacao_set.last.status}}
    + Status:  {{m.tramitacao_set.last.status}}
    + Data Fim Prazo (Tramitação): {{m.tramitacao_set.last.data_fim_prazo|default_if_none:""}}
    {% endif %} {% if m.registrovotacao_set.exists %} Data da última Votação: @@ -125,9 +128,9 @@ {% endfor %} {% endif %}

    - {% if m.tramitacao_set.all.exists %} - Acompanhar Matéria - {% endif %} + + Acompanhar Matéria +
    @@ -34,7 +34,19 @@

    -

    Voto: {{voto_parlamentar}}

    +

    + +

    Voto: + {% if voto_parlamentar == "Sim" %} + {{voto_parlamentar}} + {% elif voto_parlamentar == "Não" %} + {{voto_parlamentar}} + {% elif voto_parlamentar == "Abstenção" %} + {{voto_parlamentar}} + {% endif %} +

    +
    +



    @@ -48,38 +60,33 @@

    {% csrf_token %} -
    - - -
    - -
    - -
    +
    +
    +
    + +   + +   + +
    +
    +
    + - {% elif not permissao %} - {% if error_message %} -

    {{error_message}}

    - {% else %} -

    Usuário sem permissão para participar de votações.

    - {% endif %} - {% elif not presente %} - {% if error_message %} -

    {{error_message}}

    {% else %} -

    Usuário não presente na Sessão Plenária.

    - {% endif %} - {% else %} - {% if error_message %}

    {{error_message}}

    - {% else %} -

    Usuário não presente na Sessão Plenária e sem permissão para votações.

    +
    +
    +
    +
    {% endif %} - {% endif %} + + \ No newline at end of file diff --git a/sapl/templates/parlamentares/frente_form.html b/sapl/templates/parlamentares/frente_form.html new file mode 100644 index 000000000..5d9ae8fc1 --- /dev/null +++ b/sapl/templates/parlamentares/frente_form.html @@ -0,0 +1,128 @@ +{% extends "crud/form.html" %} +{% load i18n %} +{% load crispy_forms_tags %} +{% load common_tags %} + +{% block base_content %} +
    + {% csrf_token %} + +
    +
    + {{ form.nome|as_crispy_field }} +
    +
    + +
    +
    + {{ form.data_criacao|as_crispy_field }} +
    +
    + {{ form.data_extincao|as_crispy_field }} +
    +
    + + + +
    +
    + {{ form.descricao|as_crispy_field }} +
    +
    + +
    + +
    + +{% endblock base_content %} + +{% block extra_js %} + +{% endblock %} diff --git a/sapl/templates/parlamentares/layouts.yaml b/sapl/templates/parlamentares/layouts.yaml index c839c1b53..57a3b86ba 100644 --- a/sapl/templates/parlamentares/layouts.yaml +++ b/sapl/templates/parlamentares/layouts.yaml @@ -71,7 +71,8 @@ Filiacao: Mandato: {% trans 'Mandato' %}: - legislatura coligacao votos_recebidos - - data_fim_mandato data_expedicao_diploma titular + - data_inicio_mandato data_fim_mandato + - data_expedicao_diploma titular - tipo_afastamento - observacao diff --git a/sapl/templates/parlamentares/materias.html b/sapl/templates/parlamentares/materias.html new file mode 100644 index 000000000..c5e1448c8 --- /dev/null +++ b/sapl/templates/parlamentares/materias.html @@ -0,0 +1,57 @@ +{% extends "crud/detail.html" %} +{% load i18n %} +{% load common_tags %} + +{% block actions %}{% endblock %} + +{% block detail_content %} + +
    +

    + Matérias ({{nome_parlamentar}}) +

    +
    +

    Primeiro Autor

    +
    + +{% for autoria in autoria.0 %} + + {% for materias in autoria.1 %} + + + + + + {% endfor %} +{%endfor %} +

    Ano: {{ autoria.0 }}

    {{ materias.1 }}  + + {{ materias.2}} + +  {{ materias.3}}
    + +

    Total: {{ autoria.1 }}


    + +

    Co-Autor

    +
    + +{% for coautoria in coautoria.0 %} + + {% for materias in coautoria.1 %} + + + + + + {% endfor %} +{%endfor %} +

    Ano: {{ coautoria.0 }}

    {{ materias.1 }}  + + {{ materias.2}} + +  {{ materias.3}}
    + +

    Total: {{ coautoria.1 }}


    + + +{% endblock detail_content %} diff --git a/sapl/templates/parlamentares/parlamentar_perfil_publico.html b/sapl/templates/parlamentares/parlamentar_perfil_publico.html index 1c5ead0fc..705eac9f5 100644 --- a/sapl/templates/parlamentares/parlamentar_perfil_publico.html +++ b/sapl/templates/parlamentares/parlamentar_perfil_publico.html @@ -61,7 +61,7 @@
    -

    Fax:   {{object.numero_gab_parlamentar|default_if_none:"Não informado"}}

    +

    Fax:   {{object.fax|default_if_none:"Não informado"}}

    diff --git a/sapl/templates/parlamentares/public_composicaomesa_form.html b/sapl/templates/parlamentares/public_composicaomesa_form.html index 6eaa3f6e5..d9150da59 100644 --- a/sapl/templates/parlamentares/public_composicaomesa_form.html +++ b/sapl/templates/parlamentares/public_composicaomesa_form.html @@ -46,7 +46,7 @@ {% for p in composicao_mesa %} {% if p.parlamentar.fotografia %} - + {% else %} {% endif %} @@ -140,4 +140,4 @@ function altera_field(id_legislatura, id_sessao=null){ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/sapl/templates/parlamentares/subnav.yaml b/sapl/templates/parlamentares/subnav.yaml index 0be27f340..07632e5ca 100644 --- a/sapl/templates/parlamentares/subnav.yaml +++ b/sapl/templates/parlamentares/subnav.yaml @@ -3,6 +3,8 @@ url: parlamentar_detail - title: {% trans 'Mandatos' %} url: mandato_list +- title: {% trans 'Matérias' %} + url: parlamentar_materias - title: {% trans 'Filiações Partidárias' %} url: filiacao_list - title: {% trans 'Dependentes' %} @@ -10,7 +12,7 @@ check_permission: parlamentares.list_dependente - title: {% trans 'Comissões' %} url: participacao_parlamentar_list -- title: {% trans 'Matérias' %} +- title: {% trans 'Proposições' %} url: proposicao_parlamentar_list check_permission: materia.add_proposicao - title: {% trans 'Relatorias' %} diff --git a/sapl/templates/protocoloadm/documentoadministrativo_detail.html b/sapl/templates/protocoloadm/documentoadministrativo_detail.html deleted file mode 100644 index 4e38ce963..000000000 --- a/sapl/templates/protocoloadm/documentoadministrativo_detail.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "crud/detail.html" %} -{% load i18n %} -{% block actions %} -{% load common_tags %} - -
    - {% trans 'Tramitações' %} - {% if perms|get_change_perm:view %} - {% trans 'Editar' %} - {% endif %} - {% if perms|get_delete_perm:view %} - {% trans 'Excluir' %} - {% endif %} -
    -{% endblock actions %} diff --git a/sapl/templates/protocoloadm/documentoadministrativo_filter.html b/sapl/templates/protocoloadm/documentoadministrativo_filter.html index 978e63416..a5b29ad42 100644 --- a/sapl/templates/protocoloadm/documentoadministrativo_filter.html +++ b/sapl/templates/protocoloadm/documentoadministrativo_filter.html @@ -37,7 +37,7 @@ {{d.tipo.sigla}} {{d.numero}}/{{d.ano}} - {{d.tipo}}
    - Interessado: {{ d.interessado|default_if_none:"Não Informado"}}
    + Interessado: {{ d.interessado|default_if_none:"Não informado"}}
    Assunto: {{ d.assunto|safe }}
    {% if d.protocolo %} diff --git a/sapl/templates/protocoloadm/layouts.yaml b/sapl/templates/protocoloadm/layouts.yaml index 8b6c935d0..fad574562 100644 --- a/sapl/templates/protocoloadm/layouts.yaml +++ b/sapl/templates/protocoloadm/layouts.yaml @@ -16,9 +16,8 @@ DocumentoAdministrativo: DocumentoAcessorioAdministrativo: {% trans 'Documento Acessório' %}: - - documento tipo + - tipo autor - nome data - - autor - arquivo - assunto diff --git a/sapl/templates/protocoloadm/protocolo_filter.html b/sapl/templates/protocoloadm/protocolo_filter.html index dc00317cd..94c5c8b8a 100644 --- a/sapl/templates/protocoloadm/protocolo_filter.html +++ b/sapl/templates/protocoloadm/protocolo_filter.html @@ -42,9 +42,15 @@ Etiqueta Individual {% if p.anulado %}  ** NULO **{% endif %}
    - Assunto: {{ p.assunto_ementa|default_if_none:"Não Informado"}}
    - Data Protocolo: {{ p.data|date:"d/m/Y"|default_if_none:"Não Informado" }} - Horário: {{ p.hora|date:"G:i:s" }}
    - Interessado: {{ p.interessado }}
    + Assunto: {{ p.assunto_ementa|default_if_none:"Não informado"}}
    + Data Protocolo: {{ p.data|date:"d/m/Y"|default_if_none:"Não informado" }} - Horário: {{ p.hora|date:"G:i:s" }}
    + + {% if p.tipo_processo == 0 %} + Interessado: {{ p.interessado|default_if_none:"Não informado" }}
    + {% elif p.tipo_processo == 1 %} + Autor: {{ p.autor.nome|default_if_none:"Não informado" }}
    + {% endif %} + Natureza do Processo: {% if p.tipo_processo == 0 %} Administrativo {% elif p.tipo_processo == 1 %} Matéria Legislativa {% endif %}
    Classificação: {{ p.tipo_documento|default_if_none:p.tipo_materia }}
    diff --git a/sapl/templates/protocoloadm/protocolo_list.html b/sapl/templates/protocoloadm/protocolo_list.html index d5f3b9a8c..4ae55baf0 100644 --- a/sapl/templates/protocoloadm/protocolo_list.html +++ b/sapl/templates/protocoloadm/protocolo_list.html @@ -21,10 +21,10 @@
    Assunto: {{ p.assunto_ementa }}
    Data Protocolo: {{ p.data|date:"d/m/Y" }} - Horário: {{ p.timestamp|date:"H:m:s" }}
    - Interessado: {{ p.interessado }}
    Natureza do Processo: {% if p.tipo_processo == 0 %} - Administrativo + Administrativo
    + Interessado: {{ p.interessado }}
    {% elif p.tipo_processo == 1 %} Matéria Legislativa {% endif %}
    diff --git a/sapl/templates/protocoloadm/protocolo_mostrar.html b/sapl/templates/protocoloadm/protocolo_mostrar.html index 49dba8a90..bc8340b69 100644 --- a/sapl/templates/protocoloadm/protocolo_mostrar.html +++ b/sapl/templates/protocoloadm/protocolo_mostrar.html @@ -4,14 +4,18 @@ {% block detail_content %} Protocolo: {{ protocolo.numero|stringformat:'06d' }}/{{ protocolo.ano }}
    - Assunto: {{ protocolo.assunto_ementa|default:" Não informado." }}
    + Assunto: {{ protocolo.assunto_ementa|default:"Não informado" }}
    Data Protocolo: {{ protocolo.data|date:"d/m/Y" }} - Horário: {{ protocolo.hora|date:"H:i" }}
    - Autor: {{protocolo.autor.nome}}
    - Interessado: {{ protocolo.interessado|default:" Não informado." }}
    - + + {% if protocolo.tipo_processo == 0 %} + Interessado: {{ protocolo.interessado|default_if_none:"Não informado" }}
    + {% elif protocolo.tipo_processo == 1 %} + Autor:{{ protocolo.autor.nome|default_if_none:"Não informado" }}
    + {% endif %} + Natureza do Processo: {% if protocolo.tipo_processo == 0 %} Administrativo {% elif protocolo.tipo_processo == 1 %} Legislativo {% endif %}
    Número de Páginas: {{ protocolo.numero_paginas }}
    - Observação: {{ protocolo.observacao|default:" Não informado." }}
    + Observação: {{ protocolo.observacao|default:"Não informado" }}
    Anulado: {% if protocolo.anulado %} Sim {% else %} Não {% endif %}

    diff --git a/sapl/templates/protocoloadm/subnav.yaml b/sapl/templates/protocoloadm/subnav.yaml new file mode 100644 index 000000000..ff4feb42e --- /dev/null +++ b/sapl/templates/protocoloadm/subnav.yaml @@ -0,0 +1,7 @@ +{% load i18n common_tags %} +- title: {% trans 'Início' %} + url: documentoadministrativo_detail +- title: {% trans 'Tramitação' %} + url: tramitacaoadministrativo_list +- title: {% trans 'Documento Acessório' %} + url: documentoacessorioadministrativo_list diff --git a/sapl/templates/sessao/adicionar_varias_materias_expediente.html b/sapl/templates/sessao/adicionar_varias_materias_expediente.html index 697a809db..c8f25c923 100644 --- a/sapl/templates/sessao/adicionar_varias_materias_expediente.html +++ b/sapl/templates/sessao/adicionar_varias_materias_expediente.html @@ -6,10 +6,6 @@ {% block sections_nav %} {% endblock %} - - - - {% block detail_content %} {% block buttons %} @@ -57,15 +53,15 @@ Autores: {% for a in m.autoria_set.all %} {% if not forloop.first %} - ,    {{a.autor|default_if_none:"Não Informado"}} + ,    {{a.autor|default_if_none:""}} {% else %} -  {{a.autor|default_if_none:"Não Informado"}} +  {{a.autor|default_if_none:""}} {% endif %} {% endfor %}
    - Localização Atual:  {{m.tramitacao_set.last.unidade_tramitacao_destino|default_if_none:"Não Informada"}}
    - Status:  {{m.tramitacao_set.last.status|default_if_none:"Não Informada"}}
    - Data da última Tramitação:  {{m.tramitacao_set.last.data_tramitacao|default_if_none:"Não Informada"}}
    + Localização Atual:  {{m.tramitacao_set.last.unidade_tramitacao_destino|default_if_none:"Não informado"}}
    + Status:  {{m.tramitacao_set.last.status|default_if_none:"Não informado"}}
    + Data da última Tramitação:  {{m.tramitacao_set.last.data_tramitacao|default_if_none:"Não informado"}}
    Ementa: {{ m.ementa|safe }}

    diff --git a/sapl/templates/sessao/blocos_resumo/conteudo_multimidia.html b/sapl/templates/sessao/blocos_resumo/conteudo_multimidia.html new file mode 100644 index 000000000..4749f5ad2 --- /dev/null +++ b/sapl/templates/sessao/blocos_resumo/conteudo_multimidia.html @@ -0,0 +1,8 @@ +
    + Conteúdo Multimídia +
    +
    {{multimidia_audio}}
    +
    {{multimidia_video}}
    +
    +
    +


    \ No newline at end of file diff --git a/sapl/templates/sessao/blocos_resumo/expedientes.html b/sapl/templates/sessao/blocos_resumo/expedientes.html new file mode 100644 index 000000000..e233fcb58 --- /dev/null +++ b/sapl/templates/sessao/blocos_resumo/expedientes.html @@ -0,0 +1,17 @@ +
    + Expedientes + + + {% for e in expedientes %} + + + + {% endfor %} + +
    + {{e.tipo}}:

    +
    +

    {{e.conteudo|safe}}

    +
    +
    +
    \ No newline at end of file diff --git a/sapl/templates/sessao/blocos_resumo/identificacao_basica.html b/sapl/templates/sessao/blocos_resumo/identificacao_basica.html new file mode 100644 index 000000000..aa0ad9e46 --- /dev/null +++ b/sapl/templates/sessao/blocos_resumo/identificacao_basica.html @@ -0,0 +1,8 @@ +
    + Identificação Básica +
    + {% for b in basica %} +
    {{b}}
    + {% endfor %} +
    +
    \ No newline at end of file diff --git a/sapl/templates/sessao/blocos_resumo/lista_presenca.html b/sapl/templates/sessao/blocos_resumo/lista_presenca.html new file mode 100644 index 000000000..c89f23588 --- /dev/null +++ b/sapl/templates/sessao/blocos_resumo/lista_presenca.html @@ -0,0 +1,8 @@ +
    + Lista de Presença na Sessão +
    + {% for p in presenca_sessao %} +
    {{p.nome_parlamentar}} / {{ p.filiacao_atual }}
    + {% endfor %} +
    +
    \ No newline at end of file diff --git a/sapl/templates/sessao/blocos_resumo/lista_presenca_ordem_dia.html b/sapl/templates/sessao/blocos_resumo/lista_presenca_ordem_dia.html new file mode 100644 index 000000000..910e6a3d2 --- /dev/null +++ b/sapl/templates/sessao/blocos_resumo/lista_presenca_ordem_dia.html @@ -0,0 +1,8 @@ +
    + Lista de Presença na Ordem do Dia +
    + {% for p in presenca_ordem %} +
    {{p.nome_parlamentar}} / {{ p.filiacao_atual }}
    + {% endfor %} +
    +
    \ No newline at end of file diff --git a/sapl/templates/sessao/blocos_resumo/materias_expediente.html b/sapl/templates/sessao/blocos_resumo/materias_expediente.html new file mode 100644 index 000000000..00f385bdd --- /dev/null +++ b/sapl/templates/sessao/blocos_resumo/materias_expediente.html @@ -0,0 +1,25 @@ +
    + Matérias do Expediente + + + + + + + + + + {% for m in materia_expediente %} + + + + + + {% endfor %} + +
    MatériaEmentaResultado da Votação
    + {{m.numero}} - {{m.titulo}} +
    + Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }} +
    {{m.ementa|safe}}{{m.resultado}}
    {{m.resultado_observacao}}
    +
    \ No newline at end of file diff --git a/sapl/templates/sessao/blocos_resumo/materias_ordem_dia.html b/sapl/templates/sessao/blocos_resumo/materias_ordem_dia.html new file mode 100644 index 000000000..7e3fd4af9 --- /dev/null +++ b/sapl/templates/sessao/blocos_resumo/materias_ordem_dia.html @@ -0,0 +1,25 @@ +
    + Matérias da Ordem do Dia + + + + + + + + + + {% for m in materias_ordem %} + + + + + + {% endfor %} + +
    MatériaEmentaResultado da Votação
    + {{m.numero}} - {{m.titulo}} +
    + Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }} +
    {{m.ementa|safe}}{{m.resultado}}
    {{m.resultado_observacao}}
    +
    \ No newline at end of file diff --git a/sapl/templates/sessao/blocos_resumo/mesa_diretora.html b/sapl/templates/sessao/blocos_resumo/mesa_diretora.html new file mode 100644 index 000000000..7baf0cb67 --- /dev/null +++ b/sapl/templates/sessao/blocos_resumo/mesa_diretora.html @@ -0,0 +1,10 @@ +
    + Mesa Diretora +
    + {% for m in mesa %} +
    {{m.cargo}}: + {{m.parlamentar.nome_parlamentar}} / {{ m.parlamentar.filiacao_atual }} +
    + {% endfor %} +
    +
    \ No newline at end of file diff --git a/sapl/templates/sessao/blocos_resumo/oradores_expediente.html b/sapl/templates/sessao/blocos_resumo/oradores_expediente.html new file mode 100644 index 000000000..064b7d033 --- /dev/null +++ b/sapl/templates/sessao/blocos_resumo/oradores_expediente.html @@ -0,0 +1,14 @@ +
    + Oradores do Expediente +
    +
    Parlamentar
    +
    Discurso
    +
    +
    + {% for o in oradores %} +
    {{o.numero_ordem}} - {{o.parlamentar}}
    +
    {{o.url_discurso}}
    +
    + {% endfor %} +
    +
    \ No newline at end of file diff --git a/sapl/templates/sessao/blocos_resumo/oradores_explicacoes.html b/sapl/templates/sessao/blocos_resumo/oradores_explicacoes.html new file mode 100644 index 000000000..e97ac5aea --- /dev/null +++ b/sapl/templates/sessao/blocos_resumo/oradores_explicacoes.html @@ -0,0 +1,14 @@ +
    + Oradores das Explicações Pessoais +
    +
    Parlamentar
    +
    Discurso
    +
    +
    + {% for o in oradores_explicacoes %} +
    {{o.numero_ordem}} - {{o.parlamentar.nome_parlamentar}} / {{ o.parlamentar.filiacao_atual }}
    +
    {{o.url_discurso}}
    +
    + {% endfor %} +
    +
    \ No newline at end of file diff --git a/sapl/templates/sessao/expedientemateria_list.html b/sapl/templates/sessao/expedientemateria_list.html index 6b7e61f3f..726b9c64e 100644 --- a/sapl/templates/sessao/expedientemateria_list.html +++ b/sapl/templates/sessao/expedientemateria_list.html @@ -7,7 +7,7 @@ {% if perms|get_add_perm:view %} - {% blocktrans with verbose_name=view.verbose_name %} Reordenar Matérias {% endblocktrans %} + {% blocktrans with verbose_name=view.verbose_name %} Ajustar Ordenação {% endblocktrans %} {% blocktrans with verbose_name=view.verbose_name %} Adicionar Várias Matérias {% endblocktrans %} @@ -26,7 +26,7 @@ start: function(event, ui) { ui.item.startPos = ui.item.index(); - + }, stop: function(event, ui) { var pos_ini = ui.item.startPos; diff --git a/sapl/templates/sessao/mesa.html b/sapl/templates/sessao/mesa.html index 913135561..311a76b87 100644 --- a/sapl/templates/sessao/mesa.html +++ b/sapl/templates/sessao/mesa.html @@ -2,63 +2,248 @@ {% load i18n %} {% block detail_content %} -
    - Mesa Diretora da Sessão -
    - {% csrf_token %} -
    -
    -
    - - -
    - -
    - {% if perms.sessao %} -

    - {% if view.get_cargos_mesa %}{% endif %} -

    - - {% endif %} -
    - {% if view.get_cargos_mesa %} -
    - - -
    - -
    - {% endif %} -
    -
    -
    + + + + + + + +
    + Escolha da Composição da Mesa Diretora da Sessão Plenária +
    +
    + + +
    + +
    +

    + {% if perms.parlamentares.add_cargomesa %} + + {% endif %} +
    +
    + {% if perms.parlamentares.add_composicaomesa %} + + {% endif %} +
    + +
    + + +
    + +
    + +
    +
    {% endblock detail_content %} + {% block extra_js %} - -{% endblock extra_js %} + + + +{% endblock %} diff --git a/sapl/templates/sessao/ordemdia_list.html b/sapl/templates/sessao/ordemdia_list.html index 59a6f7cb6..e5b3d757e 100644 --- a/sapl/templates/sessao/ordemdia_list.html +++ b/sapl/templates/sessao/ordemdia_list.html @@ -7,7 +7,7 @@ {% if perms|get_add_perm:view %}
    - {% blocktrans with verbose_name=view.verbose_name %} Reordenar Matérias {% endblocktrans %} + {% blocktrans with verbose_name=view.verbose_name %} Ajustar Ordenação {% endblocktrans %} {% blocktrans with verbose_name=view.verbose_name %} Adicionar Várias Matérias {% endblocktrans %} @@ -26,7 +26,7 @@ start: function(event, ui) { ui.item.startPos = ui.item.index(); - + }, stop: function(event, ui) { var pos_ini = ui.item.startPos; diff --git a/sapl/templates/sessao/painel.html b/sapl/templates/sessao/painel.html index 2857bc4bb..5cd84ec32 100644 --- a/sapl/templates/sessao/painel.html +++ b/sapl/templates/sessao/painel.html @@ -13,12 +13,7 @@ {% block detail_content %}
    diff --git a/sapl/templates/sessao/presenca.html b/sapl/templates/sessao/presenca.html index 372bc8c88..af0221cf0 100644 --- a/sapl/templates/sessao/presenca.html +++ b/sapl/templates/sessao/presenca.html @@ -17,17 +17,37 @@
    -
    -
    - {% for parlamentar, check in view.get_presencas %} -
    - -
    - {% endfor %} -
    + +
    +
    +
    + +
    +
    + +
    + +
    + {% for parlamentar, check in view.get_presencas %} + {% if parlamentar.ativo %} +
    + +
    + {% else %} + + {% endif %} + {% endfor %} +

    @@ -55,10 +75,21 @@ {% block extra_js %} {% endblock %} diff --git a/sapl/templates/sessao/presenca_ordemdia.html b/sapl/templates/sessao/presenca_ordemdia.html index edb4055ca..9b5afdfb1 100644 --- a/sapl/templates/sessao/presenca_ordemdia.html +++ b/sapl/templates/sessao/presenca_ordemdia.html @@ -17,19 +17,39 @@ -
    + +
    - {% for parlamentar, check in view.get_presencas_ordem %}
    - +
    - {% endfor %}
    +
    + +
    + {% for parlamentar, check in view.get_presencas_ordem %} + {% if parlamentar.ativo %} +
    + +
    + {% else %} + + {% endif %} + {% endfor %} +
    +
    @@ -56,10 +76,21 @@ {% block extra_js %} {% endblock %} diff --git a/sapl/templates/sessao/resumo.html b/sapl/templates/sessao/resumo.html index d4fb23d75..b5831bdca 100644 --- a/sapl/templates/sessao/resumo.html +++ b/sapl/templates/sessao/resumo.html @@ -19,157 +19,36 @@

    -
    - Identificação Básica -
    - {% for b in basica %} -
    {{b}}
    - {% endfor %} -
    -
    -


    -
    - Conteúdo Multimídia -
    -
    {{multimidia_audio}}
    -
    {{multimidia_video}}
    -
    -
    -


    + {% include 'sessao/blocos_resumo/'|add:primeiro_ordenacao %} +


    -
    - Mesa Diretora -
    - {% for m in mesa %} -
    {{m.cargo}}: - {{m.parlamentar.nome_parlamentar}} / {{ m.parlamentar.filiacao_atual }}
    - {% endfor %} -
    -
    -


    + {% include 'sessao/blocos_resumo/'|add:segundo_ordenacao %} +


    -
    - Lista de Presença na Sessão -
    - {% for p in presenca_sessao %} -
    {{p.nome_parlamentar}} / {{ p.filiacao_atual }}
    - {% endfor %} -
    -
    -


    + {% include 'sessao/blocos_resumo/'|add:terceiro_ordenacao %} +


    -
    - Expedientes - - - {% for e in expedientes %} - - - - {% endfor %} - -
    - {{e.tipo}}:

    -
    -

    {{e.conteudo|safe}}

    -
    -
    -
    -


    + {% include 'sessao/blocos_resumo/'|add:quarto_ordenacao %} +


    -
    - Matérias do Expediente - - - - - - - - - - {% for m in materia_expediente %} - - - - - - {% endfor %} - -
    MatériaEmentaResultado da Votação
    - {{m.numero}} - {{m.titulo}} -
    - Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }} -
    {{m.ementa|safe}}{{m.resultado}}
    {{m.resultado_observacao}}
    -
    -


    + {% include 'sessao/blocos_resumo/'|add:quinto_ordenacao %} +


    -
    - Oradores do Expediente -
    -
    Parlamentar
    -
    Discurso
    -
    -
    - {% for o in oradores %} -
    {{o.numero_ordem}} - {{o.parlamentar}}
    -
    {{o.url_discurso}}
    -
    - {% endfor %} -
    -
    -


    + {% include 'sessao/blocos_resumo/'|add:sexto_ordenacao %} +


    -
    - Lista de Presença na Ordem do Dia -
    - {% for p in presenca_ordem %} -
    {{p.nome_parlamentar}} / {{ p.filiacao_atual }}
    - {% endfor %} -
    -
    -


    + {% include 'sessao/blocos_resumo/'|add:setimo_ordenacao %} +


    -
    - Matérias da Ordem do Dia - - - - - - - - - - {% for m in materias_ordem %} - - - - - - {% endfor %} - -
    MatériaEmentaResultado da Votação
    - {{m.numero}} - {{m.titulo}} -
    - Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }} -
    {{m.ementa|safe}}{{m.resultado}}
    {{m.resultado_observacao}}
    -
    + {% include 'sessao/blocos_resumo/'|add:oitavo_ordenacao %} +


    + + {% include 'sessao/blocos_resumo/'|add:nono_ordenacao %} +


    + + {% include 'sessao/blocos_resumo/'|add:decimo_ordenacao %} +


    -
    - Oradores das Explicações Pessoais -
    -
    Parlamentar
    -
    Discurso
    -
    -
    - {% for o in oradores_explicacoes %} -
    {{o.numero_ordem}} - {{o.parlamentar.nome_parlamentar}} / {{ o.parlamentar.filiacao_atual }}
    -
    {{o.url_discurso}}
    -
    - {% endfor %} -
    -
    {% endblock detail_content %} diff --git a/sapl/templates/sessao/resumo_ordenacao.html b/sapl/templates/sessao/resumo_ordenacao.html new file mode 100644 index 000000000..5d9fe816f --- /dev/null +++ b/sapl/templates/sessao/resumo_ordenacao.html @@ -0,0 +1,13 @@ +{% extends "crud/form.html" %} +{% load i18n %} +{% load crispy_forms_tags %} + +{% block title %} +

    Ordenação do Resumo

    +{% endblock %} + +{% block base_content %} + +{% crispy form %} + +{% endblock %} \ No newline at end of file diff --git a/sapl/templates/sessao/sessaoplenaria_detail.html b/sapl/templates/sessao/sessaoplenaria_detail.html deleted file mode 100644 index bd6c66ab4..000000000 --- a/sapl/templates/sessao/sessaoplenaria_detail.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "crud/detail.html" %} -{% load i18n %} -{% load crispy_forms_tags %} - - -{% block extra_actions %} -{% if 'parlamentares.can_vote' in request.user.get_all_permissions %} - -{% endif %} -{% endblock extra_actions %} diff --git a/sapl/templates/sessao/votacao/nominal_detail.html b/sapl/templates/sessao/votacao/nominal_detail.html index e27fd3c53..53d41084b 100644 --- a/sapl/templates/sessao/votacao/nominal_detail.html +++ b/sapl/templates/sessao/votacao/nominal_detail.html @@ -18,8 +18,13 @@ Votos
    {% for v in votos %} -
    {{v.parlamentar}}
    -
    {{v.voto}}
    +
    {{v.parlamentar}} - + {% if v.voto == '-1'%} + Voto não Registrado + {% else %} + {{v.voto}} + {% endif %} +
    {% endfor %}
    diff --git a/sapl/templates/sessao/votacao/nominal_edit.html b/sapl/templates/sessao/votacao/nominal_edit.html index 83d584a33..35297e36c 100644 --- a/sapl/templates/sessao/votacao/nominal_edit.html +++ b/sapl/templates/sessao/votacao/nominal_edit.html @@ -18,8 +18,15 @@ Votos
    {% for v in votos %} -
    {{v.parlamentar}}
    -
    {{v.voto}}
    + +
    {{v.parlamentar}} - + {% if v.voto == '-1'%} + Voto não Registrado + {% else %} + {{v.voto}} + {% endif %} +
    + {% endfor %}
    diff --git a/sapl/templates/sistema.html b/sapl/templates/sistema.html index 99b9c7062..72ddb240a 100644 --- a/sapl/templates/sistema.html +++ b/sapl/templates/sistema.html @@ -82,7 +82,7 @@ - +
    diff --git a/sapl/test_urls.py b/sapl/test_urls.py index 40339cef7..8b5207276 100644 --- a/sapl/test_urls.py +++ b/sapl/test_urls.py @@ -172,6 +172,27 @@ apps_url_patterns_prefixs_and_users = { 'prefixs': [ '/ta', ]}, + 'redireciona_urls': { + 'prefixs': [ + '/default_index_html', + '/consultas/parlamentar/parlamentar_', + '/consultas/comissao/comissao_', + '/consultas/pauta_sessao/pauta_sessao_', + '/consultas/sessao_plenaria/', + '/relatorios_administrativos/relatorios_administrativos_index_html', + '/tramitacaoMaterias/tramitacaoMaterias', + '/tramitacaoMaterias/materia_mostrar_proc', + '/generico/materia_pesquisar_', + '/consultas/mesa_diretora/mesa_diretora_index_html', + '/consultas/mesa_diretora/parlamentar/parlamentar_', + '/generico/norma_juridica_pesquisar_', + '/consultas/norma_juridica/norma_juridica_mostrar_proc', + '/historicoTramitacoes/historicoTramitacoes', + '/atasSessao', + '/presencaSessao', + '/resumoPropositurasAutor', + '/propositurasAnoAutorTipo', + ]}, 'lexml': { 'prefixs': [ '/lexml', @@ -235,6 +256,7 @@ apps_url_patterns_prefixs_and_users = { } +@pytest.mark.skip(reason="TODO: Lento demais. Precisa ser refatorado") @pytest.mark.parametrize('url_item', _lista_urls) def test_urlpatterns(url_item, admin_client): @@ -328,7 +350,7 @@ urls_publicas_excecoes = { '/proposicao/1', '/proposicao/create', '/proposicao/1/edit', - '/proposicao/1/delete', + '/proposicao/1/delete' }, 'get': [ diff --git a/sapl/urls.py b/sapl/urls.py index fe6bcc71e..91d3adeb6 100644 --- a/sapl/urls.py +++ b/sapl/urls.py @@ -30,11 +30,12 @@ import sapl.norma.urls import sapl.painel.urls import sapl.parlamentares.urls import sapl.protocoloadm.urls +import sapl.redireciona_urls.urls import sapl.relatorios.urls import sapl.sessao.urls urlpatterns = [ - url(r'^$', TemplateView.as_view(template_name='index.html')), + url(r'^$', TemplateView.as_view(template_name='index.html'), name='sapl_index'), url(r'^message$', TemplateView.as_view(template_name='base.html')), url(r'^admin/', include(admin.site.urls)), @@ -61,6 +62,7 @@ urlpatterns = [ # Folhas XSLT e extras referenciadas por documentos migrados do sapl 2.5 url(r'^XSLT/HTML/(?P.*)$', RedirectView.as_view( url='/static/XSLT/HTML/%(path)s', permanent=False)), + url(r'', include(sapl.redireciona_urls.urls)), ] diff --git a/sapl/utils.py b/sapl/utils.py index 81ebea6cc..7a4146fc5 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -4,9 +4,9 @@ import os import re from datetime import date from functools import wraps -from unicodedata import normalize as unicodedata_normalize from subprocess import PIPE, call from threading import Thread +from unicodedata import normalize as unicodedata_normalize import django_filters import magic @@ -159,8 +159,8 @@ class SaplGenericRelation(GenericRelation): assert isinstance(field, (tuple, list)), _( 'fields_search deve ser um array de tuplas ou listas.') - assert len(field) == 2, _( - 'cada tupla de fields_search deve possuir duas strins') + assert len(field) <= 3, _( + 'cada tupla de fields_search deve possuir até 3 strings') # TODO implementar assert para validar campos do Model e lookups @@ -368,8 +368,8 @@ def fabrica_validador_de_tipos_de_arquivo(lista, nome): def restringe_tipos_de_arquivo(value): if not os.path.splitext(value.path)[1][:1]: - raise ValidationError(_( - 'Não é possível fazer upload de arquivos sem extensão.')) + raise ValidationError(_( + 'Não é possível fazer upload de arquivos sem extensão.')) mime = magic.from_buffer(value.read(), mime=True) if mime not in lista: @@ -391,92 +391,6 @@ def intervalos_tem_intersecao(a_inicio, a_fim, b_inicio, b_fim): return maior_inicio <= menor_fim -""" -def permissoes(nome_grupo, app_label): - lista_permissoes = [] - try: - perms = list(Permission.objects.filter( - group__name=nome_grupo)) - for p in perms: - lista_permissoes.append('%s.%s' % (app_label, p.codename)) - except: - pass - return set(lista_permissoes) - - -def permission_required_for_app(app_label, login_url=None, - raise_exception=False): - - Decorator for views that checks whether a user has a particular permission - enabled, redirecting to the log-in page if necessary. - If the raise_exception parameter is given the PermissionDenied exception - is raised. - - def check_perms(user): - if user.has_module_perms(app_label): - return True - # In case the 403 handler should be called raise the exception - if raise_exception: - raise PermissionDenied - # As the last resort, show the login form - return False - return user_passes_test(check_perms, login_url=login_url) - -def permissoes_materia(): - return permissoes('Operador de Matéria', 'materia') - - -def permissoes_comissoes(): - return permissoes('Operador de Comissões', 'comissoes') - - -def permissoes_norma(): - return permissoes('Operador de Norma Jurídica', 'norma') - - -def permissoes_protocoloadm(): - return permissoes('Operador de Protocolo Administrativo', 'protocoloadm') - - -def permissoes_adm(): - return permissoes('Operador Administrativo', 'protocoloadm') - - -def permissoes_sessao(): - return permissoes('Operador de Sessão Plenária', 'sessao') - - -def permissoes_painel(): - return permissoes('Operador de Painel Eletrônico', 'painel') - - -def permissoes_autor(): - return permissoes('Autor', 'materia') - - -def permissoes_parlamentares(): - lista_permissoes = [] - try: - cts = ContentType.objects.filter(app_label='parlamentares') - perms_parlamentares = list(Permission.objects.filter( - content_type__in=cts)) - for p in perms_parlamentares: - lista_permissoes.append('parlamentares.' + p.codename) - except: - pass - return set(lista_permissoes) - - -def permissao_tb_aux(self): - u = self.request.user - if u.groups.filter(name='Operador Geral').exists() or u.is_superuser: - return True - else: - return False - -""" - - class MateriaPesquisaOrderingFilter(django_filters.OrderingFilter): choices = ( @@ -583,7 +497,7 @@ def generic_relations_for_model(model): )) -def texto_upload_path(instance, filename, subpath=''): +def texto_upload_path(instance, filename, subpath='', pk_first=False): """ O path gerado por essa função leva em conta a pk de instance. isso não é possível naturalmente em uma inclusão pois a implementação @@ -605,8 +519,8 @@ def texto_upload_path(instance, filename, subpath=''): seguida para armazenar o arquivo. """ - if subpath and '/' not in subpath: - subpath = subpath + '/' +# if subpath and '/' not in subpath: +# subpath = subpath + '/' """ TODO: Verifique possibilidade de otimização do código de normalização do filename... @@ -624,7 +538,12 @@ def texto_upload_path(instance, filename, subpath=''): if isinstance(instance, (DocumentoAdministrativo, Proposicao)): prefix = 'private' - path = './sapl/%(prefix)s/%(model_name)s/%(pk)s/%(subpath)s%(filename)s' %\ + str_path = './sapl/%(prefix)s/%(model_name)s/%(subpath)s/%(pk)s/%(filename)s' + + if pk_first: + str_path = './sapl/%(prefix)s/%(model_name)s/%(pk)s/%(subpath)s/%(filename)s' + + path = str_path %\ { 'prefix': prefix, 'model_name': instance._meta.model_name, @@ -634,19 +553,3 @@ def texto_upload_path(instance, filename, subpath=''): } return path - - -class UpdateIndexCommand(Thread): - def run(self): - call([PROJECT_DIR.child('manage.py'), 'update_index'], - stdout=PIPE) - - -def save_texto(sender, instance, **kwargs): - update_index = UpdateIndexCommand() - update_index.start() - - -def delete_texto(sender, instance, **kwargs): - update_index = UpdateIndexCommand() - update_index.start() diff --git a/sapl_30-03-16.tar b/sapl_30-03-16.tar deleted file mode 100644 index 789e73d16..000000000 Binary files a/sapl_30-03-16.tar and /dev/null differ diff --git a/scripts/set_inicio_mandato.py b/scripts/set_inicio_mandato.py new file mode 100644 index 000000000..37dfcfc0c --- /dev/null +++ b/scripts/set_inicio_mandato.py @@ -0,0 +1,10 @@ +from sapl.parlamentares.models import Mandato + + +def popula_campo_data_inicio(): + for m in Mandato.objects.all(): + m.data_inicio_mandato = m.legislatura.data_inicio + m.save() + +if __name__ == '__main__': + popula_campo_data_inicio() \ No newline at end of file diff --git a/start.sh b/start.sh index 1b00801a2..d98481b30 100755 --- a/start.sh +++ b/start.sh @@ -33,7 +33,10 @@ create_env() { echo "EMAIL_HOST = ""${EMAIL_HOST-''}" >> $FILENAME echo "EMAIL_HOST_USER = ""${EMAIL_HOST_USER-''}" >> $FILENAME echo "EMAIL_HOST_PASSWORD = ""${EMAIL_HOST_PASSWORD-''}" >> $FILENAME - + echo "EMAIL_SEND_USER = ""${EMAIL_HOST_USER-''}" >> $FILENAME + echo "DEFAULT_FROM_EMAIL = ""${EMAIL_HOST_USER-''}" >> $FILENAME + echo "SERVER_EMAIL = ""${EMAIL_HOST_USER-''}" >> $FILENAME + echo "[ENV FILE] done." } @@ -43,12 +46,16 @@ create_env /bin/sh busy-wait.sh $DATABASE_URL -python3 manage.py migrate +python3 manage.py migrate --noinput #python3 manage.py collectstatic --no-input python3 manage.py rebuild_index --noinput & +echo "Criando usuário admin..." + user_created=$(python3 create_admin.py 2>&1) +echo $user_created + cmd=$(echo $user_created | grep 'ADMIN_USER_EXISTS') user_exists=$?