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 a06f2c81c..ca771995a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,7 @@ sapldb: ports: - "5532:5432" sapl: - image: interlegis/sapl:3.1.12-BETA + image: interlegis/sapl:3.1.18-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/api/forms.py b/sapl/api/forms.py index 41e40eacf..d1445f7df 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,93 @@ 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 + + return getattr(self, filter_for_model)(qs, data_relativa) + + def filter_parlamentar(self, queryset, data_relativa): + # não leva em conta afastamentos + if not data_relativa: + data_relativa = timezone.now() + + 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).distinct() + + return qs + + def filter_frente(self, queryset, data_relativa): + # implementar regras específicas para frente + return queryset + + def filter_comissao(self, queryset, data_relativa): + # implementar regras específicas para comissao + return queryset + + def filter_orgao(self, queryset, data_relativa): + # implementar regras específicas para orgao + return queryset + + def filter_bancada(self, queryset, data_relativa): + # implementar regras específicas para bancada + return queryset + + def filter_bloco(self, queryset, data_relativa): + # implementar regras específicas para bloco + 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 b245470a1..8ecd74d5a 100644 --- a/sapl/base/forms.py +++ b/sapl/base/forms.py @@ -1,4 +1,3 @@ -import django_filters from crispy_forms.bootstrap import FieldWithButtons, InlineRadios, StrictButton from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML, Button, Div, Field, Fieldset, Layout, Row @@ -13,8 +12,9 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db import models, transaction from django.forms import ModelForm -from django.utils.translation import ugettext_lazy as _ from django.utils.translation import string_concat +from django.utils.translation import ugettext_lazy as _ +import django_filters from sapl.base.models import Autor, TipoAutor from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column, @@ -28,6 +28,7 @@ from sapl.utils import (RANGE_ANOS, ChoiceWithoutValidationField, from .models import AppConfig, CasaLegislativa + ACTION_CREATE_USERS_AUTOR_CHOICE = [ ('C', _('Criar novo Usuário')), ('A', _('Associar um usuário existente')), @@ -336,8 +337,11 @@ class AutorForm(ModelForm): _('O Registro definido (%s-%s) não está na base de %s.' ) % (cd['autor_related'], cd['q'], tipo.descricao)) - if qs_autor.filter(object_id=cd['autor_related']).exists(): - autor = qs_autor.filter(object_id=cd['autor_related']).first() + qs_autor_selected = qs_autor.filter( + object_id=cd['autor_related'], + content_type_id=cd['tipo'].content_type_id) + if qs_autor_selected.exists(): + autor = qs_autor_selected.first() raise ValidationError( _('Já existe um autor Cadastrado para %s' ) % autor.autor_related) @@ -714,6 +718,7 @@ class ConfiguracoesAppForm(ModelForm): class RecuperarSenhaForm(PasswordResetForm): + def __init__(self, *args, **kwargs): row1 = to_row( [('email', 12)]) @@ -728,7 +733,7 @@ class RecuperarSenhaForm(PasswordResetForm): def clean(self): super(RecuperarSenhaForm, self).clean() - + email_existente = User.objects.filter( email=self.data['email']).exists() @@ -740,6 +745,7 @@ class RecuperarSenhaForm(PasswordResetForm): class NovaSenhaForm(SetPasswordForm): + def __init__(self, user, *args, **kwargs): self.user = user super(NovaSenhaForm, self).__init__(user, *args, **kwargs) @@ -750,5 +756,5 @@ class NovaSenhaForm(SetPasswordForm): self.helper = FormHelper() self.helper.layout = Layout( - row1, - form_actions(save_label='Enviar')) + row1, + form_actions(save_label='Enviar')) diff --git a/sapl/base/migrations/0005_auto_20170802_1428.py b/sapl/base/migrations/0005_auto_20170802_1428.py new file mode 100644 index 000000000..d97c0448b --- /dev/null +++ b/sapl/base/migrations/0005_auto_20170802_1428.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-08-02 14:28 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0004_auto_20170714_1838'), + ] + + operations = [ + migrations.AlterField( + model_name='casalegislativa', + name='codigo', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Codigo'), + ), + ] diff --git a/sapl/base/migrations/0006_auto_20170802_1908.py b/sapl/base/migrations/0006_auto_20170802_1908.py new file mode 100644 index 000000000..2a3ac7009 --- /dev/null +++ b/sapl/base/migrations/0006_auto_20170802_1908.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-08-02 22:08 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0005_auto_20170802_1428'), + ] + + operations = [ + migrations.AlterField( + model_name='casalegislativa', + name='codigo', + field=models.CharField(blank=True, default='', max_length=100, verbose_name='Codigo'), + preserve_default=False, + ), + ] 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 f2f81ab15..0a4c8d8f2 100644 --- a/sapl/base/models.py +++ b/sapl/base/models.py @@ -1,11 +1,12 @@ -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'))) @@ -18,7 +19,9 @@ class CasaLegislativa(models.Model): # TODO ajustar todos os max_length !!!! # cod_casa => id (pk) - codigo = models.CharField(max_length=100, verbose_name=_('Codigo')) + codigo = models.CharField(max_length=100, + blank=True, + verbose_name=_('Codigo')) nome = models.CharField(max_length=100, verbose_name=_('Nome')) sigla = models.CharField(max_length=100, verbose_name=_('Sigla')) endereco = models.CharField(max_length=100, verbose_name=_('Endereço')) @@ -219,6 +222,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/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/compilacao/compilacao_data_tables.sql b/sapl/compilacao/compilacao_data_tables.sql index 663dbc682..663656548 100644 --- a/sapl/compilacao/compilacao_data_tables.sql +++ b/sapl/compilacao/compilacao_data_tables.sql @@ -258,9 +258,6 @@ INSERT INTO compilacao_tipodispositivorelationship (filho_permitido_id, pai_id, INSERT INTO compilacao_tipodispositivorelationship (filho_permitido_id, pai_id, filho_de_insercao_automatica, perfil_id, quantidade_permitida, permitir_variacao) VALUES (126, 1, false, 4, -1, false); INSERT INTO compilacao_tipodispositivorelationship (filho_permitido_id, pai_id, filho_de_insercao_automatica, perfil_id, quantidade_permitida, permitir_variacao) VALUES (122, 119, false, 1, -1, false); -INSERT INTO compilacao_tipodispositivorelationship (filho_permitido_id, pai_id, filho_de_insercao_automatica, perfil_id, quantidade_permitida, permitir_variacao) VALUES (122, 119, false, 1, -1, false); - -INSERT INTO compilacao_tipodispositivorelationship (filho_permitido_id, pai_id, filho_de_insercao_automatica, perfil_id, quantidade_permitida, permitir_variacao) VALUES (122, 119, false, 2, -1, true); INSERT INTO compilacao_tipodispositivorelationship (filho_permitido_id, pai_id, filho_de_insercao_automatica, perfil_id, quantidade_permitida, permitir_variacao) VALUES (122, 119, false, 2, -1, true); 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/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 8f56f91f1..183b22dbd 100644 --- a/sapl/legacy/migration.py +++ b/sapl/legacy/migration.py @@ -327,6 +327,9 @@ def obj_desnecessario(obj): if (f.one_to_many or f.one_to_one) and f.auto_created] sem_referencia = not any(rr.related_model.objects.filter( **{rr.field.name: obj}).exists() for rr in relacoes) + if type(obj).__name__ == 'Parlamentar' and sem_referencia and \ + obj.autor.all(): + sem_referencia = False return sem_referencia @@ -655,12 +658,32 @@ def adjust_acompanhamentomateria(new, old): def adjust_documentoadministrativo(new, old): if new.numero_protocolo: - protocolo = Protocolo.objects.get(numero=new.numero_protocolo, - ano=new.ano) - new.protocolo = protocolo + try: + protocolo = Protocolo.objects.get(numero=new.numero_protocolo, + ano=new.ano) + new.protocolo = protocolo + except Exception: + try: + protocolo = Protocolo.objects.get(numero=new.numero_protocolo, + ano=new.ano+1) + new.protocolo = protocolo + except Exception: + protocolo = mommy.make(Protocolo, numero=new.numero_protocolo, + ano=new.ano) + with reversion.create_revision(): + problema = 'Protocolo Vinculado [numero_protocolo=%s, '\ + 'ano=%s] não existe' % (new.numero_protocolo, + new.ano) + descricao = 'O protocolo inexistente foi criado' + warn(problema + ' => ' + descricao) + save_relation(obj=protocolo, problema=problema, + descricao=descricao, eh_stub=True) + reversion.set_comment('Protocolo não existia.') def adjust_mandato(new, old): + if old.dat_fim_mandato: + new.data_fim_mandato = old.dat_fim_mandato if not new.data_fim_mandato: legislatura = Legislatura.objects.latest('data_fim') new.data_fim_mandato = legislatura.data_fim @@ -727,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) @@ -745,17 +768,22 @@ def adjust_normarelacionada(new, old): new.tipo_vinculo = tipo[0] -def adjust_protocolo(new, old): - if new.numero is None and not primeira_vez: - p = ProtocoloLegado.objects.filter( - ano_protocolo=new.ano).aggregate(Max('num_protocolo')) - numero_maximo = p['num_protocolo__max'] - new.numero = 1 if numero_maximo is None else numero_maximo + 1 - primeira_vez.append(True) - if new.numero is None and primeira_vez: - p = Protocolo.objects.filter( - ano=new.ano).aggregate(Max('numero')) - new.numero = p['numero__max'] + 1 +def adjust_protocolo_antes_salvar(new, old): + data_ajuste = date(2014, 11, 13) + + if old.num_protocolo is None and data_ajuste >= old.dat_protocolo: + new.numero = old.pk + + +def adjust_protocolo_depois_salvar(new, old): + if old.num_protocolo is None: + with reversion.create_revision(): + problema = 'Número do protocolo de PK %s é nulo' % new.pk + descricao = 'Número do protocolo alterado para %s!' % new.numero + warn(problema + ' => ' + descricao) + save_relation(obj=new, problema=problema, + descricao=descricao, eh_stub=False) + reversion.set_comment('Número de protocolo teve que ser alterado') def adjust_registrovotacao_antes_salvar(new, old): @@ -835,21 +863,23 @@ def adjust_normajuridica_depois_salvar(new, old): new.assuntos.add(AssuntoNorma.objects.get(pk=pk_assunto)) -def adjust_protocolo_depois_salvar(new, old): - if old.num_protocolo is None: - with reversion.create_revision(): - problema = 'Número do protocolo de PK %s é nulo' % new.pk - descricao = 'Número do protocolo alterado para %s!' % new.numero - warn(problema + ' => ' + descricao) - save_relation(obj=new, problema=problema, - descricao=descricao, eh_stub=False) - reversion.set_comment('Numero de protocolo teve que ser alterado') - - def adjust_autor(new, old): if old.cod_parlamentar: - new.autor_related = Parlamentar.objects.get(pk=old.cod_parlamentar) + try: + new.autor_related = Parlamentar.objects.get(pk=old.cod_parlamentar) + except Exception: + with reversion.create_revision(): + msg = 'Um parlamentar relacionado de PK [%s] não existia' \ + % old.cod_parlamentar + reversion.set_comment('Stub criado pela migração') + value = make_stub(Parlamentar, old.cod_parlamentar) + descricao = 'stub criado para entrada orfã!' + warn(msg + ' => ' + descricao) + save_relation(value, [], msg, descricao, + eh_stub=True) + new.autor_related = value new.nome = new.autor_related.nome_parlamentar + elif old.cod_comissao: new.autor_related = Comissao.objects.get(pk=old.cod_comissao) new.nome = new.autor_related.nome @@ -857,22 +887,14 @@ def adjust_autor(new, old): if old.col_username: if not get_user_model().objects.filter( username=old.col_username).exists(): - user = get_user_model()( - username=old.col_username, password=12345) + user = get_user_model()(username=old.col_username) + user.set_password(12345) with reversion.create_revision(): user.save() reversion.set_comment('Objeto criado pela migração') grupo_autor = Group.objects.get(name="Autor") user.groups.add(grupo_autor) - if old.cod_parlamentar: - grupo_parlamentar = Group.objects.get(name="Parlamentar") - user.groups.add(grupo_parlamentar) - - new.user = user - else: - new.user = get_user_model().objects.filter( - username=old.col_username)[0] def adjust_comissao(new, old): @@ -897,7 +919,7 @@ AJUSTE_ANTES_SALVAR = { Parlamentar: adjust_parlamentar, Participacao: adjust_participacao, Proposicao: adjust_proposicao_antes_salvar, - Protocolo: adjust_protocolo, + Protocolo: adjust_protocolo_antes_salvar, RegistroVotacao: adjust_registrovotacao_antes_salvar, TipoAfastamento: adjust_tipoafastamento, TipoProposicao: adjust_tipoproposicao, diff --git a/sapl/legacy/scripts/fix_tables.sql b/sapl/legacy/scripts/fix_tables.sql index ed93373b1..3b0d55c7b 100644 --- a/sapl/legacy/scripts/fix_tables.sql +++ b/sapl/legacy/scripts/fix_tables.sql @@ -1,20 +1,24 @@ -- 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 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 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 47eabe43b..11e2d62b7 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -13,17 +13,21 @@ from django.core.files.base import File from django.core.urlresolvers import reverse from django.db import models, transaction from django.db.models import Max -from django.forms import ModelForm, widgets +from django.forms import ModelForm, ModelChoiceField, widgets +from django.forms.fields import BooleanField from django.forms.forms import Form -from django.forms.widgets import Select +from django.forms.widgets import Select, HiddenInput +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 +from sapl.base.models import Autor, TipoAutor from sapl.comissoes.models import Comissao +from sapl.compilacao.forms import error_messages from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC, STATUS_TA_PRIVATE) from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column, @@ -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( @@ -648,24 +691,45 @@ class DespachoInicialForm(ModelForm): class AutoriaForm(ModelForm): + tipo_autor = ModelChoiceField(label=_('Tipo Autor'), + required=False, + queryset=TipoAutor.objects.all(), + empty_label=_('Selecione'),) + + data_relativa = forms.DateField( + widget=forms.HiddenInput()) + + 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, 'data_relativa', form_actions(save_label='Salvar'))) + + if not kwargs['instance']: + self.fields['autor'].choices = [] + class Meta: model = Autoria - fields = ['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 + autorias = Autoria.objects.filter( + materia=self.instance.materia, autor=cd['autor']) + pk = 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) + if ((not pk and autorias.exists()) + or (pk and autorias.exclude(pk=pk).exists())): + raise ValidationError(_('Esse Autor já foi cadastrado.')) - return self.cleaned_data + 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/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/views.py b/sapl/materia/views.py index f4b6c4e35..bfa996b68 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,7 +22,7 @@ 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 @@ -62,7 +45,25 @@ 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 +94,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 @@ -958,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, @@ -981,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, @@ -1077,28 +1090,31 @@ class AutoriaCrud(MasterDetailCrud): class CreateView(MasterDetailCrud.CreateView): form_class = AutoriaForm - def get_context_data(self, **kwargs): - context = super(CreateView, self).get_context_data(**kwargs) - autores_ativos = self.autores_ativos() + @property + def layout_key(self): + return 'AutoriaCreate' - autores = [] - for a in autores_ativos: - autores.append([a.id, a.__str__()]) + 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(MasterDetailCrud.UpdateView): + form_class = AutoriaForm - 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) + @property + def layout_key(self): + return 'AutoriaUpdate' - 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 + 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 class DespachoInicialCrud(MasterDetailCrud): @@ -1495,9 +1511,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, @@ -1678,10 +1694,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/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 9bb544a15..0905edb1b 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) @@ -101,7 +102,7 @@ if SOLR_URL: HAYSTACK_CONNECTIONS = { 'default': { 'ENGINE': SEARCH_BACKEND, - SEARCH_URL[0] : SEARCH_URL[1] + SEARCH_URL[0]: SEARCH_URL[1] }, } @@ -200,12 +201,12 @@ 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' 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/static/js/app.js b/sapl/static/js/app.js index 24728945c..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=parlamentar_set__ativo,parlamentar_set__nome_parlamentar__icontains" - var q_1 = "q_1=True," + 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($("