diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 9260e58..6ca69e1 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -12,6 +12,7 @@ django-weasyprint==2.5.0 djangorestframework==3.17.1 dnspython==2.8.0 docutils==0.22.4 +drf-spectacular==0.29.0 email-validator==2.3.0 gunicorn==25.3.0 ibge==0.0.5 @@ -34,4 +35,4 @@ weasyprint==68.1 xlsxwriter==3.2.9 djbs-theme @ git+https://github.com/interlegis/djbs-theme.git@v1.0.7 django-dashboard @ git+https://github.com/interlegis/django-dashboard.git -dx-job-controller @ git+https://github.com/interlegis/dx-job-controller.git@v1.0.0 \ No newline at end of file +dx-job-controller @ git+https://github.com/interlegis/dx-job-controller.git@v1.0.0 diff --git a/sigi/apps/servicos/api_urls.py b/sigi/apps/servicos/api_urls.py index 2991c40..924ed83 100644 --- a/sigi/apps/servicos/api_urls.py +++ b/sigi/apps/servicos/api_urls.py @@ -3,4 +3,5 @@ from sigi.apps.servicos import views urlpatterns = [ path("resumoprodutos/", views.ResumoProdutosApiView.as_view()), + path("", views.ServicoListView.as_view()), ] diff --git a/sigi/apps/servicos/filters.py b/sigi/apps/servicos/filters.py index 485b313..1e4723c 100644 --- a/sigi/apps/servicos/filters.py +++ b/sigi/apps/servicos/filters.py @@ -1,6 +1,8 @@ +import django_filters from datetime import date, timedelta from django.utils.translation import gettext as _ from django.contrib import admin +from sigi.apps.servicos.models import Servico class ServicoAtivoFilter(admin.FieldListFilter): @@ -8,9 +10,7 @@ class ServicoAtivoFilter(admin.FieldListFilter): self.model = model self.model_admin = model_admin self.parameter_name = f"{field_path}__isnull" - super().__init__( - field, request, params, model, model_admin, field_path - ) + super().__init__(field, request, params, model, model_admin, field_path) self.title = _("Serviço ativo") lookup_choices = self.lookups(request, model_admin) if lookup_choices is None: @@ -91,3 +91,25 @@ class DataUtimoUsoFilter(admin.SimpleListFilter): de = date.today() - timedelta(days=30) ate = date.today() - timedelta(days=7) return queryset.filter(data_ultimo_uso__range=(de, ate)) + + +class ServicoAPIFilter(django_filters.FilterSet): + uf = django_filters.CharFilter( + field_name="casa_legislativa__municipio__uf__sigla", + lookup_expr="iexact", + label=_("Sigla da UF"), + ) + tipo_servico = django_filters.CharFilter( + field_name="tipo_servico__sigla", + lookup_expr="iexact", + label=_("Sigla do tipo de serviço"), + ) + + class Meta: + model = Servico + fields = [ + "uf", + "tipo_servico", + "hospedagem_interlegis", + "data_ativacao", + ] diff --git a/sigi/apps/servicos/serializers.py b/sigi/apps/servicos/serializers.py index c01f733..59b55c3 100644 --- a/sigi/apps/servicos/serializers.py +++ b/sigi/apps/servicos/serializers.py @@ -1,6 +1,60 @@ from rest_framework import serializers +from yaml import serialize + +from sigi.apps.casas.models import Orgao, TipoOrgao +from sigi.apps.servicos.models import Servico, TipoServico class ProdutosSerializer(serializers.Serializer): produto = serializers.CharField(max_length=40) quantidade = serializers.IntegerField() + + +class OrgaoSerializer(serializers.ModelSerializer): + sigla = serializers.ReadOnlyField(source="get_sigla") + tipo_orgao_nome = serializers.CharField(source="tipo.nome", read_only=True) + tipo_orgao_sigla = serializers.CharField( + source="tipo.sigla", read_only=True + ) + municipio = serializers.CharField(source="municipio.nome", read_only=True) + uf = serializers.CharField(source="municipio.uf.sigla", read_only=True) + telefone = serializers.ReadOnlyField() + + class Meta: + model = Orgao + fields = [ + "nome", + "sigla", + "tipo_orgao_nome", + "tipo_orgao_sigla", + "cnpj", + "logradouro", + "bairro", + "municipio", + "cep", + "uf", + "email", + "ult_alt_endereco", + "telefone", + ] + + +class ServicoSerializer(serializers.ModelSerializer): + casa_legislativa = OrgaoSerializer(read_only=True) + tipo_servico_nome = serializers.CharField( + source="tipo_servico.nome", read_only=True + ) + tipo_servico_sigla = serializers.CharField( + source="tipo_servico.sigla", read_only=True + ) + + class Meta: + model = Servico + fields = [ + "casa_legislativa", + "tipo_servico_nome", + "tipo_servico_sigla", + "url", + "hospedagem_interlegis", + "data_ativacao", + ] diff --git a/sigi/apps/servicos/views.py b/sigi/apps/servicos/views.py index 349f95c..e6b4f8e 100644 --- a/sigi/apps/servicos/views.py +++ b/sigi/apps/servicos/views.py @@ -2,16 +2,20 @@ from django.db.models import Q, Prefetch, Count, F, Value, Case, When from django.http import HttpResponse from django.views.decorators.clickjacking import xframe_options_exempt from django.views.generic import ListView +from django_filters.rest_framework import DjangoFilterBackend from import_export import resources from import_export.fields import Field -from rest_framework import generics +from rest_framework import generics, filters from sigi.apps.casas.models import Orgao +from sigi.apps.servicos.serializers import ServicoSerializer from sigi.apps.contatos.models import UnidadeFederativa from sigi.apps.convenios.models import Convenio from sigi.apps.eventos.models import Evento, TipoEvento +from sigi.apps.servicos.filters import ServicoAPIFilter from sigi.apps.servicos.models import Servico from sigi.apps.servicos.serializers import ProdutosSerializer from sigi.apps.utils import to_ascii +from sigi.apps.utils.filters import DeterministicOrderingFilter class ServicoResource(resources.ModelResource): @@ -40,6 +44,32 @@ class ServicoResource(resources.ModelResource): return servico.casa_legislativa.telefone +class ServicoListView(generics.ListAPIView): + """Lista de serviços ativos prestados pelo Interlegis às Casas Legislativas""" + + queryset = Servico.objects.filter(data_desativacao=None).select_related( + "casa_legislativa__municipio__uf", "tipo_servico" + ) + serializer_class = ServicoSerializer + filter_backends = [ + DjangoFilterBackend, + filters.SearchFilter, + DeterministicOrderingFilter, + ] + filterset_class = ServicoAPIFilter + search_fields = [ + "casa_legislativa__nome", + "casa_legislativa__cnpj", + ] + ordering_fields = [ + "casa_legislativa__municipio__uf__sigla", + "casa_legislativa__nome", + "tipo_servico__sigla", + "data_ativacao", + ] + ordering = ["-data_ativacao"] + + class CasasAtendidasListView(ListView): model = Orgao template_name = "servicos/casas_atendidas.html" diff --git a/sigi/settings.py b/sigi/settings.py index 867983a..a7a5b49 100644 --- a/sigi/settings.py +++ b/sigi/settings.py @@ -16,7 +16,6 @@ from django.utils.translation import gettext_lazy as _ from django.conf.locale.pt_BR import formats as br_formats from djbs import djbs_constants - # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent @@ -63,6 +62,7 @@ INSTALLED_APPS = [ "import_export", "tinymce", "rest_framework", + "drf_spectacular", "sigi.apps.casas", "sigi.apps.contatos", "sigi.apps.convenios", @@ -284,6 +284,7 @@ TINYMCE_DEFAULT_CONFIG = { # Rest Framework settings REST_FRAMEWORK = { + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", "DEFAULT_RENDERER_CLASSES": [ "rest_framework.renderers.JSONRenderer", ], @@ -291,6 +292,23 @@ REST_FRAMEWORK = { "PAGE_SIZE": 100, } +# drf-spectacular settings + +SPECTACULAR_SETTINGS = { + "TITLE": "SIGI API", + "DESCRIPTION": "API de dados abertos dos Serviços prestados pelo Interlegis / Senado Federal", + "VERSION": "1.0.0", + "SERVE_INCLUDE_SCHEMA": False, + "CONTACT": { + "name": "Serviço de Gestão de Informações Educacionais do ILB/Interlegis", + "email": "lct_seginfe@senado.leg.br", + }, + "LICENSE": { + "name": "GPL-2.0.1", + "url": "https://github.com/interlegis/sigi?tab=GPL-2.0-1-ov-file#", + }, +} + # SIGI specific settings HOSPEDAGEM_PATH = Path(env("HOSPEDAGEM_PATH", default="/tmp/HOSP/")) diff --git a/sigi/templates/sigi/api/redoc.html b/sigi/templates/sigi/api/redoc.html deleted file mode 100644 index 07650ce..0000000 --- a/sigi/templates/sigi/api/redoc.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - SIGI ReDoc documentation - - - - - - - - - - - - \ No newline at end of file diff --git a/sigi/templates/sigi/api/swagger-ui.html b/sigi/templates/sigi/api/swagger-ui.html deleted file mode 100644 index 0d2ab1c..0000000 --- a/sigi/templates/sigi/api/swagger-ui.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - SIGI Swagger documentation - - - - - -
- - - - \ No newline at end of file diff --git a/sigi/urls.py b/sigi/urls.py index 31a3ed5..6d62dca 100644 --- a/sigi/urls.py +++ b/sigi/urls.py @@ -14,7 +14,11 @@ Including another URLconf 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ -from rest_framework.schemas import get_schema_view +from drf_spectacular.views import ( + SpectacularAPIView, + SpectacularSwaggerView, + SpectacularRedocView, +) from django.conf import settings from django.conf.urls.static import static from django.contrib import admin @@ -47,29 +51,15 @@ urlpatterns = [ "api/doc/", RedirectView.as_view(pattern_name="swagger-ui", permanent=False), ), - path( - "api/doc/schema.yaml", - get_schema_view( - title="SIGI rest API Schema", - description="REST API for SIGI opendata", - version="1.0.0", - ), - name="openapi-schema", - ), + path("api/schema/", SpectacularAPIView.as_view(), name="openapi-schema"), path( "api/doc/swagger-ui/", - TemplateView.as_view( - template_name="sigi/api/swagger-ui.html", - extra_context={"schema_url": "openapi-schema"}, - ), + SpectacularSwaggerView.as_view(url_name="openapi-schema"), name="swagger-ui", ), path( "api/doc/redoc/", - TemplateView.as_view( - template_name="sigi/api/redoc.html", - extra_context={"schema_url": "openapi-schema"}, - ), + SpectacularRedocView.as_view(url_name="openapi-schema"), name="redoc", ), path("api/casas/", include("sigi.apps.casas.api_urls")),