Browse Source

Add documentação e faz modificações na api/autor

pull/739/head
LeandroRoberto 8 years ago
parent
commit
877175a51f
  1. 26
      sapl/api/forms.py
  2. 38
      sapl/api/serializers.py
  3. 101
      sapl/api/views.py
  4. 28
      sapl/base/forms.py
  5. 10
      sapl/compilacao/views.py
  6. 6
      sapl/crud/base.py
  7. 39
      sapl/settings.py
  8. 2
      sapl/templates/base/autor_form.html
  9. 55
      sapl/utils.py

26
sapl/api/forms.py

@ -3,35 +3,11 @@ from django.db.models import Q
from django_filters.filters import MethodFilter, ModelChoiceFilter from django_filters.filters import MethodFilter, ModelChoiceFilter
from rest_framework.filters import FilterSet from rest_framework.filters import FilterSet
from sapl.base.forms import autores_models_generic_relations
from sapl.base.models import Autor, TipoAutor from sapl.base.models import Autor, TipoAutor
from sapl.utils import SaplGenericRelation 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): class AutorChoiceFilterSet(FilterSet):
q = MethodFilter() q = MethodFilter()
tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all()) tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all())

38
sapl/api/serializers.py

@ -26,52 +26,16 @@ class AutorChoiceSerializer(ChoiceSerializer):
class Meta: class Meta:
model = Autor model = Autor
fields = ['id', 'tipo', 'nome', 'object_id', 'autor_related', 'user'] fields = ['id', 'nome']
# 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
class AutorObjectRelatedField(serializers.RelatedField): class AutorObjectRelatedField(serializers.RelatedField):
def to_representation(self, value): def to_representation(self, value):
return str(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): class AutorSerializer(serializers.ModelSerializer):
autor_related = AutorObjectRelatedField(read_only=True) autor_related = AutorObjectRelatedField(read_only=True)
class Meta: class Meta:

101
sapl/api/views.py

