Skip to content

Conversation

@metaforx
Copy link
Collaborator

@metaforx metaforx commented Nov 6, 2025

  • Create unique operationIds for the OpenAPI schema, which allows better identification when implemented in the frontend.
  • Add naming pattern for path, levels, root, nephews for all menu endpoints.
  • Adds _retrireve to each operationId to align with the default naming pattern from OpenAPI.
  • Fallback to default if name not available
  • Not implemented if drf_spectacular is not available
  • separation of concern for schemas and views

Results in:

      operationId: breadcrumbs_path_retrieve
      operationId: menu_root_levels_retrieve
      ...

Summary by Sourcery

Generate distinct OpenAPI operationIds for menu-related endpoints by extracting schema utilities into schemas.py, wrapping views with URL-specific naming, and updating URL patterns and tests accordingly

Enhancements:

  • Introduce MenuSchema subclass to generate unique operationIds from URL names with a _retrieve suffix
  • Add schemas.py with create_view_with_url_name and menu_schema_class utilities for schema handling
  • Wrap menu, submenu, and breadcrumb views in URL patterns using create_view_with_url_name to assign distinct url_names
  • Apply @menu_schema_class to MenuView, SubMenuView, and BreadcrumbView and remove manual method_schema_decorator
  • Update tests to use the new endpoint URL names for menu, submenu, and breadcrumbs

@metaforx metaforx requested a review from fsbraun November 6, 2025 14:56
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Nov 6, 2025

Reviewer's Guide

This PR refactors the OpenAPI schema generation for menu-related endpoints by introducing a custom MenuSchema that generates unique operationIds, wrapping view registrations with create_view_with_url_name to tag URL names for schema derivation, decorating view classes with the new schema while removing inline schema decorators, and updating tests to use the newly named endpoints.

Class diagram for new OpenAPI schema utilities (MenuSchema and helpers)

classDiagram
    class MenuSchema {
        +get_operation_id()
    }
    class AutoSchema
    MenuSchema --|> AutoSchema
    class NavigationNodeSerializer
    class ViewWithUrlName {
        _url_name
    }
    class create_view_with_url_name {
        +__call__(view_class, url_name)
    }
    class menu_schema_class {
        +__call__(view_class)
    }
    class method_schema_decorator {
        +__call__(method)
    }
    class extend_placeholder_schema
    create_view_with_url_name ..> ViewWithUrlName : creates
    menu_schema_class ..> MenuSchema : applies
    method_schema_decorator ..> NavigationNodeSerializer : uses
Loading

Class diagram for updated MenuView, SubMenuView, and BreadcrumbView schema usage

classDiagram
    class BaseAPIView
    class MenuView {
        permission_classes
        serializer_class
        tag
        return_key
        +get(request)
        schema : MenuSchema
    }
    class SubMenuView {
        tag
        +populate_defaults(kwargs)
        schema : MenuSchema
    }
    class BreadcrumbView {
        tag
        return_key
        schema : MenuSchema
    }
    MenuView --|> BaseAPIView
    SubMenuView --|> MenuView
    BreadcrumbView --|> MenuView
    MenuView ..> MenuSchema : uses
    SubMenuView ..> MenuSchema : uses
    BreadcrumbView ..> MenuSchema : uses
Loading

Flow diagram for view registration with URL name for schema generation

flowchart TD
    A["urls.py path registration"] --> B["create_view_with_url_name(view_class, url_name)"]
    B --> C["ViewWithUrlName instance"]
    C --> D["View registered with _url_name"]
    D --> E["MenuSchema uses _url_name for operationId"]
Loading

File-Level Changes

Change Details Files
Add custom OpenAPI schema utilities for distinct operationIds
  • Introduce schemas.py defining MenuSchema that overrides get_operation_id to use view._url_name with a '_retrieve' suffix and fallback to default
  • Implement create_view_with_url_name to attach URL name to view classes for schema generation
  • Provide menu_schema_class decorator to assign MenuSchema to view classes
  • Include conditional imports and no-op fallbacks when drf_spectacular is unavailable
