From db79a02c858712223dc3eac5acac988b3f6d6865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniele=20Vigan=C3=B2?= Date: Thu, 16 Jan 2025 22:53:19 +0100 Subject: [PATCH] Add REST API pagination and mke REST API optional (#43) * Implement Rest API pagination * REST API must be enabled in settings * Report REST API status in the admin site settings page --- ram/bookshelf/views.py | 3 +++ ram/consist/views.py | 2 ++ ram/portal/admin.py | 8 ++++++- ram/ram/__init__.py | 2 +- ram/ram/settings.py | 6 +++++ ram/ram/urls.py | 52 +++++++++++++++++++++++------------------- ram/ram/views.py | 7 ++++++ ram/roster/views.py | 4 ++++ requirements-dev.txt | 3 +++ requirements.txt | 1 - 10 files changed, 61 insertions(+), 27 deletions(-) diff --git a/ram/bookshelf/views.py b/ram/bookshelf/views.py index a689e3b..06168b3 100644 --- a/ram/bookshelf/views.py +++ b/ram/bookshelf/views.py @@ -1,12 +1,14 @@ from rest_framework.generics import ListAPIView, RetrieveAPIView from rest_framework.schemas.openapi import AutoSchema +from ram.views import CustomLimitOffsetPagination from bookshelf.models import Book, Catalog from bookshelf.serializers import BookSerializer, CatalogSerializer class BookList(ListAPIView): serializer_class = BookSerializer + pagination_class = CustomLimitOffsetPagination def get_queryset(self): return Book.objects.get_published(self.request.user) @@ -23,6 +25,7 @@ class BookGet(RetrieveAPIView): class CatalogList(ListAPIView): serializer_class = CatalogSerializer + pagination_class = CustomLimitOffsetPagination def get_queryset(self): return Catalog.objects.get_published(self.request.user) diff --git a/ram/consist/views.py b/ram/consist/views.py index 86cb4b7..5376e87 100644 --- a/ram/consist/views.py +++ b/ram/consist/views.py @@ -1,11 +1,13 @@ from rest_framework.generics import ListAPIView, RetrieveAPIView +from ram.views import CustomLimitOffsetPagination from consist.models import Consist from consist.serializers import ConsistSerializer class ConsistList(ListAPIView): serializer_class = ConsistSerializer + pagination_class = CustomLimitOffsetPagination def get_queryset(self): return Consist.objects.get_published(self.request.user) diff --git a/ram/portal/admin.py b/ram/portal/admin.py index 510bced..cf9ee55 100644 --- a/ram/portal/admin.py +++ b/ram/portal/admin.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.contrib import admin from solo.admin import SingletonModelAdmin @@ -7,7 +8,7 @@ from portal.models import SiteConfiguration, Flatpage @admin.register(SiteConfiguration) class SiteConfigurationAdmin(SingletonModelAdmin): - readonly_fields = ("site_name",) + readonly_fields = ("site_name", "rest_api") fieldsets = ( ( None, @@ -21,6 +22,7 @@ class SiteConfigurationAdmin(SingletonModelAdmin): "currency", "footer", "footer_extended", + "rest_api", ) }, ), @@ -37,6 +39,10 @@ class SiteConfigurationAdmin(SingletonModelAdmin): ), ) + @admin.display(description="REST API enabled", boolean=True) + def rest_api(self, obj): + return settings.REST_ENABLED + @admin.register(Flatpage) class FlatpageAdmin(admin.ModelAdmin): diff --git a/ram/ram/__init__.py b/ram/ram/__init__.py index bee9cff..994ca4d 100644 --- a/ram/ram/__init__.py +++ b/ram/ram/__init__.py @@ -1,4 +1,4 @@ from ram.utils import git_suffix -__version__ = "0.15.5" +__version__ = "0.15.6" __version__ += git_suffix(__file__) diff --git a/ram/ram/settings.py b/ram/ram/settings.py index 1acf5b3..4b7a20b 100644 --- a/ram/ram/settings.py +++ b/ram/ram/settings.py @@ -142,6 +142,12 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" MEDIA_URL = "media/" MEDIA_ROOT = STORAGE_DIR / "media" +REST_ENABLED = False # Set to True to enable the REST API +REST_FRAMEWORK = { + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", + "PAGE_SIZE": 5, +} + TINYMCE_DEFAULT_CONFIG = { "height": "500px", "menubar": False, diff --git a/ram/ram/urls.py b/ram/ram/urls.py index 52e0ff4..8ff96d3 100644 --- a/ram/ram/urls.py +++ b/ram/ram/urls.py @@ -32,9 +32,6 @@ urlpatterns = [ path("tinymce/upload_image", UploadImage.as_view(), name="upload_image"), path("portal/", include("portal.urls")), path("admin/", admin.site.urls), - path("api/v1/consist/", include("consist.urls")), - path("api/v1/roster/", include("roster.urls")), - path("api/v1/bookshelf/", include("bookshelf.urls")), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # Enable the "/dcc" routing only if the "driver" app is active @@ -43,30 +40,37 @@ if apps.is_installed("driver"): path("api/v1/dcc/", include("driver.urls")), ] -if settings.DEBUG: - from django.views.generic import TemplateView - from rest_framework.schemas import get_schema_view - +if settings.REST_ENABLED: urlpatterns += [ - path( - "swagger/", - TemplateView.as_view( - template_name="swagger.html", - extra_context={"schema_url": "openapi-schema"}, - ), - name="swagger", - ), - path( - "openapi", - get_schema_view( - title="RAM - Railroad Assets Manager", - description="RAM API", - version="1.0.0", - ), - name="openapi-schema", - ), + path("api/v1/consist/", include("consist.urls")), + path("api/v1/roster/", include("roster.urls")), + path("api/v1/bookshelf/", include("bookshelf.urls")), ] + +if settings.DEBUG: if apps.is_installed("debug_toolbar"): urlpatterns += [ path("__debug__/", include("debug_toolbar.urls")), ] + if settings.REST_ENABLED: + from django.views.generic import TemplateView + from rest_framework.schemas import get_schema_view + urlpatterns += [ + path( + "swagger/", + TemplateView.as_view( + template_name="swagger.html", + extra_context={"schema_url": "openapi-schema"}, + ), + name="swagger", + ), + path( + "openapi", + get_schema_view( + title="RAM - Railroad Assets Manager", + description="RAM API", + version="1.0.0", + ), + name="openapi-schema", + ), + ] diff --git a/ram/ram/views.py b/ram/ram/views.py index 821cf53..74fa63b 100644 --- a/ram/ram/views.py +++ b/ram/ram/views.py @@ -16,6 +16,13 @@ from django.utils.text import slugify as slugify from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt +from rest_framework.pagination import LimitOffsetPagination + + +class CustomLimitOffsetPagination(LimitOffsetPagination): + default_limit = 10 + max_limit = 25 + @method_decorator(csrf_exempt, name="dispatch") class UploadImage(View): diff --git a/ram/roster/views.py b/ram/roster/views.py index 67fe369..e2845a1 100644 --- a/ram/roster/views.py +++ b/ram/roster/views.py @@ -1,12 +1,14 @@ from rest_framework.generics import ListAPIView, RetrieveAPIView from rest_framework.schemas.openapi import AutoSchema +from ram.views import CustomLimitOffsetPagination from roster.models import RollingStock from roster.serializers import RollingStockSerializer class RosterList(ListAPIView): serializer_class = RollingStockSerializer + pagination_class = CustomLimitOffsetPagination def get_queryset(self): return RollingStock.objects.get_published(self.request.user) @@ -23,6 +25,7 @@ class RosterGet(RetrieveAPIView): class RosterAddress(ListAPIView): serializer_class = RollingStockSerializer + pagination_class = CustomLimitOffsetPagination schema = AutoSchema(operation_id_base="retrieveRollingStockByAddress") def get_queryset(self): @@ -34,6 +37,7 @@ class RosterAddress(ListAPIView): class RosterClass(ListAPIView): serializer_class = RollingStockSerializer + pagination_class = CustomLimitOffsetPagination schema = AutoSchema(operation_id_base="retrieveRollingStockByClass") diff --git a/requirements-dev.txt b/requirements-dev.txt index f37be1f..988d35f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,4 +3,7 @@ pdbpp ipython flake8 pyinstrument +pyyaml +uritemplate +inflection django-debug-toolbar diff --git a/requirements.txt b/requirements.txt index f749874..bd3932c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,6 @@ django-health-check django-admin-sortable2 django-tinymce # Optional: # psycopg2-binary -# Optional: # pySerial # Required by django-countries and not always installed # by default on modern venvs (like Python 3.12 on Fedora 39) setuptools