diff --git a/sapl/api/apps.py b/sapl/api/apps.py index ced7e511b..f8adf670b 100644 --- a/sapl/api/apps.py +++ b/sapl/api/apps.py @@ -6,3 +6,6 @@ class AppConfig(apps.AppConfig): name = 'sapl.api' label = 'api' verbose_name = _('API Rest') + + def ready(self): + from . import signals diff --git a/sapl/api/core/forms.py b/sapl/api/core/forms.py deleted file mode 100644 index ea61026c7..000000000 --- a/sapl/api/core/forms.py +++ /dev/null @@ -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 diff --git a/sapl/api/core/serializers.py b/sapl/api/core/serializers.py deleted file mode 100644 index b2731e4ca..000000000 --- a/sapl/api/core/serializers.py +++ /dev/null @@ -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__' diff --git a/sapl/api/deprecated.py b/sapl/api/deprecated.py index db27f2ac1..5e6e53306 100644 --- a/sapl/api/deprecated.py +++ b/sapl/api/deprecated.py @@ -1,8 +1,5 @@ - 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 @@ -17,10 +14,11 @@ from rest_framework.generics import ListAPIView from rest_framework.mixins import ListModelMixin, RetrieveModelMixin from rest_framework.permissions import (IsAuthenticated, IsAuthenticatedOrReadOnly, AllowAny) +from rest_framework.relations import StringRelatedField from rest_framework.viewsets import GenericViewSet -from sapl.api.core.serializers import ModelChoiceSerializer, ChoiceSerializer -from sapl.api.serializers import AutorSerializer +from sapl.api.serializers import AutorSerializer, ModelChoiceSerializer,\ + ChoiceSerializer from sapl.base.models import TipoAutor, Autor, CasaLegislativa from sapl.materia.models import MateriaLegislativa from sapl.parlamentares.models import Legislatura diff --git a/sapl/api/forms.py b/sapl/api/forms.py index e69de29bb..ab8e9984f 100644 --- a/sapl/api/forms.py +++ b/sapl/api/forms.py @@ -0,0 +1,5 @@ +from drfautoapi.drfautoapi import ApiFilterSetMixin + + +class SaplFilterSetMixin(ApiFilterSetMixin): + pass diff --git a/sapl/api/schema.py b/sapl/api/schema.py new file mode 100644 index 000000000..b7ca42354 --- /dev/null +++ b/sapl/api/schema.py @@ -0,0 +1,5 @@ +from drf_spectacular.openapi import AutoSchema + + +class Schema(AutoSchema): + pass diff --git a/sapl/api/serializers.py b/sapl/api/serializers.py index d670af43e..82fbdbe3f 100644 --- a/sapl/api/serializers.py +++ b/sapl/api/serializers.py @@ -1,19 +1,68 @@ import logging from django.conf import settings +from django.contrib.contenttypes.models import ContentType from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist from django.db.models import Q from image_cropping.utils import get_backend from rest_framework import serializers +from rest_framework.fields import SerializerMethodField +from rest_framework.relations import StringRelatedField -from sapl.api.core.serializers import ModelChoiceObjectRelatedField -from sapl.base.models import Autor +from sapl.base.models import Autor, CasaLegislativa, Metadata from sapl.parlamentares.models import Parlamentar, Mandato, Legislatura -class AutorSerializer(serializers.ModelSerializer): - # AutorSerializer sendo utilizado pelo gerador automático da api devidos aos - # critérios anotados em views.py +class SaplSerializerMixin(serializers.ModelSerializer): + __str__ = SerializerMethodField() + metadata = SerializerMethodField() + + class Meta: + fields = '__all__' + + def get___str__(self, obj) -> str: + return str(obj) + + def get_metadata(self, obj) -> dict: + try: + metadata = Metadata.objects.get( + content_type=ContentType.objects.get_for_model( + obj._meta.model), + object_id=obj.id + ).metadata + except: + metadata = {} + finally: + return metadata + + +class ChoiceSerializer(serializers.Serializer): + value = serializers.SerializerMethodField() + text = serializers.SerializerMethodField() + + def get_text(self, obj): + return obj[1] + + def get_value(self, obj): + return obj[0] + + +class ModelChoiceSerializer(ChoiceSerializer): + + def get_text(self, obj): + return str(obj) + + def get_value(self, obj): + return obj.id + + +class ModelChoiceObjectRelatedField(serializers.RelatedField): + + def to_representation(self, value): + return ModelChoiceSerializer(value).data + + +class AutorSerializer(SaplSerializerMixin): autor_related = ModelChoiceObjectRelatedField(read_only=True) @@ -22,7 +71,18 @@ class AutorSerializer(serializers.ModelSerializer): fields = '__all__' -class ParlamentarSerializerPublic(serializers.ModelSerializer): +class CasaLegislativaSerializer(SaplSerializerMixin): + version = serializers.SerializerMethodField() + + def get_version(self, obj): + return settings.SAPL_VERSION + + class Meta: + model = CasaLegislativa + fields = '__all__' + + +class ParlamentarSerializerPublic(SaplSerializerMixin): class Meta: model = Parlamentar @@ -32,7 +92,7 @@ class ParlamentarSerializerPublic(serializers.ModelSerializer): "telefone_residencia", "titulo_eleitor", "fax_residencia"] -class ParlamentarSerializerVerbose(serializers.ModelSerializer): +class ParlamentarSerializerVerbose(SaplSerializerMixin): titular = serializers.SerializerMethodField('check_titular') partido = serializers.SerializerMethodField('check_partido') fotografia_cropped = serializers.SerializerMethodField('crop_fotografia') @@ -55,7 +115,8 @@ class ParlamentarSerializerVerbose(serializers.ModelSerializer): ) except Exception as e: self.logger.error(e) - self.logger.error('erro processando arquivo: %s' % obj.fotografia.path) + self.logger.error('erro processando arquivo: %s' % + obj.fotografia.path) return thumbnail_url @@ -66,7 +127,8 @@ class ParlamentarSerializerVerbose(serializers.ModelSerializer): return "" try: - legislatura = Legislatura.objects.get(id=self.context.get('legislatura')) + legislatura = Legislatura.objects.get( + id=self.context.get('legislatura')) except ObjectDoesNotExist: legislatura = Legislatura.objects.first() mandato = Mandato.objects.filter( @@ -92,7 +154,8 @@ class ParlamentarSerializerVerbose(serializers.ModelSerializer): self.logger.error("Não há legislaturas cadastradas.") return "" try: - legislatura = Legislatura.objects.get(id=self.context.get('legislatura')) + legislatura = Legislatura.objects.get( + id=self.context.get('legislatura')) except ObjectDoesNotExist: legislatura = Legislatura.objects.first() @@ -109,8 +172,8 @@ class ParlamentarSerializerVerbose(serializers.ModelSerializer): # Caso não exista filiação com essas condições except ObjectDoesNotExist: self.logger.warning("user=" + username + ". Parlamentar com (data<={} e data_desfiliacao>={}) " - "ou (data<={} e data_desfiliacao=Null)) não possui filiação." - .format(legislatura.data_fim, legislatura.data_fim, legislatura.data_fim)) + "ou (data<={} e data_desfiliacao=Null)) não possui filiação." + .format(legislatura.data_fim, legislatura.data_fim, legislatura.data_fim)) filiacao = 'Não possui filiação' # Caso exista mais de uma filiação nesse intervalo @@ -131,4 +194,5 @@ class ParlamentarSerializerVerbose(serializers.ModelSerializer): class Meta: model = Parlamentar - fields = ['id', 'nome_parlamentar', 'fotografia_cropped', 'fotografia', 'ativo', 'partido', 'titular'] + fields = ['id', 'nome_parlamentar', 'fotografia_cropped', + 'fotografia', 'ativo', 'partido', 'titular', ] diff --git a/sapl/api/signals.py b/sapl/api/signals.py new file mode 100644 index 000000000..8d262a5e4 --- /dev/null +++ b/sapl/api/signals.py @@ -0,0 +1,10 @@ +from django.conf import settings +from django.db.models.signals import post_save +from django.dispatch.dispatcher import receiver +from rest_framework.authtoken.models import Token + + +@receiver(post_save, sender=settings.AUTH_USER_MODEL) +def create_auth_token(sender, instance=None, created=False, **kwargs): + if created: + Token.objects.create(user=instance) diff --git a/sapl/api/views.py b/sapl/api/views.py index cfbea93a8..52d0f05f8 100644 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -1,8 +1,6 @@ import logging from django.conf import settings -from django.db.models.signals import post_save -from django.dispatch import receiver from django.utils.translation import ugettext_lazy as _ from rest_framework.authtoken.models import Token from rest_framework.decorators import api_view, permission_classes @@ -11,12 +9,6 @@ from rest_framework.response import Response from rest_framework.views import APIView -@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) - - @api_view(['POST']) @permission_classes([IsAdminUser]) def recria_token(request, pk): diff --git a/sapl/api/viewset.py b/sapl/api/viewset.py index 2b9a8632e..02df175d4 100644 --- a/sapl/api/viewset.py +++ b/sapl/api/viewset.py @@ -1,5 +1,7 @@ import logging +from django.apps.registry 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 @@ -8,10 +10,9 @@ 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, \ +from drfautoapi.drfautoapi import customize, ApiViewSetConstrutor, \ 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 @@ -26,7 +27,14 @@ from sapl.protocoloadm.models import DocumentoAdministrativo, \ from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao from sapl.utils import models_with_gr_for_model, choice_anos_com_sessaoplenaria -SaplApiViewSetConstrutor = SaplApiViewSetConstrutor.build_class() + +SaplApiViewSetConstrutor = ApiViewSetConstrutor.build_class( + [ + apps.get_app_config('contenttypes') + ] + [ + apps.get_app_config(n[5:]) for n in settings.SAPL_APPS + ] +) @customize(ContentType) diff --git a/sapl/settings.py b/sapl/settings.py index 978d81531..fd51a5608 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -158,7 +158,7 @@ REST_FRAMEWORK = { "rest_framework.authentication.SessionAuthentication", ), - 'DEFAULT_SCHEMA_CLASS': 'sapl.api.core.schema.Schema', + 'DEFAULT_SCHEMA_CLASS': 'sapl.api.schema.Schema', "DEFAULT_PAGINATION_CLASS": "sapl.api.pagination.StandardPagination", @@ -168,6 +168,13 @@ REST_FRAMEWORK = { ), } +DRFAUTOAPI = { + 'DEFAULT_SERIALIZER_MODULE': 'sapl.api.serializers', + 'DEFAULT_FILTER_MODULE': 'sapl.api.forms', + 'GLOBAL_SERIALIZER_MIXIN': 'sapl.api.serializers.SaplSerializerMixin', + 'GLOBAL_FILTERSET_MIXIN': 'sapl.api.forms.SaplFilterSetMixin' +} + SPECTACULAR_SETTINGS = { 'TITLE': 'Sapl API - docs', 'DESCRIPTION': 'Sapl API - Docs',