diff --git a/.gitignore b/.gitignore index d66a04a22..3bf1301ec 100644 --- a/.gitignore +++ b/.gitignore @@ -91,9 +91,12 @@ whoosh_index collected_static bower bower_components -media whoosh/ solr-4.10.2/ postgres-data/ data/ -solr-*/ \ No newline at end of file +solr-*/ + +# ignora tudo dentro de media, mas cria a pasta no checkout +media/* +!media/.gitkeep diff --git a/media/.gitkeep b/media/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/sapl/api/forms.py b/sapl/api/forms.py index 41e40eacf..f70ffe2f2 100644 --- a/sapl/api/forms.py +++ b/sapl/api/forms.py @@ -1,11 +1,15 @@ -from django.db.models import Q +from django.db.models import Q, F from django.forms.fields import CharField, MultiValueField from django.forms.widgets import MultiWidget, TextInput -from django_filters.filters import MethodFilter, ModelChoiceFilter +from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ +from django_filters.filters import MethodFilter, ModelChoiceFilter, DateFilter +from rest_framework import serializers from rest_framework.compat import django_filters from rest_framework.filters import FilterSet from sapl.base.models import Autor, TipoAutor +from sapl.parlamentares.models import Legislatura from sapl.utils import generic_relations_for_model @@ -38,11 +42,13 @@ class SaplGenericRelationSearchFilterSet(FilterSet): item.related_query_name(), field[0]) ) - if len(field) == 3 and field[2](qtext) is not None: - q_fs = q_fs | Q(**{'%s__%s%s' % ( - item.related_query_name(), - field[0], - field[1]): qtext if len(field) == 2 else field[2](qtext)}) + # if len(field) == 3 and field[2](qtext) is not + # None: + q_fs = q_fs | Q(**{'%s__%s%s' % ( + item.related_query_name(), + field[0], + field[1]): qtext if len(field) == 2 + else field[2](qtext)}) q = q & q_fs @@ -115,3 +121,109 @@ class AutorSearchForFieldFilterSet(AutorChoiceFilterSet): v = '1' if v == 'True' else '0' params[key] = v return queryset.filter(**params).distinct('nome').order_by('nome') + + +class AutoresPossiveisFilterSet(FilterSet): + data_relativa = DateFilter(method='filter_data_relativa') + tipo = MethodFilter() + + class Meta: + model = Autor + fields = ['data_relativa', 'tipo', ] + + def filter_data_relativa(self, queryset, name, value): + return queryset + + def filter_tipo(self, queryset, value): + try: + tipo = TipoAutor.objects.get(pk=value) + except: + raise serializers.ValidationError(_('Tipo de Autor inexistente.')) + + qs = queryset.filter(tipo=tipo) + + return qs + + @property + def qs(self): + qs = super().qs + + data_relativa = self.form.cleaned_data['data_relativa'] \ + if 'data_relativa' in self.form.cleaned_data else None + + tipo = self.form.cleaned_data['tipo'] \ + if 'tipo' in self.form.cleaned_data else None + + if not tipo and not data_relativa: + return qs + + if tipo: + # não precisa de try except, já foi validado em filter_tipo + tipo = TipoAutor.objects.get(pk=tipo) + if not tipo.content_type: + return qs + + filter_for_model = 'filter_%s' % tipo.content_type.model + + if not hasattr(self, filter_for_model): + return qs + + if not data_relativa: + data_relativa = timezone.now() + + return getattr(self, filter_for_model)(qs, data_relativa).distinct() + + def filter_parlamentar(self, queryset, data_relativa): + # não leva em conta afastamentos + legislatura_relativa = Legislatura.objects.filter( + data_inicio__lte=data_relativa, + data_fim__gte=data_relativa).first() + + params = { + 'parlamentar_set__mandato__data_inicio_mandato__lte': + data_relativa, + 'parlamentar_set__mandato__data_fim_mandato__gte': data_relativa + } + + if legislatura_relativa.atual(): + params['parlamentar_set__ativo'] = True + + qs = queryset.filter(**params) + + return qs + + def filter_comissao(self, queryset, data_relativa): + return queryset.filter( + Q(comissao_set__data_extincao__isnull=True, + comissao_set__data_fim_comissao__isnull=True) | + Q(comissao_set__data_extincao__gte=data_relativa, + comissao_set__data_fim_comissao__isnull=True) | + Q(comissao_set__data_extincao__gte=data_relativa, + comissao_set__data_fim_comissao__isnull=True) | + Q(comissao_set__data_extincao__isnull=True, + comissao_set__data_fim_comissao__gte=data_relativa) | + Q(comissao_set__data_extincao__gte=data_relativa, + comissao_set__data_fim_comissao__gte=data_relativa), + comissao_set__data_criacao__lte=data_relativa) + + def filter_frente(self, queryset, data_relativa): + return queryset.filter( + Q(frente_set__data_extincao__isnull=True) | + Q(frente_set__data_extincao__gte=data_relativa), + frente_set__data_criacao__lte=data_relativa) + + def filter_bancada(self, queryset, data_relativa): + return queryset.filter( + Q(bancada_set__data_extincao__isnull=True) | + Q(bancada_set__data_extincao__gte=data_relativa), + bancada_set__data_criacao__lte=data_relativa) + + def filter_bloco(self, queryset, data_relativa): + return queryset.filter( + Q(bloco_set__data_extincao__isnull=True) | + Q(bloco_set__data_extincao__gte=data_relativa), + bloco_set__data_criacao__lte=data_relativa) + + def filter_orgao(self, queryset, data_relativa): + # na implementação, não havia regras a implementar para orgao + return queryset diff --git a/sapl/api/urls.py b/sapl/api/urls.py index bf9c16c51..314b2b339 100644 --- a/sapl/api/urls.py +++ b/sapl/api/urls.py @@ -3,7 +3,8 @@ from django.conf.urls import include, url from rest_framework.routers import DefaultRouter from sapl.api.views import (AutorListView, MateriaLegislativaViewSet, - ModelChoiceView, SessaoPlenariaViewSet) + ModelChoiceView, SessaoPlenariaViewSet, + AutoresPossiveisListView, AutoresProvaveisListView) from .apps import AppConfig @@ -17,6 +18,11 @@ urlpatterns_router = router.urls urlpatterns_api = [ + url(r'^autor/provaveis', + AutoresProvaveisListView.as_view(), name='autores_provaveis_list'), + url(r'^autor/possiveis', + AutoresPossiveisListView.as_view(), name='autores_possiveis_list'), + url(r'^autor', AutorListView.as_view(), name='autor_list'), url(r'^model/(?P\d+)/(?P\d*)$', diff --git a/sapl/api/views.py b/sapl/api/views.py index 7d89b28b0..7c8ddcf8d 100644 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -9,7 +9,8 @@ from rest_framework.permissions import (AllowAny, IsAuthenticated, IsAuthenticatedOrReadOnly) from rest_framework.viewsets import GenericViewSet, ModelViewSet -from sapl.api.forms import AutorChoiceFilterSet, AutorSearchForFieldFilterSet +from sapl.api.forms import AutorChoiceFilterSet, AutorSearchForFieldFilterSet,\ + AutoresPossiveisFilterSet from sapl.api.serializers import (AutorChoiceSerializer, AutorSerializer, ChoiceSerializer, MateriaLegislativaSerializer, @@ -57,14 +58,6 @@ class AutorListView(ListAPIView): de Autores feita pelo django-filter -> processo usado nas pesquisas, o mais usado. - = 2 -> para (value, text) usados geralmente - em combobox, radiobox, checkbox, etc com pesquisa básica - de Autores mas feito para Possíveis Autores armazenados - segundo o ContentType associado ao Tipo de Autor via - relacionamento genérico. - Busca feita sem django-filter processada no get_queryset - -> processo no cadastro de autores para seleção e busca - dos possíveis autores = 3 -> Devolve instancias da classe Autor filtradas pelo django-filter @@ -78,14 +71,14 @@ class AutorListView(ListAPIView): o django-filter é desativado e a busca é feita no model do ContentType associado ao tipo. - - q_0 / q_1 - q_0 faz o código ignorar "q"... + - q_0 / q_1 - q_0 é opcional e quando usado, faz o código ignorar "q"... q_0 -> campos lookup a serem filtrados em qualquer Model que implemente SaplGenericRelation q_1 -> o valor que será pesquisado no lookup de q_0 q_0 e q_1 podem ser separados por ","... isso dará a - possibilidade de filtrar mais de um campo. + possibilidade de filtrar mais de um campo. http://localhost:8000 @@ -114,7 +107,7 @@ class AutorListView(ListAPIView): não importa o campo que vc passe de qualquer dos Models - ligados... é possível ver que models são esses, + ligados... é possível ver que models são esses, na ocasião do commit deste texto, executando: In [6]: from sapl.utils import models_with_gr_for_model @@ -127,15 +120,13 @@ class AutorListView(ListAPIView): sapl.sessao.models.Bancada, sapl.sessao.models.Bloco] - qualquer atributo destes models podem ser passados + qualquer atributo destes models podem ser passados para busca """ TR_AUTOR_CHOICE_SERIALIZER = 1 - TR_CHOICE_SERIALIZER = 2 TR_AUTOR_SERIALIZER = 3 - # FIXME aplicar permissão correta de usuário permission_classes = (IsAuthenticatedOrReadOnly,) queryset = Autor.objects.all() model = Autor @@ -152,7 +143,6 @@ class AutorListView(ListAPIView): assert tr in ( AutorListView.TR_AUTOR_CHOICE_SERIALIZER, - AutorListView.TR_CHOICE_SERIALIZER, AutorListView.TR_AUTOR_SERIALIZER), sapl_logger.info( _("Tipo do Resultado a ser fornecido não existe!")) except: @@ -161,16 +151,8 @@ class AutorListView(ListAPIView): return tr def get(self, request, *args, **kwargs): - """ - desativa o django-filter se a busca for por possiveis autores - parametro tr = TR_CHOICE_SERIALIZER - """ - if self.tr == AutorListView.TR_CHOICE_SERIALIZER: - self.filter_class = None - self.filter_backends = [] - self.serializer_class = ChoiceSerializer - - elif self.tr == AutorListView.TR_AUTOR_SERIALIZER: + + if self.tr == AutorListView.TR_AUTOR_SERIALIZER: self.serializer_class = AutorSerializer self.permission_classes = (IsAuthenticated,) @@ -179,12 +161,20 @@ class AutorListView(ListAPIView): return ListAPIView.get(self, request, *args, **kwargs) + +class AutoresProvaveisListView(ListAPIView): + + permission_classes = (IsAuthenticatedOrReadOnly,) + queryset = Autor.objects.all() + model = Autor + + filter_class = None + filter_backends = [] + serializer_class = ChoiceSerializer + def get_queryset(self): queryset = ListAPIView.get_queryset(self) - if self.filter_backends: - return queryset - params = {'content_type__isnull': False} tipo = '' @@ -252,6 +242,18 @@ class AutorListView(ListAPIView): return r +class AutoresPossiveisListView(ListAPIView): + + permission_classes = (IsAuthenticatedOrReadOnly,) + queryset = Autor.objects.all() + model = Autor + + pagination_class = None + + filter_class = AutoresPossiveisFilterSet + serializer_class = AutorChoiceSerializer + + class MateriaLegislativaViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): diff --git a/sapl/base/forms.py b/sapl/base/forms.py index 8ecd74d5a..f95cc9bb8 100644 --- a/sapl/base/forms.py +++ b/sapl/base/forms.py @@ -24,7 +24,8 @@ from sapl.sessao.models import SessaoPlenaria from sapl.settings import MAX_IMAGE_UPLOAD_SIZE from sapl.utils import (RANGE_ANOS, ChoiceWithoutValidationField, ImageThumbnailFileInput, RangeWidgetOverride, - autor_label, autor_modal, models_with_gr_for_model) + autor_label, autor_modal, models_with_gr_for_model, + qs_override_django_filter) from .models import AppConfig, CasaLegislativa @@ -501,6 +502,10 @@ class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet): row1, form_actions(save_label='Pesquisar')) ) + @property + def qs(self): + return qs_override_django_filter(self) + class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet): diff --git a/sapl/base/migrations/0007_auto_20170808_0850.py b/sapl/base/migrations/0007_auto_20170808_0850.py new file mode 100644 index 000000000..8d3b4777a --- /dev/null +++ b/sapl/base/migrations/0007_auto_20170808_0850.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2017-08-08 08:50 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0006_auto_20170802_1908'), + ] + + operations = [ + migrations.AlterModelOptions( + name='autor', + options={'ordering': ('nome',), 'verbose_name': 'Autor', 'verbose_name_plural': 'Autores'}, + ), + ] diff --git a/sapl/base/models.py b/sapl/base/models.py index a5e94b321..2917ea3ea 100644 --- a/sapl/base/models.py +++ b/sapl/base/models.py @@ -1,15 +1,17 @@ -import reversion from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models from django.utils.translation import ugettext_lazy as _ +import reversion from sapl.utils import UF, YES_NO_CHOICES, get_settings_auth_user_model + TIPO_DOCUMENTO_ADMINISTRATIVO = (('O', _('Ostensivo')), ('R', _('Restritivo'))) SEQUENCIA_NUMERACAO = (('A', _('Sequencial por ano')), + ('L', _('Sequencial por legislatura')), ('U', _('Sequencial único'))) @@ -221,6 +223,7 @@ class Autor(models.Model): verbose_name = _('Autor') verbose_name_plural = _('Autores') unique_together = (('content_type', 'object_id'), ) + ordering = ('nome',) def __str__(self): diff --git a/sapl/base/views.py b/sapl/base/views.py index 4c86ff515..718774d46 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -21,7 +21,7 @@ from sapl.base.models import Autor, TipoAutor from sapl.crud.base import CrudAux from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.parlamentares.models import Parlamentar -from sapl.sessao.models import OrdemDia, SessaoPlenaria +from sapl.sessao.models import PresencaOrdemDia, SessaoPlenaria from sapl.utils import sapl_logger from .forms import (CasaLegislativaForm, ConfiguracoesAppForm, @@ -253,9 +253,9 @@ class RelatorioPresencaSessaoView(FilterView): sessao_count=0, ordemdia_count=0) - total_ordemdia = OrdemDia.objects.order_by( - 'sessao_plenaria').filter(**param0).distinct( - 'sessao_plenaria').count() + total_ordemdia = PresencaOrdemDia.objects.filter( + **param0).distinct('sessao_plenaria__id').order_by( + 'sessao_plenaria__id').count() self.calcular_porcentagem_presenca( pls, diff --git a/sapl/comissoes/migrations/0002_auto_20170809_1236.py b/sapl/comissoes/migrations/0002_auto_20170809_1236.py new file mode 100644 index 000000000..b665a2186 --- /dev/null +++ b/sapl/comissoes/migrations/0002_auto_20170809_1236.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-08-09 12:36 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='comissao', + name='nome', + field=models.CharField(max_length=100, verbose_name='Nome'), + ), + ] diff --git a/sapl/comissoes/models.py b/sapl/comissoes/models.py index a21a20508..0b3c664ee 100644 --- a/sapl/comissoes/models.py +++ b/sapl/comissoes/models.py @@ -35,7 +35,7 @@ class Comissao(models.Model): tipo = models.ForeignKey(TipoComissao, on_delete=models.PROTECT, verbose_name=_('Tipo')) - nome = models.CharField(max_length=60, verbose_name=_('Nome')) + nome = models.CharField(max_length=100, verbose_name=_('Nome')) sigla = models.CharField(max_length=10, verbose_name=_('Sigla')) data_criacao = models.DateField(verbose_name=_('Data de Criação')) data_extincao = models.DateField( diff --git a/sapl/crispy_layout_mixin.py b/sapl/crispy_layout_mixin.py index 29cd7849f..e46a193e1 100644 --- a/sapl/crispy_layout_mixin.py +++ b/sapl/crispy_layout_mixin.py @@ -67,7 +67,10 @@ def get_field_display(obj, fieldname): ou mesmo uma método no model. """ value = getattr(obj, fieldname) - verbose_name = '' + try: + verbose_name = value.model._meta.verbose_name + except AttributeError: + verbose_name = '' else: verbose_name = str(field.verbose_name)\ diff --git a/sapl/legacy/migration.py b/sapl/legacy/migration.py index b5e16e282..fcf2f0837 100644 --- a/sapl/legacy/migration.py +++ b/sapl/legacy/migration.py @@ -750,8 +750,8 @@ def adjust_proposicao_antes_salvar(new, old): 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" + 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) @@ -898,13 +898,13 @@ def adjust_autor(new, old): def adjust_comissao(new, old): - if old.dat_extincao: - if date.today() < new.data_extincao: - new.ativa = True - else: - new.ativa = False - if not old.dat_extincao: + if not old.dat_extincao and not old.dat_fim_comissao: + new.ativa = True + elif old.dat_extincao and date.today() < new.data_extincao or \ + old.dat_fim_comissao and date.today() < new.data_fim_comissao: new.ativa = True + else: + new.ativa = False AJUSTE_ANTES_SALVAR = { diff --git a/sapl/legacy/scripts/fix_tables.sql b/sapl/legacy/scripts/fix_tables.sql index e1dddc393..1f8cdb63b 100644 --- a/sapl/legacy/scripts/fix_tables.sql +++ b/sapl/legacy/scripts/fix_tables.sql @@ -1,19 +1,22 @@ -- Apaga as restrições somente para essa sessão -SELECT REPLACE(@@sql_mode,'STRICT_TRANS_TABLES,',''); +SELECT REPLACE(@@sql_mode,'STRICT_TRANS_TABLES,','ALLOW_INVALID_DATES'); -- 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; +DROP PROCEDURE IF EXISTS cria_tipo_situacao_militar; 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; +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 UPDATE proposicao SET dat_envio = '1800-01-01' WHERE CAST(dat_envio AS CHAR(20)) = '0000-00-00 00:00:00'; 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; +-- Procedure para criar tabela tipo_situacao_militar +CREATE PROCEDURE cria_tipo_situacao_militar() BEGIN IF NOT EXISTS (SELECT * FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='tipo_situacao_militar') THEN CREATE TABLE tipo_situacao_militar (tip_situacao_militar INT AUTO_INCREMENT NOT NULL, des_tipo_situacao VARCHAR(50), ind_excluido INT, PRIMARY KEY (tip_situacao_militar)); 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 @@ -21,4 +24,5 @@ CALL verifica_campos_proposicao; CALL verifica_campos_tipo_materia_legislativa; CALL verifica_campos_sessao_plenaria_presenca; CALL cria_lexml_registro_provedor_e_publicador; +CALL cria_tipo_situacao_militar; CALL muda_vinculo_norma_juridica_ind_excluido; diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index a8cf6cb66..5f5279d39 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -15,15 +15,12 @@ from django.db import models, transaction 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.forms.models import ModelMultipleChoiceField +from django.forms.widgets import Select, CheckboxSelectMultiple, HiddenInput 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 @@ -42,7 +39,8 @@ from sapl.settings import MAX_DOC_UPLOAD_SIZE from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, ChoiceWithoutValidationField, MateriaPesquisaOrderingFilter, RangeWidgetOverride, - autor_label, autor_modal, models_with_gr_for_model) + autor_label, autor_modal, models_with_gr_for_model, + qs_override_django_filter) import sapl from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial, @@ -166,6 +164,7 @@ class AcompanhamentoMateriaForm(ModelForm): class DocumentoAcessorioForm(ModelForm): + data = forms.DateField(required=True) class Meta: model = DocumentoAcessorio @@ -592,42 +591,7 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet): @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 + return qs_override_django_filter(self) def pega_ultima_tramitacao(): @@ -693,9 +657,11 @@ class AutoriaForm(ModelForm): tipo_autor = ModelChoiceField(label=_('Tipo Autor'), required=False, - queryset= - TipoAutor.objects.all().order_by('descricao'), - empty_label='Selecione',) + queryset=TipoAutor.objects.all(), + empty_label=_('Selecione'),) + + data_relativa = forms.DateField( + widget=forms.HiddenInput(), required=False) def __init__(self, *args, **kwargs): super(AutoriaForm, self).__init__(*args, **kwargs) @@ -707,26 +673,79 @@ class AutoriaForm(ModelForm): self.helper = FormHelper() self.helper.layout = Layout( Fieldset(_('Autoria'), - row1, form_actions(save_label='Salvar'))) + row1, 'data_relativa', form_actions(save_label='Salvar'))) + + if not kwargs['instance']: + self.fields['autor'].choices = [] class Meta: model = Autoria - fields = ['tipo_autor', 'autor', 'primeiro_autor'] + fields = ['tipo_autor', 'autor', 'primeiro_autor', 'data_relativa'] def clean(self): - super(AutoriaForm, self).clean() + cd = super(AutoriaForm, self).clean() 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) + autorias = Autoria.objects.filter( + materia=self.instance.materia, autor=cd['autor']) + pk = self.instance.pk - return self.cleaned_data + if ((not pk and autorias.exists()) or + (pk and autorias.exclude(pk=pk).exists())): + raise ValidationError(_('Esse Autor já foi cadastrado.')) + + return cd + + +class AutoriaMultiCreateForm(Form): + + tipo_autor = ModelChoiceField(label=_('Tipo Autor'), + required=False, + queryset=TipoAutor.objects.all(), + empty_label=_('Selecione'),) + + data_relativa = forms.DateField( + widget=forms.HiddenInput(), required=False) + + autor = ModelMultipleChoiceField( + queryset=Autor.objects.all(), + label=_('Possiveis Autores'), + required=False, + widget=CheckboxSelectMultiple) + + autores = ModelMultipleChoiceField( + queryset=Autor.objects.all(), + required=False, + widget=HiddenInput) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + row1 = to_row([('tipo_autor', 12), ]) + + row2 = to_row([('autor', 12), ]) + + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset( + _('Autorias'), row1, row2, 'data_relativa', 'autores', + form_actions(save_label='Incluir Autores Selecionados'))) + + self.fields['autor'].choices = [] + + def clean(self): + cd = super().clean() + + if 'autores' in self.errors: + del self.errors['autores'] + + if 'autor' not in cd or not cd['autor'].exists(): + raise ValidationError( + _('Ao menos um autor deve ser selecionado para inclusão')) + + return cd class AcessorioEmLoteFilterSet(django_filters.FilterSet): diff --git a/sapl/materia/migrations/0010_auto_20170808_0850.py b/sapl/materia/migrations/0010_auto_20170808_0850.py new file mode 100644 index 000000000..b32c05b23 --- /dev/null +++ b/sapl/materia/migrations/0010_auto_20170808_0850.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2017-08-08 08:50 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('materia', '0009_auto_20170712_0951'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='autoria', + unique_together=set([('autor', 'materia')]), + ), + ] diff --git a/sapl/materia/migrations/0011_auto_20170808_1034.py b/sapl/materia/migrations/0011_auto_20170808_1034.py new file mode 100644 index 000000000..209a87c70 --- /dev/null +++ b/sapl/materia/migrations/0011_auto_20170808_1034.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2017-08-08 10:34 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('materia', '0010_auto_20170808_0850'), + ] + + operations = [ + migrations.AlterModelOptions( + name='autoria', + options={'ordering': ('-primeiro_autor', 'autor__nome'), 'verbose_name': 'Autoria', 'verbose_name_plural': 'Autorias'}, + ), + ] diff --git a/sapl/materia/models.py b/sapl/materia/models.py index 8d9eae4f5..36d375068 100644 --- a/sapl/materia/models.py +++ b/sapl/materia/models.py @@ -299,6 +299,8 @@ class Autoria(models.Model): class Meta: verbose_name = _('Autoria') verbose_name_plural = _('Autorias') + unique_together = (('autor', 'materia'), ) + ordering = ('-primeiro_autor', 'autor__nome') def __str__(self): return _('%(autor)s - %(materia)s') % { diff --git a/sapl/materia/tests/test_materia.py b/sapl/materia/tests/test_materia.py index 20f3a12fa..0090376bf 100644 --- a/sapl/materia/tests/test_materia.py +++ b/sapl/materia/tests/test_materia.py @@ -1,9 +1,9 @@ -import pytest from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.core.files.uploadedfile import SimpleUploadedFile from django.core.urlresolvers import reverse from model_mommy import mommy +import pytest from sapl.base.models import Autor, TipoAutor from sapl.comissoes.models import Comissao, TipoComissao @@ -133,14 +133,13 @@ def test_autoria_submit(admin_client): nome='Autor Teste') # Testa POST - response = admin_client.post(reverse('sapl.materia:autoria_create', - kwargs={'pk': materia_principal.pk}), - {'autor': autor.pk, - 'primeiro_autor': True, - 'materia_id': materia_principal.pk, - 'partido': '', - 'salvar': 'salvar'}, - follow=True) + response = admin_client.post( + reverse('sapl.materia:autoria_create', + kwargs={'pk': materia_principal.pk}), + {'autor': autor.pk, + 'primeiro_autor': True, + 'materia_id': materia_principal.pk, }, + follow=True) assert response.status_code == 200 # Verifica se o autor foi realmente criado @@ -227,6 +226,7 @@ def test_documento_acessorio_submit(admin_client): 'data_materia': '2016-03-21', 'autor': autor, 'ementa': 'teste_ementa', + 'data': '2016-03-21', 'salvar': 'salvar'}, follow=True) @@ -323,9 +323,7 @@ def test_form_errors_autoria(admin_client): response = admin_client.post(reverse('sapl.materia:autoria_create', kwargs={'pk': materia_principal.pk}), {'materia_id': materia_principal.pk, - 'partido': '', - 'autor': '', - 'salvar': 'salvar'}, + 'autor_id': '', }, follow=True) assert (response.context_data['form'].errors['autor'] == diff --git a/sapl/materia/urls.py b/sapl/materia/urls.py index 600fadfc6..d588f1f20 100644 --- a/sapl/materia/urls.py +++ b/sapl/materia/urls.py @@ -2,8 +2,7 @@ from django.conf.urls import include, url from sapl.materia.views import (AcompanhamentoConfirmarView, AcompanhamentoExcluirView, - AcompanhamentoMateriaView, - AdicionarVariasAutorias, AnexadaCrud, + AcompanhamentoMateriaView, AnexadaCrud, AssuntoMateriaCrud, AutoriaCrud, ConfirmarProposicao, CriarProtocoloMateriaView, DespachoInicialCrud, DocumentoAcessorioCrud, @@ -21,11 +20,12 @@ from sapl.materia.views import (AcompanhamentoConfirmarView, TipoFimRelatoriaCrud, TipoMateriaCrud, TipoProposicaoCrud, TramitacaoCrud, TramitacaoEmLoteView, UnidadeTramitacaoCrud, - proposicao_texto, recuperar_materia) + proposicao_texto, recuperar_materia, + AutoriaMultiCreateView) +from . import receivers from .apps import AppConfig -from . import receivers app_name = AppConfig.name @@ -60,9 +60,9 @@ urlpatterns_materia = [ AcompanhamentoExcluirView.as_view(), name='acompanhar_excluir'), - url(r'^materia/(?P\d+)/adicionar-varias-autorias/', - AdicionarVariasAutorias.as_view(), - name='adicionar_varias_autorias'), + url(r'^materia/(?P\d+)/autoria/multicreate', + AutoriaMultiCreateView.as_view(), + name='autoria_multicreate'), url(r'^materia/acessorio-em-lote', DocumentoAcessorioEmLoteView.as_view(), name='acessorio_em_lote'), diff --git a/sapl/materia/views.py b/sapl/materia/views.py index bc2478d49..c4cf64639 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -2,23 +2,6 @@ from datetime import datetime, date from random import choice from string import ascii_letters, digits -from .email_utils import do_envia_email_confirmacao -from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, - AdicionarVariasAutoriasFilterSet, DespachoInicialForm, - DocumentoAcessorioForm, MateriaAssuntoForm, - MateriaLegislativaFilterSet, MateriaSimplificadaForm, - PrimeiraTramitacaoEmLoteFilterSet, ReceberProposicaoForm, - RelatoriaForm, TramitacaoEmLoteFilterSet, - filtra_tramitacao_destino, - filtra_tramitacao_destino_and_status, - filtra_tramitacao_status) -from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria, - DespachoInicial, DocumentoAcessorio, MateriaAssunto, - MateriaLegislativa, Numeracao, Orgao, Origem, Proposicao, - RegimeTramitacao, Relatoria, StatusTramitacao, - TipoDocumento, TipoFimRelatoria, TipoMateriaLegislativa, - TipoProposicao, Tramitacao, UnidadeTramitacao) -from .signals import tramitacao_signal from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML from django import forms @@ -39,9 +22,8 @@ from django.views.generic import CreateView, ListView, TemplateView, UpdateView from django.views.generic.base import RedirectView from django.views.generic.edit import FormView from django_filters.views import FilterView -import sapl + from sapl.base.models import Autor, CasaLegislativa -from sapl.comissoes.models import Comissao from sapl.comissoes.models import Comissao, Participacao from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_RESTRICT, STATUS_TA_PRIVATE) @@ -54,15 +36,31 @@ from sapl.crud.base import (ACTION_CREATE, ACTION_DELETE, ACTION_DETAIL, from sapl.materia.forms import (AnexadaForm, ConfirmarProposicaoForm, LegislacaoCitadaForm, AutoriaForm, ProposicaoForm, TipoProposicaoForm, TramitacaoForm, - TramitacaoUpdateForm) -from sapl.materia.models import Autor + TramitacaoUpdateForm, AutoriaMultiCreateForm) from sapl.norma.models import LegislacaoCitada -from sapl.parlamentares.models import Parlamentar from sapl.protocoloadm.models import Protocolo from sapl.utils import (TURNO_TRAMITACAO_CHOICES, YES_NO_CHOICES, autor_label, autor_modal, gerar_hash_arquivo, get_base_url, montar_row_autor) +import sapl +from .email_utils import do_envia_email_confirmacao +from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, + AdicionarVariasAutoriasFilterSet, DespachoInicialForm, + DocumentoAcessorioForm, MateriaAssuntoForm, + MateriaLegislativaFilterSet, MateriaSimplificadaForm, + PrimeiraTramitacaoEmLoteFilterSet, ReceberProposicaoForm, + RelatoriaForm, TramitacaoEmLoteFilterSet, + filtra_tramitacao_destino, + filtra_tramitacao_destino_and_status, + filtra_tramitacao_status) +from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria, + DespachoInicial, DocumentoAcessorio, MateriaAssunto, + MateriaLegislativa, Numeracao, Orgao, Origem, Proposicao, + RegimeTramitacao, Relatoria, StatusTramitacao, + TipoDocumento, TipoFimRelatoria, TipoMateriaLegislativa, + TipoProposicao, Tramitacao, UnidadeTramitacao) +from .signals import tramitacao_signal AssuntoMateriaCrud = Crud.build(AssuntoMateria, 'assunto_materia') @@ -93,8 +91,8 @@ def proposicao_texto(request, pk): if proposicao.texto_original: if (not proposicao.data_recebimento and - proposicao.autor.user_id != request.user.id): - raise Http404 + proposicao.autor.user_id != request.user.id): + raise Http404 arquivo = proposicao.texto_original @@ -1063,6 +1061,11 @@ class DocumentoAcessorioCrud(MasterDetailCrud): def __init__(self, **kwargs): super(MasterDetailCrud.CreateView, self).__init__(**kwargs) + + def get_initial(self): + self.initial['data'] = datetime.now().date() + + return self.initial def get_context_data(self, **kwargs): context = super( @@ -1085,41 +1088,74 @@ class AutoriaCrud(MasterDetailCrud): parent_field = 'materia' help_path = '' public = [RP_LIST, RP_DETAIL] + list_field_names = ['autor', 'autor__tipo__descricao', 'primeiro_autor'] - class CreateView(MasterDetailCrud.CreateView): + class LocalBaseMixin(): form_class = AutoriaForm @property def layout_key(self): - return 'AutoriaCreate' + return None - def get_context_data(self, **kwargs): - context = super(CreateView, self).get_context_data(**kwargs) - autores_ativos = self.autores_ativos() + class CreateView(LocalBaseMixin, MasterDetailCrud.CreateView): - autores = [] + def get_initial(self): + initial = super().get_initial() + materia = MateriaLegislativa.objects.get(id=self.kwargs['pk']) + initial['data_relativa'] = materia.data_apresentacao + initial['autor'] = [] + return initial - context['form'].fields['autor'].choices = autores - return context + class UpdateView(LocalBaseMixin, MasterDetailCrud.UpdateView): - 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) + def get_initial(self): + initial = super().get_initial() + initial.update({ + 'data_relativa': self.object.materia.data_apresentacao, + 'tipo_autor': self.object.autor.tipo.id, + }) + return initial - 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 ListView(MasterDetailCrud.ListView): +class AutoriaMultiCreateView(PermissionRequiredForAppCrudMixin, FormView): + app_label = sapl.materia.apps.AppConfig.label + form_class = AutoriaMultiCreateForm + template_name = 'materia/autoria_multicreate_form.html' - def get_queryset(self): - qs = super().get_queryset() + @classmethod + def get_url_regex(cls): + return r'^(?P\d+)/%s/multicreate' % cls.model._meta.model_name + + @property + def layout_key(self): + return None + + def get_initial(self): + initial = super().get_initial() + self.materia = MateriaLegislativa.objects.get(id=self.kwargs['pk']) + initial['data_relativa'] = self.materia.data_apresentacao + initial['autores'] = self.materia.autores.all() + return initial + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['title'] = '%s (%s)' % ( + _('Adicionar Várias Autorias'), self.materia) + return context + + def get_success_url(self): + messages.add_message( + self.request, messages.SUCCESS, + _('Autorias adicionadas com sucesso.')) + return reverse( + 'sapl.materia:autoria_list', kwargs={'pk': self.materia.pk}) + + def form_valid(self, form): + autores_selecionados = form.cleaned_data['autor'] + for autor in autores_selecionados: + Autoria.objects.create(materia=self.materia, autor=autor) - return qs.order_by('-primeiro_autor', 'autor__nome') + return FormView.form_valid(self, form) class DespachoInicialCrud(MasterDetailCrud): @@ -1516,9 +1552,9 @@ class AcompanhamentoMateriaView(CreateView): base_url = get_base_url(request) destinatario = AcompanhamentoMateria.objects.get( - materia=materia, - email=email, - confirmado=False) + materia=materia, + email=email, + confirmado=False) casa = CasaLegislativa.objects.first() do_envia_email_confirmacao(base_url, @@ -1699,10 +1735,10 @@ class TramitacaoEmLoteView(PrimeiraTramitacaoEmLoteView): context['primeira_tramitacao'] = False if ('tramitacao__status' in qr and - 'tramitacao__unidade_tramitacao_destino' in qr and - qr['tramitacao__status'] and - qr['tramitacao__unidade_tramitacao_destino'] - ): + 'tramitacao__unidade_tramitacao_destino' in qr and + qr['tramitacao__status'] and + qr['tramitacao__unidade_tramitacao_destino'] + ): lista = filtra_tramitacao_destino_and_status( qr['tramitacao__status'], qr['tramitacao__unidade_tramitacao_destino']) diff --git a/sapl/protocoloadm/forms.py b/sapl/protocoloadm/forms.py index c8880960c..7fb1d2d7a 100644 --- a/sapl/protocoloadm/forms.py +++ b/sapl/protocoloadm/forms.py @@ -1,6 +1,5 @@ from datetime import datetime -import django_filters from crispy_forms.bootstrap import InlineRadios from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML, Button, Fieldset, Layout, Submit @@ -9,10 +8,11 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.db import models from django.forms import ModelForm from django.utils.translation import ugettext_lazy as _ +import django_filters -from sapl.base.models import Autor +from sapl.base.models import Autor, TipoAutor from sapl.crispy_layout_mixin import form_actions, to_row -from sapl.materia.models import TipoMateriaLegislativa, UnidadeTramitacao +from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa, UnidadeTramitacao from sapl.utils import (RANGE_ANOS, AnoNumeroOrderingFilter, RangeWidgetOverride, autor_label, autor_modal) @@ -20,6 +20,7 @@ from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo, Protocolo, TipoDocumentoAdministrativo, TramitacaoAdministrativo) + TIPOS_PROTOCOLO = [('0', 'Recebido'), ('1', 'Enviado'), ('', 'Ambos')] TIPOS_PROTOCOLO_CREATE = [('0', 'Recebido'), ('1', 'Enviado')] @@ -233,6 +234,18 @@ class AnularProcoloAdmForm(ModelForm): except ObjectDoesNotExist: raise forms.ValidationError( _("Protocolo %s/%s não existe" % (numero, ano))) + exists = False + if protocolo.tipo_materia: + exists = MateriaLegislativa.objects.filter( + numero_protocolo=protocolo.numero, ano=protocolo.ano).exists() + elif protocolo.tipo_documento: + exists = protocolo.documentoadministrativo_set.all( + ).order_by('-ano', '-numero').exists() + + if exists: + raise forms.ValidationError( + _("Protocolo %s/%s não pode ser removido pois existem" + "documentos vinculados a ele." % (numero, ano))) class Meta: model = Protocolo @@ -334,9 +347,14 @@ class ProtocoloDocumentForm(ModelForm): class ProtocoloMateriaForm(ModelForm): autor = forms.ModelChoiceField(required=True, - empty_label='------', - queryset=Autor.objects.all() - ) + empty_label='------', + queryset=Autor.objects.all() + ) + + tipo_autor = forms.ModelChoiceField(required=True, + empty_label='------', + queryset=TipoAutor.objects.all() + ) tipo_materia = forms.ModelChoiceField( label=_('Tipo de Matéria'), @@ -353,12 +371,12 @@ class ProtocoloMateriaForm(ModelForm): assunto_ementa = forms.CharField(required=True, widget=forms.Textarea, label='Ementa') - class Meta: model = Protocolo fields = ['tipo_materia', 'numero_paginas', 'autor', + 'tipo_autor', 'assunto_ementa', 'observacao'] @@ -376,9 +394,9 @@ class ProtocoloMateriaForm(ModelForm): row1 = to_row( [('tipo_materia', 4), - ('numero_paginas', 4)]) - row2 = to_row( - [('autor', 4)]) + ('numero_paginas', 2), + ('tipo_autor', 3), + ('autor', 3)]) row3 = to_row( [('assunto_ementa', 12)]) row4 = to_row( @@ -387,7 +405,7 @@ class ProtocoloMateriaForm(ModelForm): self.helper = FormHelper() self.helper.layout = Layout( Fieldset(_('Identificação da Matéria'), - row1, row2, row3, + row1, 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 9fffa3669..e26168d0d 100644 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -287,6 +287,13 @@ class ProtocoloDocumentoView(PermissionRequiredMixin, if numeracao == 'A': numero = Protocolo.objects.filter( ano=date.today().year).aggregate(Max('numero')) + elif numeracao == 'L': + legislatura = Legislatura.objects.last() + data_inicio = legislatura.data_inicio + data_fim = legislatura.data_fim + numero = Protocolo.objects.filter( + data__gte=data_inicio, data__lte=data_fim).aggregate( + Max('numero')) elif numeracao == 'U': numero = Protocolo.objects.all().aggregate(Max('numero')) diff --git a/sapl/redireciona_urls/views.py b/sapl/redireciona_urls/views.py index 8e9d5afbe..42b0fcadd 100644 --- a/sapl/redireciona_urls/views.py +++ b/sapl/redireciona_urls/views.py @@ -50,6 +50,20 @@ relatorio_materia_por_ano_autor_tipo = ( historico_tramitacoes = (app_relatorios + ':historico_tramitacoes') + +def has_iframe(url, request): + + iframe = request.GET.get( + 'iframe', + EMPTY_STRING) + if iframe: + iframe_qs= ("iframe=" + iframe) + url += ("&" if "?" in url else "?") + url += iframe_qs + + return url + + class RedirecionaSAPLIndex(RedirectView): permanent = True @@ -59,6 +73,9 @@ class RedirecionaSAPLIndex(RedirectView): url = reverse(url_pattern) except NoReverseMatch: raise UnknownUrlNameError(url_pattern) + + url = has_iframe(url, self.request) + return url @@ -90,6 +107,8 @@ class RedirecionaParlamentar(RedirectView): args = '?pk=' + numero_legislatura url = "%s%s" % (url, args) + url = has_iframe(url, self.request) + return url @@ -112,6 +131,9 @@ class RedirecionaComissao(RedirectView): url = reverse(comissao_list) except NoReverseMatch: raise UnknownUrlNameError(comissao_list) + + url = has_iframe(url, self.request) + return url @@ -151,6 +173,8 @@ class RedirecionaPautaSessao(RedirectView): args += "&tipo=&salvar=Pesquisar" url = "%s%s" % (url, args) + url = has_iframe(url, self.request) + return url @@ -198,6 +222,8 @@ class RedirecionaSessaoPlenaria(RedirectView): args += "&tipo=%s&salvar=Pesquisar" % (tipo_sessao) url = "%s%s" % (url, args) + url = has_iframe(url, self.request) + return url @@ -210,6 +236,9 @@ class RedirecionaRelatoriosList(RedirectView): url = reverse(relatorios_list) except NoReverseMatch: raise UnknownUrlNameError(relatorios_list) + + url = has_iframe(url, self.request) + return url @@ -253,6 +282,8 @@ class RedirecionaRelatoriosMateriasEmTramitacaoList(RedirectView): args += "&salvar=%s" % (salvar) url = "%s%s" % (url, args) + url = has_iframe(url, self.request) + return url @@ -260,13 +291,18 @@ class RedirecionaMateriaLegislativaDetail(RedirectView): permanent = True def get_redirect_url(self): + url = EMPTY_STRING pk = self.request.GET.get('cod_materia', EMPTY_STRING) if pk: kwargs = {'pk': pk} - return reverse(materialegislativa_detail, kwargs=kwargs) + url = reverse(materialegislativa_detail, kwargs=kwargs) else: - return reverse(materialegislativa_list) + url = reverse(materialegislativa_list) + + url = has_iframe(url, self.request) + + return url class RedirecionaMateriaLegislativaList(RedirectView): @@ -339,6 +375,8 @@ class RedirecionaMateriaLegislativaList(RedirectView): url = "%s%s" % (url, args) + url = has_iframe(url, self.request) + return url @@ -346,11 +384,14 @@ class RedirecionaMesaDiretoraView(RedirectView): permanent = True def get_redirect_url(self): + url = EMPTY_STRING try: url = reverse(parlamentar_mesa_diretora) except NoReverseMatch: raise UnknownUrlNameError(parlamentar_mesa_diretora) + url = has_iframe(url, self.request) + return url @@ -358,13 +399,18 @@ class RedirecionaNormasJuridicasDetail(RedirectView): permanent = True def get_redirect_url(self): + url = EMPTY_STRING pk_norma = self.request.GET.get('cod_norma', EMPTY_STRING) if pk_norma: kwargs = {'pk': pk_norma} - return reverse(norma_juridica_detail, kwargs=kwargs) + url = reverse(norma_juridica_detail, kwargs=kwargs) else: - return reverse(norma_juridica_pesquisa) + url = reverse(norma_juridica_pesquisa) + + url = has_iframe(url, self.request) + + return url class RedirecionaNormasJuridicasList(RedirectView): @@ -420,6 +466,8 @@ class RedirecionaNormasJuridicasList(RedirectView): url = "%s%s" % (url, args) + url = has_iframe(url, self.request) + return url @@ -475,6 +523,8 @@ class RedirecionaHistoricoTramitacoesList(RedirectView): url = "%s%s" % (url, args) + url = has_iframe(url, self.request) + return url @@ -507,6 +557,8 @@ class RedirecionaAtasList(RedirectView): url = "%s%s" % (url, args) + url = has_iframe(url, self.request) + return url @@ -539,6 +591,8 @@ class RedirecionaPresencaParlamentares(RedirectView): url = "%s%s" % (url, args) + url = has_iframe(url, self.request) + return url @@ -553,6 +607,8 @@ class RedirecionaMateriasPorAutor(RedirectView): except NoReverseMatch: raise UnknownUrlNameError(relatorio_materia_por_autor) + url = has_iframe(url, self.request) + return url @@ -574,4 +630,6 @@ class RedirecionaMateriasPorAnoAutorTipo(RedirectView): args += "&salvar=%s" % ('Pesquisar') url = "%s%s" % (url, args) + url = has_iframe(url, self.request) + return url diff --git a/sapl/sessao/migrations/0010_auto_20170810_1033.py b/sapl/sessao/migrations/0010_auto_20170810_1033.py new file mode 100644 index 000000000..b35f9e820 --- /dev/null +++ b/sapl/sessao/migrations/0010_auto_20170810_1033.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-08-10 10:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sessao', '0009_auto_20170619_1441'), + ] + + operations = [ + migrations.AddField( + model_name='registrovotacao', + name='data_hora_atualizacao', + field=models.DateTimeField(auto_now=True, null=True, verbose_name='Data'), + ), + migrations.AddField( + model_name='registrovotacao', + name='data_hora_criacao', + field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Data Criação'), + ), + ] diff --git a/sapl/sessao/models.py b/sapl/sessao/models.py index 5cd6c6501..027727731 100644 --- a/sapl/sessao/models.py +++ b/sapl/sessao/models.py @@ -400,6 +400,17 @@ class RegistroVotacao(models.Model): observacao = models.TextField( blank=True, verbose_name=_('Observações')) + data_hora_criacao = models.DateTimeField( + blank=True, null=True, + auto_now_add=True, + verbose_name=_('Data Criação')) + + data_hora_atualizacao = models.DateTimeField( + blank=True, null=True, + auto_now=True, + verbose_name=_('Data')) + + class Meta: verbose_name = _('Votação') verbose_name_plural = _('Votações') diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index c2012bd94..f72f803e1 100644 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -2267,7 +2267,7 @@ class PautaSessaoDetailView(DetailView): tipo = TipoExpediente.objects.get( id=e.tipo_id) conteudo = sub( - ' ', ' ', strip_tags(e.conteudo)) + ' ', ' ', strip_tags(e.conteudo.replace('
', '\n'))) ex = {'tipo': tipo, 'conteudo': conteudo} expedientes.append(ex) diff --git a/sapl/settings.py b/sapl/settings.py index d6590c5b4..903b38b6a 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -22,6 +22,7 @@ from unipath import Path from .temp_suppress_crispy_form_warnings import \ SUPRESS_CRISPY_FORM_WARNINGS_LOGGING + BASE_DIR = Path(__file__).ancestor(1) PROJECT_DIR = Path(__file__).ancestor(2) @@ -38,7 +39,10 @@ ALLOWED_HOSTS = ['*'] LOGIN_REDIRECT_URL = '/' LOGIN_URL = '/login/?next=' -EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +if DEBUG: + EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +else: + EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # SAPL business apps in dependency order @@ -101,7 +105,7 @@ if SOLR_URL: HAYSTACK_CONNECTIONS = { 'default': { 'ENGINE': SEARCH_BACKEND, - SEARCH_URL[0] : SEARCH_URL[1] + SEARCH_URL[0]: SEARCH_URL[1] }, } @@ -200,7 +204,7 @@ MAX_IMAGE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB # https://docs.djangoproject.com/en/1.8/topics/i18n/ LANGUAGE_CODE = 'pt-br' LANGUAGES = ( - ('pt-br','Português'), + ('pt-br', 'Português'), ) TIME_ZONE = 'America/Sao_Paulo' diff --git a/sapl/static/js/app.js b/sapl/static/js/app.js index 1afa1eec5..697564024 100644 --- a/sapl/static/js/app.js +++ b/sapl/static/js/app.js @@ -58,7 +58,7 @@ function autorModal() { autoOpen: false, modal: true, width: 500, - height: 300, + height: 340, show: { effect: "blind", duration: 500}, @@ -90,11 +90,11 @@ function autorModal() { $("#pesquisar").click(function() { var name_in_query = $("#q").val() - var q_0 = "q_0=nome__icontains" - var q_1 = "q_1=" + name_in_query - query = q_0 + "&" + q_1 + //var q_0 = "q_0=nome__icontains" + //var q_1 = name_in_query + //query = q_1 - $.get("/api/autor?" + query, function(data, status) { + $.get("/api/autor?q=" + name_in_query, function(data, status) { $("#div-resultado").children().remove(); if (data.pagination.total_entries == 0) { $("#selecionar").attr("hidden", "hidden"); @@ -111,11 +111,12 @@ function autorModal() { select.append($(""); } @@ -31,10 +36,12 @@ .attr("value", obj.value) .text(obj.text)); }); + $("#id_autor").val(autor_selecionado); } }); } }); + $("#id_tipo_autor").trigger('change'); }); {% endblock %} diff --git a/sapl/templates/materia/autoria_list.html b/sapl/templates/materia/autoria_list.html index 44b59c849..9574ed3b7 100644 --- a/sapl/templates/materia/autoria_list.html +++ b/sapl/templates/materia/autoria_list.html @@ -6,8 +6,8 @@ {% block more_buttons %} {% if perms|get_add_perm:view %} - - {% blocktrans with verbose_name=view.verbose_name %} Adicionar Várias Autorias {% endblocktrans %} + + {% trans "Adicionar Várias Autorias" %} {% endif %} diff --git a/sapl/templates/materia/autoria_multicreate_form.html b/sapl/templates/materia/autoria_multicreate_form.html new file mode 100644 index 000000000..59c3ece91 --- /dev/null +++ b/sapl/templates/materia/autoria_multicreate_form.html @@ -0,0 +1,51 @@ +{% extends "crud/form.html" %} +{% load i18n %} +{% load crispy_forms_tags %} +{% load common_tags %} + +{% block extra_js %} + +{% endblock %} diff --git a/sapl/templates/materia/layouts.yaml b/sapl/templates/materia/layouts.yaml index aac494432..4a86fe945 100644 --- a/sapl/templates/materia/layouts.yaml +++ b/sapl/templates/materia/layouts.yaml @@ -61,13 +61,6 @@ Autoria: {% trans 'Autoria' %}: - autor primeiro_autor -AutoriaCreate: - {% trans 'Autoria' %}: - - tipo_autor autor primeiro_autor - -AutoriaUpdate: - {% trans 'Autoria' %}: - - tipo_autor autor primeiro_autor DocumentoAcessorio: {% trans 'Documento Acessório' %}: diff --git a/sapl/templates/materia/materialegislativa_filter.html b/sapl/templates/materia/materialegislativa_filter.html index 285379cc5..0f7d99468 100644 --- a/sapl/templates/materia/materialegislativa_filter.html +++ b/sapl/templates/materia/materialegislativa_filter.html @@ -78,17 +78,19 @@ Data Fim Prazo (Tramitação): {{m.tramitacao_set.last.data_fim_prazo|default_if_none:""}}
{% endif %} {% if m.registrovotacao_set.exists %} - Data da última Votação: - {% if m.registrovotacao_set.last.ordem %} - - {{ m.registrovotacao_set.last.ordem.data_ordem }} - - {% elif m.registrovotacao_set.last.expediente %} - - {{ m.registrovotacao_set.last.expediente.data_ordem }} - - {% endif %} -
+ Data Votação: + {% for rv in m.registrovotacao_set.all %} + {% if rv.ordem %} + + {{ rv.ordem.data_ordem }} + + {% elif rv.expediente %} + + {{ rv.expediente.data_ordem }} + + {% endif %} +
+ {% endfor %} {% endif %} {% if m.tramitacao_set.last.data_tramitacao %} Data da última Tramitação:  {{m.tramitacao_set.last.data_tramitacao}}
diff --git a/sapl/templates/protocoloadm/protocolar_materia.html b/sapl/templates/protocoloadm/protocolar_materia.html index 744126f0d..21d0db47e 100644 --- a/sapl/templates/protocoloadm/protocolar_materia.html +++ b/sapl/templates/protocoloadm/protocolar_materia.html @@ -13,3 +13,44 @@ {% block detail_content %} {% crispy form %} {% endblock detail_content %} + +{% block extra_js %} + +{% endblock %} diff --git a/sapl/templates/sessao/pauta_sessao_detail.html b/sapl/templates/sessao/pauta_sessao_detail.html index a16f86802..82cbb686a 100644 --- a/sapl/templates/sessao/pauta_sessao_detail.html +++ b/sapl/templates/sessao/pauta_sessao_detail.html @@ -25,7 +25,7 @@ {{e.tipo}}:
-

{{e.conteudo|safe}}

+

{{e.conteudo|safe|linebreaks}}

{% endfor %} diff --git a/sapl/utils.py b/sapl/utils.py index 2f56605a7..0c0eb1cd3 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -3,6 +3,7 @@ import logging import os import re from datetime import date +from django_filters.filterset import STRICTNESS from functools import wraps from subprocess import PIPE, call from threading import Thread @@ -20,6 +21,7 @@ from django.contrib import admin from django.contrib.contenttypes.fields import (GenericForeignKey, GenericRel, GenericRelation) from django.core.exceptions import ValidationError +from django.utils import six from django.utils.translation import ugettext_lazy as _ from floppyforms import ClearableFileInput from reversion.admin import VersionAdmin @@ -557,6 +559,45 @@ def texto_upload_path(instance, filename, subpath='', pk_first=False): return path +def qs_override_django_filter(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 filiacao_data(parlamentar, data): from sapl.parlamentares.models import Filiacao