diff --git a/.gitignore b/.gitignore index 52baa1622..d66a04a22 100644 --- a/.gitignore +++ b/.gitignore @@ -93,5 +93,7 @@ bower bower_components media whoosh/ +solr-4.10.2/ postgres-data/ data/ +solr-*/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2455200e1..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 && \ @@ -32,9 +32,15 @@ COPY config/env_dockerfile /var/interlegis/sapl/sapl/.env # manage.py bower install bug: https://github.com/nvbn/django-bower/issues/51 +# 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 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 1b9a45fc1..b149ad906 100644 --- a/README.rst +++ b/README.rst @@ -30,6 +30,11 @@ Instruções para Deploy `Deploy SAPL com Nginx + Gunicorn `_ +Instalação do Solr +====================== + `Instalação e configuração do Solr `_ + + Instruções para Tradução ======================== diff --git a/config/env-sample b/config/env-sample index 33fed9806..bde081a92 100644 --- a/config/env-sample +++ b/config/env-sample @@ -1,6 +1,6 @@ DATABASE_URL = postgresql://postgres:@sapldb:/sapl -KEY -DEBUG = True +KEY +DEBUG = False EMAIL_USE_TLS = True EMAIL_PORT = 587 EMAIL_HOST = '' diff --git a/config/env_dockerfile b/config/env_dockerfile index f4a9291f7..c83fc88f1 100644 --- a/config/env_dockerfile +++ b/config/env_dockerfile @@ -1,6 +1,6 @@ DATABASE_URL = sqlite:///sapl.db SECRET_KEY = 'Dockerfile_Key' -DEBUG = True +DEBUG = False EMAIL_USE_TLS = True EMAIL_PORT = 587 EMAIL_HOST = '' diff --git a/docker-compose.yml b/docker-compose.yml index 50bdf6512..4fbbd4bea 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,7 @@ sapldb: ports: - "5532:5432" sapl: - image: interlegis/sapl:latest + image: interlegis/sapl:3.1.2-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 d8f2ec9bb..ac81ac5d3 100644 --- a/docs/deploy.rst +++ b/docs/deploy.rst @@ -18,24 +18,36 @@ alterando o variável DEBUG para false:: DEBUG = False +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 + +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 + +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. + Instalando Pacotes ------------------ Instalar o NGINX:: sudo apt-get install nginx - - + + Instalar o Gunicorn:: - sudo pip install gunicorn + sudo pip install gunicorn Preparando o NGINX ------------------ sudo nano /etc/nginx/sites-available/sapl31.conf:: - upstream ENDERECO_SITE { + upstream ENDERECO_SITE { server unix:/var/interlegis/sapl/run/gunicorn.sock fail_timeout=0; } @@ -95,11 +107,11 @@ Para uma máquina de CPU única o valor seria 3 Para rodar o gunicorn:: workon sapl - + /var/interlegis/sapl/.gunicorn_start.sh - - - + + + #Referências. http://michal.karzynski.pl/blog/2013/06/09/django-nginx-gunicorn-virtualenv-supervisor/ @@ -107,4 +119,12 @@ http://michal.karzynski.pl/blog/2013/06/09/django-nginx-gunicorn-virtualenv-supe Para multiplas aplicações Django. http://michal.karzynski.pl/blog/2013/10/29/serving-multiple-django-applications-with-nginx-gunicorn-supervisor/ - + +Compilar arquivos SASS/SCSS + +https://github.com/jrief/django-sass-processor#offline-compilation +https://github.com/jrief/django-sass-processor/issues/34#issuecomment-252611103 + +Deploy Arquivos Estáticos + +https://docs.djangoproject.com/pt-br/1.11/howto/static-files/deployment/ diff --git a/docs/instacao31.rst b/docs/instacao31.rst index 3d6a8b3eb..9a42573ac 100644 --- a/docs/instacao31.rst +++ b/docs/instacao31.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 sudo -i curl -sL https://deb.nodesource.com/setup_5.x | bash - diff --git a/docs/solr.rst b/docs/solr.rst new file mode 100644 index 000000000..a81ee9d12 --- /dev/null +++ b/docs/solr.rst @@ -0,0 +1,22 @@ +================================ +Instruções para instalar o Solr +================================ + +Solr é a ferramenta utilizada pelo SAPL 3.1 para indexar documentos para que possa ser feita +a Pesquisa Textual. + + +Dentro do diretório principal siga os seguintes passos:: + + curl -LO https://archive.apache.org/dist/lucene/solr/4.10.2/solr-4.10.2.tgz + tar xvzf solr-4.10.2.tgz + cd solr-4.10.2 + cd example + java -jar start.jar + ./manage.py build_solr_schema --filename solr-4.10.2/example/solr/collection1/conf/schema.xml + + +Após isso, deve-se parar o servidor do Solr e restartar com ``java -jar start.jar`` + + +**OBS: Toda vez que o código da pesquisa textual for modificado, os comandos de build_solr_schema e start.jar devem ser rodados, nessa mesma ordem.** \ No newline at end of file diff --git a/requirements/requirements.txt b/requirements/requirements.txt index efea2076f..45247a6ab 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -28,6 +28,7 @@ pyyaml==3.11 rtyaml==0.0.3 textract==1.5.0 unipath==1.1 +pysolr==3.6.0 python-magic==0.4.12 gunicorn==19.6.0 django-reversion==2.0.8 diff --git a/sapl/api/forms.py b/sapl/api/forms.py index c220c01b2..afa9190f4 100644 --- a/sapl/api/forms.py +++ b/sapl/api/forms.py @@ -1,5 +1,8 @@ from django.db.models import Q +from django.forms.fields import MultiValueField, CharField +from django.forms.widgets import TextInput, MultiWidget from django_filters.filters import MethodFilter, ModelChoiceFilter +from rest_framework.compat import django_filters from rest_framework.filters import FilterSet from sapl.base.models import Autor, TipoAutor @@ -35,10 +38,11 @@ class SaplGenericRelationSearchFilterSet(FilterSet): item.related_query_name(), field[0]) ) - q_fs = q_fs | Q(**{'%s__%s%s' % ( - item.related_query_name(), - field[0], - field[1]): qtext}) + if len(field) == 3 and field[2](qtext) is not None: + q_fs = q_fs | Q(**{'%s__%s%s' % ( + item.related_query_name(), + field[0], + field[1]): qtext if len(field) == 2 else field[2](qtext)}) q = q & q_fs @@ -48,6 +52,37 @@ class SaplGenericRelationSearchFilterSet(FilterSet): return queryset +class SearchForFieldWidget(MultiWidget): + + def decompress(self, value): + if value is None: + return [None, None] + return value + + def __init__(self, attrs=None): + widgets = (TextInput, TextInput) + MultiWidget.__init__(self, widgets, attrs) + + +class SearchForFieldField(MultiValueField): + widget = SearchForFieldWidget + + def __init__(self, *args, **kwargs): + fields = ( + CharField(), + CharField()) + super(SearchForFieldField, self).__init__(fields, *args, **kwargs) + + def compress(self, parameters): + if parameters: + return parameters + return None + + +class SearchForFieldFilter(django_filters.filters.MethodFilter): + field_class = SearchForFieldField + + class AutorChoiceFilterSet(SaplGenericRelationSearchFilterSet): q = MethodFilter() tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all()) @@ -60,4 +95,23 @@ class AutorChoiceFilterSet(SaplGenericRelationSearchFilterSet): def filter_q(self, queryset, value): return SaplGenericRelationSearchFilterSet.filter_q( - self, queryset, value).order_by('nome') + self, queryset, value).distinct('nome').order_by('nome') + + +class AutorSearchForFieldFilterSet(AutorChoiceFilterSet): + q = SearchForFieldFilter() + + class Meta(AutorChoiceFilterSet.Meta): + pass + + def filter_q(self, queryset, value): + + value[0] = value[0].split(',') + value[1] = value[1].split(',') + + params = {} + for key, v in list(zip(value[0], value[1])): + if v in ['True', 'False']: + v = '1' if v == 'True' else '0' + params[key] = v + return queryset.filter(**params).distinct('nome').order_by('nome') diff --git a/sapl/api/serializers.py b/sapl/api/serializers.py index a7ff559e3..60ea6692a 100644 --- a/sapl/api/serializers.py +++ b/sapl/api/serializers.py @@ -2,6 +2,7 @@ from rest_framework import serializers from sapl.base.models import Autor from sapl.materia.models import MateriaLegislativa +from sapl.sessao.models import SessaoPlenaria class ChoiceSerializer(serializers.Serializer): @@ -45,6 +46,7 @@ class AutorSerializer(serializers.ModelSerializer): class Meta: model = Autor + fields = '__all__' class MateriaLegislativaSerializer(serializers.ModelSerializer): @@ -52,3 +54,25 @@ class MateriaLegislativaSerializer(serializers.ModelSerializer): class Meta: model = MateriaLegislativa fields = '__all__' + + +class SessaoPlenariaSerializer(serializers.ModelSerializer): + + tipo = serializers.StringRelatedField(many=False) + sessao_legislativa = serializers.StringRelatedField(many=False) + legislatura = serializers.StringRelatedField(many=False) + + class Meta: + model = SessaoPlenaria + fields = ('pk', + 'tipo', + 'sessao_legislativa', + 'legislatura', + 'data_inicio', + 'hora_inicio', + 'hora_fim', + 'url_video', + 'iniciada', + 'finalizada', + 'interativa' + ) diff --git a/sapl/api/urls.py b/sapl/api/urls.py index 195c7f84a..bf9c16c51 100644 --- a/sapl/api/urls.py +++ b/sapl/api/urls.py @@ -3,7 +3,7 @@ from django.conf.urls import include, url from rest_framework.routers import DefaultRouter from sapl.api.views import (AutorListView, MateriaLegislativaViewSet, - ModelChoiceView) + ModelChoiceView, SessaoPlenariaViewSet) from .apps import AppConfig @@ -12,9 +12,9 @@ app_name = AppConfig.name router = DefaultRouter() router.register(r'materia', MateriaLegislativaViewSet) +router.register(r'sessao-plenaria', SessaoPlenariaViewSet) urlpatterns_router = router.urls - urlpatterns_api = [ url(r'^autor', AutorListView.as_view(), name='autor_list'), diff --git a/sapl/api/views.py b/sapl/api/views.py index 71aa1b225..eb1364bf7 100644 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -6,16 +6,19 @@ from rest_framework.filters import DjangoFilterBackend from rest_framework.generics import ListAPIView from rest_framework.mixins import ListModelMixin, RetrieveModelMixin from rest_framework.permissions import (IsAuthenticated, - IsAuthenticatedOrReadOnly) -from rest_framework.viewsets import GenericViewSet + IsAuthenticatedOrReadOnly, + AllowAny) +from rest_framework.viewsets import GenericViewSet, ModelViewSet -from sapl.api.forms import AutorChoiceFilterSet +from sapl.api.forms import AutorChoiceFilterSet, AutorSearchForFieldFilterSet from sapl.api.serializers import (AutorChoiceSerializer, AutorSerializer, ChoiceSerializer, MateriaLegislativaSerializer, - ModelChoiceSerializer) + ModelChoiceSerializer, + SessaoPlenariaSerializer) from sapl.base.models import Autor, TipoAutor from sapl.materia.models import MateriaLegislativa +from sapl.sessao.models import SessaoPlenaria from sapl.utils import SaplGenericRelation, sapl_logger @@ -76,7 +79,57 @@ class AutorListView(ListAPIView): o django-filter é desativado e a busca é feita no model do ContentType associado ao tipo. - Outros campos + - q_0 / q_1 - q_0 faz o código ignorar "q"... + + q_0 -> 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_completo__icontains, + parlamentar_set__ativo + &q_1=Carvalho,False + /api/autor?tr=1 + &q_0=parlamentar_set__nome_completo__icontains, + parlamentar_set__ativo + &q_1=Carvalho,True + /api/autor?tr=3 + &q_0=parlamentar_set__nome_completo__icontains, + parlamentar_set__ativo + &q_1=Carvalho,False + /api/autor?tr=3 + &q_0=parlamentar_set__nome_completo__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 @@ -122,6 +175,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): @@ -203,3 +259,14 @@ class MateriaLegislativaViewSet(ListModelMixin, queryset = MateriaLegislativa.objects.all() filter_backends = (DjangoFilterBackend,) filter_fields = ('numero', 'ano', 'tipo', ) + + +class SessaoPlenariaViewSet(ListModelMixin, + RetrieveModelMixin, + GenericViewSet): + + permission_classes = (AllowAny,) + serializer_class = SessaoPlenariaSerializer + queryset = SessaoPlenaria.objects.all() + filter_backends = (DjangoFilterBackend,) + filter_fields = ('data_inicio', 'data_fim', 'interativa') 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/models.py b/sapl/base/models.py index 9dd0f5ec3..3edef25e9 100644 --- a/sapl/base/models.py +++ b/sapl/base/models.py @@ -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..394d92e41 100644 --- a/sapl/base/search_indexes.py +++ b/sapl/base/search_indexes.py @@ -1,3 +1,4 @@ +import logging import os.path import textract @@ -7,6 +8,10 @@ from haystack import indexes from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa from sapl.norma.models import NormaJuridica +from textract.exceptions import ExtensionNotSupported + +from sapl.settings import BASE_DIR +logger = logging.getLogger(BASE_DIR.name) class DocumentoAcessorioIndex(indexes.SearchIndex, indexes.Indexable): text = indexes.CharField(document=True, use_template=True) @@ -30,17 +35,23 @@ 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', ' ') + try: + extracted_data = textract.process( + arquivo.path, + language='pt-br').decode('utf-8').replace('\n', ' ') + except ExtensionNotSupported: + return self.prepared_data + except Exception: + msg = 'Erro inesperado processando arquivo: %s' % arquivo.path + print(msg) + logger.error(msg) + return self.prepared_data extracted_data = extracted_data.replace('\t', ' ') @@ -48,8 +59,8 @@ class DocumentoAcessorioIndex(indexes.SearchIndex, indexes.Indexable): # 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 diff --git a/sapl/base/urls.py b/sapl/base/urls.py index d5e4ae98a..ea7d54a04 100644 --- a/sapl/base/urls.py +++ b/sapl/base/urls.py @@ -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/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/migracao_documentos.py b/sapl/legacy/migracao_documentos.py index 21697dc4f..8ac380b5f 100644 --- a/sapl/legacy/migracao_documentos.py +++ b/sapl/legacy/migracao_documentos.py @@ -11,6 +11,7 @@ from sapl.materia.models import (DocumentoAcessorio, MateriaLegislativa, from sapl.norma.models import NormaJuridica from sapl.parlamentares.models import Parlamentar from sapl.protocoloadm.models import DocumentoAdministrativo +from sapl.protocoloadm.models import DocumentoAcessorioAdministrativo from sapl.sessao.models import SessaoPlenaria from sapl.settings import MEDIA_ROOT from sapl.utils import delete_texto, save_texto @@ -29,6 +30,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 +55,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 +123,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 +169,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 +229,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 0d2f5be10..7efa97f73 100644 --- a/sapl/legacy/migration.py +++ b/sapl/legacy/migration.py @@ -28,7 +28,7 @@ from sapl.norma.models import (AssuntoNorma, NormaJuridica, TipoVinculoNormaJuridica, NormaRelacionada) from sapl.parlamentares.models import Parlamentar from sapl.protocoloadm.models import Protocolo, StatusTramitacaoAdministrativo -from sapl.sessao.models import ExpedienteMateria, OrdemDia +from sapl.sessao.models import ExpedienteMateria, OrdemDia, RegistroVotacao from sapl.settings import PROJECT_DIR from sapl.utils import delete_texto, normalize, save_texto @@ -411,26 +411,6 @@ class DataMigrator: if field_type == 'CharField' or field_type == 'TextField': if value is None or value == 'None': value = '' - if field.model._meta.label == 'sessao.RegistroVotacao' and \ - field.name == 'ordem' and \ - not isinstance(value, OrdemDia): - try: - new_value = ExpedienteMateria.objects.get(pk=value) - setattr(new, 'expediente', new_value) - setattr(new, field.name, None) - continue - except ObjectDoesNotExist: - msg = 'FK [%s] não encontrada para valor %s ' \ - '(em %s %s)' % ( - field.name, value, - field.model.__name__, label or '---') - with reversion.create_revision(): - value = make_stub(field.related_model, value) - descricao = 'stub criado para entrada orfã!' - warn(msg + ' => ' + descricao) - save_relation(value, [field.name], msg, descricao, - eh_stub=True) - reversion.set_comment('Stub criado pela migração') setattr(new, field.name, value) elif field.model.__name__ == 'TipoAutor' and \ field.name == 'content_type': @@ -600,11 +580,27 @@ def migrate(obj=appconfs, interativo=True): # MIGRATION_ADJUSTMENTS ##################################################### -def adjust_ordemdia(new, old): +def adjust_ordemdia_antes_salvar(new, old): # Prestar atenção 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: @@ -655,6 +651,31 @@ def adjust_protocolo(new, old): new.numero = p['numero__max'] + 1 +def adjust_registrovotacao_antes_salvar(new, old): + ordem_dia = OrdemDia.objects.filter( + pk=old.cod_ordem, materia=old.cod_materia) + expediente_materia = ExpedienteMateria.objects.filter( + pk=old.cod_ordem, materia=old.cod_materia) + + if ordem_dia and not expediente_materia: + new.ordem = ordem_dia[0] + if not ordem_dia and expediente_materia: + new.expediente = expediente_materia[0] + + +def adjust_registrovotacao_depois_salvar(new, old): + if not new.ordem and not new.expediente: + with reversion.create_revision(): + problema = 'RegistroVotacao de PK %s não possui nenhuma OrdemDia'\ + ' ou ExpedienteMateria.' % old.pk + descricao = 'RevistroVotacao deve ter no mínimo uma ordem do dia'\ + ' ou expediente vinculado.' + warn(problema + ' => ' + descricao) + save_relation(obj=new, problema=problema, + descricao=descricao, eh_stub=False) + reversion.set_comment('RegistroVotacao sem ordem ou expediente') + + def adjust_tipoproposicao(new, old): if old.ind_mat_ou_doc == 'M': new.tipo_conteudo_related = TipoMateriaLegislativa.objects.get( @@ -718,6 +739,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( @@ -748,10 +770,11 @@ AJUSTE_ANTES_SALVAR = { Comissao: adjust_comissao, NormaJuridica: adjust_normajuridica_antes_salvar, NormaRelacionada: adjust_normarelacionada, - OrdemDia: adjust_ordemdia, + OrdemDia: adjust_ordemdia_antes_salvar, Parlamentar: adjust_parlamentar, Participacao: adjust_participacao, Protocolo: adjust_protocolo, + RegistroVotacao: adjust_registrovotacao_antes_salvar, TipoProposicao: adjust_tipoproposicao, StatusTramitacao: adjust_statustramitacao, StatusTramitacaoAdministrativo: adjust_statustramitacaoadm, @@ -760,7 +783,9 @@ AJUSTE_ANTES_SALVAR = { AJUSTE_DEPOIS_SALVAR = { NormaJuridica: adjust_normajuridica_depois_salvar, + OrdemDia: adjust_ordemdia_depois_salvar, Protocolo: adjust_protocolo_depois_salvar, + RegistroVotacao: adjust_registrovotacao_depois_salvar, } # CHECKS #################################################################### diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index 7c0b6d8c8..978fce522 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -164,18 +164,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): 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/models.py b/sapl/materia/models.py index b5390f3b4..2db7c38d8 100644 --- a/sapl/materia/models.py +++ b/sapl/materia/models.py @@ -22,7 +22,6 @@ from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, SaplGenericForeignKey, EM_TRAMITACAO = [(1, 'Sim'), (0, 'Não')] - def grupo_autor(): try: grupo = Group.objects.get(name='Autor') @@ -30,7 +29,6 @@ def grupo_autor(): return None return grupo.id - @reversion.register() class TipoProposicao(models.Model): descricao = models.CharField(max_length=50, verbose_name=_('Descrição')) @@ -120,10 +118,15 @@ class Origem(models.Model): 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): - + tipo = models.ForeignKey(TipoMateriaLegislativa, on_delete=models.PROTECT, verbose_name=_('Tipo')) @@ -194,7 +197,7 @@ 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]) @@ -215,6 +218,7 @@ class MateriaLegislativa(models.Model): def __str__(self): return _('%(tipo)s nº %(numero)s de %(ano)s') % { 'tipo': self.tipo, 'numero': self.numero, 'ano': self.ano} + def data_entrada_protocolo(self): ''' @@ -231,7 +235,7 @@ class MateriaLegislativa(models.Model): pass return '' - + def delete(self, using=None, keep_parents=False): if self.texto_original: self.texto_original.delete() @@ -392,7 +396,7 @@ 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]) @@ -625,7 +629,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'), diff --git a/sapl/materia/views.py b/sapl/materia/views.py index b93d81289..2206923cc 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -7,7 +7,7 @@ from crispy_forms.layout import HTML 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.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from django.core.mail import send_mail from django.core.urlresolvers import reverse from django.http import HttpResponse, JsonResponse @@ -34,7 +34,8 @@ from sapl.crud.base import (ACTION_CREATE, ACTION_DELETE, ACTION_DETAIL, PermissionRequiredForAppCrudMixin, make_pagination) from sapl.materia.forms import (AnexadaForm, ConfirmarProposicaoForm, LegislacaoCitadaForm, ProposicaoForm, - TipoProposicaoForm) + TipoProposicaoForm, TramitacaoForm, + TramitacaoUpdateForm) from sapl.norma.models import LegislacaoCitada from sapl.protocoloadm.models import Protocolo from sapl.utils import (TURNO_TRAMITACAO_CHOICES, YES_NO_CHOICES, autor_label, @@ -162,19 +163,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)) @@ -868,6 +893,7 @@ class TramitacaoCrud(MasterDetailCrud): ordering = '-data_tramitacao', class CreateView(MasterDetailCrud.CreateView): + form_class = TramitacaoForm def get_initial(self): local = MateriaLegislativa.objects.get( @@ -884,6 +910,7 @@ class TramitacaoCrud(MasterDetailCrud): return super(CreateView, self).post(request, *args, **kwargs) class UpdateView(MasterDetailCrud.UpdateView): + form_class = TramitacaoUpdateForm def post(self, request, *args, **kwargs): materia = MateriaLegislativa.objects.get( @@ -951,25 +978,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 @@ -1355,6 +1378,11 @@ class AcompanhamentoMateriaView(CreateView): acompanhar.usuario = usuario.username acompanhar.confirmado = False acompanhar.save() + except MultipleObjectsReturned: + AcompanhamentoMateria.objects.filter( + email=email, + materia=materia, + hash=hash_txt).first() do_envia_email_confirmacao(request, materia, email) @@ -1600,15 +1628,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) @@ -1697,3 +1725,19 @@ 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() + + if ('tramitacao__status' in qr and + 'tramitacao__unidade_tramitacao_destino' in qr): + 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/migrations/0003_auto_20170510_1549.py b/sapl/norma/migrations/0003_auto_20170510_1549.py new file mode 100644 index 000000000..9cbd4962a --- /dev/null +++ b/sapl/norma/migrations/0003_auto_20170510_1549.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-05-10 15:49 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('norma', '0002_auto_20170403_1505'), + ] + + operations = [ + migrations.AlterModelOptions( + name='assuntonorma', + options={'verbose_name': 'Assunto de Norma Jurídica', 'verbose_name_plural': 'Assuntos de Normas Jurídicas'}, + ), + ] 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/models.py b/sapl/norma/models.py index 0d0b8ce6b..20166e7f5 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,7 +116,7 @@ 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') diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index 4d15ab6fd..2f1284fbf 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, @@ -206,6 +206,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' @@ -303,6 +312,7 @@ class Parlamentar(models.Model): ('nome_completo', '__icontains'), ('nome_parlamentar', '__icontains'), ('filiacao__partido__sigla', '__icontains'), + ('ativo', '', true_false_none), )) class Meta: diff --git a/sapl/parlamentares/urls.py b/sapl/parlamentares/urls.py index 29a31adfb..0d8d406da 100644 --- a/sapl/parlamentares/urls.py +++ b/sapl/parlamentares/urls.py @@ -1,6 +1,7 @@ from django.conf.urls import include, url from sapl.parlamentares.views import (altera_field_mesa, + altera_field_mesa_public_view, CargoMesaCrud, ColigacaoCrud, ComposicaoColigacaoCrud, DependenteCrud, FiliacaoCrud, FrenteCrud, FrenteList, @@ -59,6 +60,9 @@ urlpatterns = [ url(r'^mesa-diretora/altera-field-mesa/$', altera_field_mesa, name='altera_field_mesa'), + url(r'^mesa-diretora/altera-field-mesa-public-view/$', + altera_field_mesa_public_view, name='altera_field_mesa_public_view'), + url(r'^mesa-diretora/insere-parlamentar-composicao/$', insere_parlamentar_composicao, name='insere_parlamentar_composicao'), diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index 3c6ac630a..f9dc33f60 100644 --- a/sapl/parlamentares/views.py +++ b/sapl/parlamentares/views.py @@ -373,11 +373,9 @@ class ParlamentarCrud(Crud): # Caso exista mais de uma filiação nesse intervalo # Entretanto, NÃO DEVE OCORRER except MultipleObjectsReturned: - filiacao = parlamentar.filiacao_set.filter(Q( - data__lte=legislatura.data_fim, - data_desfiliacao__gte=legislatura.data_fim) | Q( - data__lte=legislatura.data_fim, - data_desfiliacao__isnull=True)).last() + row[2] = ( + 'O Parlamentar possui duas filiações conflitantes', + None) # Caso encontre UMA filiação nessas condições else: @@ -588,3 +586,94 @@ def remove_parlamentar_composicao(request): return JsonResponse( {'msg': ( 'Selecione algum parlamentar para ser excluido!', 0)}) + + +def partido_parlamentar_sessao_legislativa(sessao, parlamentar): + """ + Função para descobrir o partido do parlamentar durante + o período de uma dada Sessão Legislativa + """ + + # As condições para mostrar a filiação são: + # A data de filiacao deve ser menor que a data de fim + # da sessao legislativa e data de desfiliação deve nula, ou maior, + # ou igual a data de fim da sessao + try: + filiacao = parlamentar.filiacao_set.get(Q( + data__lte=sessao.data_fim, + data_desfiliacao__gte=sessao.data_fim) | Q( + data__lte=sessao.data_fim, + data_desfiliacao__isnull=True)) + + # Caso não exista filiação com essas condições + except ObjectDoesNotExist: + return '' + + # Caso exista mais de uma filiação nesse intervalo + # Entretanto, NÃO DEVE OCORRER + except MultipleObjectsReturned: + return 'O Parlamentar possui duas filiações conflitantes' + + # Caso encontre UMA filiação nessas condições + else: + return filiacao.partido.sigla + + +def altera_field_mesa_public_view(request): + """ + Essa função lida com qualquer alteração nos campos + da Mesa Diretora para usuários anônimos, + atualizando os campos após cada alteração + """ + + legislatura = request.GET['legislatura'] + sessoes = SessaoLegislativa.objects.filter( + legislatura=legislatura).order_by('-data_inicio') + + if not sessoes: + return JsonResponse({'msg': ('Nenhuma sessão encontrada!', 0)}) + + # Verifica se já tem uma sessão selecionada. Ocorre quando + # é alterado o campo de sessão + if request.GET['sessao']: + sessao_selecionada = request.GET['sessao'] + # Caso a mudança tenha sido no campo legislatura, a sessão + # atual deve ser a primeira daquela legislatura + else: + sessao_selecionada = sessoes.first().id + + # Atualiza os componentes da view após a mudança + lista_sessoes = [(s.id, s.__str__()) for s in sessoes] + + composicao_mesa = ComposicaoMesa.objects.filter( + sessao_legislativa=sessao_selecionada) + + cargos_ocupados = [(m.cargo.id, + m.cargo.__str__()) for m in composicao_mesa] + + parlamentares_ocupados = [(m.parlamentar.id, + m.parlamentar.__str__() + ) for m in composicao_mesa] + + lista_fotos = [] + lista_partidos = [] + + sessao = SessaoLegislativa.objects.get(id=sessao_selecionada) + for p in parlamentares_ocupados: + parlamentar = Parlamentar.objects.get(id=p[0]) + lista_partidos.append( + partido_parlamentar_sessao_legislativa(sessao, + parlamentar)) + if parlamentar.fotografia: + lista_fotos.append(parlamentar.fotografia.url) + else: + lista_fotos.append(None) + + return JsonResponse( + {'lista_parlamentares': parlamentares_ocupados, + 'lista_partidos': lista_partidos, + 'lista_cargos': cargos_ocupados, + 'lista_sessoes': lista_sessoes, + 'lista_fotos': lista_fotos, + 'sessao_selecionada': sessao_selecionada, + 'msg': ('', 1)}) diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index 9505f3bd9..b50edaaf0 100644 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -425,6 +425,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)) 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/rules/map_rules.py b/sapl/rules/map_rules.py index 05d869fd8..01493818f 100644 --- a/sapl/rules/map_rules.py +++ b/sapl/rules/map_rules.py @@ -251,6 +251,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/sessao/forms.py b/sapl/sessao/forms.py index 4303dabcc..9c2f879bf 100644 --- a/sapl/sessao/forms.py +++ b/sapl/sessao/forms.py @@ -28,7 +28,7 @@ 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 [] @@ -39,6 +39,18 @@ 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: @@ -359,3 +371,70 @@ 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=u'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=u'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): + 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/legacy.yaml b/sapl/sessao/legacy.yaml index 6e2cc2428..90188a76c 100644 --- a/sapl/sessao/legacy.yaml +++ b/sapl/sessao/legacy.yaml @@ -65,7 +65,6 @@ RegistroVotacao: numero_votos_nao: num_votos_nao numero_votos_sim: num_votos_sim observacao: txt_observacao - ordem: cod_ordem tipo_resultado_votacao: tip_resultado_votacao VotoParlamentar (RegistroVotacaoParlamentar): diff --git a/sapl/sessao/migrations/0002_sessaoplenaria_interativa.py b/sapl/sessao/migrations/0002_sessaoplenaria_interativa.py new file mode 100644 index 000000000..9a868ce58 --- /dev/null +++ b/sapl/sessao/migrations/0002_sessaoplenaria_interativa.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-05-10 15:49 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sessao', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='sessaoplenaria', + name='interativa', + field=models.NullBooleanField(choices=[(True, 'Sim'), (False, 'Não')], verbose_name='Sessão interativa'), + ), + ] 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/models.py b/sapl/sessao/models.py index e308b4187..4d7c22ca1 100644 --- a/sapl/sessao/models.py +++ b/sapl/sessao/models.py @@ -156,6 +156,9 @@ class SessaoPlenaria(models.Model): finalizada = models.NullBooleanField(blank=True, choices=YES_NO_CHOICES, verbose_name=_('Sessão finalizada?')) + interativa = models.NullBooleanField(blank=True, + choices=YES_NO_CHOICES, + verbose_name=_('Sessão interativa')) class Meta: verbose_name = _('Sessão Plenária') @@ -490,3 +493,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 new file mode 100644 index 000000000..6d6f4cd9b --- /dev/null +++ b/sapl/sessao/serializers.py @@ -0,0 +1,17 @@ +from rest_framework import serializers + +from .models import SessaoPlenaria + +class SessaoPlenariaSerializer(serializers.Serializer): + class Meta: + model = SessaoPlenaria + fields = ('tipo', + 'sessao_legislativa', + 'legislatura', + 'data_inicio', + 'hora_inicio', + 'hora_fim', + 'url_video', + 'iniciada', + 'finalizada' + ) diff --git a/sapl/sessao/urls.py b/sapl/sessao/urls.py index f237bf721..93fd953bb 100644 --- a/sapl/sessao/urls.py +++ b/sapl/sessao/urls.py @@ -10,7 +10,8 @@ from sapl.sessao.views import (AdicionarVariasMateriasExpediente, PautaSessaoDetailView, PautaSessaoListView, PesquisarPautaSessaoView, PesquisarSessaoPlenariaView, - PresencaOrdemDiaView, PresencaView, ResumoView, + PresencaOrdemDiaView, PresencaView, + ResumoOrdenacaoView, ResumoView, SessaoCrud, TipoExpedienteCrud, TipoResultadoVotacaoCrud, TipoSessaoCrud, VotacaoEditView, VotacaoExpedienteEditView, @@ -68,6 +69,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 1a78adaa4..6130eb997 100644 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -3,6 +3,7 @@ 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.forms.utils import ErrorList @@ -13,7 +14,7 @@ from django.utils.decorators import method_decorator from django.utils.html import strip_tags from django.utils.translation import ugettext_lazy as _ from django.views.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 @@ -36,13 +37,14 @@ from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm from .forms import (AdicionarVariasMateriasFilterSet, ExpedienteForm, ListMateriaForm, MesaForm, OradorExpedienteForm, OradorForm, PautaSessaoFilterSet, PresencaForm, + ResumoOrdenacaoForm, SessaoPlenariaFilterSet, VotacaoEditForm, VotacaoForm, VotacaoNominalForm) from .models import (Bancada, Bloco, CargoBancada, CargoMesa, ExpedienteMateria, ExpedienteSessao, IntegranteMesa, MateriaLegislativa, Orador, OradorExpediente, OrdemDia, - PresencaOrdemDia, RegistroVotacao, SessaoPlenaria, - SessaoPlenariaPresenca, TipoExpediente, + PresencaOrdemDia, RegistroVotacao, ResumoOrdenacao, + SessaoPlenaria, SessaoPlenariaPresenca, TipoExpediente, TipoResultadoVotacao, TipoSessaoPlenaria, VotoNominal, VotoParlamentar) @@ -930,6 +932,48 @@ class MesaView(FormMixin, DetailView): return reverse('sapl.sessao:mesa', kwargs={'pk': pk}) +class ResumoOrdenacaoView(PermissionRequiredMixin, FormView): + template_name = 'sessao/resumo_ordenacao.html' + form_class = ResumoOrdenacaoForm + permission_required = 'sessao.change_resumoordenacao' + + def get_success_url(self): + 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): template_name = 'sessao/resumo.html' model = SessaoPlenaria @@ -1022,11 +1066,14 @@ class ResumoView(DetailView): titulo = m.materia numero = m.numero_ordem - resultado = m.registrovotacao_set.all() - if resultado: - resultado = resultado[0].tipo_resultado_votacao.nome + rv = m.registrovotacao_set.first() + if rv: + resultado = rv.tipo_resultado_votacao.nome + resultado_observacao = rv.observacao + else: resultado = _('Matéria não votada') + resultado_observacao = _(' ') autoria = Autoria.objects.filter(materia_id=m.materia_id) autor = [str(x.autor) for x in autoria] @@ -1035,6 +1082,7 @@ class ResumoView(DetailView): 'titulo': titulo, 'numero': numero, 'resultado': resultado, + 'resultado_observacao': resultado_observacao, 'autor': autor } materias_expediente.append(mat) @@ -1117,13 +1165,54 @@ class ResumoView(DetailView): if not 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': 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) diff --git a/sapl/settings.py b/sapl/settings.py index 11b94b3de..694c12a95 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -84,11 +84,22 @@ INSTALLED_APPS = ( ) + SAPL_APPS +# FTS = Full Text Search +SEARCH_BACKEND = 'haystack.backends.whoosh_backend.WhooshEngine' +SEARCH_URL = ('PATH', PROJECT_DIR.child('whoosh')) + +SOLR_URL = config('SOLR_URL', cast=str, default='') +if SOLR_URL: + SEARCH_BACKEND = 'haystack.backends.solr_backend.SolrEngine' + SEARCH_URL = ('URL', config('SOLR_URL', cast=str)) + # ...or for multicore... + # 'URL': 'http://127.0.0.1:8983/solr/mysite', + HAYSTACK_CONNECTIONS = { 'default': { - 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine', - 'PATH': PROJECT_DIR.child('whoosh'), + 'ENGINE': SEARCH_BACKEND, + SEARCH_URL[0] : SEARCH_URL[1] }, } @@ -111,10 +122,7 @@ MIDDLEWARE_CLASSES = ( REST_FRAMEWORK = { - "DEFAULT_RENDERER_CLASSES": ( - "rest_framework.renderers.JSONRenderer", - # "rest_framework.renderers.BrowsableAPIRenderer", - ), + "UNICODE_JSON": False, "DEFAULT_PARSER_CLASSES": ( "rest_framework.parsers.JSONParser", ), @@ -208,6 +216,7 @@ LOCALE_PATHS = ( # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.8/howto/static-files/ + STATIC_URL = '/static/' STATIC_ROOT = PROJECT_DIR.child("collected_static") STATICFILES_DIRS = (BASE_DIR.child("static"),) diff --git a/sapl/templates/base.html b/sapl/templates/base.html index 51351e7a0..5348ee291 100644 --- a/sapl/templates/base.html +++ b/sapl/templates/base.html @@ -157,11 +157,13 @@
+ {% trans 'Logo do Interlegis' %} +

