mirror of https://github.com/interlegis/sapl.git
Browse Source
* 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 modelspull/3615/head
LeandroJataí
2 years ago
committed by
GitHub
43 changed files with 1699 additions and 1849 deletions
@ -0,0 +1 @@ |
|||||
|
# Transformar em projeto externo instalável para uso geral |
@ -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 |
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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__' |
|
@ -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 já 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') |
||||
|
@ -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,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) |
@ -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') |
||||
|
] |
||||
|
) |
@ -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 |
@ -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') |
||||
|
] |
||||
|
) |
@ -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') |
||||
|
] |
||||
|
) |
@ -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) |
@ -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') |
||||
|
] |
||||
|
) |
@ -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') |
||||
|
] |
||||
|
) |
@ -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 |
@ -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 |
@ -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) |
@ -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) |
|
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Loading…
Reference in new issue