Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring for improved Open API & typing support (allows: automatic schema generation) #20

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

metaforx
Copy link

@metaforx metaforx commented Feb 10, 2025

An opinionated refactoring of the REST API, not adding functionality beside improved Open API support. Allows automated schema generation using tools like openapi-typescript-codegen. This allows automatic use of types in frontend using Typescript.

Improved readability, but that's really a personal preference.

  • Reworked folder/file organization and naming following common django patterns (a grain of personal preference added)
  • Typed views, serializers
  • Explicitly assign serializer allowing openapi to create proper typing, needed rework of init functionality (adding objects to kwarg context insted directly implementing them in init), for example using: self.request = self.context.get("request")
  • Optional Open API schema extensions
  • Permissions via DRF permission classes and object-based permissions
  • Separated utils from views for better readability

API-Schema & Documentation
https://github.com/tfranzel/drf-spectacular
Follow installation guidelines on GitHub, only a few instructions have to be added to settings.py

Use in Frontend

  1. Create Types.
#pnpm 
"generate-openapi-types": "rm -rf ./src/@types && curl http://127.0.0.1:8080/api/schema-json/> ./openapi/schema.json && npx openapi-typescript-codegen --input openapi/schema.json --output ./src/@types"
  1. Use Automatic Endpoints from Schema
    https://nuxt-open-fetch.vercel.app/ (Nuxt specific)

  2. Validate Data from Schema/Types
    https://zod.dev/

Needs: What's missing & Bugs

  • HTML rendering is lacking, should be enabled again if needed (benefits?)
  • Caching is separated and disabled. Either enable it (but it needs some testing, as it was not working out of the box for me) or:
  • implement Redis Caching?
    - Error [PlaceholderDetailView]: unable to guess serializer. This happened to all views when using in combination with drf-spectacular. I could fix this for all other views by refactoring init, but this particular one covers a lot of specific functionality which I am not sure, what's the best approach. To get rid of this error, we need to add @extend_schema( responses=PlaceholderSerializer", ) with reworked init functionality.

Summary by Sourcery

New Features:

  • Generate Open API schema for the REST API.

- add/fix/improve openapi support
- add typing
- fix serializer assignment which resulted in DRF error warnings with swagger
- separated utils from views and serializers
- disabled caching functionality (was not working reliably and i would rather cache api with redis directly)
- remove absolute url from serializer, because i see no use in them (at least not for my projects)
Modularized serializers into distinct files for improved clarity and maintainability. Refactored placeholder caching, language, and page handling utilities into dedicated modules. Adjusted settings and base views to align with the new structure.
…w_page

Needed because nested objects are also checked but are PageContent
should be added in parent/global urlpatterns
Copy link

sourcery-ai bot commented Feb 10, 2025

Reviewer's Guide by Sourcery

This pull request refactors the REST API to enhance Open API support and improve type safety. The changes reorganize view classes, serializers, permissions, and utilities following common Django patterns. The refactoring introduces explicit serializer assignments, type hints, and modularized components to facilitate automatic schema generation and frontend type usage.

Updated API Views Class Diagram

classDiagram
    class BaseAPIView {
      <<abstract>>
      +get()
    }

    class LanguageListView {
      +get(request: Request): Response
      +@extend_schema
    }
    LanguageListView --|> BaseAPIView

    class PageTreeListView {
      +get(request, language): Response
      +permission: IsAllowedLanguage
    }
    PageTreeListView --|> BaseAPIView

    class PageDetailView {
      +get(request: Request, language: str, path: str): Response
      +permission: CanViewPage
    }
    PageDetailView --|> BaseAPIView

    class PlaceholderDetailView {
      +get(request: Request, language: str, content_type_id: int, object_id: int, slot: str): Response
      +permission: CanViewPageContent
    }
    PlaceholderDetailView --|> BaseAPIView
Loading

Updated API Serializers Class Diagram

classDiagram
    class BasePageSerializer {
      +to_representation()
    }

    class PageMetaSerializer {
      +to_representation()
      +children: List
    }
    PageMetaSerializer --|> BasePageSerializer

    class PageContentSerializer {
      +to_representation()
    }
    PageContentSerializer --|> BasePageSerializer

    class PlaceholderRenderer {
      +render_placeholder(placeholder, context, language, use_cache)
      +render_plugins(placeholder, language, context)
    }

    class PlaceholderSerializer {
      +__init__(request: Request, placeholder, language: str)
    }
    PlaceholderSerializer ..> PlaceholderRenderer : uses
Loading

File-Level Changes

Change Details Files
Refactored API Views and URL routing
  • Introduced a new BaseAPIView to centralize common view functionality and allowed methods.
  • Updated view classes (LanguageListView, PageTreeListView, PageDetailView, PlaceholderDetailView) with type hints, explicit permission assignments, and improved Open API schema annotations.
  • Reorganized URL patterns to reflect the new view structure.
djangocms_rest/views.py
djangocms_rest/views_base.py
djangocms_rest/urls.py
Enhanced Serializers and Schema Generation Support
  • Reworked serializer implementations by replacing outdated serializers with new ones (e.g., PageMetaSerializer, PageContentSerializer, and updated PlaceholderSerializer).
  • Separated serializers into dedicated files, including changes to pages, placeholders, and languages serializers, to support automatic type generation.
  • Implemented caching and conditional HTML rendering in serializers for placeholders.
djangocms_rest/serializers/pages.py
djangocms_rest/serializers/placeholders.py
djangocms_rest/serializers/languages.py
djangocms_rest/serializers/utils/cache.py
djangocms_rest/serializers/utils/render.py
Introduction of Explicit Permission Classes
  • Added new permission classes such as IsAllowedLanguage, CanViewPage, and CanViewPageContent to ensure proper access control.
  • Integrated object-level permission checks within the updated view classes.
djangocms_rest/permissions.py
Extraction of Common Utility Functions
  • Created utilities to centralize common logic like fetching Page and Placeholder objects.
  • Encapsulated logic for retrieving objects into a new utils.py module, reducing redundancy in the views.
djangocms_rest/utils.py
Minor Configuration and Version Updates
  • Updated package version to '0.1.0a' to reflect alpha status.
  • Adjusted test settings and commented out unused test app configuration.
djangocms_rest/__init__.py
tests/settings.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!
  • Generate a plan of action for an issue: Comment @sourcery-ai plan on
    an issue to generate a plan of action for it.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @metaforx - I've reviewed your changes - here's some feedback:

Overall Comments:

  • Consider adding a brief explanation of the purpose and structure of each serializer to improve maintainability.
  • It would be helpful to include a section on error handling and how the API deals with invalid requests or data.
Here's what I looked at during the review
  • 🟡 General issues: 1 issue found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟢 Complexity: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

language=language,
use_cache=True,
)
if request.GET.get("html", False):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Handling of the 'html' query parameter deserves closer attention.

Since GET parameters are strings, checking for 'html' using a truthy evaluation might be error‐prone. Consider normalizing the value (for example, comparing against 'true' case‐insensitively) to ensure consistent behavior.

Suggested change
if request.GET.get("html", False):
if request.GET.get("html", "").strip().lower() == "true":

@metaforx
Copy link
Author

as_tree is not integrated, could pages-list be printed flat when as_tree = false?
If so, we have to add this functionality.

@classmethod

63 | def many_init(cls, request, instances, *args, **kwargs):
64 | kwargs['child'] = cls(request)
65 | if kwargs.pop("as_tree", True):
66 | tree = {}
67 | for instance in instances:
68 | if instance.page.node.parent in tree:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant