diff --git a/Dockerfile b/Dockerfile index 85724ca11..42ed97c71 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ ENV BUILD_PACKAGES postgresql-dev graphviz-dev graphviz build-base git pkgconfig python3-dev libxml2-dev jpeg-dev libressl-dev libffi-dev libxslt-dev nodejs py3-lxml \ py3-magic postgresql-client poppler-utils vim -RUN apk add --no-cache python3 nginx && \ +RUN apk add --no-cache python3 nginx tzdata && \ python3 -m ensurepip && \ rm -r /usr/lib/python*/ensurepip && \ pip3 install --upgrade pip setuptools && \ @@ -30,6 +30,9 @@ RUN pip install -r /var/interlegis/sapl/requirements/dev-requirements.txt --upgr COPY config/env_dockerfile /var/interlegis/sapl/sapl/.env +# Configura timezone para BRT +# RUN cp /usr/share/zoneinfo/America/Sao_Paulo /etc/localtime && echo "America/Sao_Paulo" > /etc/timezone + # 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 diff --git a/bug.txt b/bug.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/docker-compose.yml b/docker-compose.yml index fd549bd1f..fbf0501db 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,7 @@ sapldb: ports: - "5532:5432" sapl: - image: interlegis/sapl:3.1.11-BETA + image: interlegis/sapl:3.1.17-BETA volumes: - sapl_data:/var/interlegis/sapl/data - sapl_media:/var/interlegis/sapl/media diff --git a/docs/instalacao31.rst b/docs/instalacao31.rst index 60c30df56..a7f9e3060 100644 --- a/docs/instalacao31.rst +++ b/docs/instalacao31.rst @@ -93,7 +93,7 @@ Criar o ambiente virtual de desenvolvimento para o SAPL ------------------------------------------------------- * :: - mkvirtualenv sapl -a /var/interlegis/sapl -p /usr/bin/python3 + mkvirtualenv -a /var/interlegis/sapl -p python3 -r requirements/requirements.txt sapl Instalação e configuração das dependências do projeto ----------------------------------------------------- @@ -119,9 +119,9 @@ Instalação e configuração das dependências do projeto * (caso você já possua uma instalação do postrgresql anterior ao processo de instalação do ambiente de desenvolvimento do SAPL em sua máquina e sábia como fazer, esteja livre para proceder como desejar, porém, ao configurar o arquivo ``.env`` no próximo passo, as mesmas definições deverão ser usadas) -* **Ajustar as permissões - onde sapl31 trocar por usuario**:: +* **Ajustar as permissões - onde $USER trocar por usuario**:: - sudo chown -R sapl31:sapl31 /var/interlegis/ + eval $(echo "sudo chown -R $USER:$USER /var/interlegis/") @@ -178,7 +178,7 @@ Copie a chave que aparecerá, edite o arquivo .env e altere o valor do parâmetr * Instalar as dependências do ``bower``:: - sudo chown -R sapl31:sapl31 /home/sapl31/ + eval $(echo "sudo chown -R $USER:$USER /home/$USER/") ./manage.py bower install * Atualizar e/ou criar as tabelas da base de dados para refletir o modelo da versão clonada:: diff --git a/envfile b/envfile deleted file mode 100644 index a7aef4b0f..000000000 --- a/envfile +++ /dev/null @@ -1 +0,0 @@ -EMAIL_HOST_USER=foo diff --git a/sapl/base/migrations/0004_auto_20170714_1838.py b/sapl/base/migrations/0004_auto_20170714_1838.py new file mode 100644 index 000000000..f675e2e67 --- /dev/null +++ b/sapl/base/migrations/0004_auto_20170714_1838.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-07-14 18:38 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0003_auto_20170519_1106'), + ] + + operations = [ + migrations.RemoveField( + model_name='problemamigracao', + name='eh_importante', + ), + migrations.AddField( + model_name='problemamigracao', + name='critico', + field=models.BooleanField(default=False, verbose_name='Crítico'), + ), + ] diff --git a/sapl/base/models.py b/sapl/base/models.py index 3edef25e9..f2f81ab15 100644 --- a/sapl/base/models.py +++ b/sapl/base/models.py @@ -65,8 +65,8 @@ class ProblemaMigracao(models.Model): problema = models.CharField(max_length=300, verbose_name=_('Problema')) descricao = models.CharField(max_length=300, verbose_name=_('Descrição')) eh_stub = models.BooleanField(verbose_name=_('É stub?')) - eh_importante = models.BooleanField( - default=False, verbose_name=_('É importante?')) + critico = models.BooleanField( + default=False, verbose_name=_('Crítico')) class Meta: verbose_name = _('Problema na Migração') diff --git a/sapl/base/urls.py b/sapl/base/urls.py index 2b4e52d62..500d44f04 100644 --- a/sapl/base/urls.py +++ b/sapl/base/urls.py @@ -16,7 +16,8 @@ from .views import (AppConfigCrud, CasaLegislativaCrud, HelpView, RelatorioMateriasPorAnoAutorTipoView, RelatorioMateriasPorAutorView, RelatorioMateriasTramitacaoView, - RelatorioPresencaSessaoView) + RelatorioPresencaSessaoView, + SaplSearchView) app_name = AppConfig.name @@ -100,6 +101,6 @@ urlpatterns = [ name='login'), url(r'^logout/$', views.logout, {'next_page': '/login'}, name='logout'), - url(r'^sistema/search/', include('haystack.urls')), + url(r'^sistema/search/', SaplSearchView(), name='haystack_search'), ] + recuperar_senha diff --git a/sapl/base/views.py b/sapl/base/views.py index 96b989479..4c86ff515 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -14,6 +14,8 @@ from django.utils.translation import ugettext_lazy as _ from django.views.generic.base import TemplateView from django_filters.views import FilterView +from haystack.views import SearchView + from sapl.base.forms import AutorForm, AutorFormForAdmin, TipoAutorForm from sapl.base.models import Autor, TipoAutor from sapl.crud.base import CrudAux @@ -454,3 +456,22 @@ class AppConfigCrud(CrudAux): def get(self, request, *args, **kwargs): return HttpResponseRedirect(reverse('sapl.base:appconfig_create')) + + +class SaplSearchView(SearchView): + results_per_page = 10 + + def get_context(self): + context = super(SaplSearchView, self).get_context() + + if 'models' in self.request.GET: + models = self.request.GET.getlist('models') + else: + models = [] + + context['models'] = '' + + for m in models: + context['models'] = context['models'] + '&models=' + m + + return context \ No newline at end of file diff --git a/sapl/legacy/migracao_documentos.py b/sapl/legacy/migracao_documentos.py index bd770a06e..50fc1e694 100644 --- a/sapl/legacy/migracao_documentos.py +++ b/sapl/legacy/migracao_documentos.py @@ -14,7 +14,6 @@ from sapl.protocoloadm.models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo) from sapl.sessao.models import SessaoPlenaria from sapl.settings import MEDIA_ROOT -from sapl.utils import delete_texto, save_texto # MIGRAÇÃO DE DOCUMENTOS ################################################### EXTENSOES = { @@ -192,29 +191,7 @@ 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á @@ -241,6 +218,3 @@ 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 b41eeaedf..8f56f91f1 100644 --- a/sapl/legacy/migration.py +++ b/sapl/legacy/migration.py @@ -12,7 +12,7 @@ from django.contrib.auth.models import Group from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist from django.db import OperationalError, ProgrammingError, connections, models -from django.db.models import CharField, Max, ProtectedError, TextField +from django.db.models import CharField, Max, ProtectedError, TextField, Count from django.db.models.base import ModelBase from django.db.models.signals import post_delete, post_save from model_mommy import mommy @@ -34,7 +34,7 @@ from sapl.protocoloadm.models import (DocumentoAdministrativo,Protocolo, StatusTramitacaoAdministrativo) from sapl.sessao.models import ExpedienteMateria, OrdemDia, RegistroVotacao from sapl.settings import PROJECT_DIR -from sapl.utils import delete_texto, normalize, save_texto +from sapl.utils import normalize # BASE ###################################################################### # apps to be migrated, in app dependency order (very important) @@ -161,6 +161,8 @@ def get_fk_related(field, value, label=None): ct = ContentType.objects.get(pk=13) value = TipoProposicao.objects.create( id=value, descricao='Erro', content_type=ct) + ultimo_valor = get_last_value(type(value)) + alter_sequence(type(value), ultimo_valor+1) else: value = tipo[0] else: @@ -244,6 +246,25 @@ def delete_constraints(model): (table, r[0])) +def problema_duplicatas(model, lista_duplicatas, argumentos): + for obj in lista_duplicatas: + pks = [] + string_pks = "" + problema = "%s de PK %s não é único." % (model.__name__, obj.pk) + args_dict = {k: obj.__dict__[k] + for k in set(argumentos) & set(obj.__dict__.keys())} + for dup in model.objects.filter(**args_dict): + pks.append(dup.pk) + string_pks = "(" + ", ".join(map(str, pks)) + ")" + descricao = "As entradas de PK %s são idênticas, mas " \ + "apenas uma deve existir" % string_pks + with reversion.create_revision(): + warn(problema + ' => ' + descricao) + save_relation(obj=obj, problema=problema, + descricao=descricao, eh_stub=False, critico=True) + reversion.set_comment('%s não é único.' % model.__name__) + + def recria_constraints(): constraints = Constraint.objects.all() for con in constraints: @@ -253,6 +274,8 @@ def recria_constraints(): args = [a.argumento for a in con.argumento_set.all()] args_string = '' args_string = "(" + "_".join(map(str, args[2:-1])) + ")" + model = ContentType.objects.filter( + model=con.nome_model.lower())[0].model_class() try: exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" % (nome_tabela, nome_constraint, args_string)) @@ -272,18 +295,30 @@ def recria_constraints(): args[i] = args[i] + '_id' args_string = '' args_string += "(" + ', '.join(map(str, args)) + ")" - try: - exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" % - (nome_tabela, nome_constraint, args_string)) - except ProgrammingError: - info('A constraint %s já foi recriada!' % nome_constraint) - except Exception as err: - problema = re.findall('\(.*?\)', err.args[0]) - erro('A constraint [%s] da tabela [%s] não pode ser recriada' % - (nome_constraint, nome_tabela)) - erro('Os dados %s = %s estão duplicados. ' - 'Arrume antes de recriar as constraints!' % - (problema[0], problema[1])) + + distintos = model.objects.distinct(*args) + todos = model.objects.all() + if hasattr(model, "content_type"): + distintos = distintos.exclude(content_type_id=None, + object_id=None) + todos = todos.exclude(content_type_id=None, object_id=None) + + lista_duplicatas = list(set(todos).difference(set(distintos))) + if lista_duplicatas: + problema_duplicatas(model, lista_duplicatas, args) + else: + try: + exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" % + (nome_tabela, nome_constraint, args_string)) + except ProgrammingError: + info('A constraint %s já foi recriada!' % nome_constraint) + except Exception as err: + problema = re.findall('\(.*?\)', err.args[0]) + erro('A constraint [%s] da tabela [%s] não pode ser" \ + recriada' % (nome_constraint, nome_tabela)) + erro('Os dados %s = %s estão duplicados. ' + 'Arrume antes de recriar as constraints!' % + (problema[0], problema[1])) def obj_desnecessario(obj): @@ -315,10 +350,10 @@ def save_with_id(new, id): def save_relation(obj, nome_campo='', problema='', descricao='', - eh_stub=False): + eh_stub=False, critico=False): link = ProblemaMigracao( content_object=obj, nome_campo=nome_campo, problema=problema, - descricao=descricao, eh_stub=eh_stub,) + descricao=descricao, eh_stub=eh_stub, critico=critico) link.save() @@ -373,6 +408,34 @@ def fill_vinculo_norma_juridica(): TipoVinculoNormaJuridica.objects.bulk_create(lista_objs) +# Uma anomalia no sapl 2.5 causa a duplicação de registros de votação. +# Essa duplicação deve ser eliminada para que não haja erro no sapl 3.1 +def excluir_registrovotacao_duplicados(): + duplicatas_ids = RegistroVotacao.objects.values( + 'materia', 'ordem', 'expediente').annotate( + Count('id')).order_by().filter(id__count__gt=1) + duplicatas_queryset = RegistroVotacao.objects.filter( + materia__in=[item['materia'] for item in duplicatas_ids]) + + for dup in duplicatas_queryset: + lista_dups = duplicatas_queryset.filter( + materia=dup.materia, expediente=dup.expediente, ordem=dup.ordem) + primeiro_registro = lista_dups[0] + lista_dups = lista_dups.exclude(pk=primeiro_registro.pk) + for objeto in lista_dups: + if (objeto.pk > primeiro_registro.pk): + try: + objeto.delete() + except: + assert 0 + else: + try: + primeiro_registro.delete() + primeiro_registro = objeto + except: + assert 0 + + class DataMigrator: def __init__(self): @@ -449,8 +512,6 @@ class DataMigrator: call([PROJECT_DIR.child('manage.py'), 'flush', '--database=default', '--no-input'], stdout=PIPE) - desconecta_sinais_indexacao() - fill_vinculo_norma_juridica() info('Começando migração: %s...' % obj) self._do_migrate(obj) @@ -468,11 +529,15 @@ class DataMigrator: save_relation(obj=obj, problema=msg, descricao=descricao, eh_stub=False) + info('Excluindo possíveis duplicações em RegistroVotacao...') + excluir_registrovotacao_duplicados() + info('Deletando stubs desnecessários...') while self.delete_stubs(): pass - conecta_sinais_indexacao() + info('Recriando constraints...') + recria_constraints() def _do_migrate(self, obj): if isinstance(obj, AppConfig): @@ -655,11 +720,25 @@ def adjust_participacao(new, old): new.composicao = composicao -def adjust_proposicao(new, old): +def adjust_proposicao_antes_salvar(new, old): if new.data_envio: new.ano = new.data_envio.year +def adjust_proposicao_depois_salvar(new, old): + if not hasattr(old.dat_envio, 'year') or old.dat_envio.year == 1800: + msg = "O valor do campo data_envio (DateField) da model Proposicao" + "era inválido" + descricao = 'A data 1111-11-11 foi colocada no lugar' + problema = 'O valor da data era nulo ou inválido' + warn(msg + ' => ' + descricao) + new.data_envio = date(1111, 11, 11) + with reversion.create_revision(): + save_relation(obj=new, problema=problema, + descricao=descricao, eh_stub=False) + reversion.set_comment('Ajuste de data pela migração') + + def adjust_normarelacionada(new, old): tipo = TipoVinculoNormaJuridica.objects.filter(sigla=old.tip_vinculo) assert len(tipo) == 1 @@ -817,7 +896,7 @@ AJUSTE_ANTES_SALVAR = { OrdemDia: adjust_ordemdia_antes_salvar, Parlamentar: adjust_parlamentar, Participacao: adjust_participacao, - Proposicao: adjust_proposicao, + Proposicao: adjust_proposicao_antes_salvar, Protocolo: adjust_protocolo, RegistroVotacao: adjust_registrovotacao_antes_salvar, TipoAfastamento: adjust_tipoafastamento, @@ -830,6 +909,7 @@ AJUSTE_ANTES_SALVAR = { AJUSTE_DEPOIS_SALVAR = { NormaJuridica: adjust_normajuridica_depois_salvar, OrdemDia: adjust_ordemdia_depois_salvar, + Proposicao: adjust_proposicao_depois_salvar, Protocolo: adjust_protocolo_depois_salvar, RegistroVotacao: adjust_registrovotacao_depois_salvar, } @@ -865,23 +945,3 @@ def make_with_log(model, _quantity=None, make_m2m=False, **attrs): return stub make_with_log.required = foreign_key_required - -# DISCONNECT SIGNAL ######################################################## - - -def desconecta_sinais_indexacao(): - post_save.disconnect(save_texto, NormaJuridica) - post_save.disconnect(save_texto, DocumentoAcessorio) - post_save.disconnect(save_texto, MateriaLegislativa) - post_delete.disconnect(delete_texto, NormaJuridica) - post_delete.disconnect(delete_texto, DocumentoAcessorio) - post_delete.disconnect(delete_texto, MateriaLegislativa) - - -def conecta_sinais_indexacao(): - post_save.connect(save_texto, NormaJuridica) - post_save.connect(save_texto, DocumentoAcessorio) - post_save.connect(save_texto, MateriaLegislativa) - post_delete.connect(delete_texto, NormaJuridica) - post_delete.connect(delete_texto, DocumentoAcessorio) - post_delete.connect(delete_texto, MateriaLegislativa) diff --git a/sapl/legacy/scripts/fix_tables.sql b/sapl/legacy/scripts/fix_tables.sql index ed93373b1..e1dddc393 100644 --- a/sapl/legacy/scripts/fix_tables.sql +++ b/sapl/legacy/scripts/fix_tables.sql @@ -5,6 +5,7 @@ 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; +DROP PROCEDURE IF EXISTS muda_vinculo_norma_juridica_ind_excluido; -- 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 @@ -13,8 +14,11 @@ CREATE PROCEDURE verifica_campos_tipo_materia_legislativa() BEGIN IF NOT EXISTS 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; +-- Procedure para mudar valor do campo ind_excluido da tabela vinculo_norma_juridica de 0 para string vazia '' +CREATE PROCEDURE muda_vinculo_norma_juridica_ind_excluido() BEGIN UPDATE vinculo_norma_juridica SET ind_excluido = '' WHERE trim(ind_excluido) = '0'; 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; +CALL muda_vinculo_norma_juridica_ind_excluido; diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index 67e794907..df0b72efc 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -16,10 +16,14 @@ from django.db.models import Max from django.forms import ModelForm, ModelChoiceField, widgets from django.forms.forms import Form from django.forms.widgets import Select +from django.utils import six from django.utils.encoding import force_text from django.utils.html import format_html from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ + +from django_filters.filterset import STRICTNESS + import django_filters from sapl.base.models import Autor, TipoAutor @@ -586,6 +590,45 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet): form_actions(save_label='Pesquisar')) ) + @property + def qs(self): + if not hasattr(self, '_qs'): + valid = self.is_bound and self.form.is_valid() + + if self.is_bound and not valid: + if self.strict == STRICTNESS.RAISE_VALIDATION_ERROR: + raise forms.ValidationError(self.form.errors) + elif bool(self.strict) == STRICTNESS.RETURN_NO_RESULTS: + self._qs = self.queryset.none() + return self._qs + # else STRICTNESS.IGNORE... ignoring + + # start with all the results and filter from there + qs = self.queryset.all() + for name, filter_ in six.iteritems(self.filters): + value = None + if valid: + value = self.form.cleaned_data[name] + else: + raw_value = self.form[name].value() + try: + value = self.form.fields[name].clean(raw_value) + except forms.ValidationError: + if self.strict == STRICTNESS.RAISE_VALIDATION_ERROR: + raise + elif bool(self.strict) == STRICTNESS.RETURN_NO_RESULTS: + self._qs = self.queryset.none() + return self._qs + # else STRICTNESS.IGNORE... ignoring + + if value is not None: # valid & clean data + qs = qs._next_is_sticky() + qs = filter_.filter(qs, value) + + self._qs = qs + + return self._qs + def pega_ultima_tramitacao(): ultimas_tramitacoes = Tramitacao.objects.values( @@ -654,6 +697,18 @@ class AutoriaForm(ModelForm): TipoAutor.objects.all().order_by('descricao'), empty_label='Selecione',) + def __init__(self, *args, **kwargs): + super(AutoriaForm, self).__init__(*args, **kwargs) + + row1 = to_row([('tipo_autor', 4), + ('autor', 4), + ('primeiro_autor', 4)]) + + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset(_('Autoria'), + row1, form_actions(save_label='Salvar'))) + class Meta: model = Autoria fields = ['tipo_autor', 'autor', 'primeiro_autor'] @@ -664,12 +719,13 @@ class AutoriaForm(ModelForm): if self.errors: return self.errors - if Autoria.objects.filter( - materia=self.instance.materia, - autor=self.cleaned_data['autor'], - ).exists(): - msg = _('Esse Autor já foi cadastrado.') - raise ValidationError(msg) + if not self.instance.pk: + if Autoria.objects.filter( + materia=self.instance.materia, + autor=self.cleaned_data['autor'], + ).exists(): + msg = _('Esse Autor já foi cadastrado.') + raise ValidationError(msg) return self.cleaned_data diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 902b8ec91..b76bb6784 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -959,6 +959,12 @@ class TramitacaoCrud(MasterDetailCrud): def form_valid(self, form): self.object = form.save() + if form.instance.status.indicador == 'F': + form.instance.materia.em_tramitacao = False + else: + form.instance.materia.em_tramitacao = True + form.instance.materia.save() + try: tramitacao_signal.send(sender=Tramitacao, post=self.object, @@ -982,6 +988,12 @@ class TramitacaoCrud(MasterDetailCrud): def form_valid(self, form): self.object = form.save() + if form.instance.status.indicador == 'F': + form.instance.materia.em_tramitacao = False + else: + form.instance.materia.em_tramitacao = True + form.instance.materia.save() + try: tramitacao_signal.send(sender=Tramitacao, post=self.object, @@ -1256,6 +1268,13 @@ class AutoriaCrud(MasterDetailCrud): context['form'].fields['autor'].choices = autores return context + class ListView(MasterDetailCrud.ListView): + + def get_queryset(self): + qs = super().get_queryset() + + return qs.order_by('-primeiro_autor', 'autor__nome') + class DespachoInicialCrud(MasterDetailCrud): model = DespachoInicial diff --git a/sapl/norma/forms.py b/sapl/norma/forms.py index ffdd09481..5ba72e161 100644 --- a/sapl/norma/forms.py +++ b/sapl/norma/forms.py @@ -144,7 +144,9 @@ class NormaJuridicaForm(ModelForm): texto_integral = self.cleaned_data.get('texto_integral', False) if texto_integral: if texto_integral.size > MAX_DOC_UPLOAD_SIZE: - raise ValidationError("Arquivo muito grande. ( > 5mb )") + max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024)) + raise ValidationError( + "Arquivo muito grande. ( > {0}MB )".format(max_size)) return texto_integral def save(self, commit=False): @@ -178,7 +180,7 @@ class NormaRelacionadaForm(ModelForm): def clean(self): super(NormaRelacionadaForm, self).clean() - + if self.errors: return self.errors cleaned_data = self.cleaned_data diff --git a/sapl/painel/views.py b/sapl/painel/views.py index b6abd9cc5..57ecf5c0e 100644 --- a/sapl/painel/views.py +++ b/sapl/painel/views.py @@ -439,7 +439,7 @@ def get_votos(response, materia): def get_votos_nominal(response, materia): - votos = {} + votos = [] if materia.tipo_votacao == 1: tipo_votacao = 'Simbólica' @@ -468,7 +468,7 @@ def get_votos_nominal(response, materia): else: votos_parlamentares = VotoParlamentar.objects.filter( - votacao_id=registro.id) + votacao_id=registro.id).order_by('parlamentar__nome_parlamentar') filiacao = Filiacao.objects.filter( data_desfiliacao__isnull=True, parlamentar__ativo=True) @@ -481,18 +481,18 @@ def get_votos_nominal(response, materia): try: parlamentar_partido[v.parlamentar.nome_parlamentar] except KeyError: - votos.update({v.parlamentar.id: { + votos.append({ 'parlamentar': v.parlamentar.nome_parlamentar, 'voto': str(v.voto), 'partido': str(_('Sem Registro')) - }}) + }) else: - votos.update({v.parlamentar.id: { + votos.append({ 'parlamentar': v.parlamentar.nome_parlamentar, 'voto': str(v.voto), 'partido': parlamentar_partido[ v.parlamentar.nome_parlamentar] - }}) + }) total = (registro.numero_votos_sim + registro.numero_votos_nao + diff --git a/sapl/protocoloadm/forms.py b/sapl/protocoloadm/forms.py index e513d4b1b..c8880960c 100644 --- a/sapl/protocoloadm/forms.py +++ b/sapl/protocoloadm/forms.py @@ -333,7 +333,10 @@ class ProtocoloDocumentForm(ModelForm): class ProtocoloMateriaForm(ModelForm): - autor = forms.IntegerField(widget=forms.HiddenInput(), required=False) + autor = forms.ModelChoiceField(required=True, + empty_label='------', + queryset=Autor.objects.all() + ) tipo_materia = forms.ModelChoiceField( label=_('Tipo de Matéria'), @@ -350,15 +353,6 @@ class ProtocoloMateriaForm(ModelForm): assunto_ementa = forms.CharField(required=True, widget=forms.Textarea, label='Ementa') - def clean_autor(self): - autor_field = self.cleaned_data['autor'] - try: - autor = Autor.objects.get(id=autor_field) - except ObjectDoesNotExist: - autor_field = None - else: - autor_field = autor - return autor_field class Meta: model = Protocolo @@ -368,19 +362,23 @@ class ProtocoloMateriaForm(ModelForm): 'assunto_ementa', 'observacao'] + def clean_autor(self): + autor_field = self.cleaned_data['autor'] + try: + autor = Autor.objects.get(id=autor_field.id) + except ObjectDoesNotExist: + autor_field = None + else: + autor_field = autor + return autor_field + def __init__(self, *args, **kwargs): row1 = to_row( [('tipo_materia', 4), ('numero_paginas', 4)]) row2 = to_row( - [('autor', 0), - (Button('pesquisar', - 'Pesquisar Autor', - css_class='btn btn-primary btn-sm'), 2), - (Button('limpar', - 'limpar Autor', - css_class='btn btn-primary btn-sm'), 10)]) + [('autor', 4)]) row3 = to_row( [('assunto_ementa', 12)]) row4 = to_row( @@ -389,7 +387,7 @@ class ProtocoloMateriaForm(ModelForm): self.helper = FormHelper() self.helper.layout = Layout( Fieldset(_('Identificação da Matéria'), - row1, HTML(autor_label), HTML(autor_modal), row2, row3, + row1, row2, row3, row4, form_actions(save_label='Protocolar Matéria'))) super(ProtocoloMateriaForm, self).__init__( diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index c478b6f62..9fffa3669 100644 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -1,8 +1,10 @@ from datetime import date, datetime + from braces.views import FormValidMessageMixin from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin +from django.db.models import Q from django.core.exceptions import ObjectDoesNotExist from django.core.urlresolvers import reverse from django.db.models import Max @@ -15,6 +17,7 @@ from django.views.generic.base import TemplateView from django_filters.views import FilterView import sapl +from sapl.base.models import Autor from sapl.crud.base import Crud, CrudAux, MasterDetailCrud, make_pagination from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.utils import create_barcode, get_client_ip @@ -27,6 +30,10 @@ from .forms import (AnularProcoloAdmForm, DocumentoAcessorioAdministrativoForm, from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo, Protocolo, StatusTramitacaoAdministrativo, TipoDocumentoAdministrativo, TramitacaoAdministrativo) +from sapl.parlamentares.models import Parlamentar +from sapl.protocoloadm.models import Protocolo +from sapl.comissoes.models import Comissao +from django.contrib.contenttypes.models import ContentType TipoDocumentoAdministrativoCrud = CrudAux.build( TipoDocumentoAdministrativo, '') @@ -419,12 +426,10 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView): protocolo.numero = ( numero['numero__max'] + 1) if numero['numero__max'] else 1 protocolo.ano = datetime.now().year - protocolo.data = datetime.strptime(datetime.now().strftime("%Y-%m-%d"), - '%Y-%m-%d') - protocolo.hora = datetime.strptime(datetime.now().strftime("%H:%M"), - '%H:%M') - protocolo.timestamp = datetime.strptime( - datetime.now().strftime("%Y-%m-%d %H:%M"), "%Y-%m-%d %H:%M") + protocolo.data = datetime.now().date() + protocolo.hora = datetime.now().time() + protocolo.timestamp = datetime.now() + protocolo.tipo_protocolo = 0 protocolo.tipo_processo = '1' # TODO validar o significado protocolo.anulado = False @@ -440,6 +445,30 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView): protocolo.save() return redirect(self.get_success_url(protocolo)) + def get_context_data(self, **kwargs): + context = super(CreateView, self).get_context_data(**kwargs) + autores_ativos = self.autores_ativos() + + autores = [] + autores.append(['0', '------']) + for a in autores_ativos: + autores.append([a.id, a.__str__()]) + + context['form'].fields['autor'].choices = autores + return context + + def autores_ativos(self): + lista_parlamentares = Parlamentar.objects.filter(ativo=True).values_list('id', flat=True) + model_parlamentar = ContentType.objects.get_for_model(Parlamentar) + autor_parlamentar = Autor.objects.filter(content_type=model_parlamentar, object_id__in=lista_parlamentares) + + lista_comissoes = Comissao.objects.filter(Q(data_extincao__isnull=True)|Q(data_extincao__gt=date.today())).values_list('id', flat=True) + model_comissao = ContentType.objects.get_for_model(Comissao) + autor_comissoes = Autor.objects.filter(content_type=model_comissao, object_id__in=lista_comissoes) + autores_outros = Autor.objects.exclude(content_type__in=[model_parlamentar, model_comissao]) + q = autor_parlamentar | autor_comissoes | autores_outros + return q + class ProtocoloMateriaTemplateView(PermissionRequiredMixin, TemplateView): diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index 16ca533e3..61cadbf71 100644 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -1400,9 +1400,8 @@ class VotacaoEditView(SessaoPermissionMixin): ordem_id = kwargs['oid'] if(int(request.POST['anular_votacao']) == 1): - RegistroVotacao.objects.filter( - materia_id=materia_id, - ordem_id=ordem_id).last().delete() + for r in RegistroVotacao.objects.filter(ordem_id=ordem_id): + r.delete() ordem = OrdemDia.objects.get( sessao_plenaria_id=self.object.id, @@ -2165,14 +2164,8 @@ class VotacaoExpedienteEditView(SessaoPermissionMixin): expediente_id = kwargs['oid'] if(int(request.POST['anular_votacao']) == 1): - try: - RegistroVotacao.objects.get( - materia_id=materia_id, - expediente_id=expediente_id).delete() - except MultipleObjectsReturned: - RegistroVotacao.objects.filter( - materia_id=materia_id, - expediente_id=expediente_id).last().delete() + for r in RegistroVotacao.objects.filter(expediente_id=expediente_id): + r.delete() expediente = ExpedienteMateria.objects.get( sessao_plenaria_id=self.object.id, @@ -2296,11 +2289,13 @@ class PautaSessaoDetailView(DetailView): ementa = o.observacao titulo = o.materia numero = o.numero_ordem - + situacao = o.materia.tramitacao_set.last().status + if situacao is None: + situacao = _("Não informada") # Verificar resultado - resultado = o.registrovotacao_set.all() - if resultado: - resultado = resultado[0].tipo_resultado_votacao.nome + rv = o.registrovotacao_set.all() + if rv: + resultado = rv[0].tipo_resultado_votacao.nome else: resultado = _('Matéria não votada') @@ -2313,6 +2308,8 @@ class PautaSessaoDetailView(DetailView): 'titulo': titulo, 'numero': numero, 'resultado': resultado, + 'resultado_observacao': resultado_observacao, + 'situacao': situacao, 'autor': autor } materias_ordem.append(mat) diff --git a/sapl/settings.py b/sapl/settings.py index 7ca99b1bf..d6590c5b4 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -193,7 +193,7 @@ EMAIL_SEND_USER = config('EMAIL_SEND_USER', cast=str, default='') DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', cast=str, default='') SERVER_EMAIL = config('SERVER_EMAIL', cast=str, default='') -MAX_DOC_UPLOAD_SIZE = 10 * 1024 * 1024 # 10MB +MAX_DOC_UPLOAD_SIZE = 20 * 1024 * 1024 # 20MB MAX_IMAGE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB # Internationalization @@ -205,7 +205,7 @@ LANGUAGES = ( TIME_ZONE = 'America/Sao_Paulo' USE_I18N = True -USE_L10N = False +USE_L10N = True USE_TZ = False # DATE_FORMAT = 'N j, Y' DATE_FORMAT = 'd/m/Y' diff --git a/sapl/templates/crud/detail.html b/sapl/templates/crud/detail.html index 64bd7203a..1653a11e4 100644 --- a/sapl/templates/crud/detail.html +++ b/sapl/templates/crud/detail.html @@ -114,4 +114,6 @@ {% include "paginacao.html" %} {% endblock table_content %} + + {% block extra_js %}{% endblock %} {% endblock base_content %} diff --git a/sapl/templates/materia/materialegislativa_filter.html b/sapl/templates/materia/materialegislativa_filter.html index d7b0bb3ec..285379cc5 100644 --- a/sapl/templates/materia/materialegislativa_filter.html +++ b/sapl/templates/materia/materialegislativa_filter.html @@ -60,10 +60,12 @@ {% endif %} {% if m.autoria_set.all %} - Autor: - {% for a in m.autoria_set.all %} - {% if a.primeiro_autor %} -  {{a.autor.nome}} + Autor: + {% for a in m.autoria_set.all %} + {% if not forloop.first %} +
{{a.autor}} + {% else %} +  {{a.autor}} {% endif %} {% endfor %}
diff --git a/sapl/templates/navbar.yaml b/sapl/templates/navbar.yaml index 502fb09f1..47d58eec4 100644 --- a/sapl/templates/navbar.yaml +++ b/sapl/templates/navbar.yaml @@ -36,6 +36,8 @@ - title: {% trans 'Proposições' %} url: sapl.materia:proposicao_list check_permission: materia.add_proposicao + - title: {% trans 'Relatórios' %} + url: sapl.base:relatorios_list - title: {% trans 'Sessões Plenárias' %} url: sapl.sessao:pesquisar_sessao - title: {% trans 'Tramitação em Lote' %} diff --git a/sapl/templates/painel/index.html b/sapl/templates/painel/index.html index b78fb7774..98eabb6ab 100644 --- a/sapl/templates/painel/index.html +++ b/sapl/templates/painel/index.html @@ -181,25 +181,38 @@ else if (data["presentes_expediente"] != null){ presentes_ordem_dia = data["presentes_expediente"] } - if( (data["tipo_resultado"] == "Aprovado por unanimidade") || (data["tipo_resultado"] == "Aprovado por maioria") || (data["tipo_resultado"] == "Rejeitado")){ - if(data["tipo_votacao"] == "Nominal") { - jQuery.each(data["votos"], function(index, parlamentar) { - $('
  • ', {text: parlamentar.parlamentar + ' - ' + parlamentar.partido + ' - Voto: ' + parlamentar.voto}).appendTo(presentes); + presentes.append(''); + if( (data["tipo_resultado"] == "Aprovado por Unanimidade") || (data["tipo_resultado"] == "Aprovado por maioria") || (data["tipo_resultado"] == "Rejeitado")) { + if (data["tipo_votacao"] == "Nominal") { + jQuery.each(data["votos"], function (index, parlamentar) { + $('#parlamentares_list').append('') }); } - else{ - jQuery.each(presentes_ordem_dia, function(index, parlamentar) { - $('
  • ', {text: parlamentar.nome + ' - ' + parlamentar.partido}).appendTo(presentes); - }); + else { + jQuery.each(data["votos"], function (index, parlamentar) { + $('#parlamentares_list').append('
  • ') + }); } } else{ - jQuery.each(presentes_ordem_dia, function(index, parlamentar) { - $('
  • ', {text: parlamentar.nome + ' - ' + parlamentar.partido}).appendTo(presentes); - }); + jQuery.each(presentes_ordem_dia, function (index, parlamentar) { + $('#parlamentares_list').append('
  • ') + }); } + presentes.append('
    ' + + parlamentar.parlamentar + + ' ' + + parlamentar.partido + ' ' + + show_voto(parlamentar.voto) + '
    ' + + parlamentar.parlamentar + + ' ' + + parlamentar.partido + '
    ' + + parlamentar.nome + + ' ' + + parlamentar.partido + '
    '); + //console.debug(presentes_ordem_dia) var votacao = $("#votacao") if (data["num_presentes_ordem_dia"] != null) { @@ -280,5 +293,15 @@ }) })(); }); + + function show_voto(voto) { + if (voto == "Sim"){ + return ' Sim ' + } + else if (voto == "Não"){ + return ' Não ' + } + return voto + } diff --git a/sapl/templates/protocoloadm/MateriaTemplate.html b/sapl/templates/protocoloadm/MateriaTemplate.html index fc490fcc5..05d1f65ad 100644 --- a/sapl/templates/protocoloadm/MateriaTemplate.html +++ b/sapl/templates/protocoloadm/MateriaTemplate.html @@ -3,7 +3,7 @@ {% block base_content %}
    diff --git a/sapl/templates/protocoloadm/protocolo_list.html b/sapl/templates/protocoloadm/protocolo_list.html index 4ae55baf0..8abcb0d7c 100644 --- a/sapl/templates/protocoloadm/protocolo_list.html +++ b/sapl/templates/protocoloadm/protocolo_list.html @@ -20,7 +20,7 @@ Etiqueta Individual
    Assunto: {{ p.assunto_ementa }}
    - Data Protocolo: {{ p.data|date:"d/m/Y" }} - Horário: {{ p.timestamp|date:"H:m:s" }}
    + Data Protocolo: {{ p.data|date:"d/m/Y" }} - Horário: {{ p.hora|date:"H:m:s" }}
    Natureza do Processo: {% if p.tipo_processo == 0 %} Administrativo
    diff --git a/sapl/templates/search/search.html b/sapl/templates/search/search.html index 4efae2cc5..3ea67cc86 100644 --- a/sapl/templates/search/search.html +++ b/sapl/templates/search/search.html @@ -93,9 +93,13 @@ {% if page.has_previous or page.has_next %}
    - {% if page.has_previous %}{% endif %}« Previous{% if page.has_previous %}{% endif %} + {% if page.has_previous %} + + {% endif %}« Anterior{% if page.has_previous %}{% endif %} | - {% if page.has_next %}{% endif %}Next »{% if page.has_next %}{% endif %} + {% if page.has_next %} + + {% endif %}Próxima »{% if page.has_next %}{% endif %}
    {% endif %} {% else %} diff --git a/sapl/templates/sessao/mesa.html b/sapl/templates/sessao/mesa.html index 311a76b87..9e046761d 100644 --- a/sapl/templates/sessao/mesa.html +++ b/sapl/templates/sessao/mesa.html @@ -20,7 +20,9 @@
    - Escolha da Composição da Mesa Diretora da Sessão Plenária + {% if perms.sessao.add_integrantemesa %} + Escolha da Composição da Mesa Diretora da Sessão Plenária + {% endif %}
    @@ -35,12 +37,12 @@


    - {% if perms.parlamentares.add_cargomesa %} + {% if perms.sessao.add_integrantemesa %} {% endif %}

    - {% if perms.parlamentares.add_composicaomesa %} + {% if perms.sessao.add_integrantemesa %} {% endif %}
    diff --git a/sapl/templates/sessao/pauta_sessao_detail.html b/sapl/templates/sessao/pauta_sessao_detail.html index e4f41f312..a16f86802 100644 --- a/sapl/templates/sessao/pauta_sessao_detail.html +++ b/sapl/templates/sessao/pauta_sessao_detail.html @@ -3,7 +3,7 @@ {% load crispy_forms_tags %} {% block base_content %} -
  • +
    Impressão PDF
    Identificação Básica @@ -25,7 +25,7 @@ {% endfor %} @@ -36,7 +36,7 @@
    Matérias do Expediente
    {{e.tipo}}:
    -

    {{e.conteudo}}

    +

    {{e.conteudo|safe}}

    - + @@ -45,13 +45,13 @@ {% for m in materia_expediente %} - - - + + {% endfor %}
    Matéria Ementa
    + {{m.numero}} - {{m.titulo}}
    Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }}
    {{m.ementa|safe}}{{m.situacao}}{{m.ementa|safe}}{{m.situacao}}
    @@ -78,7 +78,7 @@
    Matérias da Ordem do Dia - + @@ -87,13 +87,13 @@ {% for m in materias_ordem %} - - - + + {% endfor %}
    Matéria Ementa
    + {{m.numero}} - {{m.titulo}}
    Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }}
    {{m.ementa|safe}}{{m.situacao}}{{m.ementa|safe}}{{m.situacao}}
    diff --git a/scrap-js.js b/scrap-js.js deleted file mode 100644 index 0d5558627..000000000 --- a/scrap-js.js +++ /dev/null @@ -1,35 +0,0 @@ -{# #}scr