@ -1,15 +1,16 @@
from django.db.models import Q from django.db.models import Q
from django.http import Http404 from django.http import Http404
from django.utils.translation import ugettext_lazy as _
from rest_framework.filters import DjangoFilterBackend from rest_framework.filters import DjangoFilterBackend
from rest_framework.generics import ListAPIView 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.forms import AutorChoiceFilterSet
from sapl.api.serializers import ChoiceSerializer, AutorSerializer,\ from sapl.api.serializers import ChoiceSerializer, AutorSerializer,\
AutorChoiceSerializer AutorChoiceSerializer
from sapl.base.models import Autor, TipoAutor from sapl.base.models import Autor, TipoAutor
from sapl.utils import SaplGenericRelation from sapl.utils import SaplGenericRelation, sapl_logger
class AutorListView(ListAPIView): class AutorListView(ListAPIView):
@ -17,12 +18,28 @@ class AutorListView(ListAPIView):
Listagem de Autores com filtro para autores cadastrados Listagem de Autores com filtro para autores cadastrados
e/ou possíveis autores. 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 - 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 - q - busca textual no nome do Autor ou em fields_search
declarados no field SaplGenericRelation das GenericFks declarados no field SaplGenericRelation das GenericFks
@ -30,22 +47,54 @@ class AutorListView(ListAPIView):
estiver presente a variável `provaveis`. Em caso estiver presente a variável `provaveis`. Em caso
contrário, o django-filter é desativado e a busca é feita contrário, o django-filter é desativado e a busca é feita
no model do ContentType associado ao tipo. 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 # FIXME aplicar permissão correta de usuário
permission_classes = (IsAuthenticated,) permission_classes = (AllowAny,)
serializer_class = AutorSerializer serializer_class = AutorSerializer
queryset = Autor.objects.all() queryset = Autor.objects.all()
model = Autor 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): 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 if self.tr == AutorListView.TR_CHOICE_SERIALIZER:
self.filter_backends = [] if provaveis else [DjangoFilterBackend] self.filter_class = None
self.serializer_class = ChoiceSerializer\ self.filter_backends = []
if provaveis else AutorChoiceSerializer 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) return ListAPIView.get(self, request, *args, **kwargs)
@ -81,11 +130,19 @@ class AutorListView(ListAPIView):
field.related_model == Autor, field.related_model == Autor,
model_class._meta.get_fields(include_hidden=True))) 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( qs = model_class.objects.all()
fields[0].fields_search[0][0])
q_filter = Q() q_filter = Q()
if q: if q:
@ -102,8 +159,10 @@ class AutorListView(ListAPIView):
qs = qs.filter(q_filter).distinct( qs = qs.filter(q_filter).distinct(
fields[0].fields_search[0][0]) 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 += list(qs)
r.sort(key=lambda x: x[1])
if tipos.count() > 1:
r.sort(key=lambda x: x[1].upper())
return r return r

28
sapl/base/forms.py

@ -23,7 +23,8 @@ from sapl.materia.models import MateriaLegislativa
from sapl.sessao.models import SessaoPlenaria from sapl.sessao.models import SessaoPlenaria
from sapl.settings import MAX_IMAGE_UPLOAD_SIZE from sapl.settings import MAX_IMAGE_UPLOAD_SIZE
from sapl.utils import (RANGE_ANOS, ImageThumbnailFileInput, from sapl.utils import (RANGE_ANOS, ImageThumbnailFileInput,
RangeWidgetOverride, autor_label, autor_modal) RangeWidgetOverride, autor_label, autor_modal,
SaplGenericRelation)
from .models import AppConfig, CasaLegislativa 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): class TipoAutorForm(ModelForm):
content_type = forms.ModelChoiceField( content_type = forms.ModelChoiceField(

10
sapl/compilacao/views.py

@ -1,7 +1,7 @@
import logging
import sys
from collections import OrderedDict from collections import OrderedDict
from datetime import datetime, timedelta from datetime import datetime, timedelta
import logging
import sys
from braces.views import FormMessagesMixin from braces.views import FormMessagesMixin
from django import forms 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.dateparse import parse_date
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.encoding import force_text 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 string_concat
from django.utils.translation import ugettext_lazy as _
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from django.views.generic.edit import (CreateView, DeleteView, FormView, 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, from sapl.compilacao.utils import (DISPOSITIVO_SELECT_RELATED,
DISPOSITIVO_SELECT_RELATED_EDIT) DISPOSITIVO_SELECT_RELATED_EDIT)
from sapl.crud.base import Crud, CrudListView, make_pagination from sapl.crud.base import Crud, CrudListView, make_pagination
from sapl.settings import BASE_DIR
TipoNotaCrud = Crud.build(TipoNota, 'tipo_nota') TipoNotaCrud = Crud.build(TipoNota, 'tipo_nota')
TipoVideCrud = Crud.build(TipoVide, 'tipo_vide') TipoVideCrud = Crud.build(TipoVide, 'tipo_vide')
@ -55,7 +57,7 @@ VeiculoPublicacaoCrud = Crud.build(VeiculoPublicacao, 'veiculo_publicacao')
TipoDispositivoCrud = Crud.build( TipoDispositivoCrud = Crud.build(
TipoDispositivo, 'tipo_dispositivo') TipoDispositivo, 'tipo_dispositivo')
logger = logging.getLogger(__name__) logger = logging.getLogger(BASE_DIR.name)
def get_integrations_view_names(): def get_integrations_view_names():

6
sapl/crud/base.py

@ -13,17 +13,19 @@ from django.db import models
from django.http.response import Http404 from django.http.response import Http404
from django.utils.decorators import classonlymethod from django.utils.decorators import classonlymethod
from django.utils.encoding import force_text 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 string_concat
from django.utils.translation import ugettext_lazy as _
from django.views.generic import (CreateView, DeleteView, DetailView, ListView, from django.views.generic import (CreateView, DeleteView, DetailView, ListView,
UpdateView) UpdateView)
from django.views.generic.base import ContextMixin from django.views.generic.base import ContextMixin
from django.views.generic.list import MultipleObjectMixin from django.views.generic.list import MultipleObjectMixin
from sapl.crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display from sapl.crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display
from sapl.settings import BASE_DIR
from sapl.utils import normalize 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 = \ ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \
'list', 'create', 'detail', 'update', 'delete' 'list', 'create', 'detail', 'update', 'delete'

39
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/ See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
""" """
import logging
import sys
from decouple import config from decouple import config
from dj_database_url import parse as db_url from dj_database_url import parse as db_url
from unipath import Path from unipath import Path
@ -20,6 +23,7 @@ from unipath import Path
from .temp_suppress_crispy_form_warnings import \ from .temp_suppress_crispy_form_warnings import \
SUPRESS_CRISPY_FORM_WARNINGS_LOGGING SUPRESS_CRISPY_FORM_WARNINGS_LOGGING
BASE_DIR = Path(__file__).ancestor(1) BASE_DIR = Path(__file__).ancestor(1)
PROJECT_DIR = Path(__file__).ancestor(2) PROJECT_DIR = Path(__file__).ancestor(2)
@ -222,10 +226,41 @@ SASS_PROCESSOR_INCLUDE_DIRS = (BOWER_COMPONENTS_ROOT.child(
'bower_components', 'bootstrap-sass', 'assets', 'stylesheets'), '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 # FIXME update cripy-forms and remove this
# hack to suppress many annoying warnings from crispy_forms # hack to suppress many annoying warnings from crispy_forms
# see sapl.temp_suppress_crispy_form_warnings # see sapl.temp_suppress_crispy_form_warnings
LOGGING = SUPRESS_CRISPY_FORM_WARNINGS_LOGGING 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

2
sapl/templates/base/autor_form.html

@ -32,7 +32,7 @@ $(document).ready(function(){
var formData = { var formData = {
'q' : q, 'q' : q,
'tipo' : pk, 'tipo' : pk,
'provaveis' : '' 'tr' : '2' // tipo_resultado = 2 - api fornecerá possíveis Autores
} }
$.get(url, formData).done(function(data) { $.get(url, formData).done(function(data) {
active('pesquisa'); active('pesquisa');

55
sapl/utils.py

@ -4,7 +4,6 @@ from unicodedata import normalize as unicodedata_normalize
import hashlib import hashlib
import logging import logging
import magic
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Button from crispy_forms.layout import HTML, Button
from django import forms from django import forms
@ -18,13 +17,14 @@ from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from floppyforms import ClearableFileInput from floppyforms import ClearableFileInput
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row
<<<<<<< 2783b194c573b2b8182c47369b2d251ae6cf8880
import magic
from sapl.settings import BASE_DIR from sapl.settings import BASE_DIR
sapl_logger = logging.getLogger(BASE_DIR.name) sapl_logger = logging.getLogger(BASE_DIR.name)
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row
def normalize(txt): def normalize(txt):
return unicodedata_normalize( return unicodedata_normalize(
@ -96,10 +96,57 @@ def montar_helper_autor(self):
class SaplGenericRelation(GenericRelation): 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): 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 self.fields_search = fields_search
super().__init__(to, **kwargs) super().__init__(to, **kwargs)

Loading…
Cancel
Save