Browse Source

Refatora a API (#3606)

* revert: remove url model_list deprecated de api

* impl: isola api para futura transf em interlegis/drfautoapi

* refactor: ref sapl/api para impl drfautoapi

* elimina viewset.py

* separa customização de classes em modulos

* cria class method import_modules

* cria class method router()

* altera nome de classmethod

* elimina endpoint obsoleto e migra outro"

* migra endpoint deprecated para drfautoapi

* migra endpoint .../provaveis para drfautoapi

* migra endpoint api/autor para drfautoapi

* rebuild frontend

* inclui em drfautoapi a construção individual para models
pull/3615/head
LeandroJataí 2 years ago
committed by GitHub
parent
commit
e3ea566aa1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      drfautoapi/__init__.py
  2. 364
      drfautoapi/drfautoapi.py
  3. 27
      frontend/src/__global/js/functions.js
  4. 114
      frontend/webpack-stats.json
  5. 3
      sapl/api/apps.py
  6. 310
      sapl/api/core/__init__.py
  7. 116
      sapl/api/core/filters.py
  8. 25
      sapl/api/core/forms.py
  9. 50
      sapl/api/core/serializers.py
  10. 669
      sapl/api/deprecated.py
  11. 182
      sapl/api/forms.py
  12. 0
      sapl/api/schema.py
  13. 201
      sapl/api/serializers.py
  14. 10
      sapl/api/signals.py
  15. 51
      sapl/api/urls.py
  16. 89
      sapl/api/views.py
  17. 11
      sapl/api/views_audiencia.py
  18. 180
      sapl/api/views_base.py
  19. 12
      sapl/api/views_comissoes.py
  20. 12
      sapl/api/views_compilacao.py
  21. 129
      sapl/api/views_materia.py
  22. 14
      sapl/api/views_norma.py
  23. 11
      sapl/api/views_painel.py
  24. 119
      sapl/api/views_parlamentares.py
  25. 102
      sapl/api/views_protocoloadm.py
  26. 47
      sapl/api/views_sessao.py
  27. 418
      sapl/api/viewset.py
  28. 37
      sapl/materia/forms.py
  29. 7
      sapl/materia/views.py
  30. 6
      sapl/protocoloadm/urls.py
  31. 9
      sapl/settings.py
  32. 2
      sapl/static/sapl/frontend/js/global.babaa14f.js
  33. BIN
      sapl/static/sapl/frontend/js/global.babaa14f.js.gz
  34. 2
      sapl/static/sapl/frontend/js/global.e8c9c610.js
  35. 0
      sapl/static/sapl/frontend/js/global.e8c9c610.js.LICENSE.txt
  36. BIN
      sapl/static/sapl/frontend/js/global.e8c9c610.js.gz
  37. 10
      sapl/templates/base/autor_form.html
  38. 24
      sapl/templates/materia/autoria_form.html
  39. 20
      sapl/templates/materia/autoria_multicreate_form.html
  40. 23
      sapl/templates/materia/materialegislativa_form.html
  41. 49
      sapl/templates/materia/tipoproposicao_form.html
  42. 25
      sapl/templates/norma/autorianorma_form.html
  43. 25
      sapl/templates/protocoloadm/protocolar_materia.html

1
drfautoapi/__init__.py

@ -0,0 +1 @@
# Transformar em projeto externo instalável para uso geral

364
drfautoapi/drfautoapi.py

@ -0,0 +1,364 @@
from collections import OrderedDict
import importlib
import inspect
import logging
from django.apps.config import AppConfig
from django.apps.registry import apps
from django.conf import settings
from django.contrib.postgres.fields.jsonb import JSONField
from django.db.models.base import ModelBase
from django.db.models.fields.files import FileField
from django.template.defaultfilters import capfirst
from django.utils.translation import ugettext_lazy as _
import django_filters
from django_filters.constants import ALL_FIELDS
from django_filters.filters import CharFilter
from django_filters.filterset import FilterSet
from django_filters.rest_framework.backends import DjangoFilterBackend
from django_filters.utils import resolve_field, get_all_model_fields
from rest_framework import serializers as rest_serializers
from rest_framework.response import Response
from rest_framework.routers import DefaultRouter
from rest_framework.viewsets import ModelViewSet
logger = logging.getLogger(__name__)
class ApiFilterSetMixin(FilterSet):
o = CharFilter(method='filter_o')
class Meta:
fields = '__all__'
filter_overrides = {
FileField: {
'filter_class': django_filters.CharFilter,
'extra': lambda f: {
'lookup_expr': 'exact',
},
},
JSONField: {
'filter_class': django_filters.CharFilter,
'extra': lambda f: {
'lookup_expr': 'exact',
},
},
}
def filter_o(self, queryset, name, value):
try:
return queryset.order_by(
*map(str.strip, value.split(',')))
except:
return queryset
@classmethod
def get_fields(cls):
model = cls._meta.model
fields_model = get_all_model_fields(model)
fields_filter = cls._meta.fields
exclude = cls._meta.exclude
if exclude is not None and fields_filter is None:
fields_filter = ALL_FIELDS
fields = fields_filter if isinstance(fields_filter, dict) else {}
for f_str in fields_model:
if f_str not in fields:
f = model._meta.get_field(f_str)
if f.many_to_many:
fields[f_str] = ['exact']
continue
fields[f_str] = ['exact']
def get_keys_lookups(cl, sub_f):
r = []
for lk, lv in cl.items():
if lk == 'contained_by':
continue
sflk = f'{sub_f}{"__" if sub_f else ""}{lk}'
r.append(sflk)
if hasattr(lv, 'class_lookups'):
r += get_keys_lookups(lv.class_lookups, sflk)
if hasattr(lv, 'output_field') and hasattr(lv, 'output_field.class_lookups'):
r.append(f'{sflk}{"__" if sflk else ""}range')
r += get_keys_lookups(lv.output_field.class_lookups, sflk)
return r
fields[f_str] = list(
set(fields[f_str] + get_keys_lookups(f.class_lookups, '')))
# Remove excluded fields
exclude = exclude or []
fields = [(f, lookups)
for f, lookups in fields.items() if f not in exclude]
return OrderedDict(fields)
@classmethod
def filter_for_field(cls, f, name, lookup_expr='exact'):
# Redefine método estático para ignorar filtro para
# fields que não possuam lookup_expr informado
f, lookup_type = resolve_field(f, lookup_expr)
default = {
'field_name': name,
'label': capfirst(f.verbose_name),
'lookup_expr': lookup_expr
}
filter_class, params = cls.filter_for_lookup(
f, lookup_type)
default.update(params)
if filter_class is not None:
return filter_class(**default)
return None
class BusinessRulesNotImplementedMixin:
http_method_names = ['get', 'head', 'options', 'trace']
def create(self, request, *args, **kwargs):
raise Exception(_("POST Create não implementado"))
def update(self, request, *args, **kwargs):
raise Exception(_("PUT and PATCH não implementado"))
def delete(self, request, *args, **kwargs):
raise Exception(_("DELETE Delete não implementado"))
class ApiViewSetConstrutor():
_built_sets = {}
class ApiViewSet(ModelViewSet):
filter_backends = (DjangoFilterBackend,)
@classmethod
def get_viewset_for_model(cls, model):
return cls._built_sets[model._meta.app_config][model]
@classmethod
def update(cls, other):
cls._built_sets.update(other._built_sets)
@classmethod
def import_modules(cls, modules):
for m in modules:
importlib.import_module(m)
@classmethod
def router(cls, router_class=DefaultRouter):
router = router_class()
for app, built_sets in cls._built_sets.items():
for model, viewset in built_sets.items():
router.register(
f'{app.label}/{model._meta.model_name}', viewset)
return router
@classmethod
def build_class(cls, apps_or_models):
DRFAUTOAPI = settings.DRFAUTOAPI
serializers_classes = {}
filters_classes = {}
global_serializer_mixin = rest_serializers.ModelSerializer
global_filter_class = ApiFilterSetMixin
try:
if DRFAUTOAPI:
if 'DEFAULT_SERIALIZER_MODULE' in DRFAUTOAPI:
serializers = importlib.import_module(
DRFAUTOAPI['DEFAULT_SERIALIZER_MODULE']
)
serializers_classes = inspect.getmembers(serializers)
serializers_classes = {i[0]: i[1] for i in filter(
lambda x: x[0].endswith('Serializer'),
serializers_classes
)}
if 'DEFAULT_FILTER_MODULE' in DRFAUTOAPI:
filters = importlib.import_module(
DRFAUTOAPI['DEFAULT_FILTER_MODULE']
)
filters_classes = inspect.getmembers(filters)
filters_classes = {i[0]: i[1] for i in filter(
lambda x: x[0].endswith('FilterSet'),
filters_classes
)}
if 'GLOBAL_SERIALIZER_MIXIN' in DRFAUTOAPI:
cs = DRFAUTOAPI['GLOBAL_SERIALIZER_MIXIN'].split('.')
module = importlib.import_module(
'.'.join(cs[0:-1]))
global_serializer_mixin = getattr(module, cs[-1])
if 'GLOBAL_FILTERSET_MIXIN' in DRFAUTOAPI:
cs = DRFAUTOAPI['GLOBAL_FILTERSET_MIXIN'].split('.')
m = importlib.import_module('.'.join(cs[0:-1]))
global_filter_class = getattr(m, cs[-1])
except Exception as e:
logger.error(e)
built_sets = {}
def build(_model):
object_name = _model._meta.object_name
serializer_name = f'{object_name}Serializer'
_serializer_class = serializers_classes.get(
serializer_name, global_serializer_mixin)
filter_name = f'{object_name}FilterSet'
_filterset_class = filters_classes.get(
filter_name, global_filter_class)
def create_class():
_meta_serializer = object if not hasattr(
_serializer_class, 'Meta') else _serializer_class.Meta
class ApiSerializer(_serializer_class):
class Meta(_meta_serializer):
if not hasattr(_meta_serializer, 'ref_name'):
ref_name = f'{object_name}Serializer'
if not hasattr(_meta_serializer, 'model'):
model = _model
if hasattr(_meta_serializer, 'exclude'):
exclude = _meta_serializer.exclude
else:
if not hasattr(_meta_serializer, 'fields'):
fields = '__all__'
elif _meta_serializer.fields != '__all__':
fields = list(_meta_serializer.fields)
else:
fields = _meta_serializer.fields
_meta_filterset = object if not hasattr(
_filterset_class, 'Meta') else _filterset_class.Meta
class ApiFilterSet(_filterset_class):
class Meta(_meta_filterset, ):
if not hasattr(_meta_filterset, 'model'):
model = _model
class ModelApiViewSet(ApiViewSetConstrutor.ApiViewSet):
queryset = _model.objects.all()
filterset_class = ApiFilterSet
serializer_class = ApiSerializer
return ModelApiViewSet
viewset = create_class()
viewset.__name__ = '%sModelViewSet' % _model.__name__
return viewset
for am in apps_or_models:
if isinstance(am, ModelBase):
app = am._meta.app_config
else:
app = am
if app not in cls._built_sets:
cls._built_sets[app] = {}
if am != app:
cls._built_sets[app][am] = build(am)
continue
for model in app.get_models():
cls._built_sets[app][model] = build(model)
return cls
# Toda Classe construida acima, pode ser redefinida e aplicado quaisquer
# das possibilidades para uma classe normal criada a partir de
# rest_framework.viewsets.ModelViewSet conforme exemplo para a classe autor
# decorator que processa um endpoint detail trivial com base no model passado,
# Um endpoint detail geralmente é um conteúdo baseado numa FK com outros possíveis filtros
# e os passados pelo proprio cliente, além de o serializer e o filterset
# ser desse model passado
class wrapper_queryset_response_for_drf_action(object):
def __init__(self, model):
self.model = model
def __call__(self, cls):
def wrapper(instance_view, *args, **kwargs):
# recupera a viewset do model anotado
iv = instance_view
viewset_from_model = ApiViewSetConstrutor._built_sets[
self.model._meta.app_config][self.model]
# apossa da instancia da viewset mae do action
# em uma viewset que processa dados do model passado no decorator
iv.queryset = viewset_from_model.queryset
iv.serializer_class = viewset_from_model.serializer_class
iv.filterset_class = viewset_from_model.filterset_class
iv.queryset = instance_view.filter_queryset(
iv.get_queryset())
# chama efetivamente o metodo anotado que deve devolver um queryset
# com os filtros específicos definido pelo programador customizador
qs = cls(instance_view, *args, **kwargs)
page = iv.paginate_queryset(qs)
data = iv.get_serializer(
page if page is not None else qs, many=True).data
return iv.get_paginated_response(
data) if page is not None else Response(data)
return wrapper
# decorator para recuperar e transformar o default
class customize(object):
def __init__(self, model):
self.model = model
def __call__(self, cls):
class _ApiViewSet(
cls,
ApiViewSetConstrutor._built_sets[
self.model._meta.app_config][self.model]
):
pass
if hasattr(_ApiViewSet, 'build'):
_ApiViewSet = _ApiViewSet.build()
ApiViewSetConstrutor._built_sets[
self.model._meta.app_config][self.model] = _ApiViewSet
return _ApiViewSet

27
frontend/src/__global/js/functions.js

@ -10,11 +10,11 @@ window.refreshDatePicker = function () {
} }
window.getCookie = function (name) { window.getCookie = function (name) {
var cookieValue = null let cookieValue = null
if (document.cookie && document.cookie !== '') { if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';') const cookies = document.cookie.split(';')
for (var i = 0; i < cookies.length; i++) { for (let i = 0; i < cookies.length; i++) {
var cookie = $.trim(cookies[i]) const cookie = $.trim(cookies[i])
if (cookie.substring(0, name.length + 1) === name + '=') { if (cookie.substring(0, name.length + 1) === name + '=') {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1)) cookieValue = decodeURIComponent(cookie.substring(name.length + 1))
break break
@ -26,7 +26,7 @@ window.getCookie = function (name) {
window.autorModal = function () { window.autorModal = function () {
$(function () { $(function () {
var dialog = $('#modal_autor').dialog({ const dialog = $('#modal_autor').dialog({
autoOpen: false, autoOpen: false,
modal: true, modal: true,
width: 500, width: 500,
@ -65,12 +65,11 @@ window.autorModal = function () {
}) })
$('#pesquisar').click(function () { $('#pesquisar').click(function () {
var name_in_query = $('#q').val() const json_data = {
// var q_0 = "q_0=nome__icontains" q: $('#q').val()
// var q_1 = name_in_query // get_all: true
// query = q_1 }
$.get('/api/base/autor', json_data, function (data) {
$.get('/api/autor?q=' + name_in_query, function (data) {
$('#div-resultado') $('#div-resultado')
.children() .children()
.remove() .remove()
@ -82,15 +81,15 @@ window.autorModal = function () {
return return
} }
var select = $( const select = $(
'<select id="resultados" style="min-width: 90%; max-width:90%;" size="5"/>' '<select id="resultados" style="min-width: 90%; max-width:90%;" size="5"/>'
) )
data.results.forEach(function (item) { data.results.forEach(function (item) {
select.append( select.append(
$('<option>') $('<option>')
.attr('value', item.value) .attr('value', item.id)
.text(item.text) .text(item.nome)
) )
}) })

114
frontend/webpack-stats.json

@ -1,16 +1,16 @@
{ {
"status": "done", "status": "done",
"assets": { "assets": {
"fonts/fa-brands-400.86c7e1fa.woff2": {
"name": "fonts/fa-brands-400.86c7e1fa.woff2",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/fonts/fa-brands-400.86c7e1fa.woff2",
"publicPath": "/static/sapl/frontend/fonts/fa-brands-400.86c7e1fa.woff2"
},
"fonts/fa-brands-400.f5defc2e.ttf": { "fonts/fa-brands-400.f5defc2e.ttf": {
"name": "fonts/fa-brands-400.f5defc2e.ttf", "name": "fonts/fa-brands-400.f5defc2e.ttf",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/fonts/fa-brands-400.f5defc2e.ttf", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/fonts/fa-brands-400.f5defc2e.ttf",
"publicPath": "/static/sapl/frontend/fonts/fa-brands-400.f5defc2e.ttf" "publicPath": "/static/sapl/frontend/fonts/fa-brands-400.f5defc2e.ttf"
}, },
"fonts/fa-brands-400.86c7e1fa.woff2": {
"name": "fonts/fa-brands-400.86c7e1fa.woff2",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/fonts/fa-brands-400.86c7e1fa.woff2",
"publicPath": "/static/sapl/frontend/fonts/fa-brands-400.86c7e1fa.woff2"
},
"fonts/fa-regular-400.e0550912.woff2": { "fonts/fa-regular-400.e0550912.woff2": {
"name": "fonts/fa-regular-400.e0550912.woff2", "name": "fonts/fa-regular-400.e0550912.woff2",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/fonts/fa-regular-400.e0550912.woff2", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/fonts/fa-regular-400.e0550912.woff2",
@ -41,10 +41,10 @@
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/css/global.45591136.css", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/css/global.45591136.css",
"publicPath": "/static/sapl/frontend/css/global.45591136.css" "publicPath": "/static/sapl/frontend/css/global.45591136.css"
}, },
"js/global.babaa14f.js": { "js/global.e8c9c610.js": {
"name": "js/global.babaa14f.js", "name": "js/global.e8c9c610.js",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/global.babaa14f.js", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/global.e8c9c610.js",
"publicPath": "/static/sapl/frontend/js/global.babaa14f.js" "publicPath": "/static/sapl/frontend/js/global.e8c9c610.js"
}, },
"css/parlamentar.cd5dc5a8.css": { "css/parlamentar.cd5dc5a8.css": {
"name": "css/parlamentar.cd5dc5a8.css", "name": "css/parlamentar.cd5dc5a8.css",
@ -476,16 +476,21 @@
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/chunk-vendors.874df7f4.js.LICENSE.txt", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/chunk-vendors.874df7f4.js.LICENSE.txt",
"publicPath": "/static/sapl/frontend/js/chunk-vendors.874df7f4.js.LICENSE.txt" "publicPath": "/static/sapl/frontend/js/chunk-vendors.874df7f4.js.LICENSE.txt"
}, },
"js/global.babaa14f.js.LICENSE.txt": { "js/global.e8c9c610.js.LICENSE.txt": {
"name": "js/global.babaa14f.js.LICENSE.txt", "name": "js/global.e8c9c610.js.LICENSE.txt",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/global.babaa14f.js.LICENSE.txt", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/global.e8c9c610.js.LICENSE.txt",
"publicPath": "/static/sapl/frontend/js/global.babaa14f.js.LICENSE.txt" "publicPath": "/static/sapl/frontend/js/global.e8c9c610.js.LICENSE.txt"
}, },
"fonts/fa-v4compatibility.7e7e1dad.ttf.gz": { "fonts/fa-v4compatibility.7e7e1dad.ttf.gz": {
"name": "fonts/fa-v4compatibility.7e7e1dad.ttf.gz", "name": "fonts/fa-v4compatibility.7e7e1dad.ttf.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/fonts/fa-v4compatibility.7e7e1dad.ttf.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/fonts/fa-v4compatibility.7e7e1dad.ttf.gz",
"publicPath": "/static/sapl/frontend/fonts/fa-v4compatibility.7e7e1dad.ttf.gz" "publicPath": "/static/sapl/frontend/fonts/fa-v4compatibility.7e7e1dad.ttf.gz"
}, },
"js/global.e8c9c610.js.gz": {
"name": "js/global.e8c9c610.js.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/global.e8c9c610.js.gz",
"publicPath": "/static/sapl/frontend/js/global.e8c9c610.js.gz"
},
"js/parlamentar.25e7f0fa.js.gz": { "js/parlamentar.25e7f0fa.js.gz": {
"name": "js/parlamentar.25e7f0fa.js.gz", "name": "js/parlamentar.25e7f0fa.js.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/parlamentar.25e7f0fa.js.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/parlamentar.25e7f0fa.js.gz",
@ -501,11 +506,6 @@
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/painel.7aa779e9.js.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/painel.7aa779e9.js.gz",
"publicPath": "/static/sapl/frontend/js/painel.7aa779e9.js.gz" "publicPath": "/static/sapl/frontend/js/painel.7aa779e9.js.gz"
}, },
"js/global.babaa14f.js.gz": {
"name": "js/global.babaa14f.js.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/global.babaa14f.js.gz",
"publicPath": "/static/sapl/frontend/js/global.babaa14f.js.gz"
},
"js/compilacao.1c9473f1.js.gz": { "js/compilacao.1c9473f1.js.gz": {
"name": "js/compilacao.1c9473f1.js.gz", "name": "js/compilacao.1c9473f1.js.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/compilacao.1c9473f1.js.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/compilacao.1c9473f1.js.gz",
@ -521,26 +521,26 @@
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/img/down_arrow_select.jpg.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/img/down_arrow_select.jpg.gz",
"publicPath": "/static/sapl/frontend/img/down_arrow_select.jpg.gz" "publicPath": "/static/sapl/frontend/img/down_arrow_select.jpg.gz"
}, },
"js/skins/content/dark/content.css.gz": {
"name": "js/skins/content/dark/content.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/dark/content.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/content/dark/content.css.gz"
},
"js/skins/content/dark/content.min.css.gz": { "js/skins/content/dark/content.min.css.gz": {
"name": "js/skins/content/dark/content.min.css.gz", "name": "js/skins/content/dark/content.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/dark/content.min.css.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/dark/content.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/content/dark/content.min.css.gz" "publicPath": "/static/sapl/frontend/js/skins/content/dark/content.min.css.gz"
}, },
"js/skins/content/default/content.css.gz": { "js/skins/content/dark/content.css.gz": {
"name": "js/skins/content/default/content.css.gz", "name": "js/skins/content/dark/content.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/default/content.css.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/dark/content.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/content/default/content.css.gz" "publicPath": "/static/sapl/frontend/js/skins/content/dark/content.css.gz"
}, },
"js/skins/content/default/content.min.css.gz": { "js/skins/content/default/content.min.css.gz": {
"name": "js/skins/content/default/content.min.css.gz", "name": "js/skins/content/default/content.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/default/content.min.css.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/default/content.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/content/default/content.min.css.gz" "publicPath": "/static/sapl/frontend/js/skins/content/default/content.min.css.gz"
}, },
"js/skins/content/default/content.css.gz": {
"name": "js/skins/content/default/content.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/default/content.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/content/default/content.css.gz"
},
"js/skins/content/document/content.css.gz": { "js/skins/content/document/content.css.gz": {
"name": "js/skins/content/document/content.css.gz", "name": "js/skins/content/document/content.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/document/content.css.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/document/content.css.gz",
@ -591,16 +591,16 @@
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/content.inline.css.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/content.inline.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/oxide/content.inline.css.gz" "publicPath": "/static/sapl/frontend/js/skins/ui/oxide/content.inline.css.gz"
}, },
"js/skins/ui/oxide/content.inline.min.css.gz": {
"name": "js/skins/ui/oxide/content.inline.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/content.inline.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/oxide/content.inline.min.css.gz"
},
"js/skins/ui/oxide/content.min.css.gz": { "js/skins/ui/oxide/content.min.css.gz": {
"name": "js/skins/ui/oxide/content.min.css.gz", "name": "js/skins/ui/oxide/content.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/content.min.css.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/content.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/oxide/content.min.css.gz" "publicPath": "/static/sapl/frontend/js/skins/ui/oxide/content.min.css.gz"
}, },
"js/skins/ui/oxide/content.inline.min.css.gz": {
"name": "js/skins/ui/oxide/content.inline.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/content.inline.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/oxide/content.inline.min.css.gz"
},
"js/skins/ui/oxide/skin.shadowdom.css.gz": { "js/skins/ui/oxide/skin.shadowdom.css.gz": {
"name": "js/skins/ui/oxide/skin.shadowdom.css.gz", "name": "js/skins/ui/oxide/skin.shadowdom.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/skin.shadowdom.css.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/skin.shadowdom.css.gz",
@ -641,16 +641,16 @@
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide-dark/skin.shadowdom.css.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide-dark/skin.shadowdom.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/oxide-dark/skin.shadowdom.css.gz" "publicPath": "/static/sapl/frontend/js/skins/ui/oxide-dark/skin.shadowdom.css.gz"
}, },
"js/skins/ui/oxide-dark/skin.shadowdom.min.css.gz": {
"name": "js/skins/ui/oxide-dark/skin.shadowdom.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide-dark/skin.shadowdom.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/oxide-dark/skin.shadowdom.min.css.gz"
},
"js/skins/ui/oxide/skin.min.css.gz": { "js/skins/ui/oxide/skin.min.css.gz": {
"name": "js/skins/ui/oxide/skin.min.css.gz", "name": "js/skins/ui/oxide/skin.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/skin.min.css.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/skin.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/oxide/skin.min.css.gz" "publicPath": "/static/sapl/frontend/js/skins/ui/oxide/skin.min.css.gz"
}, },
"js/skins/ui/oxide-dark/skin.shadowdom.min.css.gz": {
"name": "js/skins/ui/oxide-dark/skin.shadowdom.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide-dark/skin.shadowdom.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/oxide-dark/skin.shadowdom.min.css.gz"
},
"js/skins/ui/tinymce-5/content.css.gz": { "js/skins/ui/tinymce-5/content.css.gz": {
"name": "js/skins/ui/tinymce-5/content.css.gz", "name": "js/skins/ui/tinymce-5/content.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/content.css.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/content.css.gz",
@ -671,6 +671,11 @@
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/content.min.css.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/content.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/content.min.css.gz" "publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/content.min.css.gz"
}, },
"js/skins/ui/oxide-dark/skin.min.css.gz": {
"name": "js/skins/ui/oxide-dark/skin.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide-dark/skin.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/oxide-dark/skin.min.css.gz"
},
"js/skins/ui/oxide-dark/skin.css.gz": { "js/skins/ui/oxide-dark/skin.css.gz": {
"name": "js/skins/ui/oxide-dark/skin.css.gz", "name": "js/skins/ui/oxide-dark/skin.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide-dark/skin.css.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide-dark/skin.css.gz",
@ -696,21 +701,21 @@
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.inline.css.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.inline.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.inline.css.gz" "publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.inline.css.gz"
}, },
"js/skins/ui/tinymce-5-dark/content.inline.min.css.gz": {
"name": "js/skins/ui/tinymce-5-dark/content.inline.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.inline.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.inline.min.css.gz"
},
"js/skins/ui/oxide-dark/skin.min.css.gz": {
"name": "js/skins/ui/oxide-dark/skin.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide-dark/skin.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/oxide-dark/skin.min.css.gz"
},
"js/skins/ui/tinymce-5-dark/content.min.css.gz": { "js/skins/ui/tinymce-5-dark/content.min.css.gz": {
"name": "js/skins/ui/tinymce-5-dark/content.min.css.gz", "name": "js/skins/ui/tinymce-5-dark/content.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.min.css.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.min.css.gz" "publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.min.css.gz"
}, },
"js/skins/ui/tinymce-5/skin.css.gz": {
"name": "js/skins/ui/tinymce-5/skin.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/skin.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/skin.css.gz"
},
"js/skins/ui/tinymce-5-dark/content.inline.min.css.gz": {
"name": "js/skins/ui/tinymce-5-dark/content.inline.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.inline.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.inline.min.css.gz"
},
"js/skins/ui/tinymce-5-dark/skin.shadowdom.css.gz": { "js/skins/ui/tinymce-5-dark/skin.shadowdom.css.gz": {
"name": "js/skins/ui/tinymce-5-dark/skin.shadowdom.css.gz", "name": "js/skins/ui/tinymce-5-dark/skin.shadowdom.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/skin.shadowdom.css.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/skin.shadowdom.css.gz",
@ -721,21 +726,16 @@
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/skin.shadowdom.min.css.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/skin.shadowdom.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5-dark/skin.shadowdom.min.css.gz" "publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5-dark/skin.shadowdom.min.css.gz"
}, },
"js/skins/ui/tinymce-5/skin.css.gz": { "js/skins/ui/tinymce-5/skin.min.css.gz": {
"name": "js/skins/ui/tinymce-5/skin.css.gz", "name": "js/skins/ui/tinymce-5/skin.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/skin.css.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/skin.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/skin.css.gz" "publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/skin.min.css.gz"
}, },
"js/chunk-vendors.874df7f4.js.LICENSE.txt.gz": { "js/chunk-vendors.874df7f4.js.LICENSE.txt.gz": {
"name": "js/chunk-vendors.874df7f4.js.LICENSE.txt.gz", "name": "js/chunk-vendors.874df7f4.js.LICENSE.txt.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/chunk-vendors.874df7f4.js.LICENSE.txt.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/chunk-vendors.874df7f4.js.LICENSE.txt.gz",
"publicPath": "/static/sapl/frontend/js/chunk-vendors.874df7f4.js.LICENSE.txt.gz" "publicPath": "/static/sapl/frontend/js/chunk-vendors.874df7f4.js.LICENSE.txt.gz"
}, },
"js/skins/ui/tinymce-5/skin.min.css.gz": {
"name": "js/skins/ui/tinymce-5/skin.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/skin.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/skin.min.css.gz"
},
"fonts/fa-regular-400.3edb9004.ttf.gz": { "fonts/fa-regular-400.3edb9004.ttf.gz": {
"name": "fonts/fa-regular-400.3edb9004.ttf.gz", "name": "fonts/fa-regular-400.3edb9004.ttf.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/fonts/fa-regular-400.3edb9004.ttf.gz", "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/fonts/fa-regular-400.3edb9004.ttf.gz",
@ -782,7 +782,7 @@
"css/chunk-vendors.9904f9d0.css", "css/chunk-vendors.9904f9d0.css",
"js/chunk-vendors.874df7f4.js", "js/chunk-vendors.874df7f4.js",
"css/global.45591136.css", "css/global.45591136.css",
"js/global.babaa14f.js" "js/global.e8c9c610.js"
], ],
"parlamentar": [ "parlamentar": [
"css/chunk-vendors.9904f9d0.css", "css/chunk-vendors.9904f9d0.css",

3
sapl/api/apps.py

@ -6,3 +6,6 @@ class AppConfig(apps.AppConfig):
name = 'sapl.api' name = 'sapl.api'
label = 'api' label = 'api'
verbose_name = _('API Rest') verbose_name = _('API Rest')
def ready(self):
from . import signals

310
sapl/api/core/__init__.py

@ -1,310 +0,0 @@
import logging
from django import apps
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils import timezone
from django.utils.decorators import classonlymethod
from django.utils.translation import ugettext_lazy as _
from django_filters.rest_framework.backends import DjangoFilterBackend
from rest_framework import serializers as rest_serializers
from rest_framework.authtoken.models import Token
from rest_framework.decorators import action, api_view, permission_classes
from rest_framework.fields import SerializerMethodField
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from sapl.api.core.filters import SaplFilterSetMixin
from sapl.api.permissions import SaplModelPermissions
from sapl.base.models import Metadata
# ATENÇÃO: MUDANÇAS NO CORE DEVEM SER REALIZADAS COM
# EXTREMA CAUTELA
class BusinessRulesNotImplementedMixin:
def create(self, request, *args, **kwargs):
raise Exception(_("POST Create não implementado"))
def update(self, request, *args, **kwargs):
raise Exception(_("PUT and PATCH não implementado"))
def delete(self, request, *args, **kwargs):
raise Exception(_("DELETE Delete não implementado"))
class SaplApiViewSetConstrutor():
class SaplApiViewSet(ModelViewSet):
filter_backends = (DjangoFilterBackend,)
_built_sets = {}
@classonlymethod
def get_class_for_model(cls, model):
return cls._built_sets[model._meta.app_config][model]
@classonlymethod
def build_class(cls):
import inspect
from sapl.api.core import serializers
# Carrega todas as classes de sapl.api.serializers que possuam
# "Serializer" como Sufixo.
serializers_classes = inspect.getmembers(serializers)
serializers_classes = {i[0]: i[1] for i in filter(
lambda x: x[0].endswith('Serializer'),
serializers_classes
)}
# Carrega todas as classes de sapl.api.forms que possuam
# "FilterSet" como Sufixo.
from sapl.api.core import forms
filters_classes = inspect.getmembers(forms)
filters_classes = {i[0]: i[1] for i in filter(
lambda x: x[0].endswith('FilterSet'),
filters_classes
)}
built_sets = {}
def build(_model):
object_name = _model._meta.object_name
# Caso Exista, pega a classe sapl.api.serializers.{model}Serializer
# ou utiliza a base do drf para gerar uma automática para o model
serializer_name = f'{object_name}Serializer'
_serializer_class = serializers_classes.get(
serializer_name, rest_serializers.ModelSerializer)
# Caso Exista, pega a classe sapl.api.core.forms.{model}FilterSet
# ou utiliza a base definida em
# sapl.api.core.filters.SaplFilterSetMixin
filter_name = f'{object_name}FilterSet'
_filterset_class = filters_classes.get(
filter_name, SaplFilterSetMixin)
def create_class():
_meta_serializer = object if not hasattr(
_serializer_class, 'Meta') else _serializer_class.Meta
# Define uma classe padrão para serializer caso não tenha sido
# criada a classe sapl.api.core.serializers.{model}Serializer
class SaplSerializer(_serializer_class):
__str__ = SerializerMethodField()
metadata = SerializerMethodField()
class Meta(_meta_serializer):
if not hasattr(_meta_serializer, 'ref_name'):
ref_name = f'{object_name}Serializer'
if not hasattr(_meta_serializer, 'model'):
model = _model
if hasattr(_meta_serializer, 'exclude'):
exclude = _meta_serializer.exclude
else:
if not hasattr(_meta_serializer, 'fields'):
fields = '__all__'
elif _meta_serializer.fields != '__all__':
fields = list(_meta_serializer.fields) + [
'__str__', 'metadata']
else:
fields = _meta_serializer.fields
def get___str__(self, obj) -> str:
return str(obj)
def get_metadata(self, obj):
try:
metadata = Metadata.objects.get(
content_type=ContentType.objects.get_for_model(
obj._meta.model),
object_id=obj.id
).metadata
except:
metadata = {}
finally:
return metadata
_meta_filterset = object if not hasattr(
_filterset_class, 'Meta') else _filterset_class.Meta
# Define uma classe padrão para filtro caso não tenha sido
# criada a classe sapl.api.forms.{model}FilterSet
class SaplFilterSet(_filterset_class):
class Meta(_meta_filterset):
if not hasattr(_meta_filterset, 'model'):
model = _model
# Define uma classe padrão ModelViewSet de DRF
class ModelSaplViewSet(SaplApiViewSetConstrutor.SaplApiViewSet):
queryset = _model.objects.all()
# Utiliza o filtro customizado pela classe
# sapl.api.core.forms.{model}FilterSet
# ou utiliza o trivial SaplFilterSet definido acima
filterset_class = SaplFilterSet
# Utiliza o serializer customizado pela classe
# sapl.api.core.serializers.{model}Serializer
# ou utiliza o trivial SaplSerializer definido acima
serializer_class = SaplSerializer
return ModelSaplViewSet
viewset = create_class()
viewset.__name__ = '%sModelSaplViewSet' % _model.__name__
return viewset
apps_sapl = [
apps.apps.get_app_config('contenttypes')
] + [
apps.apps.get_app_config(n[5:]) for n in settings.SAPL_APPS
]
for app in apps_sapl:
cls._built_sets[app] = {}
for model in app.get_models():
cls._built_sets[app][model] = build(model)
return cls
"""
1. Constroi uma rest_framework.viewsets.ModelViewSet para
todos os models de todas as apps do sapl
2. Define DjangoFilterBackend como ferramenta de filtro dos campos
3. Define Serializer como a seguir:
3.1 - Define um Serializer genérico para cada módel
3.2 - Recupera Serializer customizado em sapl.api.core.serializers
3.3 - Para todo model é opcional a existência de
sapl.api.core.serializers.{model}Serializer.
Caso não seja definido um Serializer customizado, utiliza-se o trivial
4. Define um FilterSet como a seguir:
4.1 - Define um FilterSet genérico para cada módel
4.2 - Recupera FilterSet customizado em sapl.api.core.forms
4.3 - Para todo model é opcional a existência de
sapl.api.core.forms.{model}FilterSet.
Caso não seja definido um FilterSet customizado, utiliza-se o trivial
4.4 - todos os campos que aceitam lookup 'exact'
podem ser filtrados por default
5. SaplApiViewSetConstrutor não cria padrões e/ou exige conhecimento alem dos
exigidos pela DRF.
6. As rotas são criadas seguindo nome da app e nome do model
http://localhost:9000/api/{applabel}/{model_name}/
e seguem as variações definidas em:
https://www.django-rest-framework.org/api-guide/routers/#defaultrouter
7. Todas as viewsets construídas por SaplApiViewSetConstrutor e suas rotas
(paginate list, detail, edit, create, delete)
bem como testes em ambiente de desenvolvimento podem ser conferidas em:
http://localhost:9000/api/
desde que settings.DEBUG=True
**SaplApiViewSetConstrutor._built_sets** é um dict de dicts de models conforme:
{
...
'audiencia': {
'tipoaudienciapublica': TipoAudienciaPublicaViewSet,
'audienciapublica': AudienciaPublicaViewSet,
'anexoaudienciapublica': AnexoAudienciaPublicaViewSet
...
},
...
'base': {
'casalegislativa': CasaLegislativaViewSet,
'appconfig': AppConfigViewSet,
...
}
...
}
"""
# Toda Classe construida acima, pode ser redefinida e aplicado quaisquer
# das possibilidades para uma classe normal criada a partir de
# rest_framework.viewsets.ModelViewSet conforme exemplo para a classe autor
# decorator que processa um endpoint detail trivial com base no model passado,
# Um endpoint detail geralmente é um conteúdo baseado numa FK com outros possíveis filtros
# e os passados pelo proprio cliente, além de o serializer e o filterset
# ser desse model passado
class wrapper_queryset_response_for_drf_action(object):
def __init__(self, model):
self.model = model
def __call__(self, cls):
def wrapper(instance_view, *args, **kwargs):
# recupera a viewset do model anotado
iv = instance_view
viewset_from_model = SaplApiViewSetConstrutor._built_sets[
self.model._meta.app_config][self.model]
# apossa da instancia da viewset mae do action
# em uma viewset que processa dados do model passado no decorator
iv.queryset = viewset_from_model.queryset
iv.serializer_class = viewset_from_model.serializer_class
iv.filterset_class = viewset_from_model.filterset_class
iv.queryset = instance_view.filter_queryset(
iv.get_queryset())
# chama efetivamente o metodo anotado que deve devolver um queryset
# com os filtros específicos definido pelo programador customizador
qs = cls(instance_view, *args, **kwargs)
page = iv.paginate_queryset(qs)
data = iv.get_serializer(
page if page is not None else qs, many=True).data
return iv.get_paginated_response(
data) if page is not None else Response(data)
return wrapper
# decorator para recuperar e transformar o default
class customize(object):
def __init__(self, model):
self.model = model
def __call__(self, cls):
class _SaplApiViewSet(
cls,
SaplApiViewSetConstrutor._built_sets[
self.model._meta.app_config][self.model]
):
pass
if hasattr(_SaplApiViewSet, 'build'):
_SaplApiViewSet = _SaplApiViewSet.build()
SaplApiViewSetConstrutor._built_sets[
self.model._meta.app_config][self.model] = _SaplApiViewSet
return _SaplApiViewSet

116
sapl/api/core/filters.py

@ -1,116 +0,0 @@
from collections import OrderedDict
from django.contrib.postgres.fields.jsonb import JSONField
from django.db.models.fields.files import FileField
from django.template.defaultfilters import capfirst
import django_filters
from django_filters.constants import ALL_FIELDS
from django_filters.filters import CharFilter
from django_filters.filterset import FilterSet
from django_filters.utils import resolve_field, get_all_model_fields
# ATENÇÃO: MUDANÇAS NO CORE DEVEM SER REALIZADAS COM
# EXTREMA CAUTELA E CONSCIENTE DOS IMPACTOS NA API
class SaplFilterSetMixin(FilterSet):
o = CharFilter(method='filter_o')
class Meta:
fields = '__all__'
filter_overrides = {
FileField: {
'filter_class': django_filters.CharFilter,
'extra': lambda f: {
'lookup_expr': 'exact',
},
},
JSONField: {
'filter_class': django_filters.CharFilter,
'extra': lambda f: {
'lookup_expr': 'exact',
},
},
}
def filter_o(self, queryset, name, value):
try:
return queryset.order_by(
*map(str.strip, value.split(',')))
except:
return queryset
@classmethod
def get_fields(cls):
model = cls._meta.model
fields_model = get_all_model_fields(model)
fields_filter = cls._meta.fields
exclude = cls._meta.exclude
if exclude is not None and fields_filter is None:
fields_filter = ALL_FIELDS
fields = fields_filter if isinstance(fields_filter, dict) else {}
for f_str in fields_model:
if f_str not in fields:
f = model._meta.get_field(f_str)
if f.many_to_many:
fields[f_str] = ['exact']
continue
fields[f_str] = ['exact']
def get_keys_lookups(cl, sub_f):
r = []
for lk, lv in cl.items():
if lk == 'contained_by':
continue
sflk = f'{sub_f}{"__" if sub_f else ""}{lk}'
r.append(sflk)
if hasattr(lv, 'class_lookups'):
r += get_keys_lookups(lv.class_lookups, sflk)
if hasattr(lv, 'output_field') and hasattr(lv, 'output_field.class_lookups'):
r.append(f'{sflk}{"__" if sflk else ""}range')
r += get_keys_lookups(lv.output_field.class_lookups, sflk)
return r
fields[f_str] = list(
set(fields[f_str] + get_keys_lookups(f.class_lookups, '')))
# Remove excluded fields
exclude = exclude or []
fields = [(f, lookups)
for f, lookups in fields.items() if f not in exclude]
return OrderedDict(fields)
@classmethod
def filter_for_field(cls, f, name, lookup_expr='exact'):
# Redefine método estático para ignorar filtro para
# fields que não possuam lookup_expr informado
f, lookup_type = resolve_field(f, lookup_expr)
default = {
'field_name': name,
'label': capfirst(f.verbose_name),
'lookup_expr': lookup_expr
}
filter_class, params = cls.filter_for_lookup(
f, lookup_type)
default.update(params)
if filter_class is not None:
return filter_class(**default)
return None

25
sapl/api/core/forms.py

@ -1,25 +0,0 @@
from sapl.api.core.filters import SaplFilterSetMixin
from sapl.sessao.models import SessaoPlenaria
# ATENÇÃO: MUDANÇAS NO CORE DEVEM SER REALIZADAS COM
# EXTREMA CAUTELA E CONSCIENTE DOS IMPACTOS NA API
# FILTER SET dentro do core devem ser criados se o intuíto é um filter-set
# para o list da api.
# filter_set para actions, devem ser criados fora do core.
# A CLASSE SessaoPlenariaFilterSet não é necessária
# o construtor da api construiría uma igual
# mas está aqui para demonstrar que caso queira customizar um filter_set
# que a api consiga recuperá-lo, para os endpoints básicos
# deve seguir os critérios de nomenclatura e herança
# class [Model]FilterSet(SaplFilterSetMixin):
# class Meta(SaplFilterSetMixin.Meta):
class SessaoPlenariaFilterSet(SaplFilterSetMixin):
class Meta(SaplFilterSetMixin.Meta):
model = SessaoPlenaria

50
sapl/api/core/serializers.py

@ -1,50 +0,0 @@
import logging
from django.conf import settings
from rest_framework import serializers
from rest_framework.relations import StringRelatedField
from sapl.base.models import CasaLegislativa
class IntRelatedField(StringRelatedField):
def to_representation(self, value):
return int(value)
class ChoiceSerializer(serializers.Serializer):
value = serializers.SerializerMethodField()
text = serializers.SerializerMethodField()
def get_text(self, obj):
return obj[1]
def get_value(self, obj):
return obj[0]
class ModelChoiceSerializer(ChoiceSerializer):
def get_text(self, obj):
return str(obj)
def get_value(self, obj):
return obj.id
class ModelChoiceObjectRelatedField(serializers.RelatedField):
def to_representation(self, value):
return ModelChoiceSerializer(value).data
class CasaLegislativaSerializer(serializers.ModelSerializer):
version = serializers.SerializerMethodField()
def get_version(self, obj):
return settings.SAPL_VERSION
class Meta:
model = CasaLegislativa
fields = '__all__'

669
sapl/api/deprecated.py

@ -1,676 +1,27 @@
import logging
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.db.models import Q
from django.forms.fields import CharField, MultiValueField
from django.forms.widgets import MultiWidget, TextInput
from django.http import Http404
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django_filters.filters import CharFilter, ModelChoiceFilter, DateFilter
from django_filters.rest_framework.backends import DjangoFilterBackend from django_filters.rest_framework.backends import DjangoFilterBackend
from django_filters.rest_framework.filterset import FilterSet
from rest_framework import serializers
from rest_framework.generics import ListAPIView
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
from rest_framework.permissions import (IsAuthenticated, from rest_framework.permissions import AllowAny
IsAuthenticatedOrReadOnly, AllowAny)
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from sapl.api.core.serializers import ModelChoiceSerializer, ChoiceSerializer from sapl.api.serializers import SessaoPlenariaECidadaniaSerializer
from sapl.api.serializers import AutorSerializer from sapl.sessao.models import SessaoPlenaria
from sapl.base.models import TipoAutor, Autor, CasaLegislativa
from sapl.materia.models import MateriaLegislativa
from sapl.parlamentares.models import Legislatura
from sapl.sessao.models import SessaoPlenaria, OrdemDia
from sapl.utils import SaplGenericRelation
from sapl.utils import generic_relations_for_model
class SaplGenericRelationSearchFilterSet(FilterSet):
q = CharFilter(method='filter_q')
def filter_q(self, queryset, name, value):
query = value.split(' ')
if query:
q = Q()
for qtext in query:
if not qtext:
continue
q_fs = Q(nome__icontains=qtext)
order_by = []
for gr in generic_relations_for_model(self._meta.model):
sgr = gr[1]
for item in sgr:
if item.related_model != self._meta.model:
continue
flag_order_by = True
for field in item.fields_search:
if flag_order_by:
flag_order_by = False
order_by.append('%s__%s' % (
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)})
q = q & q_fs
if q:
queryset = queryset.filter(q).order_by(*order_by)
return queryset
class SearchForFieldWidget(MultiWidget):
def decompress(self, value):
if value is None:
return [None, None]
return value
def __init__(self, attrs=None):
widgets = (TextInput, TextInput)
MultiWidget.__init__(self, widgets, attrs)
class SearchForFieldField(MultiValueField):
widget = SearchForFieldWidget
def __init__(self, *args, **kwargs):
fields = (
CharField(),
CharField())
super(SearchForFieldField, self).__init__(fields, *args, **kwargs)
def compress(self, parameters):
if parameters:
return parameters
return None
class SearchForFieldFilter(CharFilter):
field_class = SearchForFieldField
class AutorChoiceFilterSet(SaplGenericRelationSearchFilterSet):
q = CharFilter(method='filter_q')
tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all())
class Meta:
model = Autor
fields = ['q',
'tipo',
'nome', ]
def filter_q(self, queryset, name, value):
return super().filter_q(
queryset, name, value).distinct('nome').order_by('nome')
class AutorSearchForFieldFilterSet(AutorChoiceFilterSet):
q = SearchForFieldFilter(method='filter_q')
class Meta(AutorChoiceFilterSet.Meta):
pass
def filter_q(self, queryset, name, value):
value[0] = value[0].split(',')
value[1] = value[1].split(',')
params = {}
for key, v in list(zip(value[0], value[1])):
if v in ['True', 'False']:
v = '1' if v == 'True' else '0'
params[key] = v
return queryset.filter(**params).distinct('nome').order_by('nome')
class AutoresPossiveisFilterSet(FilterSet):
logger = logging.getLogger(__name__)
data_relativa = DateFilter(method='filter_data_relativa')
tipo = CharFilter(method='filter_tipo')
class Meta:
model = Autor
fields = ['data_relativa', 'tipo', ]
def filter_data_relativa(self, queryset, name, value):
return queryset
def filter_tipo(self, queryset, name, value):
try:
self.logger.debug(
"Tentando obter TipoAutor correspondente à pk {}.".format(value))
tipo = TipoAutor.objects.get(pk=value)
except:
self.logger.error("TipoAutor(pk={}) inexistente.".format(value))
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:
return qs
tipo = TipoAutor.objects.get(pk=tipo)
if not tipo.content_type:
return qs
filter_for_model = 'filter_%s' % tipo.content_type.model
if not hasattr(self, filter_for_model):
return qs
if not data_relativa:
data_relativa = timezone.now()
return getattr(self, filter_for_model)(qs, data_relativa).distinct()
def filter_parlamentar(self, queryset, data_relativa):
# não leva em conta afastamentos
legislatura_relativa = Legislatura.objects.filter(
data_inicio__lte=data_relativa,
data_fim__gte=data_relativa).first()
q = Q(
parlamentar_set__mandato__data_inicio_mandato__lte=data_relativa,
parlamentar_set__mandato__data_fim_mandato__isnull=True) | Q(
parlamentar_set__mandato__data_inicio_mandato__lte=data_relativa,
parlamentar_set__mandato__data_fim_mandato__gte=data_relativa)
if legislatura_relativa.atual():
q = q & Q(parlamentar_set__ativo=True)
legislatura_anterior = self.request.GET.get('legislatura_anterior', 'False')
if legislatura_anterior.lower() == 'true':
legislaturas = Legislatura.objects.filter(
data_fim__lte=data_relativa).order_by('-data_fim')[:2]
if len(legislaturas) == 2:
_, leg_anterior = legislaturas
q = q | Q(parlamentar_set__mandato__data_inicio_mandato__gte=leg_anterior.data_inicio)
qs = queryset.filter(q)
return qs
def filter_comissao(self, queryset, data_relativa):
return queryset.filter(
Q(comissao_set__data_extincao__isnull=True,
comissao_set__data_fim_comissao__isnull=True) |
Q(comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__isnull=True) |
Q(comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__isnull=True) |
Q(comissao_set__data_extincao__isnull=True,
comissao_set__data_fim_comissao__gte=data_relativa) |
Q(comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__gte=data_relativa),
comissao_set__data_criacao__lte=data_relativa)
def filter_frente(self, queryset, data_relativa):
return queryset.filter(
Q(frente_set__data_extincao__isnull=True) |
Q(frente_set__data_extincao__gte=data_relativa),
frente_set__data_criacao__lte=data_relativa)
def filter_bancada(self, queryset, data_relativa):
return queryset.filter(
Q(bancada_set__data_extincao__isnull=True) |
Q(bancada_set__data_extincao__gte=data_relativa),
bancada_set__data_criacao__lte=data_relativa)
def filter_bloco(self, queryset, data_relativa):
return queryset.filter(
Q(bloco_set__data_extincao__isnull=True) |
Q(bloco_set__data_extincao__gte=data_relativa),
bloco_set__data_criacao__lte=data_relativa)
def filter_orgao(self, queryset, data_relativa):
# na implementação, não havia regras a implementar para orgao
return queryset
class AutorChoiceSerializer(ModelChoiceSerializer):
def get_text(self, obj):
return obj.nome
class Meta:
model = Autor
fields = ['id', 'nome']
class MateriaLegislativaOldSerializer(serializers.ModelSerializer):
class Meta:
model = MateriaLegislativa
fields = '__all__'
class SessaoPlenariaOldSerializer(serializers.ModelSerializer):
codReuniao = serializers.SerializerMethodField('get_pk_sessao')
codReuniaoPrincipal = serializers.SerializerMethodField('get_pk_sessao')
txtTituloReuniao = serializers.SerializerMethodField('get_name')
txtSiglaOrgao = serializers.SerializerMethodField('get_sigla_orgao')
txtApelido = serializers.SerializerMethodField('get_name')
txtNomeOrgao = serializers.SerializerMethodField('get_nome_orgao')
codEstadoReuniao = serializers.SerializerMethodField(
'get_estadoSessaoPlenaria')
txtTipoReuniao = serializers.SerializerMethodField('get_tipo_sessao')
txtObjeto = serializers.SerializerMethodField('get_assunto_sessao')
txtLocal = serializers.SerializerMethodField('get_endereco_orgao')
bolReuniaoConjunta = serializers.SerializerMethodField(
'get_reuniao_conjunta')
bolHabilitarEventoInterativo = serializers.SerializerMethodField(
'get_iterativo')
idYoutube = serializers.SerializerMethodField('get_url')
codEstadoTransmissaoYoutube = serializers.SerializerMethodField(
'get_estadoTransmissaoYoutube')
datReuniaoString = serializers.SerializerMethodField('get_date')
# Constantes SessaoPlenaria (de 1-9) (apenas 3 serão usados)
SESSAO_FINALIZADA = 4
SESSAO_EM_ANDAMENTO = 3
SESSAO_CONVOCADA = 2
# Constantes EstadoTranmissaoYoutube (de 0 a 2)
TRANSMISSAO_ENCERRADA = 2
TRANSMISSAO_EM_ANDAMENTO = 1
SEM_TRANSMISSAO = 0
class Meta:
model = SessaoPlenaria
fields = (
'codReuniao',
'codReuniaoPrincipal',
'txtTituloReuniao',
'txtSiglaOrgao',
'txtApelido',
'txtNomeOrgao',
'codEstadoReuniao',
'txtTipoReuniao',
'txtObjeto',
'txtLocal',
'bolReuniaoConjunta',
'bolHabilitarEventoInterativo',
'idYoutube',
'codEstadoTransmissaoYoutube',
'datReuniaoString'
)
def __init__(self, *args, **kwargs):
super(SessaoPlenariaOldSerializer, self).__init__(args, kwargs)
def get_pk_sessao(self, obj):
return obj.pk
def get_name(self, obj):
return obj.__str__()
def get_estadoSessaoPlenaria(self, obj):
if obj.finalizada:
return self.SESSAO_FINALIZADA
elif obj.iniciada:
return self.SESSAO_EM_ANDAMENTO
else:
return self.SESSAO_CONVOCADA
def get_tipo_sessao(self, obj):
return obj.tipo.__str__()
def get_url(self, obj):
return obj.url_video if obj.url_video else None
def get_iterativo(self, obj):
return obj.interativa if obj.interativa else False
def get_date(self, obj):
return "{} {}{}".format(
obj.data_inicio.strftime("%d/%m/%Y"),
obj.hora_inicio,
":00"
)
def get_estadoTransmissaoYoutube(self, obj):
if obj.url_video:
if obj.finalizada:
return self.TRANSMISSAO_ENCERRADA
else:
return self.TRANSMISSAO_EM_ANDAMENTO
else:
return self.SEM_TRANSMISSAO
def get_assunto_sessao(self, obj):
pauta_sessao = ''
ordem_dia = OrdemDia.objects.filter(sessao_plenaria=obj.pk)
pauta_sessao = ', '.join([i.materia.__str__() for i in ordem_dia])
return str(pauta_sessao)
def get_endereco_orgao(self, obj):
return self.casa().endereco
def get_reuniao_conjunta(self, obj):
return False
def get_sigla_orgao(self, obj):
return self.casa().sigla
def get_nome_orgao(self, obj):
return self.casa().nome
def casa(self):
casa = CasaLegislativa.objects.first()
return casa
class ModelChoiceView(ListAPIView):
"""
Deprecated
TODO Migrar para customização na api automática
"""
# FIXME aplicar permissão correta de usuário
permission_classes = (IsAuthenticated,)
serializer_class = ModelChoiceSerializer
def get(self, request, *args, **kwargs):
self.model = ContentType.objects.get_for_id(
self.kwargs['content_type']).model_class()
pagination = request.GET.get('pagination', '')
if pagination == 'False':
self.pagination_class = None
return ListAPIView.get(self, request, *args, **kwargs)
def get_queryset(self):
return self.model.objects.all()
class AutorListView(ListAPIView):
"""
Deprecated
TODO Migrar para customização na api automática
Listagem de Autores com filtro para autores cadastrados
e/ou possíveis autores.
- tr - tipo do resultado
Prepera Lista de Autores para 2 cenários distintos
- default = 1
= 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.
= 3 -> Devolve instancias da classe Autor filtradas pelo
django-filter
- tipo - chave primária do Tipo de Autor a ser filtrado
- q - busca textual no nome do Autor ou em fields_search
declarados no field SaplGenericRelation das GenericFks
A busca textual acontece via django-filter com a
variável `tr` igual 1 ou 3. Em caso contrário,
o django-filter é desativado e a busca é feita
no model do ContentType associado ao tipo.
- 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.
http://localhost:8000
/api/autor?tr=1&q_0=parlamentar_set__ativo&q_1=False
/api/autor?tr=1&q_0=parlamentar_set__ativo&q_1=True
/api/autor?tr=3&q_0=parlamentar_set__ativo&q_1=False
/api/autor?tr=3&q_0=parlamentar_set__ativo&q_1=True
http://localhost:8000
/api/autor?tr=1
&q_0=parlamentar_set__nome_parlamentar__icontains,
parlamentar_set__ativo
&q_1=Carvalho,False
/api/autor?tr=1
&q_0=parlamentar_set__nome_parlamentar__icontains,
parlamentar_set__ativo
&q_1=Carvalho,True
/api/autor?tr=3
&q_0=parlamentar_set__nome_parlamentar__icontains,
parlamentar_set__ativo
&q_1=Carvalho,False
/api/autor?tr=3
&q_0=parlamentar_set__nome_parlamentar__icontains,
parlamentar_set__ativo
&q_1=Carvalho,True
não importa o campo que vc passe de qualquer dos Models
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
In [7]: models_with_gr_for_model(Autor)
Out[7]:
[sapl.parlamentares.models.Parlamentar,
sapl.parlamentares.models.Frente,
sapl.comissoes.models.Comissao,
sapl.materia.models.Orgao,
sapl.sessao.models.Bancada,
sapl.sessao.models.Bloco]
qualquer atributo destes models podem ser passados
para busca
"""
logger = logging.getLogger(__name__)
TR_AUTOR_CHOICE_SERIALIZER = 1
TR_AUTOR_SERIALIZER = 3
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = Autor.objects.all()
model = Autor
filter_class = AutorChoiceFilterSet
filter_backends = (DjangoFilterBackend,)
serializer_class = AutorChoiceSerializer
@property
def tr(self):
username = self.request.user.username
try:
tr = int(self.request.GET.get
('tr', AutorListView.TR_AUTOR_CHOICE_SERIALIZER))
if tr not in (AutorListView.TR_AUTOR_CHOICE_SERIALIZER,
AutorListView.TR_AUTOR_SERIALIZER):
return AutorListView.TR_AUTOR_CHOICE_SERIALIZER
except Exception as e:
self.logger.error('user=' + username + '. ' + str(e))
return AutorListView.TR_AUTOR_CHOICE_SERIALIZER
return tr
def get(self, request, *args, **kwargs):
if self.tr == AutorListView.TR_AUTOR_SERIALIZER:
self.serializer_class = AutorSerializer
self.permission_classes = (IsAuthenticated,)
if self.filter_class and 'q_0' in request.GET:
self.filter_class = AutorSearchForFieldFilterSet
return ListAPIView.get(self, request, *args, **kwargs)
class AutoresProvaveisListView(ListAPIView):
"""
Deprecated
TODO Migrar para customização na api automática
"""
logger = logging.getLogger(__name__)
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = Autor.objects.all()
model = Autor
filter_class = None
filter_backends = []
serializer_class = ChoiceSerializer
def get_queryset(self):
params = {'content_type__isnull': False}
username = self.request.user.username
tipo = ''
try:
tipo = int(self.request.GET.get('tipo', ''))
if tipo:
params['id'] = tipo
except Exception as e:
self.logger.error('user= ' + username + '. ' + str(e))
pass
tipos = TipoAutor.objects.filter(**params)
if not tipos.exists() and tipo:
raise Http404()
r = []
for tipo in tipos:
q = self.request.GET.get('q', '').strip()
model_class = tipo.content_type.model_class()
fields = list(filter(
lambda field: isinstance(field, SaplGenericRelation) and
field.related_model == Autor,
model_class._meta.get_fields(include_hidden=True)))
"""
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()
q_filter = Q()
if q:
for item in fields:
if item.related_model != Autor:
continue
q_fs = Q()
for field in item.fields_search:
q_fs = q_fs | Q(**{'%s%s' % (
field[0],
field[1]): q})
q_filter = q_filter & q_fs
qs = qs.filter(q_filter).distinct(
fields[0].fields_search[0][0]).order_by(
fields[0].fields_search[0][0])
else:
qs = qs.order_by(fields[0].fields_search[0][0])
qs = qs.values_list(
'id', fields[0].fields_search[0][0])
r += list(qs)
if tipos.count() > 1:
r.sort(key=lambda x: x[1].upper())
return r
class AutoresPossiveisListView(ListAPIView):
"""
Deprecated
TODO Migrar para customização na api automática
"""
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = Autor.objects.all()
model = Autor
pagination_class = None
filter_class = AutoresPossiveisFilterSet
serializer_class = AutorChoiceSerializer
class MateriaLegislativaViewSet(ListModelMixin,
RetrieveModelMixin,
GenericViewSet):
"""
Deprecated
TODO Migrar para customização na api automática
"""
permission_classes = (IsAuthenticated,)
serializer_class = MateriaLegislativaOldSerializer
queryset = MateriaLegislativa.objects.all()
filter_backends = (DjangoFilterBackend,)
filter_fields = ('numero', 'ano', 'tipo',)
class SessaoPlenariaViewSet(ListModelMixin, class SessaoPlenariaViewSet(ListModelMixin,
RetrieveModelMixin, RetrieveModelMixin,
GenericViewSet): GenericViewSet):
""" """
Deprecated Deprecated - Será eliminado na versão 3.2
TODO Migrar para customização na api automática * TODO:
* eliminar endpoint, transferido para SaplApiViewSetConstrutor
* /api/sessao-planaria -> /api/sessao/sessaoplenaria/ecidadania
* /api/sessao-planaria/{pk} -> /api/sessao/sessaoplenaria/{pk}/ecidadania
* verificar se ainda permanece necessidade desses endpoint's
""" """
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
serializer_class = SessaoPlenariaOldSerializer serializer_class = SessaoPlenariaECidadaniaSerializer
queryset = SessaoPlenaria.objects.all() queryset = SessaoPlenaria.objects.all()
filter_backends = (DjangoFilterBackend,) filter_backends = (DjangoFilterBackend,)
filter_fields = ('data_inicio', 'data_fim', 'interativa') filter_fields = ('data_inicio', 'data_fim', 'interativa')

182
sapl/api/forms.py

@ -0,0 +1,182 @@
import logging
from django.db.models import Q
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django_filters.filters import CharFilter, DateFilter, ModelChoiceFilter
from django_filters.filterset import FilterSet
from rest_framework import serializers
from drfautoapi.drfautoapi import ApiFilterSetMixin
from sapl.base.models import TipoAutor, Autor
from sapl.parlamentares.models import Legislatura
from sapl.utils import generic_relations_for_model
logger = logging.getLogger(__name__)
class SaplFilterSetMixin(ApiFilterSetMixin):
pass
class AutorFilterSet(SaplFilterSetMixin):
q = CharFilter(method='filter_q')
tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all())
def filter_q(self, queryset, name, value):
query = value.split(' ')
if query:
q = Q()
for qtext in query:
if not qtext:
continue
q_fs = Q(nome__icontains=qtext) | Q(
tipo__descricao__icontains=qtext)
order_by = []
for gr in generic_relations_for_model(self._meta.model):
sgr = gr[1]
for item in sgr:
if item.related_model != self._meta.model:
continue
flag_order_by = True
for field in item.fields_search:
if flag_order_by:
flag_order_by = False
order_by.append('%s__%s' % (
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)})
q = q & q_fs
if q:
queryset = queryset.filter(q).order_by(*order_by)
return queryset.distinct()
class AutoresPossiveisFilterSet(SaplFilterSetMixin):
data_relativa = DateFilter(method='filter_data_relativa')
tipo = CharFilter(method='filter_tipo')
class Meta:
model = Autor
fields = ['data_relativa', 'tipo', ]
def filter_data_relativa(self, queryset, name, value):
return queryset
def filter_tipo(self, queryset, name, value):
try:
logger.debug(
"Tentando obter TipoAutor correspondente à pk {}.".format(value))
tipo = TipoAutor.objects.get(pk=value)
except:
logger.error("TipoAutor(pk={}) inexistente.".format(value))
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:
return qs
tipo = TipoAutor.objects.get(pk=tipo)
if not tipo.content_type:
return qs
filter_for_model = 'filter_%s' % tipo.content_type.model
if not hasattr(self, filter_for_model):
return qs
if not data_relativa:
data_relativa = timezone.now()
return getattr(self, filter_for_model)(qs, data_relativa).distinct()
def filter_parlamentar(self, queryset, data_relativa):
# não leva em conta afastamentos
legislatura_relativa = Legislatura.objects.filter(
data_inicio__lte=data_relativa,
data_fim__gte=data_relativa).first()
q = Q(
parlamentar_set__mandato__data_inicio_mandato__lte=data_relativa,
parlamentar_set__mandato__data_fim_mandato__isnull=True) | Q(
parlamentar_set__mandato__data_inicio_mandato__lte=data_relativa,
parlamentar_set__mandato__data_fim_mandato__gte=data_relativa)
if legislatura_relativa.atual():
q = q & Q(parlamentar_set__ativo=True)
legislatura_anterior = self.request.GET.get(
'legislatura_anterior', 'False')
if legislatura_anterior.lower() == 'true':
legislaturas = Legislatura.objects.filter(
data_fim__lte=data_relativa).order_by('-data_fim')[:2]
if len(legislaturas) == 2:
_, leg_anterior = legislaturas
q = q | Q(
parlamentar_set__mandato__data_inicio_mandato__gte=leg_anterior.data_inicio)
qs = queryset.filter(q)
return qs
def filter_comissao(self, queryset, data_relativa):
return queryset.filter(
Q(comissao_set__data_extincao__isnull=True,
comissao_set__data_fim_comissao__isnull=True) |
Q(comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__isnull=True) |
Q(comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__isnull=True) |
Q(comissao_set__data_extincao__isnull=True,
comissao_set__data_fim_comissao__gte=data_relativa) |
Q(comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__gte=data_relativa),
comissao_set__data_criacao__lte=data_relativa)
def filter_frente(self, queryset, data_relativa):
return queryset.filter(
Q(frente_set__data_extincao__isnull=True) |
Q(frente_set__data_extincao__gte=data_relativa),
frente_set__data_criacao__lte=data_relativa)
def filter_bancada(self, queryset, data_relativa):
return queryset.filter(
Q(bancada_set__data_extincao__isnull=True) |
Q(bancada_set__data_extincao__gte=data_relativa),
bancada_set__data_criacao__lte=data_relativa)
def filter_bloco(self, queryset, data_relativa):
return queryset.filter(
Q(bloco_set__data_extincao__isnull=True) |
Q(bloco_set__data_extincao__gte=data_relativa),
bloco_set__data_criacao__lte=data_relativa)
def filter_orgao(self, queryset, data_relativa):
# na implementação, não havia regras a implementar para orgao
return queryset

0
sapl/api/core/schema.py → sapl/api/schema.py

201
sapl/api/serializers.py

@ -1,19 +1,68 @@
import logging import logging
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
from django.db.models import Q from django.db.models import Q
from image_cropping.utils import get_backend from image_cropping.utils import get_backend
from rest_framework import serializers from rest_framework import serializers
from rest_framework.fields import SerializerMethodField
from sapl.api.core.serializers import ModelChoiceObjectRelatedField from sapl.base.models import Autor, CasaLegislativa, Metadata
from sapl.base.models import Autor
from sapl.parlamentares.models import Parlamentar, Mandato, Legislatura from sapl.parlamentares.models import Parlamentar, Mandato, Legislatura
from sapl.sessao.models import OrdemDia, SessaoPlenaria
class AutorSerializer(serializers.ModelSerializer): class SaplSerializerMixin(serializers.ModelSerializer):
# AutorSerializer sendo utilizado pelo gerador automático da api devidos aos __str__ = SerializerMethodField()
# critérios anotados em views.py metadata = SerializerMethodField()
class Meta:
fields = '__all__'
def get___str__(self, obj) -> str:
return str(obj)
def get_metadata(self, obj) -> dict:
try:
metadata = Metadata.objects.get(
content_type=ContentType.objects.get_for_model(
obj._meta.model),
object_id=obj.id
).metadata
except:
metadata = {}
finally:
return metadata
class ChoiceSerializer(serializers.Serializer):
value = serializers.SerializerMethodField()
text = serializers.SerializerMethodField()
def get_text(self, obj):
return obj[1]
def get_value(self, obj):
return obj[0]
class ModelChoiceSerializer(ChoiceSerializer):
def get_text(self, obj):
return str(obj)
def get_value(self, obj):
return obj.id
class ModelChoiceObjectRelatedField(serializers.RelatedField):
def to_representation(self, value):
return ModelChoiceSerializer(value).data
class AutorSerializer(SaplSerializerMixin):
autor_related = ModelChoiceObjectRelatedField(read_only=True) autor_related = ModelChoiceObjectRelatedField(read_only=True)
@ -22,7 +71,18 @@ class AutorSerializer(serializers.ModelSerializer):
fields = '__all__' fields = '__all__'
class ParlamentarSerializerPublic(serializers.ModelSerializer): class CasaLegislativaSerializer(SaplSerializerMixin):
version = serializers.SerializerMethodField()
def get_version(self, obj):
return settings.SAPL_VERSION
class Meta:
model = CasaLegislativa
fields = '__all__'
class ParlamentarSerializerPublic(SaplSerializerMixin):
class Meta: class Meta:
model = Parlamentar model = Parlamentar
@ -32,7 +92,7 @@ class ParlamentarSerializerPublic(serializers.ModelSerializer):
"telefone_residencia", "titulo_eleitor", "fax_residencia"] "telefone_residencia", "titulo_eleitor", "fax_residencia"]
class ParlamentarSerializerVerbose(serializers.ModelSerializer): class ParlamentarSerializerVerbose(SaplSerializerMixin):
titular = serializers.SerializerMethodField('check_titular') titular = serializers.SerializerMethodField('check_titular')
partido = serializers.SerializerMethodField('check_partido') partido = serializers.SerializerMethodField('check_partido')
fotografia_cropped = serializers.SerializerMethodField('crop_fotografia') fotografia_cropped = serializers.SerializerMethodField('crop_fotografia')
@ -55,7 +115,8 @@ class ParlamentarSerializerVerbose(serializers.ModelSerializer):
) )
except Exception as e: except Exception as e:
self.logger.error(e) self.logger.error(e)
self.logger.error('erro processando arquivo: %s' % obj.fotografia.path) self.logger.error('erro processando arquivo: %s' %
obj.fotografia.path)
return thumbnail_url return thumbnail_url
@ -66,7 +127,8 @@ class ParlamentarSerializerVerbose(serializers.ModelSerializer):
return "" return ""
try: try:
legislatura = Legislatura.objects.get(id=self.context.get('legislatura')) legislatura = Legislatura.objects.get(
id=self.context.get('legislatura'))
except ObjectDoesNotExist: except ObjectDoesNotExist:
legislatura = Legislatura.objects.first() legislatura = Legislatura.objects.first()
mandato = Mandato.objects.filter( mandato = Mandato.objects.filter(
@ -92,7 +154,8 @@ class ParlamentarSerializerVerbose(serializers.ModelSerializer):
self.logger.error("Não há legislaturas cadastradas.") self.logger.error("Não há legislaturas cadastradas.")
return "" return ""
try: try:
legislatura = Legislatura.objects.get(id=self.context.get('legislatura')) legislatura = Legislatura.objects.get(
id=self.context.get('legislatura'))
except ObjectDoesNotExist: except ObjectDoesNotExist:
legislatura = Legislatura.objects.first() legislatura = Legislatura.objects.first()
@ -131,4 +194,120 @@ class ParlamentarSerializerVerbose(serializers.ModelSerializer):
class Meta: class Meta:
model = Parlamentar model = Parlamentar
fields = ['id', 'nome_parlamentar', 'fotografia_cropped', 'fotografia', 'ativo', 'partido', 'titular'] fields = ['id', 'nome_parlamentar', 'fotografia_cropped',
'fotografia', 'ativo', 'partido', 'titular', ]
class SessaoPlenariaECidadaniaSerializer(serializers.ModelSerializer):
codReuniao = serializers.SerializerMethodField('get_pk_sessao')
codReuniaoPrincipal = serializers.SerializerMethodField('get_pk_sessao')
txtTituloReuniao = serializers.SerializerMethodField('get_name')
txtSiglaOrgao = serializers.SerializerMethodField('get_sigla_orgao')
txtApelido = serializers.SerializerMethodField('get_name')
txtNomeOrgao = serializers.SerializerMethodField('get_nome_orgao')
codEstadoReuniao = serializers.SerializerMethodField(
'get_estadoSessaoPlenaria')
txtTipoReuniao = serializers.SerializerMethodField('get_tipo_sessao')
txtObjeto = serializers.SerializerMethodField('get_assunto_sessao')
txtLocal = serializers.SerializerMethodField('get_endereco_orgao')
bolReuniaoConjunta = serializers.SerializerMethodField(
'get_reuniao_conjunta')
bolHabilitarEventoInterativo = serializers.SerializerMethodField(
'get_iterativo')
idYoutube = serializers.SerializerMethodField('get_url')
codEstadoTransmissaoYoutube = serializers.SerializerMethodField(
'get_estadoTransmissaoYoutube')
datReuniaoString = serializers.SerializerMethodField('get_date')
# Constantes SessaoPlenaria (de 1-9) (apenas 3 serão usados)
SESSAO_FINALIZADA = 4
SESSAO_EM_ANDAMENTO = 3
SESSAO_CONVOCADA = 2
# Constantes EstadoTranmissaoYoutube (de 0 a 2)
TRANSMISSAO_ENCERRADA = 2
TRANSMISSAO_EM_ANDAMENTO = 1
SEM_TRANSMISSAO = 0
class Meta:
model = SessaoPlenaria
fields = (
'codReuniao',
'codReuniaoPrincipal',
'txtTituloReuniao',
'txtSiglaOrgao',
'txtApelido',
'txtNomeOrgao',
'codEstadoReuniao',
'txtTipoReuniao',
'txtObjeto',
'txtLocal',
'bolReuniaoConjunta',
'bolHabilitarEventoInterativo',
'idYoutube',
'codEstadoTransmissaoYoutube',
'datReuniaoString'
)
def get_pk_sessao(self, obj):
return obj.pk
def get_name(self, obj):
return obj.__str__()
def get_estadoSessaoPlenaria(self, obj):
if obj.finalizada:
return self.SESSAO_FINALIZADA
elif obj.iniciada:
return self.SESSAO_EM_ANDAMENTO
else:
return self.SESSAO_CONVOCADA
def get_tipo_sessao(self, obj):
return obj.tipo.__str__()
def get_url(self, obj):
return obj.url_video if obj.url_video else None
def get_iterativo(self, obj):
return obj.interativa if obj.interativa else False
def get_date(self, obj):
return "{} {}{}".format(
obj.data_inicio.strftime("%d/%m/%Y"),
obj.hora_inicio,
":00"
)
def get_estadoTransmissaoYoutube(self, obj):
if obj.url_video:
if obj.finalizada:
return self.TRANSMISSAO_ENCERRADA
else:
return self.TRANSMISSAO_EM_ANDAMENTO
else:
return self.SEM_TRANSMISSAO
def get_assunto_sessao(self, obj):
pauta_sessao = ''
ordem_dia = OrdemDia.objects.filter(sessao_plenaria=obj.pk)
pauta_sessao = ', '.join([i.materia.__str__() for i in ordem_dia])
return str(pauta_sessao)
def get_endereco_orgao(self, obj):
return self.casa().endereco
def get_reuniao_conjunta(self, obj):
return False
def get_sigla_orgao(self, obj):
return self.casa().sigla
def get_nome_orgao(self, obj):
return self.casa().nome
def casa(self):
casa = CasaLegislativa.objects.first()
return casa

10
sapl/api/signals.py

@ -0,0 +1,10 @@
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch.dispatcher import receiver
from rest_framework.authtoken.models import Token
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)

51
sapl/api/urls.py

@ -3,61 +3,42 @@ from django.conf.urls import include, url
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, \ from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, \
SpectacularRedocView SpectacularRedocView
from rest_framework.authtoken.views import obtain_auth_token from rest_framework.authtoken.views import obtain_auth_token
from rest_framework.routers import DefaultRouter
from sapl.api.deprecated import MateriaLegislativaViewSet, SessaoPlenariaViewSet, \ from sapl.api.deprecated import SessaoPlenariaViewSet
AutoresProvaveisListView, AutoresPossiveisListView, AutorListView, \ from sapl.api.views import AppVersionView, recria_token,\
ModelChoiceView SaplApiViewSetConstrutor
from sapl.api.views import AppVersionView, recria_token
from sapl.api.viewset import SaplApiViewSetConstrutor
from .apps import AppConfig from .apps import AppConfig
app_name = AppConfig.name app_name = AppConfig.name
router = DefaultRouter() router = SaplApiViewSetConstrutor.router()
router.register(r'materia$', MateriaLegislativaViewSet)
router.register(r'sessao-plenaria', SessaoPlenariaViewSet)
for app, built_sets in SaplApiViewSetConstrutor._built_sets.items(): # TODO: eliminar endpoint, transferido para SaplApiViewSetConstrutor
for view_prefix, viewset in built_sets.items(): # verificar se ainda permanece necessidade desses endpoint's
router.register(app.label + '/' + # /api/sessao-planaria -> /api/sessao/sessaoplenaria/ecidadania
view_prefix._meta.model_name, viewset) # /api/sessao-planaria/{pk} -> /api/sessao/sessaoplenaria/{pk}/ecidadania
router.register(r'sessao-plenaria', SessaoPlenariaViewSet,
basename='sessao_plenaria_old')
urlpatterns_router = router.urls urlpatterns_router = router.urls
urlpatterns_api_doc = [ urlpatterns_api_doc = [
# Optional UI:
url('^schema/swagger-ui/', url('^schema/swagger-ui/',
SpectacularSwaggerView.as_view(url_name='sapl.api:schema_api'), name='swagger_ui_schema_api'), SpectacularSwaggerView.as_view(url_name='sapl.api:schema_api'),
name='swagger_ui_schema_api'),
url('^schema/redoc/', url('^schema/redoc/',
SpectacularRedocView.as_view(url_name='sapl.api:schema_api'), name='redoc_schema_api'), SpectacularRedocView.as_view(url_name='sapl.api:schema_api'),
# YOUR PATTERNS name='redoc_schema_api'),
url('^schema/', SpectacularAPIView.as_view(), name='schema_api'), url('^schema/', SpectacularAPIView.as_view(), name='schema_api'),
] ]
# TODO: refatorar para customização da api automática
deprecated_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<content_type>\d+)/(?P<pk>\d*)$',
ModelChoiceView.as_view(), name='model_list'),
]
urlpatterns = [ urlpatterns = [
url(r'^api/', include(deprecated_urlpatterns_api)),
url(r'^api/', include(urlpatterns_api_doc)), url(r'^api/', include(urlpatterns_api_doc)),
url(r'^api/', include(urlpatterns_router)), url(r'^api/', include(urlpatterns_router)),
url(r'^api/version', AppVersionView.as_view()),
url(r'^api/recriar-token/(?P<pk>\d*)$', recria_token, name="recria_token"),
url(r'^api/version', AppVersionView.as_view()),
url(r'^api/auth/token$', obtain_auth_token), url(r'^api/auth/token$', obtain_auth_token),
url(r'^api/recriar-token/(?P<pk>\d*)$', recria_token, name="recria_token"),
# implementar caminho para autenticação
# https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/
# url(r'^api/auth/', include('rest_framework.urls', namespace='rest_framework')),
] ]

89
sapl/api/views.py

@ -1,20 +1,15 @@
import logging import logging
from django.conf import settings from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from rest_framework.decorators import api_view, permission_classes from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated, IsAdminUser from rest_framework.permissions import IsAuthenticated, IsAdminUser
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from drfautoapi.drfautoapi import ApiViewSetConstrutor
@receiver(post_save, sender=settings.AUTH_USER_MODEL) logger = logging.getLogger(__name__)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
@api_view(['POST']) @api_view(['POST'])
@ -38,3 +33,83 @@ class AppVersionView(APIView):
'is_authenticated': request.user.is_authenticated, 'is_authenticated': request.user.is_authenticated,
} }
return Response(content) return Response(content)
SaplApiViewSetConstrutor = ApiViewSetConstrutor
SaplApiViewSetConstrutor.import_modules([
'sapl.api.views_audiencia',
'sapl.api.views_base',
'sapl.api.views_comissoes',
'sapl.api.views_compilacao',
'sapl.api.views_materia',
'sapl.api.views_norma',
'sapl.api.views_painel',
'sapl.api.views_parlamentares',
'sapl.api.views_protocoloadm',
'sapl.api.views_sessao',
])
"""
1. ApiViewSetConstrutor constroi uma rest_framework.viewsets.ModelViewSet
para todos os models de todas as app_configs passadas no list
2. Define DjangoFilterBackend como ferramenta de filtro dos campos
3. Define Serializer como a seguir:
3.1 - Define um Serializer genérico para cada módel
3.1.1 - se existir um DEFAULT_SERIALIZER_MODULE em settings,
recupera Serializer customizados no módulo DEFAULT_SERIALIZER_MODULE
3.2 - Para todo model é opcional a existência de {model}Serializer.
Caso não seja definido um Serializer customizado, utiliza-se o genérico
3.3 - Caso exista GLOBAL_SERIALIZER_MIXIN definido,
utiliza este Serializer para construir o genérico de 3.1
4. Define um FilterSet como a seguir:
4.1 - Define um FilterSet genérico para cada módel
4.1.1 - se existir um DEFAULT_FILTER_MODULE em settings,
recupera o FilterSet customizado no módulo DEFAULT_FILTER_MODULE
4.2 - Para todo model é opcional a existência de {model}FilterSet.
Caso não seja definido um FilterSet customizado, utiliza-se o genérico
4.3 - Caso exista GLOBAL_FILTERSET_MIXIN definido,
utiliza este FilterSet para construir o genérico de 4.1
4.4 - Caso não exista GLOBAL_FILTERSET_MIXIN, será aplicado
drfautoapi.drjautoapi.ApiFilterSetMixin que inclui parametro para:
- order_by: através do parâmetro "o"
- amplia os lookups aceitos pelo FilterSet default
para os aceitos pelo django sem a necessidade de criar
fields específicos em um FilterSet customizado.
5. ApiViewSetConstrutor não cria padrões e/ou exige conhecimento alem dos
exigidos pela DRF.
6. As rotas são criadas seguindo nome da app e nome do model
http://localhost:9000/api/{applabel}/{model_name}/
e seguem as variações definidas em:
https://www.django-rest-framework.org/api-guide/routers/#defaultrouter
**ApiViewSetConstrutor._built_sets** é um dict de dicts de models conforme:
{
...
'audiencia': {
'tipoaudienciapublica': TipoAudienciaPublicaViewSet,
'audienciapublica': AudienciaPublicaViewSet,
'anexoaudienciapublica': AnexoAudienciaPublicaViewSet
...
},
...
'base': {
'casalegislativa': CasaLegislativaViewSet,
'appconfig': AppConfigViewSet,
...
}
...
}
"""

11
sapl/api/views_audiencia.py

@ -0,0 +1,11 @@
from django.apps.registry import apps
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
customize, wrapper_queryset_response_for_drf_action
AudienciaApiViewSetConstrutor = ApiViewSetConstrutor.build_class(
[
apps.get_app_config('audiencia')
]
)

180
sapl/api/views_base.py

@ -0,0 +1,180 @@
import logging
from django.apps.registry import apps
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.http.response import Http404
from rest_framework.decorators import action
from rest_framework.response import Response
from drfautoapi.drfautoapi import ApiViewSetConstrutor, customize
from sapl.api.forms import AutoresPossiveisFilterSet
from sapl.api.serializers import ChoiceSerializer
from sapl.base.models import Autor, TipoAutor
from sapl.utils import models_with_gr_for_model, SaplGenericRelation
logger = logging.getLogger(__name__)
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('contenttypes'),
apps.get_app_config('base')
]
)
@customize(ContentType)
class _ContentTypeSet:
http_method_names = ['get', 'head', 'options', 'trace']
@customize(Autor)
class _AutorViewSet:
"""
Nesta customização do que foi criado em
ApiViewSetConstrutor além do ofertado por
rest_framework.viewsets.ModelViewSet, dentre outras customizações
possíveis, foi adicionado as rotas referentes aos relacionamentos genéricos
* padrão de ModelViewSet
* /api/base/autor/ POST - create
* /api/base/autor/ GET - list
* /api/base/autor/{pk}/ GET - detail
* /api/base/autor/{pk}/ PUT - update
* /api/base/autor/{pk}/ PATCH - partial_update
* /api/base/autor/{pk}/ DELETE - destroy
* rotas desta classe local criadas pelo método build local:
* /api/base/autor/parlamentar
devolve apenas autores que são parlamentares
* /api/base/autor/comissao
devolve apenas autores que são comissões
* /api/base/autor/bloco
devolve apenas autores que são blocos parlamentares
* /api/base/autor/bancada
devolve apenas autores que são bancadas parlamentares
* /api/base/autor/frente
devolve apenas autores que são Frene parlamentares
* /api/base/autor/orgao
devolve apenas autores que são Órgãos
"""
def list_for_content_type(self, content_type):
qs = self.get_queryset()
qs = qs.filter(content_type=content_type)
page = self.paginate_queryset(qs)
if page is not None:
serializer = self.serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(page, many=True)
return Response(serializer.data)
@classmethod
def build(cls):
models_with_gr_for_autor = models_with_gr_for_model(Autor)
for _model in models_with_gr_for_autor:
@action(detail=False, name=_model._meta.model_name)
def actionclass(self, request, *args, **kwargs):
model = getattr(self, self.action)._AutorViewSet__model
content_type = ContentType.objects.get_for_model(model)
return self.list_for_content_type(content_type)
func = actionclass
func.mapping['get'] = func.kwargs['name']
func.url_name = func.kwargs['name']
func.url_path = func.kwargs['name']
func.__name__ = func.kwargs['name']
func.__model = _model
setattr(cls, _model._meta.model_name, func)
return cls
@action(detail=False)
def possiveis(self, request, *args, **kwargs):
self.filterset_class = AutoresPossiveisFilterSet
return self.list(request, *args, **kwargs)
@action(detail=False)
def provaveis(self, request, *args, **kwargs):
self.get_queryset = self.provaveis__get_queryset
self.filter_backends = []
self.filterset_class = None
self.serializer_class = ChoiceSerializer
return self.list(request, *args, **kwargs)
def provaveis__get_queryset(self):
params = {'content_type__isnull': False}
username = self.request.user.username
tipo = ''
try:
tipo = int(self.request.GET.get('tipo', ''))
if tipo:
params['id'] = tipo
except Exception as e:
logger.error('user= ' + username + '. ' + str(e))
pass
tipos = TipoAutor.objects.filter(**params)
if not tipos.exists() and tipo:
raise Http404()
r = []
for tipo in tipos:
q = self.request.GET.get('q', '').strip()
model_class = tipo.content_type.model_class()
fields = list(filter(
lambda field: isinstance(field, SaplGenericRelation) and
field.related_model == Autor,
model_class._meta.get_fields(include_hidden=True)))
"""
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()
q_filter = Q()
if q:
for item in fields:
if item.related_model != Autor:
continue
q_fs = Q()
for field in item.fields_search:
q_fs = q_fs | Q(**{'%s%s' % (
field[0],
field[1]): q})
q_filter = q_filter & q_fs
qs = qs.filter(q_filter).distinct(
fields[0].fields_search[0][0]).order_by(
fields[0].fields_search[0][0])
else:
qs = qs.order_by(fields[0].fields_search[0][0])
qs = qs.values_list(
'id', fields[0].fields_search[0][0])
r += list(qs)
if tipos.count() > 1:
r.sort(key=lambda x: x[1].upper())
return r

12
sapl/api/views_comissoes.py

@ -0,0 +1,12 @@
from django.apps.registry import apps
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
customize, wrapper_queryset_response_for_drf_action
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('comissoes')
]
)

12
sapl/api/views_compilacao.py

@ -0,0 +1,12 @@
from django.apps.registry import apps
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
customize, wrapper_queryset_response_for_drf_action
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('compilacao')
]
)

129
sapl/api/views_materia.py

@ -0,0 +1,129 @@
from django.apps.registry import apps
from django.db.models import Q
from rest_framework.decorators import action
from rest_framework.response import Response
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
customize, wrapper_queryset_response_for_drf_action
from sapl.api.permissions import SaplModelPermissions
from sapl.materia.models import TipoMateriaLegislativa, Tramitacao,\
MateriaLegislativa, Proposicao
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('materia')
]
)
@customize(Proposicao)
class _ProposicaoViewSet:
"""
list:
Retorna lista de Proposições
* Permissões:
* Usuário Dono:
* Pode listar todas suas Proposições
* Usuário Conectado ou Anônimo:
* Pode listar todas as Proposições incorporadas
retrieve:
Retorna uma proposição passada pelo 'id'
* Permissões:
* Usuário Dono:
* Pode recuperar qualquer de suas Proposições
* Usuário Conectado ou Anônimo:
* Pode recuperar qualquer das proposições incorporadas
"""
class ProposicaoPermission(SaplModelPermissions):
def has_permission(self, request, view):
if request.method == 'GET':
return True
# se a solicitação é list ou detail, libera o teste de permissão
# e deixa o get_queryset filtrar de acordo com a regra de
# visibilidade das proposições, ou seja:
# 1. proposição incorporada é proposição pública
# 2. não incorporada só o autor pode ver
else:
perm = super().has_permission(request, view)
return perm
# não é list ou detail, então passa pelas regras de permissão e,
# depois disso ainda passa pelo filtro de get_queryset
permission_classes = (ProposicaoPermission,)
def get_queryset(self):
qs = super().get_queryset()
q = Q(data_recebimento__isnull=False, object_id__isnull=False)
if not self.request.user.is_anonymous:
autor_do_usuario_logado = self.request.user.autor_set.first()
# se usuário logado é operador de algum autor
if autor_do_usuario_logado:
q = Q(autor=autor_do_usuario_logado)
# se é operador de protocolo, ve qualquer coisa enviada
if self.request.user.has_perm('protocoloadm.list_protocolo'):
q = Q(data_envio__isnull=False) | Q(
data_devolucao__isnull=False)
qs = qs.filter(q)
return qs
@customize(MateriaLegislativa)
class _MateriaLegislativaViewSet:
class Meta:
ordering = ['-ano', 'tipo', 'numero']
@action(detail=True, methods=['GET'])
def ultima_tramitacao(self, request, *args, **kwargs):
materia = self.get_object()
if not materia.tramitacao_set.exists():
return Response({})
ultima_tramitacao = materia.tramitacao_set.order_by(
'-data_tramitacao', '-id').first()
serializer_class = MateriaApiViewSetConstrutor.get_viewset_for_model(
Tramitacao).serializer_class(ultima_tramitacao)
return Response(serializer_class.data)
@action(detail=True, methods=['GET'])
def anexadas(self, request, *args, **kwargs):
self.queryset = self.get_object().anexadas.all()
return self.list(request, *args, **kwargs)
@customize(TipoMateriaLegislativa)
class _TipoMateriaLegislativaViewSet:
@action(detail=True, methods=['POST'])
def change_position(self, request, *args, **kwargs):
result = {
'status': 200,
'message': 'OK'
}
d = request.data
if 'pos_ini' in d and 'pos_fim' in d:
if d['pos_ini'] != d['pos_fim']:
pk = kwargs['pk']
TipoMateriaLegislativa.objects.reposicione(pk, d['pos_fim'])
return Response(result)

14
sapl/api/views_norma.py

@ -0,0 +1,14 @@
from django.apps.registry import apps
from rest_framework.decorators import action
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
customize, wrapper_queryset_response_for_drf_action
from sapl.norma.models import NormaJuridica
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('norma')
]
)

11
sapl/api/views_painel.py

@ -0,0 +1,11 @@
from django.apps.registry import apps
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
customize, wrapper_queryset_response_for_drf_action
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('painel')
]
)

119
sapl/api/views_parlamentares.py

@ -0,0 +1,119 @@
from django.apps.registry import apps
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from rest_framework.decorators import action
from rest_framework.response import Response
from drfautoapi.drfautoapi import customize, ApiViewSetConstrutor, \
wrapper_queryset_response_for_drf_action
from sapl.api.permissions import SaplModelPermissions
from sapl.api.serializers import ParlamentarSerializerVerbose, \
ParlamentarSerializerPublic
from sapl.materia.models import Proposicao
from sapl.parlamentares.models import Mandato, Legislatura
from sapl.parlamentares.models import Parlamentar
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('parlamentares')
]
)
@customize(Parlamentar)
class _ParlamentarViewSet:
class ParlamentarPermission(SaplModelPermissions):
def has_permission(self, request, view):
if request.method == 'GET':
return True
else:
perm = super().has_permission(request, view)
return perm
permission_classes = (ParlamentarPermission,)
def get_serializer(self, *args, **kwargs):
if not self.request.user.has_perm('parlamentares.add_parlamentar'):
self.serializer_class = ParlamentarSerializerPublic
return super().get_serializer(*args, **kwargs)
@action(detail=True)
def proposicoes(self, request, *args, **kwargs):
"""
Lista de proposições públicas de parlamentar específico
:param int id: - Identificador do parlamentar que se quer recuperar as proposições
:return: uma lista de proposições
"""
# /api/parlamentares/parlamentar/{id}/proposicoes/
# recupera proposições enviadas e incorporadas do parlamentar
# deve coincidir com
# /parlamentar/{pk}/proposicao
return self.get_proposicoes(**kwargs)
@wrapper_queryset_response_for_drf_action(model=Proposicao)
def get_proposicoes(self, **kwargs):
return self.get_queryset().filter(
data_envio__isnull=False,
data_recebimento__isnull=False,
cancelado=False,
autor__object_id=kwargs['pk'],
autor__content_type=ContentType.objects.get_for_model(Parlamentar)
)
@action(detail=False, methods=['GET'])
def search_parlamentares(self, request, *args, **kwargs):
nome = request.query_params.get('nome_parlamentar', '')
parlamentares = Parlamentar.objects.filter(
nome_parlamentar__icontains=nome)
serializer_class = ParlamentarSerializerVerbose(
parlamentares, many=True, context={'request': request})
return Response(serializer_class.data)
@customize(Legislatura)
class _LegislaturaViewSet:
@action(detail=True)
def parlamentares(self, request, *args, **kwargs):
def get_serializer_context():
return {
'request': self.request, 'legislatura': kwargs['pk']
}
def get_serializer_class():
return ParlamentarSerializerVerbose
self.get_serializer_context = get_serializer_context
self.get_serializer_class = get_serializer_class
return self.get_parlamentares()
@wrapper_queryset_response_for_drf_action(model=Parlamentar)
def get_parlamentares(self):
try:
legislatura = Legislatura.objects.get(pk=self.kwargs['pk'])
except ObjectDoesNotExist:
return Response("")
filter_params = {
'legislatura': legislatura,
'data_inicio_mandato__gte': legislatura.data_inicio,
'data_fim_mandato__lte': legislatura.data_fim,
}
mandatos = Mandato.objects.filter(
**filter_params).order_by('-data_inicio_mandato')
parlamentares = self.get_queryset().filter(
mandato__in=mandatos).distinct()
return parlamentares

102
sapl/api/views_protocoloadm.py

@ -0,0 +1,102 @@
from django.apps.registry import apps
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
customize, wrapper_queryset_response_for_drf_action
from sapl.api.permissions import SaplModelPermissions
from sapl.base.models import AppConfig, DOC_ADM_OSTENSIVO
from sapl.protocoloadm.models import DocumentoAdministrativo, \
DocumentoAcessorioAdministrativo, TramitacaoAdministrativo, Anexado
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('protocoloadm')
]
)
@customize(DocumentoAdministrativo)
class _DocumentoAdministrativoViewSet:
class DocumentoAdministrativoPermission(SaplModelPermissions):
def has_permission(self, request, view):
if request.method == 'GET':
comportamento = AppConfig.attr('documentos_administrativos')
if comportamento == DOC_ADM_OSTENSIVO:
return True
"""
Diante da lógica implementada na manutenção de documentos
administrativos:
- Se o comportamento é doc adm ostensivo, deve passar pelo
teste de permissões sem avaliá-las
- se o comportamento é doc adm restritivo, deve passar pelo
teste de permissões avaliando-as
"""
return super().has_permission(request, view)
permission_classes = (DocumentoAdministrativoPermission,)
def get_queryset(self):
"""
mesmo tendo passado pelo teste de permissões, deve ser filtrado,
pelo campo restrito. Sendo este igual a True, disponibilizar apenas
a um usuário conectado. Apenas isso, sem critérios outros de permissão,
conforme implementado em DocumentoAdministrativoCrud
"""
qs = super().get_queryset()
if self.request.user.is_anonymous:
qs = qs.exclude(restrito=True)
return qs
@customize(DocumentoAcessorioAdministrativo)
class _DocumentoAcessorioAdministrativoViewSet:
permission_classes = (
_DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission,)
def get_queryset(self):
qs = super().get_queryset()
if self.request.user.is_anonymous:
qs = qs.exclude(documento__restrito=True)
return qs
@customize(TramitacaoAdministrativo)
class _TramitacaoAdministrativoViewSet:
# TODO: Implementar regras de manutenção das post, put, patch
# tramitacação de adm possui regras previstas de limitação de origem
# destino
http_method_names = ['get', 'head', 'options', 'trace']
permission_classes = (
_DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission,)
def get_queryset(self):
qs = super().get_queryset()
if self.request.user.is_anonymous:
qs = qs.exclude(documento__restrito=True)
return qs
@customize(Anexado)
class _AnexadoViewSet:
# TODO: Implementar regras de manutenção post, put, patch
# anexado deve possuir controle que impeça anexação cíclica
http_method_names = ['get', 'head', 'options', 'trace']
permission_classes = (
_DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission,)
def get_queryset(self):
qs = super().get_queryset()
if self.request.user.is_anonymous:
qs = qs.exclude(documento__restrito=True)
return qs

47
sapl/api/views_sessao.py

@ -0,0 +1,47 @@
from django.apps.registry import apps
from rest_framework.decorators import action
from rest_framework.response import Response
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
customize, wrapper_queryset_response_for_drf_action
from sapl.api.serializers import ChoiceSerializer,\
SessaoPlenariaECidadaniaSerializer
from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao
from sapl.utils import choice_anos_com_sessaoplenaria
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('sessao')
]
)
@customize(SessaoPlenaria)
class _SessaoPlenariaViewSet:
@action(detail=False)
def years(self, request, *args, **kwargs):
years = choice_anos_com_sessaoplenaria()
serializer = ChoiceSerializer(years, many=True)
return Response(serializer.data)
@action(detail=True)
def expedientes(self, request, *args, **kwargs):
return self.get_expedientes()
@wrapper_queryset_response_for_drf_action(model=ExpedienteSessao)
def get_expedientes(self):
return self.get_queryset().filter(sessao_plenaria_id=self.kwargs['pk'])
@action(detail=True)
def ecidadania(self, request, *args, **kwargs):
self.serializer_class = SessaoPlenariaECidadaniaSerializer
return self.retrieve(request, *args, **kwargs)
@action(detail=False, url_path='ecidadania')
def ecidadania_list(self, request, *args, **kwargs):
self.serializer_class = SessaoPlenariaECidadaniaSerializer
return self.list(request, *args, **kwargs)

418
sapl/api/viewset.py

@ -1,418 +0,0 @@
import logging
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q
from django.utils.decorators import classonlymethod
from django.utils.translation import ugettext_lazy as _
from rest_framework.decorators import action
from rest_framework.response import Response
from sapl.api.core import customize, SaplApiViewSetConstrutor, \
wrapper_queryset_response_for_drf_action, \
BusinessRulesNotImplementedMixin
from sapl.api.core.serializers import ChoiceSerializer
from sapl.api.permissions import SaplModelPermissions
from sapl.api.serializers import ParlamentarSerializerVerbose, \
ParlamentarSerializerPublic
from sapl.base.models import Autor, AppConfig, DOC_ADM_OSTENSIVO
from sapl.materia.models import Proposicao, TipoMateriaLegislativa, \
MateriaLegislativa, Tramitacao
from sapl.norma.models import NormaJuridica
from sapl.parlamentares.models import Mandato, Legislatura
from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import DocumentoAdministrativo, \
DocumentoAcessorioAdministrativo, TramitacaoAdministrativo, Anexado
from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao
from sapl.utils import models_with_gr_for_model, choice_anos_com_sessaoplenaria
SaplApiViewSetConstrutor = SaplApiViewSetConstrutor.build_class()
@customize(ContentType)
class _ContentTypeSet:
http_method_names = ['get', 'head', 'options', 'trace']
@customize(Autor)
class _AutorViewSet:
# Customização para AutorViewSet com implementação de actions específicas
"""
Nesta customização do que foi criado em
SaplApiViewSetConstrutor além do ofertado por
rest_framework.viewsets.ModelViewSet, dentre outras customizações
possíveis, foi adicionado as rotas referentes aos relacionamentos genéricos
* padrão de ModelViewSet
/api/base/autor/ POST - create
/api/base/autor/ GET - list
/api/base/autor/{pk}/ GET - detail
/api/base/autor/{pk}/ PUT - update
/api/base/autor/{pk}/ PATCH - partial_update
/api/base/autor/{pk}/ DELETE - destroy
* rotas desta classe local criadas pelo método build:
/api/base/autor/parlamentar
devolve apenas autores que são parlamentares
/api/base/autor/comissao
devolve apenas autores que são comissões
/api/base/autor/bloco
devolve apenas autores que são blocos parlamentares
/api/base/autor/bancada
devolve apenas autores que são bancadas parlamentares
/api/base/autor/frente
devolve apenas autores que são Frene parlamentares
/api/base/autor/orgao
devolve apenas autores que são Órgãos
"""
def list_for_content_type(self, content_type):
qs = self.get_queryset()
qs = qs.filter(content_type=content_type)
page = self.paginate_queryset(qs)
if page is not None:
serializer = self.serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(page, many=True)
return Response(serializer.data)
@classonlymethod
def build(cls):
models_with_gr_for_autor = models_with_gr_for_model(Autor)
for _model in models_with_gr_for_autor:
@action(detail=False, name=_model._meta.model_name)
def actionclass(self, request, *args, **kwargs):
model = getattr(self, self.action)._AutorViewSet__model
content_type = ContentType.objects.get_for_model(model)
return self.list_for_content_type(content_type)
func = actionclass
func.mapping['get'] = func.kwargs['name']
func.url_name = func.kwargs['name']
func.url_path = func.kwargs['name']
func.__name__ = func.kwargs['name']
func.__model = _model
setattr(cls, _model._meta.model_name, func)
return cls
@customize(Parlamentar)
class _ParlamentarViewSet:
class ParlamentarPermission(SaplModelPermissions):
def has_permission(self, request, view):
if request.method == 'GET':
return True
else:
perm = super().has_permission(request, view)
return perm
permission_classes = (ParlamentarPermission,)
def get_serializer(self, *args, **kwargs):
if not self.request.user.has_perm('parlamentares.add_parlamentar'):
self.serializer_class = ParlamentarSerializerPublic
return super().get_serializer(*args, **kwargs)
@action(detail=True)
def proposicoes(self, request, *args, **kwargs):
"""
Lista de proposições públicas de parlamentar específico
:param int id: - Identificador do parlamentar que se quer recuperar as proposições
:return: uma lista de proposições
"""
# /api/parlamentares/parlamentar/{id}/proposicoes/
# recupera proposições enviadas e incorporadas do parlamentar
# deve coincidir com
# /parlamentar/{pk}/proposicao
return self.get_proposicoes(**kwargs)
@wrapper_queryset_response_for_drf_action(model=Proposicao)
def get_proposicoes(self, **kwargs):
return self.get_queryset().filter(
data_envio__isnull=False,
data_recebimento__isnull=False,
cancelado=False,
autor__object_id=kwargs['pk'],
autor__content_type=ContentType.objects.get_for_model(Parlamentar)
)
@action(detail=False, methods=['GET'])
def search_parlamentares(self, request, *args, **kwargs):
nome = request.query_params.get('nome_parlamentar', '')
parlamentares = Parlamentar.objects.filter(
nome_parlamentar__icontains=nome)
serializer_class = ParlamentarSerializerVerbose(
parlamentares, many=True, context={'request': request})
return Response(serializer_class.data)
@customize(Legislatura)
class _LegislaturaViewSet:
@action(detail=True)
def parlamentares(self, request, *args, **kwargs):
def get_serializer_context():
return {
'request': self.request, 'legislatura': kwargs['pk']
}
def get_serializer_class():
return ParlamentarSerializerVerbose
self.get_serializer_context = get_serializer_context
self.get_serializer_class = get_serializer_class
return self.get_parlamentares()
@wrapper_queryset_response_for_drf_action(model=Parlamentar)
def get_parlamentares(self):
try:
legislatura = Legislatura.objects.get(pk=self.kwargs['pk'])
except ObjectDoesNotExist:
return Response("")
filter_params = {
'legislatura': legislatura,
'data_inicio_mandato__gte': legislatura.data_inicio,
'data_fim_mandato__lte': legislatura.data_fim,
}
mandatos = Mandato.objects.filter(
**filter_params).order_by('-data_inicio_mandato')
parlamentares = self.get_queryset().filter(
mandato__in=mandatos).distinct()
return parlamentares
@customize(Proposicao)
class _ProposicaoViewSet:
"""
list:
Retorna lista de Proposições
* Permissões:
* Usuário Dono:
* Pode listar todas suas Proposições
* Usuário Conectado ou Anônimo:
* Pode listar todas as Proposições incorporadas
retrieve:
Retorna uma proposição passada pelo 'id'
* Permissões:
* Usuário Dono:
* Pode recuperar qualquer de suas Proposições
* Usuário Conectado ou Anônimo:
* Pode recuperar qualquer das proposições incorporadas
"""
class ProposicaoPermission(SaplModelPermissions):
def has_permission(self, request, view):
if request.method == 'GET':
return True
# se a solicitação é list ou detail, libera o teste de permissão
# e deixa o get_queryset filtrar de acordo com a regra de
# visibilidade das proposições, ou seja:
# 1. proposição incorporada é proposição pública
# 2. não incorporada só o autor pode ver
else:
perm = super().has_permission(request, view)
return perm
# não é list ou detail, então passa pelas regras de permissão e,
# depois disso ainda passa pelo filtro de get_queryset
permission_classes = (ProposicaoPermission,)
def get_queryset(self):
qs = super().get_queryset()
q = Q(data_recebimento__isnull=False, object_id__isnull=False)
if not self.request.user.is_anonymous:
autor_do_usuario_logado = self.request.user.autor_set.first()
# se usuário logado é operador de algum autor
if autor_do_usuario_logado:
q = Q(autor=autor_do_usuario_logado)
# se é operador de protocolo, ve qualquer coisa enviada
if self.request.user.has_perm('protocoloadm.list_protocolo'):
q = Q(data_envio__isnull=False) | Q(
data_devolucao__isnull=False)
qs = qs.filter(q)
return qs
@customize(MateriaLegislativa)
class _MateriaLegislativaViewSet:
class Meta:
ordering = ['-ano', 'tipo', 'numero']
@action(detail=True, methods=['GET'])
def ultima_tramitacao(self, request, *args, **kwargs):
materia = self.get_object()
if not materia.tramitacao_set.exists():
return Response({})
ultima_tramitacao = materia.tramitacao_set.order_by(
'-data_tramitacao', '-id').first()
serializer_class = SaplApiViewSetConstrutor.get_class_for_model(
Tramitacao).serializer_class(ultima_tramitacao)
return Response(serializer_class.data)
@action(detail=True, methods=['GET'])
def anexadas(self, request, *args, **kwargs):
self.queryset = self.get_object().anexadas.all()
return self.list(request, *args, **kwargs)
@customize(TipoMateriaLegislativa)
class _TipoMateriaLegislativaViewSet:
@action(detail=True, methods=['POST'])
def change_position(self, request, *args, **kwargs):
result = {
'status': 200,
'message': 'OK'
}
d = request.data
if 'pos_ini' in d and 'pos_fim' in d:
if d['pos_ini'] != d['pos_fim']:
pk = kwargs['pk']
TipoMateriaLegislativa.objects.reposicione(pk, d['pos_fim'])
return Response(result)
@customize(DocumentoAdministrativo)
class _DocumentoAdministrativoViewSet:
class DocumentoAdministrativoPermission(SaplModelPermissions):
def has_permission(self, request, view):
if request.method == 'GET':
comportamento = AppConfig.attr('documentos_administrativos')
if comportamento == DOC_ADM_OSTENSIVO:
return True
"""
Diante da lógica implementada na manutenção de documentos
administrativos:
- Se o comportamento é doc adm ostensivo, deve passar pelo
teste de permissões sem avaliá-las
- se o comportamento é doc adm restritivo, deve passar pelo
teste de permissões avaliando-as
"""
return super().has_permission(request, view)
permission_classes = (DocumentoAdministrativoPermission,)
def get_queryset(self):
"""
mesmo tendo passado pelo teste de permissões, deve ser filtrado,
pelo campo restrito. Sendo este igual a True, disponibilizar apenas
a um usuário conectado. Apenas isso, sem critérios outros de permissão,
conforme implementado em DocumentoAdministrativoCrud
"""
qs = super().get_queryset()
if self.request.user.is_anonymous:
qs = qs.exclude(restrito=True)
return qs
@customize(DocumentoAcessorioAdministrativo)
class _DocumentoAcessorioAdministrativoViewSet:
permission_classes = (
_DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission,)
def get_queryset(self):
qs = super().get_queryset()
if self.request.user.is_anonymous:
qs = qs.exclude(documento__restrito=True)
return qs
@customize(TramitacaoAdministrativo)
class _TramitacaoAdministrativoViewSet(BusinessRulesNotImplementedMixin):
# TODO: Implementar regras de manutenção das tramitações de docs adms
permission_classes = (
_DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission,)
def get_queryset(self):
qs = super().get_queryset()
if self.request.user.is_anonymous:
qs = qs.exclude(documento__restrito=True)
return qs
@customize(Anexado)
class _AnexadoViewSet(BusinessRulesNotImplementedMixin):
permission_classes = (
_DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission,)
def get_queryset(self):
qs = super().get_queryset()
if self.request.user.is_anonymous:
qs = qs.exclude(documento__restrito=True)
return qs
@customize(SessaoPlenaria)
class _SessaoPlenariaViewSet:
@action(detail=False)
def years(self, request, *args, **kwargs):
years = choice_anos_com_sessaoplenaria()
serializer = ChoiceSerializer(years, many=True)
return Response(serializer.data)
@action(detail=True)
def expedientes(self, request, *args, **kwargs):
return self.get_expedientes()
@wrapper_queryset_response_for_drf_action(model=ExpedienteSessao)
def get_expedientes(self):
return self.get_queryset().filter(sessao_plenaria_id=self.kwargs['pk'])
@customize(NormaJuridica)
class _NormaJuridicaViewset:
@action(detail=False, methods=['GET'])
def destaques(self, request, *args, **kwargs):
self.queryset = self.get_queryset().filter(norma_de_destaque=True)
return self.list(request, *args, **kwargs)

37
sapl/materia/forms.py

@ -1490,10 +1490,20 @@ class TramitacaoEmLoteFilterSet(django_filters.FilterSet):
class TipoProposicaoForm(ModelForm): class TipoProposicaoForm(ModelForm):
content_types_choices = [
(
f'{ct.app_label}/{ct.model}',
ct
)
for k, ct in ContentType.objects.get_for_models(
*models_with_gr_for_model(TipoProposicao)
).items()
]
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
content_type = forms.ModelChoiceField( content_type = forms.ChoiceField(
queryset=ContentType.objects.all(), choices=content_types_choices,
label=TipoProposicao._meta.get_field('content_type').verbose_name, label=TipoProposicao._meta.get_field('content_type').verbose_name,
required=True, required=True,
help_text=TipoProposicao._meta.get_field('content_type').help_text) help_text=TipoProposicao._meta.get_field('content_type').help_text)
@ -1550,18 +1560,6 @@ class TipoProposicaoForm(ModelForm):
super(TipoProposicaoForm, self).__init__(*args, **kwargs) super(TipoProposicaoForm, self).__init__(*args, **kwargs)
content_types = ContentType.objects.get_for_models(
*models_with_gr_for_model(TipoProposicao))
self.fields['content_type'].choices = [
(ct.pk, ct) for k, ct in content_types.items()]
# Ordena por id
self.fields['content_type'].choices.sort(key=lambda x: x[0])
if self.instance.pk:
self.fields[
'tipo_conteudo_related'].initial = self.instance.object_id
def clean(self): def clean(self):
super(TipoProposicaoForm, self).clean() super(TipoProposicaoForm, self).clean()
@ -1570,7 +1568,16 @@ class TipoProposicaoForm(ModelForm):
cd = self.cleaned_data cd = self.cleaned_data
content_type = cd['content_type'] content_type = cd['content_type'].split('/')
content_type = ContentType.objects.filter(
app_label=content_type[0],
model=content_type[1]).first()
cd['content_type'] = content_type
if not content_type:
self.logger.error("Meta Tipo Inexistente")
raise ValidationError(
_('Meta Tipo Inexistente.'))
if 'tipo_conteudo_related' not in cd or not cd[ if 'tipo_conteudo_related' not in cd or not cd[
'tipo_conteudo_related']: 'tipo_conteudo_related']:

7
sapl/materia/views.py

@ -477,6 +477,13 @@ class TipoProposicaoCrud(CrudAux):
form_class = TipoProposicaoForm form_class = TipoProposicaoForm
layout_key = None layout_key = None
def get_initial(self):
initial = CrudAux.UpdateView.get_initial(self)
ct = self.object.content_type
initial['content_type'] = f'{ct.app_label}/{ct.model}'
initial['tipo_conteudo_related'] = self.object.object_id
return initial
def criar_materia_proposicao(proposicao): def criar_materia_proposicao(proposicao):
tipo_materia = TipoMateriaLegislativa.objects.get( tipo_materia = TipoMateriaLegislativa.objects.get(

6
sapl/protocoloadm/urls.py

@ -124,12 +124,6 @@ urlpatterns_sistema = [
include(TipoDocumentoAdministrativoCrud.get_urls())), include(TipoDocumentoAdministrativoCrud.get_urls())),
url(r'^sistema/status-tramitacao-adm/', url(r'^sistema/status-tramitacao-adm/',
include(StatusTramitacaoAdministrativoCrud.get_urls())), include(StatusTramitacaoAdministrativoCrud.get_urls())),
# FIXME: Usado para pesquisar autor- SOLUÇÃO-foi transformado em api/autor
# Melhor forma de fazer?
# Deve mudar de app?
# url(r'^protocoloadm/pesquisar-autor',
# pesquisa_autores, name='pesquisar_autor'),
] ]
urlpatterns = (urlpatterns_documento_administrativo + urlpatterns = (urlpatterns_documento_administrativo +

9
sapl/settings.py

@ -157,7 +157,7 @@ REST_FRAMEWORK = {
"rest_framework.authentication.SessionAuthentication", "rest_framework.authentication.SessionAuthentication",
), ),
'DEFAULT_SCHEMA_CLASS': 'sapl.api.core.schema.Schema', 'DEFAULT_SCHEMA_CLASS': 'sapl.api.schema.Schema',
"DEFAULT_PAGINATION_CLASS": "sapl.api.pagination.StandardPagination", "DEFAULT_PAGINATION_CLASS": "sapl.api.pagination.StandardPagination",
@ -167,6 +167,13 @@ REST_FRAMEWORK = {
), ),
} }
DRFAUTOAPI = {
'DEFAULT_SERIALIZER_MODULE': 'sapl.api.serializers',
'DEFAULT_FILTER_MODULE': 'sapl.api.forms',
'GLOBAL_SERIALIZER_MIXIN': 'sapl.api.serializers.SaplSerializerMixin',
'GLOBAL_FILTERSET_MIXIN': 'sapl.api.forms.SaplFilterSetMixin'
}
SPECTACULAR_SETTINGS = { SPECTACULAR_SETTINGS = {
'TITLE': 'Sapl API - docs', 'TITLE': 'Sapl API - docs',
'DESCRIPTION': 'Sapl API - Docs', 'DESCRIPTION': 'Sapl API - Docs',

2
sapl/static/sapl/frontend/js/global.babaa14f.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/frontend/js/global.babaa14f.js.gz

Binary file not shown.

2
sapl/static/sapl/frontend/js/global.e8c9c610.js

File diff suppressed because one or more lines are too long

0
sapl/static/sapl/frontend/js/global.babaa14f.js.LICENSE.txt → sapl/static/sapl/frontend/js/global.e8c9c610.js.LICENSE.txt

BIN
sapl/static/sapl/frontend/js/global.e8c9c610.js.gz

Binary file not shown.

10
sapl/templates/base/autor_form.html

@ -28,18 +28,18 @@ $(document).ready(function(){
var update_search = function(pk, atualizar=true) { var update_search = function(pk, atualizar=true) {
var q = $('#id_q').val(); var q = $('#id_q').val();
var url = '{% url 'sapl.api:autores_provaveis_list'%}' var url = '{% url 'sapl.api:autor-provaveis'%}'
var formData = { var formData = {
'q' : q, q: q,
'tipo' : pk tipo: pk
} }
$.get(url, formData).done(function(data) { $.get(url, formData).done(function(data) {
active('pesquisa'); active('pesquisa');
if (atualizar) { if (atualizar) {
var radios = $("#div_id_autor_related > div").html(''); var radios = $("#div_id_autor_related > div").html('');
data.results.forEach(function (val, index) { data.results.forEach(function (val, index) {
var html_radio = '<div class="radio"><label><input type="radio" name="autor_related" id="id_id_autor_related_'+index+'" value="'+val.value+'">'+val.text+'</label></div>'; var html_radio = '<div class="radio bg-light p-1"><label class="d-block"><input type="radio" name="autor_related" id="id_id_autor_related_'+index+'" value="'+val.value+'">'+val.text+'</label></div>';
radios.append(html_radio); radios.append(html_radio);
}); });
@ -54,7 +54,7 @@ $(document).ready(function(){
} }
if (data.pagination.total_entries > 10) if (data.pagination.total_entries > 10)
radios.before('<div class="alert alert-info" role="alert"><strong>{% trans "Foram encontrados" %} '+data.pagination.total_entries+' {% trans "registros"%}</strong>'+' {% trans "mas será mostrado apenas os 10 primeiros resultados. Coloque mais informações no campo pesquisa para refinar sua busca."%}</div>'); radios.before('<div class="alert alert-info" role="alert"><strong>{% trans "Foram encontrados" %} '+data.pagination.total_entries+' {% trans "registros"%}</strong>'+' {% trans "mas será mostrado apenas os 10 primeiros resultados.<br>Coloque mais informações no campo pesquisa para refinar sua busca."%}</div>');
else if (data.pagination.total_entries == 0) else if (data.pagination.total_entries == 0)
radios.before('<div class="alert alert-info" role="alert"><strong>{% trans "Não foram encontrados registros com os termos de pesquisa informados." %}</div>'); radios.before('<div class="alert alert-info" role="alert"><strong>{% trans "Não foram encontrados registros com os termos de pesquisa informados." %}</div>');
} }

24
sapl/templates/materia/autoria_form.html

@ -5,15 +5,6 @@
{% block extra_js %} {% block extra_js %}
<script language="Javascript"> <script language="Javascript">
function compare(a, b) {
if (a.text < b.text)
return -1;
if (a.text > b.text)
return 1;
return 0;
}
$(document).ready(function() { $(document).ready(function() {
$("#id_tipo_autor").change(function() { $("#id_tipo_autor").change(function() {
var tipo_selecionado = $("#id_tipo_autor").val(); var tipo_selecionado = $("#id_tipo_autor").val();
@ -22,19 +13,20 @@
if (tipo_selecionado !== undefined && tipo_selecionado !== null) { if (tipo_selecionado !== undefined && tipo_selecionado !== null) {
var json_data = { var json_data = {
tipo : tipo_selecionado, tipo : tipo_selecionado,
data_relativa : $("#id_data_relativa").val() data_relativa : $("#id_data_relativa").val(),
o: 'nome',
get_all: true
} }
$.getJSON("/api/autor/possiveis", json_data, function(data){ $.getJSON("{% url 'sapl.api:autor-possiveis' %}", json_data, function(data){
if (data) { if (data) {
var results = data.sort(compare); if (data.length > 1) {
if (results.length > 1) {
$("#id_autor").append("<option>-----</option>"); $("#id_autor").append("<option>-----</option>");
} }
$.each(results, function(idx, obj) { $.each(data, function(idx, obj) {
$("#id_autor") $("#id_autor")
.append($("<option></option>") .append($("<option></option>")
.attr("value", obj.value) .attr("value", obj.id)
.text(obj.text)); .text(obj.nome));
}); });
$("#id_autor").val(autor_selecionado); $("#id_autor").val(autor_selecionado);
} }

20
sapl/templates/materia/autoria_multicreate_form.html

@ -7,30 +7,27 @@
<script language="Javascript"> <script language="Javascript">
$(document).ready(function() { $(document).ready(function() {
var autores_pre_cadastrados = $.parseJSON($("#id_autores").val()); var autores_pre_cadastrados = $.parseJSON($("#id_autores").val());
$("#id_tipo_autor").change(function() { $("#id_tipo_autor").change(function() {
var tipo_selecionado = $("#id_tipo_autor").val(); var tipo_selecionado = $("#id_tipo_autor").val();
$("#id_autor option").remove() $("#id_autor option").remove()
if (tipo_selecionado !== '') { if (tipo_selecionado !== '') {
var json_data = { var json_data = {
tipo : tipo_selecionado, tipo : tipo_selecionado,
data_relativa : $("#id_data_relativa").val() data_relativa : $("#id_data_relativa").val(),
o: 'nome',
get_all: true
} }
$.getJSON("/api/autor/possiveis", json_data, function(data){ $.getJSON("{% url 'sapl.api:autor-possiveis' %}", json_data, function(data){
$("#div_id_autor > div").html(''); $("#div_id_autor > div").html('');
if (data) { if (data) {
var results = data; $.each(data, function(idx, obj) {
if (autores_pre_cadastrados.indexOf(obj.id) !== -1)
$.each(results, function(idx, obj) {
if (autores_pre_cadastrados.indexOf(obj.value) !== -1)
return ; return ;
$('<input/>') $('<input/>')
.attr('type', 'checkbox') .attr('type', 'checkbox')
.attr('name','autor') .attr('name','autor')
.attr('id', 'id_autor_'+idx) .attr('id', 'id_autor_'+idx)
.attr('value', obj.value) .attr('value', obj.id)
.appendTo( .appendTo(
$('<label/>') $('<label/>')
.appendTo( .appendTo(
@ -40,8 +37,7 @@
) )
) )
) )
.after(obj.text) .after(obj.nome)
}); });
} }
}); });

23
sapl/templates/materia/materialegislativa_form.html

@ -20,14 +20,6 @@
} }
$("#id_tipo, #id_ano").change(recuperar_numero_ano); $("#id_tipo, #id_ano").change(recuperar_numero_ano);
function compare(a, b) {
if (a.text < b.text)
return -1;
if (a.text > b.text)
return 1;
return 0;
}
var modal_estilos = 'display: block;' var modal_estilos = 'display: block;'
+'width: 85%; max-width: 600px;' +'width: 85%; max-width: 600px;'
+'background: #fff; padding: 15px;' +'background: #fff; padding: 15px;'
@ -72,19 +64,20 @@
if (tipo_selecionado !== undefined && tipo_selecionado !== null){ if (tipo_selecionado !== undefined && tipo_selecionado !== null){
var json_data = { var json_data = {
tipo : tipo_selecionado, tipo : tipo_selecionado,
data_relativa : $("#id_data_apresentacao").val() data_relativa : $("#id_data_apresentacao").val(),
o: 'nome',
get_all: true
} }
$.getJSON("/api/autor/possiveis", json_data, function(data) { $.getJSON("{% url 'sapl.api:autor-possiveis' %}", json_data, function(data){
if (data) { if (data) {
var results = data.sort(compare); if (data.length > 1) {
if (results.length > 1) {
$("#id_autor").append("<option>-----</option>"); $("#id_autor").append("<option>-----</option>");
} }
$.each(results, function(idx, obj) { $.each(data, function(idx, obj) {
$("#id_autor") $("#id_autor")
.append($("<option></option>") .append($("<option></option>")
.attr("value", obj.value) .attr("value", obj.id)
.text(obj.text)); .text(obj.nome));
}); });
$("#id_autor").val(autor_selecionado); $("#id_autor").val(autor_selecionado);
} }

49
sapl/templates/materia/tipoproposicao_form.html

@ -20,12 +20,13 @@ $(document).ready(function(){
var initial_select = parseInt($("input[name=tipo_conteudo_related]").val()); var initial_select = parseInt($("input[name=tipo_conteudo_related]").val());
$("input[name=tipo_conteudo_related]").remove(); $("input[name=tipo_conteudo_related]").remove();
$('#id_content_type').change(function(event) { $('#id_content_type').change(function(event) {
var pk = this[event.target.selectedIndex].value; const url_part_pattern = this[event.target.selectedIndex].value
var url = '{% url 'sapl.api:model_list' 0 ''%}' const url = `/api/${url_part_pattern}/?o=id&page=1&page_size=100`
url = url.replace('0', pk) + '?pagination=False'
$.get(url).done(function(data) { const rad_tip_conteudo = $("#div_id_tipo_conteudo_related_radio > div").html('');
if (data === undefined || data.length == 0) { const popule_rad_tip_conteudo = function(url_get) {
$.get(url_get).done(function(response) {
if (response.pagination.total_entries === 0) {
$( "#dialog" ).dialog({ $( "#dialog" ).dialog({
resizable: false, resizable: false,
height: "auto", height: "auto",
@ -39,21 +40,29 @@ $(document).ready(function(){
}); });
return; return;
} }
response.results.forEach(function (val, index) {
var radios = $("#div_id_tipo_conteudo_related_radio > div").html(''); var html_radio = `
data.forEach(function (val, index) { <div class="radio">
var html_radio = '<div class="radio"><label id="id_tipo_conteudo_related_'+index+'"><input type="radio" name="tipo_conteudo_related" id="id_tipo_conteudo_related_'+index+'" value="'+val.value+'"'+(initial_select ?' checked="checked"':'')+'>'+val.text+'</label></div>'; <label id="id_tipo_conteudo_related_${val.id}">
<input type="radio"
if (val.value === initial_select) name="tipo_conteudo_related"
id="id_tipo_conteudo_related_${val.id}"
value="${val.id}"
${(initial_select ?'checked="checked"':'')}>
${val.__str__}
</label>
</div>`
if (val.id === initial_select)
initial_select=0; initial_select=0;
radios.append(html_radio); rad_tip_conteudo.append(html_radio);
}); })
}); if(response.pagination.links.next !== null )
}); popule_rad_tip_conteudo(response.pagination.links.next)
$('#id_content_type').trigger('change'); })
}); }
popule_rad_tip_conteudo(url)
})
$('#id_content_type').trigger('change')
})
</script> </script>
{% endblock %} {% endblock %}

25
sapl/templates/norma/autorianorma_form.html

@ -5,17 +5,7 @@
{% block extra_js %} {% block extra_js %}
<script language="Javascript"> <script language="Javascript">
function compare(a, b) {
if (a.text < b.text)
return -1;
if (a.text > b.text)
return 1;
return 0;
}
$(document).ready(function() { $(document).ready(function() {
function atualiza_select_autor() { function atualiza_select_autor() {
var tipo_selecionado = $("#id_tipo_autor").val(); var tipo_selecionado = $("#id_tipo_autor").val();
var autor_selecionado = $("#id_autor").val(); var autor_selecionado = $("#id_autor").val();
@ -24,19 +14,20 @@
var json_data = { var json_data = {
tipo : tipo_selecionado, tipo : tipo_selecionado,
data_relativa : $("#id_data_relativa").val(), data_relativa : $("#id_data_relativa").val(),
legislatura_anterior: $('#id_legislatura_anterior').is(':checked') legislatura_anterior: $('#id_legislatura_anterior').is(':checked'),
o: 'nome',
get_all: true
} }
$.getJSON("/api/autor/possiveis", json_data, function(data){ $.getJSON("{% url 'sapl.api:autor-possiveis' %}", json_data, function(data){
if (data) { if (data) {
var results = data.sort(compare); if (data.length > 1) {
if (results.length > 1) {
$("#id_autor").append("<option>-----</option>"); $("#id_autor").append("<option>-----</option>");
} }
$.each(results, function(idx, obj) { $.each(data, function(idx, obj) {
$("#id_autor") $("#id_autor")
.append($("<option></option>") .append($("<option></option>")
.attr("value", obj.value) .attr("value", obj.id)
.text(obj.text)); .text(obj.nome));
}); });
$("#id_autor").val(autor_selecionado); $("#id_autor").val(autor_selecionado);
} }

25
sapl/templates/protocoloadm/protocolar_materia.html

@ -8,16 +8,7 @@
{% block extra_js %} {% block extra_js %}
<script language="Javascript"> <script language="Javascript">
function compare(a, b) {
if (a.text < b.text)
return -1;
if (a.text > b.text)
return 1;
return 0;
}
$(document).ready(function() { $(document).ready(function() {
$("input[name=data_hora_manual]").change(function(event) { $("input[name=data_hora_manual]").change(function(event) {
if (this.value === 'True' && this.checked) if (this.value === 'True' && this.checked)
$("#protocolo_data_hora_manual").show(); $("#protocolo_data_hora_manual").show();
@ -62,26 +53,26 @@
$("#id_autor option").remove() $("#id_autor option").remove()
if (tipo_selecionado !== undefined && tipo_selecionado !== null) { if (tipo_selecionado !== undefined && tipo_selecionado !== null) {
var json_data = { var json_data = {
tipo : tipo_selecionado tipo : tipo_selecionado,
o: 'nome',
get_all: true
} }
$.getJSON("/api/autor/possiveis", json_data, function(data){ $.getJSON("{% url 'sapl.api:autor-possiveis' %}", json_data, function(data){
if (data) { if (data) {
var results = data.sort(compare); if (data.length > 1) {
if (results.length > 1) {
$("#id_autor").append("<option>-----</option>"); $("#id_autor").append("<option>-----</option>");
} }
$.each(results, function(idx, obj) { $.each(data, function(idx, obj) {
$("#id_autor") $("#id_autor")
.append($("<option></option>") .append($("<option></option>")
.attr("value", obj.value) .attr("value", obj.id)
.text(obj.text)); .text(obj.nome));
}); });
$("#id_autor").prop("selectedIndex", 0); $("#id_autor").prop("selectedIndex", 0);
} }
}); });
} }
}); });
}); });
</script> </script>
{% endblock %} {% endblock %}

Loading…
Cancel
Save