djangocms_rest/schemas.py
Refactor URL routing to tag views with explicit URL names
  • Import create_view_with_url_name in urls.py
  • Wrap all MenuView, SubMenuView, and BreadcrumbView registrations with create_view_with_url_name and descriptive url_name strings
  • Rename path names to reflect patterns (menu-levels, menu-levels-path, submenu-path, breadcrumbs-level, etc.)
djangocms_rest/urls.py
Apply schema decorator in view classes and remove inline schema logic
  • Annotate MenuView, SubMenuView, and BreadcrumbView with @menu_schema_class
  • Remove the try/except block and method_schema_decorator from views.py
  • Drop the @method_schema_decorator on the get() method, cleaning up inline extend_schema usage
djangocms_rest/views.py
Update tests to reflect new endpoint naming conventions
  • Adjust reverse() calls in test_menu.py to use the new URL names (menu-levels, menu-levels-path, submenu-path, breadcrumbs-path, etc.)
  • Ensure tests still cover fallback and default behaviors under the new naming scheme
tests/endpoints/test_menu.py

Possibly linked issues

  • #123: The PR refactors menu endpoint URLs to generate unique operationIds in OpenAPI schema, directly implementing the issue's solution for better endpoint distinction.

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!

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

@codecov
Copy link

codecov bot commented Nov 6, 2025

Codecov Report

❌ Patch coverage is 87.80488% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.94%. Comparing base (569c302) to head (9ebf6ba).

Files with missing lines Patch % Lines
djangocms_rest/schemas.py 85.71% 4 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #80      +/-   ##
==========================================
- Coverage   91.23%   90.94%   -0.30%     
==========================================
  Files          18       19       +1     
  Lines         844      872      +28     
  Branches       97       99       +2     
==========================================
+ Hits          770      793      +23     
- Misses         46       50       +4     
- Partials       28       29       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@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 there - I've reviewed your changes - here's some feedback:

  • Consider programmatically constructing the repetitive URL patterns and names (e.g., via a loop or helper) to reduce boilerplate and minimize risk of naming mismatches across menu endpoints.
  • Add a test that introspects the generated OpenAPI schema and asserts the new operationId values so you can verify distinct IDs are applied as intended.
  • In the ImportError fallback for create_view_with_url_name, you might still attach the _url_name metadata even when drf-spectacular isn’t installed to keep view metadata consistent.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider programmatically constructing the repetitive URL patterns and names (e.g., via a loop or helper) to reduce boilerplate and minimize risk of naming mismatches across menu endpoints.
- Add a test that introspects the generated OpenAPI schema and asserts the new operationId values so you can verify distinct IDs are applied as intended.
- In the ImportError fallback for create_view_with_url_name, you might still attach the `_url_name` metadata even when drf-spectacular isn’t installed to keep view metadata consistent.

## Individual Comments

### Comment 1
<location> `tests/endpoints/test_menu.py:124` </location>
<code_context>

     def test_non_existing_root_id(self):
         url = reverse(
-            "menu",
+            "menu-root-levels",
             kwargs={
                 "language": "en",
+                "root_id": "I_DO_NOT_EXIST",
                 "from_level": 0,
                 "to_level": 100,
                 "extra_inactive": 0,
                 "extra_active": 100,
-                "root_id": "I_DO_NOT_EXIST",
             },
         )
         response = self.client.get(url)
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding assertions for the specific error response structure and status code.

Additionally, assert the error message or response structure to verify the API returns a meaningful error payload and to strengthen coverage for error handling.

```suggestion
        response = self.client.get(url)
        # Assert status code (assuming 404 for non-existing root_id)
        assert response.status_code == 404

        # Assert error response structure
        data = response.json()
        assert "detail" in data or "error" in data

        # Assert error message is meaningful
        if "detail" in data:
            assert "not found" in data["detail"].lower() or "does not exist" in data["detail"].lower()
        elif "error" in data:
            assert "not found" in data["error"].lower() or "does not exist" in data["error"].lower()
```
</issue_to_address>

