diff --git a/.gitignore b/.gitignore index 83de47a86..52baa1622 100644 --- a/.gitignore +++ b/.gitignore @@ -92,4 +92,6 @@ collected_static bower bower_components media -whoosh/ \ No newline at end of file +whoosh/ +postgres-data/ +data/ diff --git a/.travis.yml b/.travis.yml index bf5ee38fd..0e0a27ad4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,3 +20,7 @@ script: - ./manage.py bower install - py.test --create-db # - ./test_and_check_qa.sh + +addons: + hosts: + - 127.0.0.1 sapldb diff --git a/Dockerfile b/Dockerfile index 9880dc4ef..59bee177e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,53 +1,46 @@ -FROM ubuntu:15.04 - -RUN locale-gen en_US.UTF-8 -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en -ENV LC_ALL en_US.UTF-8 - -RUN mkdir /sapl - -RUN echo "deb http://archive.ubuntu.com/ubuntu/ vivid universe" | tee -a "/etc/apt/sources.list" - -RUN \ - apt-get update && \ - apt-get install -y -f \ - software-properties-common \ - libpq-dev \ - graphviz-dev \ - graphviz \ - build-essential \ - git \ - pkg-config \ - python3-dev \ - libxml2-dev \ - libjpeg-dev \ - libssl-dev \ - libffi-dev \ - libxslt1-dev \ - python3-setuptools \ - curl - -# use python3 in pip -RUN easy_install3 pip lxml - -# install nodejs -RUN DEBIAN_FRONTEND=noninteractive curl -sL https://deb.nodesource.com/setup_5.x | bash - -RUN DEBIAN_FRONTEND=noninteractive apt-get install -y nodejs - -# install bower -RUN npm install -g bower - -# Bower aceitar root -RUN touch /root/.bowerrc -RUN chmod 751 /root/.bowerrc -RUN echo "{ \"allow_root\": true }" >> /root/.bowerrc - -WORKDIR /sapl - -ADD . /sapl - -RUN pip install -r requirements/dev-requirements.txt -RUN pip install --upgrade setuptools - -# RUN python3 manage.py bower install +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 + +RUN apk add --no-cache python3 nginx && \ + python3 -m ensurepip && \ + rm -r /usr/lib/python*/ensurepip && \ + pip3 install --upgrade pip setuptools && \ + rm -r /root/.cache && \ + rm -f /etc/nginx/conf.d/* + +RUN mkdir -p /var/interlegis/sapl && \ + apk add --update --no-cache $BUILD_PACKAGES && \ + npm install -g bower && \ + npm cache clean + +WORKDIR /var/interlegis/sapl/ + +ADD . /var/interlegis/sapl/ + +COPY start.sh /var/interlegis/sapl/ +COPY config/nginx/sapl.conf /etc/nginx/conf.d +COPY config/nginx/nginx.conf /etc/nginx/nginx.conf + +RUN pip install -r /var/interlegis/sapl/requirements/requirements.txt --upgrade setuptools && \ + rm -r /root/.cache && \ + rm -r /tmp/* + +COPY config/env_dockerfile /var/interlegis/sapl/sapl/.env + +# manage.py bower install bug: https://github.com/nvbn/django-bower/issues/51 + +RUN python3 manage.py bower_install -- --allow-root --no-input && \ + python3 manage.py collectstatic --no-input && \ + rm -rf /var/interlegis/sapl/sapl/.env && \ + rm -rf /var/interlegis/sapl/sapl.db + +RUN chmod +x /var/interlegis/sapl/start.sh && \ + ln -sf /dev/stdout /var/log/nginx/access.log && \ + ln -sf /dev/stderr /var/log/nginx/error.log + +VOLUME ["/var/interlegis/sapl/data", "/var/interlegis/sapl/media"] + +CMD ["/var/interlegis/sapl/start.sh"] diff --git a/bug.txt b/bug.txt new file mode 100644 index 000000000..e69de29bb diff --git a/busy-wait.sh b/busy-wait.sh new file mode 100644 index 000000000..73fde5c6b --- /dev/null +++ b/busy-wait.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +while true; do + COUNT_PG=`psql $1 -c '\l \q' | grep sapl | wc -l` + if ! [ "$COUNT_PG" -eq "0" ]; then + break + fi + echo "Esperando Database Setup" + sleep 10 +done diff --git a/config/env-sample b/config/env-sample new file mode 100644 index 000000000..c358f9017 --- /dev/null +++ b/config/env-sample @@ -0,0 +1,8 @@ +DATABASE_URL = postgresql://postgres:@sapldb:/sapl +KEY +DEBUG = False +EMAIL_USE_TLS = True +EMAIL_PORT = 587 +EMAIL_HOST = '' +EMAIL_HOST_USER = '' +EMAIL_HOST_PASSWORD = '' diff --git a/config/env_dockerfile b/config/env_dockerfile new file mode 100644 index 000000000..c83fc88f1 --- /dev/null +++ b/config/env_dockerfile @@ -0,0 +1,8 @@ +DATABASE_URL = sqlite:///sapl.db +SECRET_KEY = 'Dockerfile_Key' +DEBUG = False +EMAIL_USE_TLS = True +EMAIL_PORT = 587 +EMAIL_HOST = '' +EMAIL_HOST_USER = '' +EMAIL_HOST_PASSWORD = '' diff --git a/config/nginx/nginx.conf b/config/nginx/nginx.conf new file mode 100644 index 000000000..cfea596f1 --- /dev/null +++ b/config/nginx/nginx.conf @@ -0,0 +1,31 @@ +user nginx; +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile off; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/config/nginx/sapl.conf b/config/nginx/sapl.conf new file mode 100644 index 000000000..ef3dac421 --- /dev/null +++ b/config/nginx/sapl.conf @@ -0,0 +1,39 @@ +upstream sapl_server { + + server unix:/var/interlegis/sapl/run/gunicorn.sock fail_timeout=0; + +} + +server { + + listen 80; + server_name sapl.test; + + client_max_body_size 4G; + + location /static/ { + alias /var/interlegis/sapl/collected_static/; + } + + location /media/ { + alias /var/interlegis/sapl/media/; + } + + location / { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_set_header Host $http_host; + + proxy_redirect off; + + + if (!-f $request_filename) { + proxy_pass http://sapl_server; + break; + } + } + error_page 500 502 503 504 /500.html; + location = /500.html { + root /var/interlegis/sapl/sapl/static/; + } +} diff --git a/create_admin.py b/create_admin.py new file mode 100644 index 000000000..7fe03a65c --- /dev/null +++ b/create_admin.py @@ -0,0 +1,33 @@ +import sys +import os +import django + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sapl.settings") +django.setup() + +from django.contrib.auth.models import User + +def create_superuser(): + 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 '' + + if User.objects.filter(username=username).exists(): + print("[SUPERUSER] User %s already exists. Exiting without change." % username) + sys.exit('ADMIN_USER_EXISTS') + else: + if not password: + print("[SUPERUSER] Environment variable $ADMIN_PASSWORD for user %s was not set. Leaving..." % username) + sys.exit('MISSING_ADMIN_PASSWORD') + + print("[SUPERUSER] Creating superuser...") + + u = User.objects.create_superuser(username=username, password=password, email=email) + u.save() + + print("[SUPERUSER] Done.") + + sys.exit(0) + +if __name__ == '__main__': + create_superuser() diff --git a/docker-compose.yml b/docker-compose.yml index 8de57e3aa..50bdf6512 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,17 +1,20 @@ -localhost: +sapldb: image: postgres environment: POSTGRES_PASSWORD: sapl POSTGRES_USER: sapl POSTGRES_DB: sapl + PGDATA : /var/lib/postgresql/data/ + volumes: + - sapldb_data:/var/lib/postgresql/data/ ports: - "5532:5432" -web: - build: . - command: /bin/sh -c "/bin/sh busy-wait.sh && python3 manage.py bower install && python3 manage.py migrate && /bin/sh gunicorn_start.sh /sapl" +sapl: + image: interlegis/sapl:latest volumes: - - .:/sapl - ports: - - "8000:8000" + - sapl_data:/var/interlegis/sapl/data + - sapl_media:/var/interlegis/sapl/media links: - - localhost + - sapldb + ports: + - "80:80" diff --git a/docker-env.sh b/docker-env.sh new file mode 100644 index 000000000..07528b0f1 --- /dev/null +++ b/docker-env.sh @@ -0,0 +1,6 @@ +#/bin/bash + +KEY=`python gen-key.py` +echo $KEY + +sed -e "s/SECRET_KEY = None/SECRET_KEY = $KEY/g" config/env-sample diff --git a/envfile b/envfile new file mode 100644 index 000000000..a7aef4b0f --- /dev/null +++ b/envfile @@ -0,0 +1 @@ +EMAIL_HOST_USER=foo diff --git a/genkey.py b/genkey.py new file mode 100644 index 000000000..85f399c54 --- /dev/null +++ b/genkey.py @@ -0,0 +1,7 @@ +import random + +def generate_secret(): + return ''.join([random.SystemRandom().choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)]) + +if __name__ == '__main__': + print(generate_secret()) diff --git a/gunicorn_start.sh b/gunicorn_start.sh index 34147cb97..79666cf19 100755 --- a/gunicorn_start.sh +++ b/gunicorn_start.sh @@ -12,7 +12,7 @@ then fi NAME="SAPL" # Name of the application (*) -DJANGODIR=/var/interlegis/sapl # Django project directory (*) +DJANGODIR=/var/interlegis/sapl/ # Django project directory (*) SOCKFILE=/var/interlegis/sapl/run/gunicorn.sock # we will communicate using this unix socket (*) USER=`whoami` # the user to run as (*) GROUP=`whoami` # the group to run as (*) @@ -23,11 +23,14 @@ DJANGO_WSGI_MODULE=sapl.wsgi # WSGI module name (*) echo "Starting $NAME as `whoami` on base dir $SAPL_DIR" -# Activate the virtual environment -cd $DJANGODIR -source /var/interlegis/.virtualenvs/sapl/bin/activate -export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE -export PYTHONPATH=$DJANGODIR:$PYTHONPATH +# parameter can be passed to run without virtualenv +if [[ "$@" != "no-venv" ]]; then + # Activate the virtual environment + cd $DJANGODIR + source /var/interlegis/.virtualenvs/sapl/bin/activate + export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE + export PYTHONPATH=$DJANGODIR:$PYTHONPATH +fi # Create the run directory if it doesn't exist RUNDIR=$(dirname $SOCKFILE) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 6d7592cda..7fd7dbd80 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -15,7 +15,7 @@ django-extra-views==0.8.0 django-filter==0.15.3 django-floppyforms==1.6.2 django-model-utils==2.5 -django-sass-processor==0.4.6 +django-sass-processor==0.5.3 djangorestframework drfdocs easy-thumbnails==2.3 diff --git a/sapl/.env_test b/sapl/.env_test index 5c2c60ec1..9416d402d 100644 --- a/sapl/.env_test +++ b/sapl/.env_test @@ -1,4 +1,4 @@ -DATABASE_URL = postgresql://postgres:@localhost:/sapl +DATABASE_URL = postgresql://postgres:@sapldb:/sapl SECRET_KEY=TravisTest DEBUG=False EMAIL_USE_TLS = True diff --git a/sapl/base/migrations/0002_auto_20170331_1900.py b/sapl/base/migrations/0002_auto_20170331_1900.py new file mode 100644 index 000000000..5ee726647 --- /dev/null +++ b/sapl/base/migrations/0002_auto_20170331_1900.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-03-31 19:00 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Argumento', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('argumento', models.CharField(max_length=50, verbose_name='Argumento')), + ], + options={ + 'verbose_name': 'Argumento da constraint', + 'verbose_name_plural': 'Argumentos da constraint', + }, + ), + migrations.CreateModel( + name='Constraint', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nome_tabela', models.CharField(max_length=50, verbose_name='Nome da tabela')), + ('nome_constraint', models.CharField(max_length=100, verbose_name='Nome da constraint')), + ('nome_model', models.CharField(max_length=50, verbose_name='Nome da model')), + ('tipo_constraint', models.CharField(max_length=50, verbose_name='Tipo da constraint')), + ], + options={ + 'verbose_name': 'Constraint removida', + 'verbose_name_plural': 'Constraints removidas', + }, + ), + migrations.AddField( + model_name='problemamigracao', + name='eh_importante', + field=models.BooleanField(default=False, verbose_name='É importante?'), + ), + migrations.AddField( + model_name='argumento', + name='constraint', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='base.Constraint'), + ), + ] diff --git a/sapl/base/models.py b/sapl/base/models.py index 8825ea2a4..9dd0f5ec3 100644 --- a/sapl/base/models.py +++ b/sapl/base/models.py @@ -61,16 +61,45 @@ class ProblemaMigracao(models.Model): content_object = GenericForeignKey('content_type', 'object_id') nome_campo = models.CharField(max_length=100, blank=True, - verbose_name='Nome do(s) Campo(s)') + verbose_name=_('Nome do(s) Campo(s)')) problema = models.CharField(max_length=300, verbose_name=_('Problema')) descricao = models.CharField(max_length=300, verbose_name=_('Descrição')) - eh_stub = models.BooleanField(verbose_name='É stub?') + eh_stub = models.BooleanField(verbose_name=_('É stub?')) + eh_importante = models.BooleanField( + default=False, verbose_name=_('É importante?')) class Meta: verbose_name = _('Problema na Migração') verbose_name_plural = _('Problemas na Migração') +@reversion.register() +class Constraint(models.Model): + nome_tabela = models.CharField( + max_length=50, verbose_name=_('Nome da tabela')) + nome_constraint = models.CharField( + max_length=100, verbose_name=_('Nome da constraint')) + nome_model = models.CharField( + max_length=50, verbose_name=_('Nome da model')) + tipo_constraint = models.CharField( + max_length=50, verbose_name=_('Tipo da constraint')) + + class Meta: + verbose_name = _('Constraint removida') + verbose_name_plural = _('Constraints removidas') + + +@reversion.register() +class Argumento(models.Model): + constraint = models.ForeignKey(Constraint) + argumento = models.CharField( + max_length=50, verbose_name=_('Argumento')) + + class Meta: + verbose_name = _('Argumento da constraint') + verbose_name_plural = _('Argumentos da constraint') + + @reversion.register() class AppConfig(models.Model): diff --git a/sapl/env-backup b/sapl/env-backup new file mode 100644 index 000000000..5824d3eec --- /dev/null +++ b/sapl/env-backup @@ -0,0 +1,8 @@ +SECRET_KEY=qbl0+_+h6^u_k0ggkv&+ff&56s!d(*k+vk^_eu3-to_w&k-kp@ +DATABASE_URL = postgresql://sapl:sapl@localhost:5432/sapl +DEBUG = True +EMAIL_USE_TLS = True +EMAIL_PORT = 587 +EMAIL_HOST = '' +EMAIL_HOST_USER = '' +EMAIL_HOST_PASSWORD = '' diff --git a/sapl/legacy/management/commands/recria_constraints.py b/sapl/legacy/management/commands/recria_constraints.py new file mode 100644 index 000000000..9e999e5f6 --- /dev/null +++ b/sapl/legacy/management/commands/recria_constraints.py @@ -0,0 +1,12 @@ +from django.core.management.base import BaseCommand + +from sapl.legacy.migration import recria_constraints + + +class Command(BaseCommand): + + help = (u'Recria constraints do PostgreSQL excluidas durante ' + 'migração de dados') + + def handle(self, *args, **options): + recria_constraints() diff --git a/sapl/legacy/migracao_documentos.py b/sapl/legacy/migracao_documentos.py index 4d9877fa7..21697dc4f 100644 --- a/sapl/legacy/migracao_documentos.py +++ b/sapl/legacy/migracao_documentos.py @@ -4,6 +4,7 @@ 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) @@ -12,6 +13,8 @@ from sapl.parlamentares.models import Parlamentar from sapl.protocoloadm.models import 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 = { @@ -160,7 +163,29 @@ def migrar_docs_por_ids(tipo): tipo.__name__, id, destino)) +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) + + def migrar_documentos(): + # precisamos excluir os sinais de post_save e post_delete para não que o + # computador não trave com a criação de threads desnecessárias + desconecta_sinais_indexacao() + # aqui supomos que uma pasta chamada sapl_documentos está em MEDIA_ROOT # com o conteúdo da pasta de mesmo nome do zope # Os arquivos da pasta serão movidos para a nova estrutura e a pasta será @@ -186,3 +211,6 @@ def migrar_documentos(): len(sobrando))) for doc in sobrando: print(' {}'. format(doc)) + # + # reconexão dos sinais desligados no inicio da migração de documentos + conecta_sinais_indexacao() diff --git a/sapl/legacy/migration.py b/sapl/legacy/migration.py index ef87a3960..0d2f5be10 100644 --- a/sapl/legacy/migration.py +++ b/sapl/legacy/migration.py @@ -13,22 +13,24 @@ 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.base import ModelBase +from django.db.models.signals import post_delete, post_save from model_mommy import mommy from model_mommy.mommy import foreign_key_required, make -from sapl.base.models import Autor, ProblemaMigracao +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 (StatusTramitacao, TipoDocumento, +from sapl.materia.models import (DocumentoAcessorio, MateriaLegislativa, + StatusTramitacao, TipoDocumento, TipoMateriaLegislativa, TipoProposicao, Tramitacao) from sapl.norma.models import (AssuntoNorma, NormaJuridica, - TipoVinculoNormaJuridica) + TipoVinculoNormaJuridica, NormaRelacionada) from sapl.parlamentares.models import Parlamentar from sapl.protocoloadm.models import Protocolo, StatusTramitacaoAdministrativo from sapl.sessao.models import ExpedienteMateria, OrdemDia from sapl.settings import PROJECT_DIR -from sapl.utils import normalize +from sapl.utils import delete_texto, normalize, save_texto # BASE ###################################################################### # apps to be migrated, in app dependency order (very important) @@ -109,6 +111,10 @@ def warn(msg): print('CUIDADO! ' + msg) +def erro(msg): + print('ERRO: ' + msg) + + def get_fk_related(field, value, label=None): if value is None and field.null is False: value = 0 @@ -124,9 +130,7 @@ def get_fk_related(field, value, label=None): if not field.null: fields_dict = get_fields_dict(field.related_model) # Cria stub ao final da tabela para evitar erros - pk = 1 - if hasattr(field.related_model.objects.last(), 'pk'): - pk = field.related_model.objects.last().pk + pk = get_last_value(field.related_model) with reversion.create_revision(): reversion.set_comment('Stub criado pela migração') value = mommy.make( @@ -197,6 +201,12 @@ def iter_sql_records(sql, db): record.__dict__.update(zip(fieldnames, row)) yield record +# Todos os models têm no máximo uma constraint unique together +# Isso é necessário para que o método delete_constraints funcione corretamente +assert all(len(model._meta.unique_together) <= 1 + for app in appconfs + for model in app.models.values()) + def delete_constraints(model): # pega nome da unique constraint dado o nome da tabela @@ -210,40 +220,66 @@ def delete_constraints(model): for r in result: if r[0].endswith('key'): words_list = r[0].split('_') - one_to_one_constraints.append([table, r[0], words_list, model]) + constraint = Constraint.objects.create( + nome_tabela=table, nome_constraint=r[0], + nome_model=model.__name__, tipo_constraint='one_to_one') + for w in words_list: + Argumento.objects.create(constraint=constraint, argumento=w) else: - args = None - args_list = [] if model._meta.unique_together: - args = model._meta.unique_together[0] - args_list = list(args) - unique_constraints.append([table, r[0], args_list, model]) + args_list = model._meta.unique_together[0] + constraint = Constraint.objects.create( + nome_tabela=table, nome_constraint=r[0], + nome_model=model.__name__, + tipo_constraint='unique_together') + for a in args_list: + Argumento.objects.create(constraint=constraint, + argumento=a) warn('Excluindo unique constraint de nome %s' % r[0]) exec_sql("ALTER TABLE %s DROP CONSTRAINT %s;" % (table, r[0])) -def recreate_constraints(): - if one_to_one_constraints: - for constraint in one_to_one_constraints: - table, name, args, model = constraint +def recria_constraints(): + constraints = Constraint.objects.all() + for con in constraints: + if con.tipo_constraint == 'one_to_one': + nome_tabela = con.nome_tabela + nome_constraint = con.nome_constraint + args = [a.argumento for a in con.argumento_set.all()] args_string = '' args_string = "(" + "_".join(map(str, args[2:-1])) + ")" - exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" % - (table, name, args_string)) - if unique_constraints: - for constraint in unique_constraints: - table, name, args, model = constraint + 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) + if con.tipo_constraint == 'unique_together': + nome_tabela = con.nome_tabela + nome_constraint = con.nome_constraint + # Pegando explicitamente o primeiro valor do filter, + # pois pode ser que haja mais de uma ocorrência + model = ContentType.objects.filter( + model=con.nome_model.lower())[0].model_class() + args = [a.argumento for a in con.argumento_set.all()] for i in range(len(args)): if isinstance(model._meta.get_field(args[i]), models.ForeignKey): args[i] = args[i] + '_id' args_string = '' args_string += "(" + ', '.join(map(str, args)) + ")" - exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" % - (table, name, args_string)) - one_to_one_constraints.clear() - unique_constraints.clear() + 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): @@ -262,8 +298,8 @@ def get_last_value(model): def alter_sequence(model, id): sequence_name = '%s_id_seq' % model._meta.db_table - exec_sql('ALTER SEQUENCE %s RESTART WITH %s MINVALUE %s;' % ( - sequence_name, id, id)) + exec_sql('ALTER SEQUENCE %s RESTART WITH %s MINVALUE -1;' % ( + sequence_name, id)) def save_with_id(new, id): @@ -278,8 +314,7 @@ def save_relation(obj, nome_campo='', problema='', descricao='', eh_stub=False): link = ProblemaMigracao( content_object=obj, nome_campo=nome_campo, problema=problema, - descricao=descricao, eh_stub=eh_stub, - ) + descricao=descricao, eh_stub=eh_stub,) link.save() @@ -302,20 +337,34 @@ def get_fields_dict(model): def fill_vinculo_norma_juridica(): - lista = [('A', 'Altera a norma'), - ('R', 'Revoga integralmente a norma'), - ('P', 'Revoga parcialmente a norma'), - ('T', 'Revoga integralmente por consolidação'), - ('C', 'Norma Correlata'), - ('S', 'Ressalva a Norma'), - ('E', 'Reedita a Norma'), - ('I', 'Reedita a Norma com Alteração'), - ('G', 'Regulamenta a Norma'), - ('K', 'Suspende parcialmente a norma'), - ('L', 'Suspende integralmente a norma'), - ('N', 'Julgada integralmente inconstitucional'), - ('O', 'Julgada parcialmente inconstitucional')] - lista_objs = [TipoVinculoNormaJuridica(sigla=item[0], descricao=item[1]) + lista = [('A', 'Altera o(a)', + 'Alterado(a) pelo(a)'), + ('R', 'Revoga integralmente o(a)', + 'Revogado(a) integralmente pelo(a)'), + ('P', 'Revoga parcialmente o(a)', + 'Revogado(a) parcialmente pelo(a)'), + ('T', 'Revoga integralmente por consolidação', + 'Revogado(a) integralmente por consolidação'), + ('C', 'Norma correlata', + 'Norma correlata'), + ('S', 'Ressalva o(a)', + 'Ressalvada pelo(a)'), + ('E', 'Reedita o(a)', + 'Reeditada pelo(a)'), + ('I', 'Reedita com alteração o(a)', + 'Reeditada com alteração pelo(a)'), + ('G', 'Regulamenta o(a)', + 'Regulamentada pelo(a)'), + ('K', 'Suspende parcialmente o(a)', + 'Suspenso(a) parcialmente pelo(a)'), + ('L', 'Suspende integralmente o(a)', + 'Suspenso(a) integralmente pelo(a)'), + ('N', 'Julga integralmente inconstitucional', + 'Julgada integralmente inconstitucional'), + ('O', 'Julga parcialmente inconstitucional', + 'Julgada parcialmente inconstitucional')] + lista_objs = [TipoVinculoNormaJuridica( + sigla=item[0], descricao_ativa=item[1], descricao_passiva=item[2]) for item in lista] TipoVinculoNormaJuridica.objects.bulk_create(lista_objs) @@ -416,6 +465,9 @@ 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) @@ -427,7 +479,7 @@ class DataMigrator: for obj in self.to_delete: msg = 'A entrada de PK %s da model %s não pode ser ' \ 'excluida' % (obj.pk, obj._meta.model_name) - descricao = 'Um ou mais objetos protegidos ' + descricao = 'Um ou mais objetos protegidos' warn(msg + ' => ' + descricao) save_relation(obj=obj, problema=msg, descricao=descricao, eh_stub=False) @@ -435,8 +487,8 @@ class DataMigrator: info('Deletando stubs desnecessários...') while self.delete_stubs(): pass - info('Recriando unique constraints...') - # recreate_constraints() + + conecta_sinais_indexacao() def _do_migrate(self, obj): if isinstance(obj, AppConfig): @@ -504,6 +556,10 @@ class DataMigrator: if getattr(old, 'ind_excluido', False): self.to_delete.append(new) + # necessário para ajustar sequence da tabela para o ultimo valor de id + ultimo_valor = get_last_value(model) + alter_sequence(model, ultimo_valor+1) + def delete_ind_excluido(self): excluidos = 0 for obj in self.to_delete: @@ -580,6 +636,12 @@ def adjust_participacao(new, old): new.composicao = composicao +def adjust_normarelacionada(new, old): + tipo = TipoVinculoNormaJuridica.objects.filter(sigla=old.tip_vinculo) + assert len(tipo) == 1 + new.tipo_vinculo = tipo[0] + + def adjust_protocolo(new, old): if new.numero is None and not primeira_vez: p = ProtocoloLegado.objects.filter( @@ -685,6 +747,7 @@ AJUSTE_ANTES_SALVAR = { Autor: adjust_autor, Comissao: adjust_comissao, NormaJuridica: adjust_normajuridica_antes_salvar, + NormaRelacionada: adjust_normarelacionada, OrdemDia: adjust_ordemdia, Parlamentar: adjust_parlamentar, Participacao: adjust_participacao, @@ -731,3 +794,23 @@ 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/fix_tables.sql b/sapl/legacy/scripts/fix_tables.sql index 479a2ed5e..ed93373b1 100644 --- a/sapl/legacy/scripts/fix_tables.sql +++ b/sapl/legacy/scripts/fix_tables.sql @@ -2,15 +2,19 @@ SELECT REPLACE(@@sql_mode,'STRICT_TRANS_TABLES,',''); -- Exclui procedures caso já existam DROP PROCEDURE IF EXISTS verifica_campos_proposicao; +DROP PROCEDURE IF EXISTS verifica_campos_tipo_materia_legislativa; DROP PROCEDURE IF EXISTS verifica_campos_sessao_plenaria_presenca; DROP PROCEDURE IF EXISTS cria_lexml_registro_provedor_e_publicador; -- Procedure para criar campo num_proposicao em proposicao CREATE PROCEDURE verifica_campos_proposicao() BEGIN IF NOT EXISTS (SELECT * FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='proposicao' AND column_name='num_proposicao') THEN ALTER TABLE proposicao ADD COLUMN num_proposicao INT(11) NULL after txt_justif_devolucao; END IF; END; +-- Procedure para criar campo iind_num_automatica em tipo_materia_legislativa +CREATE PROCEDURE verifica_campos_tipo_materia_legislativa() BEGIN IF NOT EXISTS (SELECT * FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='tipo_materia_legislativa' AND column_name='ind_num_automatica') THEN ALTER TABLE tipo_materia_legislativa ADD COLUMN ind_num_automatica BOOLEAN NULL DEFAULT FALSE after des_tipo_materia; END IF; IF NOT EXISTS (SELECT * FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='tipo_materia_legislativa' AND column_name='quorum_minimo_votacao') THEN ALTER TABLE tipo_materia_legislativa ADD COLUMN quorum_minimo_votacao INT(11) NULL after ind_num_automatica; END IF; END; -- Procedure para criar campos cod_presenca_sessao (sendo a nova PK da tabela) e dat_sessao em sessao_plenaria_presenca CREATE PROCEDURE verifica_campos_sessao_plenaria_presenca() BEGIN IF NOT EXISTS (SELECT * FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='sessao_plenaria_presenca' AND column_name='cod_presenca_sessao') THEN ALTER TABLE sessao_plenaria_presenca DROP PRIMARY KEY, ADD cod_presenca_sessao INT AUTO_INCREMENT PRIMARY KEY FIRST; END IF; IF NOT EXISTS (SELECT * FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='sessao_plenaria_presenca' AND column_name='dat_sessao') THEN ALTER TABLE sessao_plenaria_presenca ADD COLUMN dat_sessao DATE NULL after cod_parlamentar; END IF; END; -- Procedure para criar tabela lexml_registro_provedor e lexml_registro_publicador CREATE PROCEDURE cria_lexml_registro_provedor_e_publicador() BEGIN IF NOT EXISTS (SELECT * FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='lexml_registro_publicador') THEN CREATE TABLE lexml_registro_publicador (cod_publicador INT AUTO_INCREMENT NOT NULL, id_publicador INT, nom_publicador VARCHAR(255), adm_email VARCHAR(50), sigla VARCHAR(255), nom_responsavel VARCHAR(255), tipo VARCHAR(50), id_responsavel INT, PRIMARY KEY (cod_publicador)); END IF; IF NOT EXISTS (SELECT * FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='lexml_registro_provedor') THEN CREATE TABLE lexml_registro_provedor (cod_provedor INT AUTO_INCREMENT NOT NULL, id_provedor INT, nom_provedor VARCHAR(255), sgl_provedor VARCHAR(15), adm_email VARCHAR(50), nom_responsavel VARCHAR(255), tipo VARCHAR(50), id_responsavel INT, xml_provedor LONGTEXT, PRIMARY KEY (cod_provedor)); END IF; END; -- Executa as procedures criadas acima CALL verifica_campos_proposicao; +CALL verifica_campos_tipo_materia_legislativa; CALL verifica_campos_sessao_plenaria_presenca; CALL cria_lexml_registro_provedor_e_publicador; diff --git a/sapl/legacy/scripts/migra_um_db.sh b/sapl/legacy/scripts/migra_um_db.sh index 26240d8d4..f55dfb53a 100755 --- a/sapl/legacy/scripts/migra_um_db.sh +++ b/sapl/legacy/scripts/migra_um_db.sh @@ -23,3 +23,9 @@ echo "--- MIGRACAO DE DADOS ---" | tee -a $LOG echo >> $LOG DATABASE_NAME=$1 ./manage.py migracao_25_31 -f --settings sapl.legacy_migration_settings |& tee -a $LOG echo >> $LOG + + +echo "--- RECRIANDO CONSTRAINTS ---" | tee -a $LOG +echo >> $LOG +DATABASE_NAME=$1 ./manage.py recria_constraints --settings sapl.legacy_migration_settings |& tee -a $LOG +echo >> $LOG diff --git a/sapl/materia/migrations/0004_auto_20170504_1751.py b/sapl/materia/migrations/0004_auto_20170504_1751.py new file mode 100644 index 000000000..49a7e8938 --- /dev/null +++ b/sapl/materia/migrations/0004_auto_20170504_1751.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-05-04 17:51 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('materia', '0003_auto_20170403_1505'), + ] + + operations = [ + migrations.AlterField( + model_name='assuntomateria', + name='dispositivo', + field=models.CharField(blank=True, default='', max_length=200, verbose_name='Descrição do Dispositivo Legal'), + preserve_default=False, + ), + migrations.AlterField( + model_name='materiaassunto', + name='assunto', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='materia.AssuntoMateria', verbose_name='Assunto'), + ), + migrations.AlterField( + model_name='materiaassunto', + name='materia', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='materia.MateriaLegislativa', verbose_name='Matéria'), + ), + ] diff --git a/sapl/materia/models.py b/sapl/materia/models.py index 1a9d2afff..b5390f3b4 100644 --- a/sapl/materia/models.py +++ b/sapl/materia/models.py @@ -4,6 +4,7 @@ import reversion from django.contrib.auth.models import Group from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.utils import formats from django.utils.translation import ugettext_lazy as _ @@ -215,6 +216,22 @@ class MateriaLegislativa(models.Model): return _('%(tipo)s nº %(numero)s de %(ano)s') % { 'tipo': self.tipo, 'numero': self.numero, 'ano': self.ano} + def data_entrada_protocolo(self): + ''' + hack: recuperar a data de entrada do protocolo sem gerar + dependência circular + ''' + from sapl.protocoloadm.models import Protocolo + if self.ano and self.numero_protocolo: + try: + return Protocolo.objects.get( + ano=self.ano, + numero=self.numero_protocolo).data + except ObjectDoesNotExist: + pass + + return '' + def delete(self, using=None, keep_parents=False): if self.texto_original: self.texto_original.delete() @@ -668,9 +685,19 @@ class Proposicao(models.Model): ) def __str__(self): - return '%s %s/%s' % (Proposicao._meta.verbose_name, - self.numero_proposicao, - self.ano) + if self.ano and self.numero_proposicao: + return '%s %s/%s' % (Proposicao._meta.verbose_name, + self.numero_proposicao, + self.ano) + else: + if len(self.descricao) < 30: + descricao = self.descricao[:28] + ' ...' + else: + descricao = self.descricao + + return '%s %s/%s' % (Proposicao._meta.verbose_name, + self.id, + descricao) def delete(self, using=None, keep_parents=False): if self.texto_original: diff --git a/sapl/materia/signals.py b/sapl/materia/signals.py index d913e9cd8..9f08b104c 100644 --- a/sapl/materia/signals.py +++ b/sapl/materia/signals.py @@ -1,29 +1,9 @@ -from subprocess import PIPE, call -from threading import Thread - from django.db.models.signals import post_delete, post_save - -from sapl.settings import PROJECT_DIR +from sapl.utils import save_texto, delete_texto from .models import DocumentoAcessorio, MateriaLegislativa -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() - - post_save.connect(save_texto, sender=MateriaLegislativa) post_save.connect(save_texto, sender=DocumentoAcessorio) post_delete.connect(delete_texto, sender=MateriaLegislativa) diff --git a/sapl/norma/legacy.yaml b/sapl/norma/legacy.yaml index 17a7f083d..3294561b8 100644 --- a/sapl/norma/legacy.yaml +++ b/sapl/norma/legacy.yaml @@ -42,8 +42,6 @@ LegislacaoCitada: subsecao: des_subsecao titulo: des_titulo -# TODO Descomentar quando a issue #832 for concluida -# TipoVinculoNormaJuridica (VinculoNormaJuridica): -# norma_referente: cod_norma_referente -# norma_referida: cod_norma_referida -# tipo_vinculo: tip_vinculo +NormaRelacionada (VinculoNormaJuridica): + norma_principal: cod_norma_referente + norma_relacionada: cod_norma_referida diff --git a/sapl/norma/signals.py b/sapl/norma/signals.py index 20d405e49..3089e563e 100644 --- a/sapl/norma/signals.py +++ b/sapl/norma/signals.py @@ -1,28 +1,8 @@ -from subprocess import PIPE, call -from threading import Thread - from django.db.models.signals import post_delete, post_save - -from sapl.settings import PROJECT_DIR +from sapl.utils import save_texto, delete_texto from .models import NormaJuridica -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() - - post_save.connect(save_texto, sender=NormaJuridica) post_delete.connect(delete_texto, sender=NormaJuridica) diff --git a/sapl/parlamentares/migrations/0002_auto_20170504_1751.py b/sapl/parlamentares/migrations/0002_auto_20170504_1751.py new file mode 100644 index 000000000..7e7039e94 --- /dev/null +++ b/sapl/parlamentares/migrations/0002_auto_20170504_1751.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-05-04 17:51 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='coligacao', + name='numero_votos', + field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Nº Votos Recebidos (Coligação)'), + ), + migrations.AlterField( + model_name='mandato', + name='votos_recebidos', + field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Votos Recebidos (Mandato)'), + ), + ] diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index d09f8dfac..4d15ab6fd 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -85,7 +85,8 @@ class Coligacao(models.Model): verbose_name=_('Legislatura')) nome = models.CharField(max_length=50, verbose_name=_('Nome')) numero_votos = models.PositiveIntegerField( - blank=True, null=True, verbose_name=_('Nº Votos Recebidos')) + blank=True, null=True, + verbose_name=_('Nº Votos Recebidos (Coligação)')) class Meta: verbose_name = _('Coligação') @@ -448,7 +449,7 @@ class Mandato(models.Model): tipo_causa_fim_mandato = models.PositiveIntegerField(blank=True, null=True) data_fim_mandato = models.DateField(verbose_name=_('Fim do Mandato')) votos_recebidos = models.PositiveIntegerField( - blank=True, null=True, verbose_name=_('Votos Recebidos')) + blank=True, null=True, verbose_name=_('Votos Recebidos (Mandato)')) data_expedicao_diploma = models.DateField( verbose_name=_('Expedição do Diploma')) titular = models.BooleanField( diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index bb431f582..3c6ac630a 100644 --- a/sapl/parlamentares/views.py +++ b/sapl/parlamentares/views.py @@ -1,7 +1,7 @@ from django.contrib import messages -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from django.core.urlresolvers import reverse, reverse_lazy -from django.db.models import F +from django.db.models import F, Q from django.http import JsonResponse from django.http.response import HttpResponseRedirect from django.utils.datastructures import MultiValueDictKeyError @@ -345,8 +345,47 @@ class ParlamentarCrud(Crud): # Tira Link do avatar_html e coloca no nome for row in context['rows']: + # Coloca a filiação atual ao invés da última + if row[0][1]: + # Pega o Parlamentar por meio da pk + parlamentar = Parlamentar.objects.get( + id=(row[0][1].split('/')[-1])) + + # Pega a Legislatura + legislatura = Legislatura.objects.get( + id=context['legislatura_id']) + + # As condições para mostrar a filiação são: + # A data de filiacao deve ser menor que a data de fim + # da legislatura e data de desfiliação deve nula, ou maior, + # ou igual a data de fim da legislatura + try: + filiacao = parlamentar.filiacao_set.get(Q( + data__lte=legislatura.data_fim, + data_desfiliacao__gte=legislatura.data_fim) | Q( + data__lte=legislatura.data_fim, + data_desfiliacao__isnull=True)) + + # Caso não exista filiação com essas condições + except ObjectDoesNotExist: + row[2] = ('Não possui filiação', None) + + # 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() + + # Caso encontre UMA filiação nessas condições + else: + row[2] = (filiacao.partido.sigla, None) + row[1] = (row[1][0], row[0][1]) row[0] = (row[0][0], None) + return context diff --git a/sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py b/sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py index a4648b71d..8890b9dcf 100644 --- a/sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py +++ b/sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py @@ -101,7 +101,7 @@ def inf_basicas(inf_basicas_dic): tmp += '\t\tInformações Básicas\n' tmp += '\t\t\n' - tmp += '\t\t\t \n' + tmp += '\t\t\t
\n' tmp += '\t\t
\n' tmp += '\t\tTipo da Sessão: ' + \ nom_sessao + '\n' @@ -120,7 +120,7 @@ def mesa(lst_mesa): tmp = '' tmp += '\t\tMesa Diretora\n' tmp += '\t\t\n' - tmp += '\t\t\t \n' + tmp += '\t\t\t
\n' tmp += '\t\t
\n' for mesa in lst_mesa: tmp += '\t\t' + \ @@ -136,7 +136,7 @@ def presenca(lst_presenca_sessao): tmp = '' tmp += '\t\tLista de Presença da Sessão\n' tmp += '\t\t\n' - tmp += '\t\t\t \n' + tmp += '\t\t\t
\n' tmp += '\t\t
\n' for presenca in lst_presenca_sessao: tmp += '\t\t' + \ @@ -154,10 +154,10 @@ def expedientes(lst_expedientes): tmp += '\t\t\n' tmp += '\t\t\t \n' tmp += '\t\t\n' - for expediente in lst_expedientes: - tmp += '\t\t' + \ + for idx, expediente in enumerate(lst_expedientes): + tmp += '\t\t' + '
' + \ expediente['nom_expediente'] + ':
\n' + \ - '' + \ + '' + \ expediente['txt_expediente'] + '\n' tmp += '\t\t\n' tmp += '\t\t\t \n' @@ -171,18 +171,22 @@ def expediente_materia(lst_expediente_materia): tmp = '' tmp += '\t\tMatérias do Expediente\n\n' tmp += '\t\t\n' - tmp += '\t\t\t \n' + tmp += '\t\t\t
\n' tmp += '\t\t
\n' tmp += '\n' tmp += 'MatériaEmentaResultado da Votação\n' for expediente_materia in lst_expediente_materia: - tmp += '' + str(expediente_materia['num_ordem']) + ' - ' + expediente_materia['id_materia'] + '\n' + 'Turno: ' + expediente_materia[ - 'des_turno'] + '\n' + 'Autor: ' + expediente_materia['nom_autor'] + '\n' + tmp += '' + str(expediente_materia['num_ordem']) + ' - ' + expediente_materia['id_materia'] + '\n' + 'Turno: ' + str(expediente_materia[ + 'des_turno']) + '\n' + 'Autor: ' + str(expediente_materia['nom_autor']) + '\n' txt_ementa = expediente_materia['txt_ementa'].replace('&', '&') tmp += '' + txt_ementa + '\n' tmp += '' + \ - str(expediente_materia['nom_resultado']) + '\n' + '' + \ - '\n' + str(expediente_materia['nom_resultado']) + '
\n' + '' + if expediente_materia['votacao_observacao'] != txt_ementa: + tmp += str(expediente_materia['votacao_observacao']) + else: + tmp += ' ' + tmp += '\n' tmp += '\t\t\n' return tmp @@ -195,7 +199,7 @@ def oradores_expediente(lst_oradores_expediente): tmp = '' tmp += '\t\tOradores do Expediente\n' tmp += '\t\t\n' - tmp += '\t\t\t \n' + tmp += '\t\t\t
\n' tmp += '\t\t
\n' for orador_expediente in lst_oradores_expediente: tmp += '\t\t' + str(orador_expediente['num_ordem']) + ' - ' + orador_expediente[ @@ -210,7 +214,7 @@ def presenca_ordem_dia(lst_presenca_ordem_dia): tmp = '' tmp += '\t\tLista de Presença da Ordem do Dia\n' tmp += '\t\t\n' - tmp += '\t\t\t \n' + tmp += '\t\t\t
\n' tmp += '\t\t
\n' for presenca_ordem_dia in lst_presenca_ordem_dia: tmp += '\t\t' + \ @@ -226,7 +230,7 @@ def votacao(lst_votacao): tmp = '' tmp += 'Matérias da Ordem do Dia\n\n' tmp += '\t\t\n' - tmp += '\t\t\t \n' + tmp += '\t\t\t
\n' tmp += '\t\t
\n' tmp += '\n' tmp += 'MatériaEmentaResultado da Votação\n' @@ -236,8 +240,12 @@ def votacao(lst_votacao): txt_ementa = votacao['txt_ementa'].replace('&', '&') tmp += '' + txt_ementa + '\n' tmp += '' + \ - str(votacao['nom_resultado']) + '\n' + '' + \ - '\n' + str(votacao['nom_resultado']) + '
\n' + '' + if votacao['votacao_observacao'] != txt_ementa: + tmp += str(votacao['votacao_observacao']) + else: + tmp += ' ' + tmp += '\n' tmp += '\t\t\n' return tmp @@ -250,7 +258,7 @@ def oradores(lst_oradores): tmp = '' tmp += '\t\tOradores das Explicações Pessoais\n' tmp += '\t\t\n' - tmp += '\t\t\t \n' + tmp += '\t\t\t
\n' tmp += '\t\t
\n' for orador in lst_oradores: tmp += '\t\t' + \ @@ -290,6 +298,7 @@ def principal(cabecalho_dic, rodape_dic, imagem, sessao, inf_basicas_dic, lst_me tmp += oradores(lst_oradores) tmp += '\t\n' tmp += '\n' + tmp_pdf = parseString(tmp) return tmp_pdf # if hasattr(context.temp_folder,arquivoPdf): diff --git a/sapl/relatorios/views.py b/sapl/relatorios/views.py index 9e482abda..4b5416c2c 100644 --- a/sapl/relatorios/views.py +++ b/sapl/relatorios/views.py @@ -1,5 +1,8 @@ from datetime import datetime +import re +import html + from django.core.exceptions import ObjectDoesNotExist from django.http import Http404, HttpResponse from django.utils.translation import ugettext_lazy as _ @@ -518,14 +521,28 @@ def get_sessao_plenaria(sessao, casa): # Exibe os Expedientes lst_expedientes = [] - for tip_expediente in TipoExpediente.objects.all(): - for expediente in ExpedienteSessao.objects.filter( - sessao_plenaria=sessao, tipo=tip_expediente): - dic_expedientes = {} - dic_expedientes["nom_expediente"] = str(tip_expediente) - dic_expedientes["txt_expediente"] = (expediente.conteudo) - if dic_expedientes: - lst_expedientes.append(dic_expedientes) + expedientes = ExpedienteSessao.objects.filter( + sessao_plenaria=sessao).order_by('tipo__nome') + + for e in expedientes: + + dic_expedientes = {} + dic_expedientes["nom_expediente"] = e.tipo.nome + conteudo = e.conteudo + + # unescape HTML codes + # https://github.com/interlegis/sapl/issues/1046 + conteudo = re.sub('style=".*?"', '', conteudo) + conteudo = html.unescape(conteudo) + + # escape special character '&' + # https://github.com/interlegis/sapl/issues/1009 + conteudo = conteudo.replace('&', '&') + + dic_expedientes["txt_expediente"] = conteudo + + if dic_expedientes: + lst_expedientes.append(dic_expedientes) # Lista das matérias do Expediente, incluindo o resultado das votacoes lst_expediente_materia = [] @@ -594,18 +611,16 @@ def get_sessao_plenaria(sessao, casa): dic_expediente_materia["nom_autor"] = 'Desconhecido' dic_expediente_materia["votacao_observacao"] = ' ' - if not expediente_materia.resultado: - resultado = RegistroVotacao.objects.filter( - tipo_resultado_votacao=expediente_materia.tipo_votacao) - - for i in resultado: + resultados = expediente_materia.registrovotacao_set.all() + if resultados: + for i in resultados: dic_expediente_materia["nom_resultado"] = ( i.tipo_resultado_votacao.nome) dic_expediente_materia["votacao_observacao"] = ( - expediente_materia.observacao) + i.observacao) else: dic_expediente_materia["nom_resultado"] = _("Matéria não votada") - dic_expediente_materia["votacao_observacao"] = _("Vazio") + dic_expediente_materia["votacao_observacao"] = _(" ") lst_expediente_materia.append(dic_expediente_materia) # Lista dos oradores do Expediente @@ -664,9 +679,9 @@ def get_sessao_plenaria(sessao, casa): materia=votacao.materia).first() if numeracao is not None: dic_votacao["des_numeracao"] = ( - str(numeracao.numero) + + str(numeracao.numero_materia) + '/' + - str(numeracao.ano)) + str(numeracao.ano_materia)) dic_votacao["des_turno"] = ' ' tramitacao = Tramitacao.objects.filter( @@ -710,16 +725,15 @@ def get_sessao_plenaria(sessao, casa): dic_votacao["nom_autor"] = 'Desconhecido' dic_votacao["votacao_observacao"] = ' ' - if not votacao.resultado: - resultado = RegistroVotacao.objects.filter( - tipo_resultado_votacao=votacao.tipo_votacao) - for i in resultado: + resultados = votacao.registrovotacao_set.all() + if resultados: + for i in resultados: dic_votacao["nom_resultado"] = i.tipo_resultado_votacao.nome if votacao.observacao: - dic_votacao["votacao_observacao"] = votacao.observacao + dic_votacao["votacao_observacao"] = i.observacao else: dic_votacao["nom_resultado"] = _("Matéria não votada") - dic_votacao["votacao_observacao"] = _("Vazio") + dic_votacao["votacao_observacao"] = _(" ") lst_votacao.append(dic_votacao) # Lista dos oradores nas Explicações Pessoais @@ -1086,11 +1100,11 @@ def get_pauta_sessao(sessao, casa): dic_votacao["ordem_observacao"] = votacao.observacao dic_votacao["des_numeracao"] = ' ' - numeracao = Numeracao.objects.filter(materia=materia) - # if numeracao is not None: - # numeracao = numeracao.first() - # dic_votacao["des_numeracao"] = str( - # numeracao.numero) + '/' + str(numeracao.ano) +# numeracao = Numeracao.objects.filter(materia=materia) +# if numeracao is not None: +# numeracao = numeracao.first() +# dic_votacao["des_numeracao"] = str( +# numeracao.numero_materia) + '/' + str(numeracao.ano_materia) dic_votacao["nom_autor"] = ' ' autoria = Autoria.objects.filter( diff --git a/sapl/rules/map_rules.py b/sapl/rules/map_rules.py index 906592fd0..05d869fd8 100644 --- a/sapl/rules/map_rules.py +++ b/sapl/rules/map_rules.py @@ -197,6 +197,8 @@ rules_group_geral = { (base.CasaLegislativa, __listdetailchange__), (base.ProblemaMigracao, []), + (base.Argumento, []), + (base.Constraint, []), (base.TipoAutor, __base__), (base.Autor, __base__), diff --git a/sapl/rules/tests/test_rules.py b/sapl/rules/tests/test_rules.py index 3662477b9..6fc7eb3fe 100644 --- a/sapl/rules/tests/test_rules.py +++ b/sapl/rules/tests/test_rules.py @@ -6,7 +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 +from sapl.base.models import (CasaLegislativa, ProblemaMigracao, Argumento, + Constraint) from sapl.compilacao.models import (PerfilEstruturalTextoArticulado, TipoDispositivo, TipoDispositivoRelationship) @@ -56,11 +57,15 @@ def test_models_in_rules_patterns(model_item): __fp__in__test_permission_of_models_in_rules_patterns = { map_rules.RP_ADD: [CasaLegislativa, ProblemaMigracao, + Argumento, + Constraint, TipoDispositivo, TipoDispositivoRelationship, PerfilEstruturalTextoArticulado], map_rules.RP_CHANGE: [ProblemaMigracao, + Argumento, + Constraint, AcompanhamentoMateria, TipoDispositivo, TipoDispositivoRelationship, @@ -68,17 +73,23 @@ __fp__in__test_permission_of_models_in_rules_patterns = { map_rules.RP_DELETE: [CasaLegislativa, ProblemaMigracao, + Argumento, + Constraint, TipoDispositivo, TipoDispositivoRelationship, PerfilEstruturalTextoArticulado], map_rules.RP_LIST: [ProblemaMigracao, + Argumento, + Constraint, AcompanhamentoMateria, TipoDispositivo, TipoDispositivoRelationship, PerfilEstruturalTextoArticulado], map_rules.RP_DETAIL: [ProblemaMigracao, + Argumento, + Constraint, AcompanhamentoMateria, TipoDispositivo, TipoDispositivoRelationship, diff --git a/sapl/sessao/forms.py b/sapl/sessao/forms.py index ded56d4cd..4303dabcc 100644 --- a/sapl/sessao/forms.py +++ b/sapl/sessao/forms.py @@ -83,11 +83,11 @@ class ExpedienteMateriaForm(ModelForm): def clean_numero_ordem(self): sessao = self.instance.sessao_plenaria - ex = ExpedienteMateria.objects.filter( + numero_ordem_exists = ExpedienteMateria.objects.filter( sessao_plenaria=sessao, - numero_ordem=self.cleaned_data['numero_ordem']).count() + numero_ordem=self.cleaned_data['numero_ordem']).exists() - if ex >= 1: + if numero_ordem_exists: msg = _('Esse número de ordem já existe.') raise ValidationError(msg) @@ -139,6 +139,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() + + if numero_ordem_exists: + 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 diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index e56cb749e..852444752 100644 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -28,7 +28,7 @@ from sapl.materia.models import (Autoria, DocumentoAcessorio, TipoMateriaLegislativa, Tramitacao) from sapl.materia.views import MateriaLegislativaPesquisaView from sapl.norma.models import NormaJuridica -from sapl.parlamentares.models import (Legislatura, Parlamentar, +from sapl.parlamentares.models import (Filiacao, Legislatura, Parlamentar, SessaoLegislativa) from sapl.sessao.apps import AppConfig from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm @@ -164,7 +164,10 @@ class MateriaOrdemDiaCrud(MasterDetailCrud): def get_rows(self, object_list): for obj in object_list: - if not obj.resultado: + exist_resultado = obj.registrovotacao_set.filter( + materia=obj.materia + ).exists() + if not exist_resultado: if obj.votacao_aberta: url = '' if obj.tipo_votacao == 1: @@ -208,6 +211,11 @@ class MateriaOrdemDiaCrud(MasterDetailCrud): else: obj.resultado = '''Não há resultado''' else: + resultado = obj.registrovotacao_set.get( + materia_id=obj.materia_id) + resultado_descricao = resultado.tipo_resultado_votacao.nome + resultado_observacao = resultado.observacao + if self.request.user.has_module_perms(AppConfig.label): url = '' if obj.tipo_votacao == 1: @@ -228,10 +236,14 @@ class MateriaOrdemDiaCrud(MasterDetailCrud): 'pk': obj.sessao_plenaria_id, 'oid': obj.materia_id, 'mid': obj.pk}) - obj.resultado = '%s' % (url, - obj.resultado) + obj.resultado = ('%s
%s' % + (url, + resultado_descricao, + resultado_observacao)) else: - obj.resultado = '%s' % (obj.resultado) + obj.resultado = ('%s
%s' % + (resultado_descricao, + resultado_observacao)) return [self._as_row(obj) for obj in object_list] @@ -268,7 +280,10 @@ class ExpedienteMateriaCrud(MasterDetailCrud): def get_rows(self, object_list): for obj in object_list: - if not obj.resultado: + exist_resultado = obj.registrovotacao_set.filter( + materia=obj.materia + ).exists() + if not exist_resultado: if obj.votacao_aberta: url = '' if obj.tipo_votacao == 1: @@ -310,7 +325,10 @@ class ExpedienteMateriaCrud(MasterDetailCrud): obj.resultado = btn_abrir else: url = '' - + resultado = obj.registrovotacao_set.get( + materia_id=obj.materia_id) + resultado_descricao = resultado.tipo_resultado_votacao.nome + resultado_observacao = resultado.observacao if self.request.user.has_module_perms(AppConfig.label): if obj.tipo_votacao == 1: url = reverse( @@ -331,8 +349,14 @@ class ExpedienteMateriaCrud(MasterDetailCrud): 'pk': obj.sessao_plenaria_id, 'oid': obj.materia_id, 'mid': obj.pk}) - obj.resultado = '%s' % (url, - obj.resultado) + obj.resultado = ('%s
%s' % + (url, + resultado_descricao, + resultado_observacao)) + else: + obj.resultado = ('%s
%s' % + (resultado_descricao, + resultado_observacao)) return [self._as_row(obj) for obj in object_list] class CreateView(MasterDetailCrud.CreateView): @@ -982,12 +1006,14 @@ class ResumoView(DetailView): materias_expediente = [] for m in materias: + ementa = m.observacao titulo = m.materia numero = m.numero_ordem - if m.resultado: - resultado = m.resultado + resultado = m.registrovotacao_set.all() + if resultado: + resultado = resultado[0].tipo_resultado_votacao.nome else: resultado = _('Matéria não votada') @@ -1039,7 +1065,6 @@ class ResumoView(DetailView): # Matérias Ordem do Dia ordem = OrdemDia.objects.filter( sessao_plenaria_id=self.object.id) - materias_ordem = [] for o in ordem: ementa = o.observacao @@ -1047,8 +1072,9 @@ class ResumoView(DetailView): numero = o.numero_ordem # Verificar resultado - if o.resultado: - resultado = o.resultado + resultado = o.registrovotacao_set.filter(materia=o.materia) + if resultado: + resultado = resultado[0].tipo_resultado_votacao.nome else: resultado = _('Matéria não votada') @@ -1066,6 +1092,24 @@ class ResumoView(DetailView): context.update({'materias_ordem': materias_ordem}) + # ===================================================================== + # Oradores nas Explicações Pessoais + oradores_explicacoes = [] + for orador in Orador.objects.filter(sessao_plenaria_id=self.object.id): + for parlamentar in Parlamentar.objects.filter( + id=orador.parlamentar.id): + partido_sigla = Filiacao.objects.filter( + parlamentar=parlamentar).last().partido.sigla + if not partido_sigla: + partido_sigla = '' + oradores = { + 'numero_ordem': orador.numero_ordem, + 'parlamentar': parlamentar, + 'sgl_partido': partido_sigla + } + oradores_explicacoes.append(oradores) + context.update({'oradores_explicacoes': oradores_explicacoes}) + return self.render_to_response(context) @@ -1196,6 +1240,7 @@ class VotacaoEditView(SessaoPermissionMixin): ordem_id=ordem_id).last() votacao_existente = {'observacao': sub( ' ', ' ', strip_tags(votacao.observacao)), + 'resultado': votacao.tipo_resultado_votacao.nome, 'tipo_resultado': votacao.tipo_resultado_votacao_id} context.update({'votacao_titulo': titulo, @@ -1506,6 +1551,7 @@ class VotacaoNominalEditView(SessaoPermissionMixin): 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, @@ -1738,6 +1784,7 @@ class VotacaoNominalExpedienteEditView(SessaoPermissionMixin): 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, @@ -1972,6 +2019,7 @@ class VotacaoExpedienteEditView(SessaoPermissionMixin): expediente_id=expediente_id).last() votacao_existente = {'observacao': sub( ' ', ' ', strip_tags(votacao.observacao)), + 'resultado': votacao.tipo_resultado_votacao.nome, 'tipo_resultado': votacao.tipo_resultado_votacao_id} context.update({'votacao_titulo': titulo, @@ -2065,9 +2113,9 @@ class PautaSessaoDetailView(DetailView): situacao = m.materia.tramitacao_set.last().status if situacao is None: situacao = _("Não informada") - - if m.resultado: - resultado = m.resultado + resultado = m.registrovotacao_set.all() + if resultado: + resultado = resultado[0].tipo_resultado_votacao.nome else: resultado = _('Matéria não votada') @@ -2118,8 +2166,9 @@ class PautaSessaoDetailView(DetailView): numero = o.numero_ordem # Verificar resultado - if o.resultado: - resultado = o.resultado + resultado = o.registrovotacao_set.all() + if resultado: + resultado = resultado[0].tipo_resultado_votacao.nome else: resultado = _('Matéria não votada') @@ -2413,12 +2462,13 @@ class AdicionarVariasMateriasOrdemDia(AdicionarVariasMateriasExpediente): @csrf_exempt +@permission_required('sessao.change_expedientemateria', + 'sessao.change_ordemdia') def mudar_ordem_materia_sessao(request): # Pega os dados vindos da requisição posicao_inicial = int(request.POST['pos_ini']) + 1 posicao_final = int(request.POST['pos_fim']) + 1 pk_sessao = int(request.POST['pk_sessao']) - pk_list = request.POST.getlist('pk_list[]') materia = request.POST['materia'] diff --git a/sapl/static/styles/style_tinymce.css b/sapl/static/styles/style_tinymce.css deleted file mode 100644 index d2909b025..000000000 --- a/sapl/static/styles/style_tinymce.css +++ /dev/null @@ -1,8 +0,0 @@ -.mce-content-body { - font-family: "Open Sans" "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; - font-style: normal; - font-weight: normal; - line-height: 1.5; - font-size: 1em; - color: #444444; -} \ No newline at end of file diff --git a/sapl/templates/base/RelatorioPresencaSessao_filter.html b/sapl/templates/base/RelatorioPresencaSessao_filter.html index 8aeae7aa6..c9c92328b 100644 --- a/sapl/templates/base/RelatorioPresencaSessao_filter.html +++ b/sapl/templates/base/RelatorioPresencaSessao_filter.html @@ -37,7 +37,7 @@ {% for p in parlamentares %} - {{p}} / {{p.filiacao_set.first.partido.sigla|default:"Sem Partido"}} + {{p}} / {{p.filiacao_atual|default:"Sem Partido"}} {{p.sessao_count}} {{p.sessao_porc}} {{p.ordemdia_count}} diff --git a/sapl/templates/materia/materialegislativa_filter.html b/sapl/templates/materia/materialegislativa_filter.html index cd459c61c..4af743b7b 100644 --- a/sapl/templates/materia/materialegislativa_filter.html +++ b/sapl/templates/materia/materialegislativa_filter.html @@ -47,6 +47,14 @@ Apresentação: {{ m.data_apresentacao }}
{% endif %} + {% if m.numeracao_set.first %} + Processo: {{ m.numeracao_set.first.numero_materia }} / {{ m.numeracao_set.first.ano_materia }} +
+ {% endif %} + {% if m.numero_protocolo %} + Protocolo: {{m.numero_protocolo}}/{{m.ano}}    Data Entrada: {{m.data_entrada_protocolo}}
+ + {% endif %} {% if m.autoria_set.all %} Autores: {% for a in m.autoria_set.all %} @@ -57,7 +65,7 @@ {% endif %} {% endfor %}
- {% endif %} + {% endif %} {% if m.tramitacao_set.last.unidade_tramitacao_destino %} Localização Atual:  {{m.tramitacao_set.last.unidade_tramitacao_destino}}
{% endif %} @@ -79,6 +87,7 @@ {% endif %} {% if m.tramitacao_set.last.data_tramitacao %} Data da última Tramitação:  {{m.tramitacao_set.last.data_tramitacao}}
+ Ultima Ação:   {{m.tramitacao_set.last.texto}}
{% endif %} {% if m.anexo_de.exists %} {% for a in m.materia_anexada_set.all %} diff --git a/sapl/templates/parlamentares/parlamentar_perfil_publico.html b/sapl/templates/parlamentares/parlamentar_perfil_publico.html index 11bfc6bfc..1c5ead0fc 100644 --- a/sapl/templates/parlamentares/parlamentar_perfil_publico.html +++ b/sapl/templates/parlamentares/parlamentar_perfil_publico.html @@ -31,7 +31,7 @@
-

Partido:   {{object.filiacao_set.first.partido|default_if_none:"Não informado"}}

+

Partido:   {{object.filiacao_atual|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 15c62ab28..fbd780a30 100644 --- a/sapl/templates/parlamentares/public_composicaomesa_form.html +++ b/sapl/templates/parlamentares/public_composicaomesa_form.html @@ -51,7 +51,7 @@ {% endif %} {{p.parlamentar.nome_parlamentar}} - {{p.parlamentar.filiacao_set.last.partido.sigla}} + {{p.parlamentar.filiacao_atual}} {{p.cargo}} {% endfor %} diff --git a/sapl/templates/protocoloadm/protocolo_mostrar.html b/sapl/templates/protocoloadm/protocolo_mostrar.html index 94d604461..49dba8a90 100644 --- a/sapl/templates/protocoloadm/protocolo_mostrar.html +++ b/sapl/templates/protocoloadm/protocolo_mostrar.html @@ -6,6 +6,7 @@ Protocolo: {{ protocolo.numero|stringformat:'06d' }}/{{ protocolo.ano }}
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." }}
Natureza do Processo: {% if protocolo.tipo_processo == 0 %} Administrativo {% elif protocolo.tipo_processo == 1 %} Legislativo {% endif %}
diff --git a/sapl/templates/sessao/presenca.html b/sapl/templates/sessao/presenca.html index bbdb934a4..372bc8c88 100644 --- a/sapl/templates/sessao/presenca.html +++ b/sapl/templates/sessao/presenca.html @@ -23,7 +23,7 @@
{% endfor %} @@ -42,7 +42,7 @@ {% for parlamentar, check in view.get_presencas %} {% if check %}
-
+
{% endif %} {% endfor %} diff --git a/sapl/templates/sessao/presenca_ordemdia.html b/sapl/templates/sessao/presenca_ordemdia.html index 1b66b53b1..edb4055ca 100644 --- a/sapl/templates/sessao/presenca_ordemdia.html +++ b/sapl/templates/sessao/presenca_ordemdia.html @@ -24,7 +24,7 @@
{% endfor %} @@ -44,7 +44,7 @@ {% for parlamentar, check in view.get_presencas_ordem %} {% if check %}
-
+
{% endif %} {% endfor %} diff --git a/sapl/templates/sessao/resumo.html b/sapl/templates/sessao/resumo.html index 2dcb586d9..37554992a 100644 --- a/sapl/templates/sessao/resumo.html +++ b/sapl/templates/sessao/resumo.html @@ -42,7 +42,8 @@ Mesa Diretora
{% for m in mesa %} -
{{m.cargo}}: {{m.parlamentar.nome_parlamentar}}
+
{{m.cargo}}: + {{m.parlamentar.nome_parlamentar}} / {{ m.parlamentar.filiacao_atual }}
{% endfor %}
@@ -52,7 +53,7 @@ Lista de Presença na Sessão
{% for p in presenca_sessao %} -
{{p}}
+
{{p.nome_parlamentar}} / {{ p.filiacao_atual }}
{% endfor %}
@@ -112,8 +113,9 @@
{% for o in oradores %} -
{{o.numero_ordem}} - {{o.parlamentar}}
-
{{o.url_discurso}}
+
{{o.numero_ordem}} - {{o.parlamentar}}
+
{{o.url_discurso}}
+
{% endfor %}
@@ -123,7 +125,7 @@ Lista de Presença na Ordem do Dia
{% for p in presenca_ordem %} -
{{p}}
+
{{p.nome_parlamentar}} / {{ p.filiacao_atual }}
{% endfor %}
@@ -154,4 +156,20 @@ + +
+ 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/votacao/votacao_edit.html b/sapl/templates/sessao/votacao/votacao_edit.html index 5c614ea00..753c49541 100644 --- a/sapl/templates/sessao/votacao/votacao_edit.html +++ b/sapl/templates/sessao/votacao/votacao_edit.html @@ -26,12 +26,10 @@
- Resultado da Votação: - {% for tipo in view.get_tipos_votacao %} - {% if votacao.tipo_resultado == tipo.id %} - {{ tipo.nome }} - {% endif %} - {% endfor %} +
+ Resultado da Votação: {{ votacao.resultado }} +
+
diff --git a/sapl/utils.py b/sapl/utils.py index 3a857f337..81ebea6cc 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -5,6 +5,8 @@ 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 import django_filters import magic @@ -22,7 +24,7 @@ from floppyforms import ClearableFileInput from reversion.admin import VersionAdmin from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row -from sapl.settings import BASE_DIR +from sapl.settings import BASE_DIR, PROJECT_DIR sapl_logger = logging.getLogger(BASE_DIR.name) @@ -632,3 +634,19 @@ 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/scripts_docker/shell_sapl.sh b/scripts_docker/shell_sapl.sh index cfddefcdc..4c993a61d 100755 --- a/scripts_docker/shell_sapl.sh +++ b/scripts_docker/shell_sapl.sh @@ -1,2 +1,3 @@ #!/bin/bash -docker run -ti sapl_web /bin/bash + +docker run --rm -ti sapl_web /bin/sh diff --git a/start.sh b/start.sh new file mode 100755 index 000000000..1b00801a2 --- /dev/null +++ b/start.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +create_env() { + echo "[ENV FILE] creating .env file..." + # check if file exists + if [ -f "/var/interlegis/sapl/data/secret.key" ]; then + KEY=`cat /var/interlegis/sapl/data/secret.key` + else + KEY=`python3 genkey.py` + echo $KEY > data/secret.key + fi + + FILENAME="/var/interlegis/sapl/sapl/.env" + + if [ -z "${DATABASE_URL:-}" ]; then + DATABASE_URL="postgresql://sapl:sapl@sapldb:5432/sapl" + fi + + # ALWAYS replace the content of .env variable + # If want to conditionally create only if absent then use IF below + # if [ ! -f $FILENAME ]; then + + touch $FILENAME + + + # explicitly use '>' to erase any previous content + echo "SECRET_KEY="$KEY > $FILENAME + # now only appends + echo "DATABASE_URL = "$DATABASE_URL >> $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 + echo "EMAIL_HOST_USER = ""${EMAIL_HOST_USER-''}" >> $FILENAME + echo "EMAIL_HOST_PASSWORD = ""${EMAIL_HOST_PASSWORD-''}" >> $FILENAME + + echo "[ENV FILE] done." +} + +create_env + +#python3 manage.py bower install + +/bin/sh busy-wait.sh $DATABASE_URL + +python3 manage.py migrate +#python3 manage.py collectstatic --no-input +python3 manage.py rebuild_index --noinput & + +user_created=$(python3 create_admin.py 2>&1) + +cmd=$(echo $user_created | grep 'ADMIN_USER_EXISTS') +user_exists=$? + +cmd=$(echo $user_created | grep 'MISSING_ADMIN_PASSWORD') +lack_pwd=$? + +if [ $user_exists -eq 0 ]; then + echo "[SUPERUSER CREATION] User admin already exists. Not creating" +fi + +if [ $lack_pwd -eq 0 ]; then + echo "[SUPERUSER] Environment variable $ADMIN_PASSWORD for superuser admin was not set. Leaving container" + # return -1 +fi + + +/bin/sh gunicorn_start.sh no-venv & +/usr/sbin/nginx -g "daemon off;"