- Desenvolvido pelo Interlegis em software livre e aberto. + Desenvolvido pelo Interlegis em software livre e aberto.

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..a4b09d796 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 @@
- +
- - -
-
-
-
-
-
-
diff --git a/sapl/templates/materia/materialegislativa_filter.html b/sapl/templates/materia/materialegislativa_filter.html index 4af743b7b..ec65ebd19 100644 --- a/sapl/templates/materia/materialegislativa_filter.html +++ b/sapl/templates/materia/materialegislativa_filter.html @@ -58,10 +58,11 @@ {% if m.autoria_set.all %} Autores: {% for a in m.autoria_set.all %} - {% if not forloop.first %} - ,    {{a.autor}} - {% else %} -  {{a.autor}} + {% if a.primeiro_autor %} + {% if not forloop.first %} + ,    + {% endif %} +  {{a.autor.nome}} {% endif %} {% endfor %}
@@ -125,6 +126,9 @@ {% endfor %} {% endif %}

+ {% if m.tramitacao_set.all.exists %} + Acompanhar Matéria + {% endif %} {% endfor %} {% else %} 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/parlamentares/composicaomesa_form.html b/sapl/templates/parlamentares/composicaomesa_form.html index 855313442..6ba96835f 100644 --- a/sapl/templates/parlamentares/composicaomesa_form.html +++ b/sapl/templates/parlamentares/composicaomesa_form.html @@ -158,8 +158,7 @@ // Atualiza os campos após alguma operação de mudança da Legislatura/Sessao ou // Inserção/Remoção function altera_field(id_legislatura, id_sessao=null, msg=null){ - // Pega o novo valor do campo que foi modificado (Sessao/Legislatura) ou utiliza - // o valor da Legislatura, por conveniência, quando há alguma inserção ou remoção + // Pega o novo valor dos campos modificados var sessao_value = id_sessao var legislatura_value = id_legislatura 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 fbd780a30..d9150da59 100644 --- a/sapl/templates/parlamentares/public_composicaomesa_form.html +++ b/sapl/templates/parlamentares/public_composicaomesa_form.html @@ -10,7 +10,7 @@
- {% for l in legislaturas %}
- {% for s in sessoes %}
-
- 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}}
-
-


