From 877175a51f6eb27c062900856391cfe33d854c7a Mon Sep 17 00:00:00 2001 From: LeandroRoberto Date: Fri, 14 Oct 2016 11:32:38 -0300 Subject: [PATCH] =?UTF-8?q?Add=20documenta=C3=A7=C3=A3o=20e=20faz=20modifi?= =?UTF-8?q?ca=C3=A7=C3=B5es=20na=20api/autor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/api/forms.py | 26 +------ sapl/api/serializers.py | 38 +---------- sapl/api/views.py | 101 ++++++++++++++++++++++------ sapl/base/forms.py | 28 +++++++- sapl/compilacao/views.py | 10 +-- sapl/crud/base.py | 6 +- sapl/settings.py | 39 ++++++++++- sapl/templates/base/autor_form.html | 2 +- sapl/utils.py | 55 +++++++++++++-- 9 files changed, 208 insertions(+), 97 deletions(-) diff --git a/sapl/api/forms.py b/sapl/api/forms.py index 57fb48bdc..5ac75f00f 100644 --- a/sapl/api/forms.py +++ b/sapl/api/forms.py @@ -3,35 +3,11 @@ from django.db.models import Q from django_filters.filters import MethodFilter, ModelChoiceFilter from rest_framework.filters import FilterSet +from sapl.base.forms import autores_models_generic_relations from sapl.base.models import Autor, TipoAutor from sapl.utils import SaplGenericRelation -def autores_models_generic_relations(): - models_of_generic_relations = list(map( - lambda x: x.related_model, - filter( - lambda obj: obj.is_relation and - hasattr(obj, 'field') and - isinstance(obj, GenericRel), - - Autor._meta.get_fields(include_hidden=True)) - )) - - models = list(map( - lambda x: (x, - list(filter( - lambda field: ( - isinstance( - field, SaplGenericRelation) and - field.related_model == Autor), - x._meta.get_fields(include_hidden=True)))), - models_of_generic_relations - )) - - return models - - class AutorChoiceFilterSet(FilterSet): q = MethodFilter() tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all()) diff --git a/sapl/api/serializers.py b/sapl/api/serializers.py index b07befcf1..1dc502450 100644 --- a/sapl/api/serializers.py +++ b/sapl/api/serializers.py @@ -26,52 +26,16 @@ class AutorChoiceSerializer(ChoiceSerializer): class Meta: model = Autor - fields = ['id', 'tipo', 'nome', 'object_id', 'autor_related', 'user'] - -# Models que apontaram uma GenericRelation com Autor - - -def autores_models_generic_relations(): - models_of_generic_relations = list(map( - lambda x: x.related_model, - filter( - lambda obj: obj.is_relation and - hasattr(obj, 'field') and - isinstance(obj, GenericRel), - - Autor._meta.get_fields(include_hidden=True)) - )) - - models = list(map( - lambda x: (x, - list(filter( - lambda field: ( - isinstance( - field, SaplGenericRelation) and - field.related_model == Autor), - x._meta.get_fields(include_hidden=True)))), - models_of_generic_relations - )) - - return models + fields = ['id', 'nome'] class AutorObjectRelatedField(serializers.RelatedField): def to_representation(self, value): - return str(value) - for gr in autores_models_generic_relations(): - if isinstance(value, gr[0]): - verbose_name = gr[0]._meta.verbose_name - fields_search = gr[1][0].fields_search - - raise Exception(_('Erro na seleção de autor')) - class AutorSerializer(serializers.ModelSerializer): - autor_related = AutorObjectRelatedField(read_only=True) class Meta: diff --git a/sapl/api/views.py b/sapl/api/views.py index 42cfa847e..036627182 100644 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -1,15 +1,16 @@ from django.db.models import Q from django.http import Http404 +from django.utils.translation import ugettext_lazy as _ from rest_framework.filters import DjangoFilterBackend from rest_framework.generics import ListAPIView -from rest_framework.permissions import IsAuthenticated +from rest_framework.permissions import IsAuthenticated, AllowAny from sapl.api.forms import AutorChoiceFilterSet from sapl.api.serializers import ChoiceSerializer, AutorSerializer,\ AutorChoiceSerializer from sapl.base.models import Autor, TipoAutor -from sapl.utils import SaplGenericRelation +from sapl.utils import SaplGenericRelation, sapl_logger class AutorListView(ListAPIView): @@ -17,12 +18,28 @@ class AutorListView(ListAPIView): Listagem de Autores com filtro para autores já cadastrados e/ou possíveis autores. + - tr - tipo do resultado + Prepera Lista de Autores para 3 cenários distintos + -default = 0 + + = 1 -> para (value, text) usados geralmente + em combobox, radiobox, checkbox, etc com pesquisa básica + 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 + - tipo - chave primária do Tipo de Autor a ser filtrado - - provaveis - variável sem relevância de valor, porém, sua presença - faz com que a AutorListView - mostre a lista de provaveis Autores armazenados segundo o - ContentType associado ao Tipo de Autor via relacionamento - genérico. - q - busca textual no nome do Autor ou em fields_search declarados no field SaplGenericRelation das GenericFks @@ -30,22 +47,54 @@ class AutorListView(ListAPIView): estiver presente a variável `provaveis`. Em caso contrário, o django-filter é desativado e a busca é feita no model do ContentType associado ao tipo. + + Outros campos """ + + TR_AUTOR_CHOICE_SERIALIZER = 1 + TR_CHOICE_SERIALIZER = 2 + TR_AUTOR_SERIALIZER = 3 + # FIXME aplicar permissão correta de usuário - permission_classes = (IsAuthenticated,) + permission_classes = (AllowAny,) serializer_class = AutorSerializer queryset = Autor.objects.all() model = Autor + filter_class = AutorChoiceFilterSet + filter_backends = (DjangoFilterBackend, ) + serializer_class = AutorChoiceSerializer + + @property + def tr(self): + try: + tr = int(self.request.GET.get + ('tr', AutorListView.TR_AUTOR_CHOICE_SERIALIZER)) + + 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: + return AutorListView.TR_AUTOR_CHOICE_SERIALIZER + else: + return tr + def get(self, request, *args, **kwargs): """ - desativa o django-filter se a busca for por provaveis autores + desativa o django-filter se a busca for por possiveis autores + parametro tr = TR_CHOICE_SERIALIZER """ - provaveis = 'provaveis' in request.GET - self.filter_class = None if provaveis else AutorChoiceFilterSet - self.filter_backends = [] if provaveis else [DjangoFilterBackend] - self.serializer_class = ChoiceSerializer\ - if provaveis else AutorChoiceSerializer + + 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: + self.serializer_class = AutorSerializer + self.permission_classes = (IsAuthenticated,) return ListAPIView.get(self, request, *args, **kwargs) @@ -81,11 +130,19 @@ class AutorListView(ListAPIView): field.related_model == Autor, model_class._meta.get_fields(include_hidden=True))) - # retirar assert - assert len(fields) == 1 + """ + fields - é um array de SaplGenericRelation que deve possuir o + atributo fields_search. Verifique na documentação da classe + a estrutura de fields_search. + """ + + assert len(fields) >= 1, (_( + 'Não foi encontrado em %(model)s um atributo do tipo ' + 'SaplGenericRelation que use o model %(model_autor)s') % { + 'model': model_class._meta.verbose_name, + 'model_autor': Autor._meta.verbose_name}) - qs = model_class.objects.all().order_by( - fields[0].fields_search[0][0]) + qs = model_class.objects.all() q_filter = Q() if q: @@ -102,8 +159,10 @@ class AutorListView(ListAPIView): qs = qs.filter(q_filter).distinct( fields[0].fields_search[0][0]) - qs = qs.values_list('id', fields[0].fields_search[0][0]) - + qs = qs.order_by(fields[0].fields_search[0][0]).values_list( + 'id', fields[0].fields_search[0][0]) r += list(qs) - r.sort(key=lambda x: x[1]) + + if tipos.count() > 1: + r.sort(key=lambda x: x[1].upper()) return r diff --git a/sapl/base/forms.py b/sapl/base/forms.py index 937f5e382..cca62d89b 100644 --- a/sapl/base/forms.py +++ b/sapl/base/forms.py @@ -23,7 +23,8 @@ from sapl.materia.models import MateriaLegislativa from sapl.sessao.models import SessaoPlenaria from sapl.settings import MAX_IMAGE_UPLOAD_SIZE from sapl.utils import (RANGE_ANOS, ImageThumbnailFileInput, - RangeWidgetOverride, autor_label, autor_modal) + RangeWidgetOverride, autor_label, autor_modal, + SaplGenericRelation) from .models import AppConfig, CasaLegislativa @@ -44,6 +45,31 @@ STATUS_USER_CHOICE = [ ] +def autores_models_generic_relations(): + models_of_generic_relations = list(map( + lambda x: x.related_model, + filter( + lambda obj: obj.is_relation and + hasattr(obj, 'field') and + isinstance(obj, GenericRel), + + Autor._meta.get_fields(include_hidden=True)) + )) + + models = list(map( + lambda x: (x, + list(filter( + lambda field: ( + isinstance( + field, SaplGenericRelation) and + field.related_model == Autor), + x._meta.get_fields(include_hidden=True)))), + models_of_generic_relations + )) + + return models + + class TipoAutorForm(ModelForm): content_type = forms.ModelChoiceField( diff --git a/sapl/compilacao/views.py b/sapl/compilacao/views.py index c125b3688..ccafcb6c2 100644 --- a/sapl/compilacao/views.py +++ b/sapl/compilacao/views.py @@ -1,7 +1,7 @@ -import logging -import sys from collections import OrderedDict from datetime import datetime, timedelta +import logging +import sys from braces.views import FormMessagesMixin from django import forms @@ -20,8 +20,8 @@ from django.shortcuts import get_object_or_404, redirect from django.utils.dateparse import parse_date from django.utils.decorators import method_decorator from django.utils.encoding import force_text -from django.utils.translation import ugettext_lazy as _ from django.utils.translation import string_concat +from django.utils.translation import ugettext_lazy as _ from django.views.generic.base import TemplateView from django.views.generic.detail import DetailView from django.views.generic.edit import (CreateView, DeleteView, FormView, @@ -47,6 +47,8 @@ from sapl.compilacao.models import (Dispositivo, Nota, from sapl.compilacao.utils import (DISPOSITIVO_SELECT_RELATED, DISPOSITIVO_SELECT_RELATED_EDIT) from sapl.crud.base import Crud, CrudListView, make_pagination +from sapl.settings import BASE_DIR + TipoNotaCrud = Crud.build(TipoNota, 'tipo_nota') TipoVideCrud = Crud.build(TipoVide, 'tipo_vide') @@ -55,7 +57,7 @@ VeiculoPublicacaoCrud = Crud.build(VeiculoPublicacao, 'veiculo_publicacao') TipoDispositivoCrud = Crud.build( TipoDispositivo, 'tipo_dispositivo') -logger = logging.getLogger(__name__) +logger = logging.getLogger(BASE_DIR.name) def get_integrations_view_names(): diff --git a/sapl/crud/base.py b/sapl/crud/base.py index f479d3ea1..dcbb74820 100644 --- a/sapl/crud/base.py +++ b/sapl/crud/base.py @@ -13,17 +13,19 @@ from django.db import models from django.http.response import Http404 from django.utils.decorators import classonlymethod from django.utils.encoding import force_text -from django.utils.translation import ugettext_lazy as _ from django.utils.translation import string_concat +from django.utils.translation import ugettext_lazy as _ from django.views.generic import (CreateView, DeleteView, DetailView, ListView, UpdateView) from django.views.generic.base import ContextMixin from django.views.generic.list import MultipleObjectMixin from sapl.crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display +from sapl.settings import BASE_DIR from sapl.utils import normalize -logger = logging.getLogger(__name__) + +logger = logging.getLogger(BASE_DIR.name) ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \ 'list', 'create', 'detail', 'update', 'delete' diff --git a/sapl/settings.py b/sapl/settings.py index 7112c70cc..2c60ef15d 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -13,6 +13,9 @@ Quick-start development settings - unsuitable for production See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ """ +import logging +import sys + from decouple import config from dj_database_url import parse as db_url from unipath import Path @@ -20,6 +23,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) @@ -222,10 +226,41 @@ SASS_PROCESSOR_INCLUDE_DIRS = (BOWER_COMPONENTS_ROOT.child( 'bower_components', 'bootstrap-sass', 'assets', 'stylesheets'), ) +# suprime texto de ajuda default do django-filter +FILTERS_HELP_TEXT_FILTER = False + + # FIXME update cripy-forms and remove this # hack to suppress many annoying warnings from crispy_forms # see sapl.temp_suppress_crispy_form_warnings LOGGING = SUPRESS_CRISPY_FORM_WARNINGS_LOGGING -# suprime texto de ajuda default do django-filter -FILTERS_HELP_TEXT_FILTER = False + +LOGGING_CONSOLE = config('LOGGING_CONSOLE', default=False, cast=bool) +if DEBUG and LOGGING_CONSOLE: + # Descomentar linha abaixo fará com que logs aparecam, inclusive SQL + # LOGGING['handlers']['console']['level'] = 'DEBUG' + LOGGING['loggers']['django']['level'] = 'DEBUG' + LOGGING.update({ + 'formatters': { + 'verbose': { + 'format': '%(levelname)s %(asctime)s %(pathname)s ' + '%(funcName)s %(message)s' + }, + 'simple': { + 'format': '%(levelname)s %(message)s' + }, + }, + }) + LOGGING['handlers']['console']['formatter'] = 'verbose' + LOGGING['loggers'][BASE_DIR.name] = { + 'handlers': ['console'], + 'level': 'DEBUG', + } + + +def excepthook(*args): + logging.getLogger(BASE_DIR.name).error( + 'Uncaught exception:', exc_info=args) + +sys.excepthook = excepthook diff --git a/sapl/templates/base/autor_form.html b/sapl/templates/base/autor_form.html index 470cc7f13..48e379037 100644 --- a/sapl/templates/base/autor_form.html +++ b/sapl/templates/base/autor_form.html @@ -32,7 +32,7 @@ $(document).ready(function(){ var formData = { 'q' : q, 'tipo' : pk, - 'provaveis' : '' + 'tr' : '2' // tipo_resultado = 2 - api fornecerá possíveis Autores } $.get(url, formData).done(function(data) { active('pesquisa'); diff --git a/sapl/utils.py b/sapl/utils.py index b4c8b24fa..bd2f53e84 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -4,7 +4,6 @@ from unicodedata import normalize as unicodedata_normalize import hashlib import logging -import magic from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML, Button from django import forms @@ -18,13 +17,14 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied, ValidationError from django.utils.translation import ugettext_lazy as _ from floppyforms import ClearableFileInput +from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row +<<<<<<< 2783b194c573b2b8182c47369b2d251ae6cf8880 +import magic from sapl.settings import BASE_DIR sapl_logger = logging.getLogger(BASE_DIR.name) -from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row - def normalize(txt): return unicodedata_normalize( @@ -96,10 +96,57 @@ def montar_helper_autor(self): class SaplGenericRelation(GenericRelation): + """ + Extenção da class GenericRelation para implmentar o atributo fields_search + + fields_search é uma tupla de tuplas de dois strings no padrão de construção + de campos porém com [Field Lookups][ref_1] + + exemplo: + [No Model Parlamentar em][ref_2] existe a implementação dessa + classe no atributo autor. Parlamentar possui três informações + relevantes para buscas realacionadas a Autor: + + - nome_completo; + - nome_parlamentar; e + - filiacao__partido__sigla + + que devem ser pesquisados, coincidentemente + pelo FieldLookup __icontains + + portanto a estrutura de fields_search seria: + fields_search=( + ('nome_completo', '__icontains'), + ('nome_parlamentar', '__icontains'), + ('filiacao__partido__sigla', '__icontains'), + ) + + + [ref_1]: https://docs.djangoproject.com/el/1.10/topics/db/queries/#field-lookups + [ref_2]: https://github.com/interlegis/sapl/blob/master/sapl/parlamentares/models.py + """ def __init__(self, to, fields_search=(), **kwargs): - assert fields_search + assert 'related_query_name' in kwargs, _( + 'SaplGenericRelation não pode ser instanciada sem ' + 'related_query_name.') + + assert fields_search, _( + 'SaplGenericRelation não pode ser instanciada sem fields_search.') + + for field in fields_search: + # descomente para ver todas os campos que são elementos de busca + #print(kwargs['related_query_name'], field) + + assert isinstance(field, (tuple, list)), _( + 'fields_search deve ser um array de tuplas ou listas.') + + assert len(field) == 2, _( + 'cada tupla de fields_search deve possuir duas strins') + + # TODO implementar assert para validar campos do Model e lookups + self.fields_search = fields_search super().__init__(to, **kwargs)