### Comment 2
<location> `djangocms_rest/schemas.py:3` </location>
<code_context>
+"""OpenAPI schema generation utilities for djangocms-rest."""
+
+try:
+    from drf_spectacular.openapi import AutoSchema
+    from drf_spectacular.types import OpenApiTypes
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring the schema utilities to use a single import-time feature flag and unified conditional definitions for reduced duplication and clearer logic.

```python
# schemas.py

# 1) Single import‐time check instead of repeating try/except
try:
    from drf_spectacular.openapi import AutoSchema
    from drf_spectacular.types import OpenApiTypes
    from drf_spectacular.utils import (
        OpenApiParameter, OpenApiResponse, extend_schema,
    )
    HAS_SPECTACULAR = True
except ImportError:
    HAS_SPECTACULAR = False

# 2) Custom AutoSchema lives only if spectacular is installed
if HAS_SPECTACULAR:
    class MenuSchema(AutoSchema):
        """
        Uses a stored `_url_name` on the view to generate operation_id,
        falling back to the parent impl if missing.
        """
        def get_operation_id(self):
            url_name = getattr(self.view, "_url_name", None) \
                       or getattr(self.view.__class__, "_url_name", None)
            if url_name:
                return url_name.replace("-", "_") + "_retrieve"
            return super().get_operation_id()

# 3) Unified decorator/factory defs that noop when HAS_SPECTACULAR is False

def menu_schema_class(cls):
    """Attach MenuSchema() to a view class, or no‐op."""
    if HAS_SPECTACULAR:
        cls.schema = MenuSchema()
    return cls

def method_schema_decorator(fn):
    """
    Force many=True on NavigationNodeSerializer for list endpoints,
    or no‐op if spectacular is absent.
    """
    if HAS_SPECTACULAR:
        from djangocms_rest.serializers.menus import NavigationNodeSerializer
        response = OpenApiResponse(response=NavigationNodeSerializer(many=True))
        return extend_schema(responses=response)(fn)
    return fn

# 4) Single factory for as_view + url_name
def as_view_with_url_name(viewset, *args, url_name=None, **kwargs):
    """
    Wrap as_view, store `_url_name` on the returned view for schema.
    """
    view = viewset.as_view(*args, **kwargs)
    if HAS_SPECTACULAR and url_name:
        view._url_name = url_name
    return view

# 5) Single placeholder decorator for shared parameters
if HAS_SPECTACULAR:
    extend_placeholder_schema = extend_schema(
        parameters=[
            OpenApiParameter(
                name="html",
                type=OpenApiTypes.INT,
                location="query",
                description="Include HTML rendering (set to 1)",
                required=False,
                enum=[1],
            ),
            OpenApiParameter(
                name="preview",
                type=OpenApiTypes.BOOL,
                location="query",
                description="Preview unpublished content",
                required=False,
            ),
        ]
    )
else:
    extend_placeholder_schema = lambda fn: fn
```

Key reductions:
- One `HAS_SPECTACULAR` flag at module top instead of duplicated stubs.
- Conditional class and decorator definitions that either apply spectacular logic or noop.
- Merged `create_view_with_url_name` and decorator into a single `as_view_with_url_name`.
- No repeated `# pragma: no cover` blocks or multiple try/except layers.
</issue_to_address>

### Comment 3
<location> `djangocms_rest/schemas.py:25-27` </location>
<code_context>
        def get_operation_id(self):
            """Override to use URL name stored in view as operation_id."""
            try:
                url_name = getattr(self.view, "_url_name", None)
                if not url_name and hasattr(self.view, "__class__"):
                    url_name = getattr(self.view.__class__, "_url_name", None)

                if url_name:
                    operation_id = url_name.replace("-", "_") + "_retrieve"
                    return operation_id

                # Fallback to default
                return super().get_operation_id()
            except Exception:
                return super().get_operation_id()

</code_context>

<issue_to_address>
**suggestion (code-quality):** Inline variable that is immediately returned ([`inline-immediately-returned-variable`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/inline-immediately-returned-variable/))

```suggestion
                    return url_name.replace("-", "_") + "_retrieve"
```
</issue_to_address>

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.

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