+ {% 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çãoObservação 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/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/utils.py b/sapl/utils.py index 81ebea6cc..618a3f4f8 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -1,15 +1,13 @@ -import hashlib -import logging -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 hashlib +import logging +import os +import re -import django_filters -import magic from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML, Button from django import forms @@ -22,10 +20,13 @@ from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ from floppyforms import ClearableFileInput from reversion.admin import VersionAdmin +import django_filters +import magic from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row from sapl.settings import BASE_DIR, PROJECT_DIR + sapl_logger = logging.getLogger(BASE_DIR.name) @@ -159,8 +160,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 +369,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 +392,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 = ( @@ -605,8 +520,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 +539,7 @@ 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' %\ + path = './sapl/%(prefix)s/%(model_name)s/%(subpath)s/%(pk)s/%(filename)s' %\ { 'prefix': prefix, 'model_name': instance._meta.model_name, @@ -637,6 +552,7 @@ def texto_upload_path(instance, filename, subpath=''): class UpdateIndexCommand(Thread): + def run(self): call([PROJECT_DIR.child('manage.py'), 'update_index'], stdout=PIPE) diff --git a/start.sh b/start.sh index 8a213affd..1b00801a2 100755 --- a/start.sh +++ b/start.sh @@ -27,7 +27,7 @@ create_env() { echo "SECRET_KEY="$KEY > $FILENAME # now only appends echo "DATABASE_URL = "$DATABASE_URL >> $FILENAME - echo "DEBUG = ""${DEBUG-True}" >> $FILENAME + echo "DEBUG = ""${DEBUG-False}" >> $FILENAME echo "EMAIL_USE_TLS = ""${USE_TLS-True}" >> $FILENAME echo "EMAIL_PORT = ""${EMAIL_PORT-587}" >> $FILENAME echo "EMAIL_HOST = ""${EMAIL_HOST-''}" >> $FILENAME