From 3694e40c046ef37916b9c3fb59d0def59f84f8a1 Mon Sep 17 00:00:00 2001 From: "IGNY8 VPS (Salman)" Date: Sun, 16 Nov 2025 04:48:14 +0000 Subject: [PATCH] Enhance API documentation and schema management by implementing explicit tag configurations for Swagger and ReDoc. Introduce postprocessing hooks to filter auto-generated tags, ensuring only defined tags are used. Update viewsets across various modules to utilize the new tagging system, improving clarity and organization in API documentation. --- =0.27.0 | 36 ++++++++++ backend/igny8_core/api/schema_extensions.py | 46 ++++++++++++ backend/igny8_core/auth/urls.py | 25 ++++++- backend/igny8_core/auth/views.py | 71 +++++++++++++++++++ backend/igny8_core/modules/billing/views.py | 12 ++++ backend/igny8_core/modules/planner/views.py | 25 +++++++ .../modules/system/integration_views.py | 9 +++ .../modules/system/settings_views.py | 41 +++++++++++ backend/igny8_core/modules/system/views.py | 25 +++++++ backend/igny8_core/modules/writer/views.py | 25 +++++++ backend/igny8_core/settings.py | 51 ++++++++++++- 11 files changed, 363 insertions(+), 3 deletions(-) create mode 100644 =0.27.0 diff --git a/=0.27.0 b/=0.27.0 new file mode 100644 index 00000000..f5d18029 --- /dev/null +++ b/=0.27.0 @@ -0,0 +1,36 @@ +Collecting drf-spectacular + Downloading drf_spectacular-0.29.0-py3-none-any.whl.metadata (14 kB) +Requirement already satisfied: Django>=2.2 in /usr/local/lib/python3.11/site-packages (from drf-spectacular) (5.2.8) +Requirement already satisfied: djangorestframework>=3.10.3 in /usr/local/lib/python3.11/site-packages (from drf-spectacular) (3.16.1) +Collecting uritemplate>=2.0.0 (from drf-spectacular) + Downloading uritemplate-4.2.0-py3-none-any.whl.metadata (2.6 kB) +Collecting PyYAML>=5.1 (from drf-spectacular) + Downloading pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (2.4 kB) +Collecting jsonschema>=2.6.0 (from drf-spectacular) + Downloading jsonschema-4.25.1-py3-none-any.whl.metadata (7.6 kB) +Collecting inflection>=0.3.1 (from drf-spectacular) + Downloading inflection-0.5.1-py2.py3-none-any.whl.metadata (1.7 kB) +Requirement already satisfied: asgiref>=3.8.1 in /usr/local/lib/python3.11/site-packages (from Django>=2.2->drf-spectacular) (3.10.0) +Requirement already satisfied: sqlparse>=0.3.1 in /usr/local/lib/python3.11/site-packages (from Django>=2.2->drf-spectacular) (0.5.3) +Collecting attrs>=22.2.0 (from jsonschema>=2.6.0->drf-spectacular) + Downloading attrs-25.4.0-py3-none-any.whl.metadata (10 kB) +Collecting jsonschema-specifications>=2023.03.6 (from jsonschema>=2.6.0->drf-spectacular) + Downloading jsonschema_specifications-2025.9.1-py3-none-any.whl.metadata (2.9 kB) +Collecting referencing>=0.28.4 (from jsonschema>=2.6.0->drf-spectacular) + Downloading referencing-0.37.0-py3-none-any.whl.metadata (2.8 kB) +Collecting rpds-py>=0.7.1 (from jsonschema>=2.6.0->drf-spectacular) + Downloading rpds_py-0.28.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.1 kB) +Requirement already satisfied: typing-extensions>=4.4.0 in /usr/local/lib/python3.11/site-packages (from referencing>=0.28.4->jsonschema>=2.6.0->drf-spectacular) (4.15.0) +Downloading drf_spectacular-0.29.0-py3-none-any.whl (105 kB) +Downloading inflection-0.5.1-py2.py3-none-any.whl (9.5 kB) +Downloading jsonschema-4.25.1-py3-none-any.whl (90 kB) +Downloading attrs-25.4.0-py3-none-any.whl (67 kB) +Downloading jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB) +Downloading pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (806 kB) + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 806.6/806.6 kB 7.8 MB/s 0:00:00 +Downloading referencing-0.37.0-py3-none-any.whl (26 kB) +Downloading rpds_py-0.28.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (382 kB) +Downloading uritemplate-4.2.0-py3-none-any.whl (11 kB) +Installing collected packages: uritemplate, rpds-py, PyYAML, inflection, attrs, referencing, jsonschema-specifications, jsonschema, drf-spectacular + +Successfully installed PyYAML-6.0.3 attrs-25.4.0 drf-spectacular-0.29.0 inflection-0.5.1 jsonschema-4.25.1 jsonschema-specifications-2025.9.1 referencing-0.37.0 rpds-py-0.28.0 uritemplate-4.2.0 diff --git a/backend/igny8_core/api/schema_extensions.py b/backend/igny8_core/api/schema_extensions.py index 9589315a..69b92e67 100644 --- a/backend/igny8_core/api/schema_extensions.py +++ b/backend/igny8_core/api/schema_extensions.py @@ -7,6 +7,52 @@ from drf_spectacular.plumbing import build_bearer_security_scheme_object from drf_spectacular.utils import extend_schema, OpenApiResponse from rest_framework import status +# Explicit tags we want to keep (from SPECTACULAR_SETTINGS) +EXPLICIT_TAGS = {'Authentication', 'Planner', 'Writer', 'System', 'Billing'} + + +def postprocess_schema_filter_tags(result, generator, request, public): + """ + Postprocessing hook to remove auto-generated tags and keep only explicit tags. + This prevents duplicate tags from URL patterns (auth, planner, etc.) + """ + # First, filter tags from all paths (operations) + if 'paths' in result: + for path, methods in result['paths'].items(): + for method, operation in methods.items(): + if isinstance(operation, dict) and 'tags' in operation: + # Keep only explicit tags from the operation + filtered_tags = [ + tag for tag in operation['tags'] + if tag in EXPLICIT_TAGS + ] + + # If no explicit tags found, infer from path + if not filtered_tags: + if '/auth/' in path or '/api/v1/auth/' in path: + filtered_tags = ['Authentication'] + elif '/planner/' in path or '/api/v1/planner/' in path: + filtered_tags = ['Planner'] + elif '/writer/' in path or '/api/v1/writer/' in path: + filtered_tags = ['Writer'] + elif '/system/' in path or '/api/v1/system/' in path: + filtered_tags = ['System'] + elif '/billing/' in path or '/api/v1/billing/' in path: + filtered_tags = ['Billing'] + + operation['tags'] = filtered_tags + + # Now filter the tags list - keep only explicit tag definitions + # The tags list contains tag definitions with 'name' and 'description' + if 'tags' in result and isinstance(result['tags'], list): + # Keep only tags that match our explicit tag names + result['tags'] = [ + tag for tag in result['tags'] + if isinstance(tag, dict) and tag.get('name') in EXPLICIT_TAGS + ] + + return result + class JWTAuthenticationExtension(OpenApiAuthenticationExtension): """ diff --git a/backend/igny8_core/auth/urls.py b/backend/igny8_core/auth/urls.py index b52c62bc..6f41cc45 100644 --- a/backend/igny8_core/auth/urls.py +++ b/backend/igny8_core/auth/urls.py @@ -7,10 +7,11 @@ from rest_framework.routers import DefaultRouter from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status, permissions +from drf_spectacular.utils import extend_schema from .views import ( GroupsViewSet, UsersViewSet, AccountsViewSet, SubscriptionsViewSet, SiteUserAccessViewSet, PlanViewSet, SiteViewSet, SectorViewSet, - IndustryViewSet, SeedKeywordViewSet, AuthViewSet + IndustryViewSet, SeedKeywordViewSet ) from .serializers import RegisterSerializer, LoginSerializer, ChangePasswordSerializer, UserSerializer from .models import User @@ -29,9 +30,14 @@ router.register(r'sites', SiteViewSet, basename='site') router.register(r'sectors', SectorViewSet, basename='sector') router.register(r'industries', IndustryViewSet, basename='industry') router.register(r'seed-keywords', SeedKeywordViewSet, basename='seed-keyword') -router.register(r'auth', AuthViewSet, basename='auth') +# Note: AuthViewSet removed - using direct APIView endpoints instead (login, register, etc.) +@extend_schema( + tags=['Authentication'], + summary='User Registration', + description='Register a new user account' +) class RegisterView(APIView): """Registration endpoint.""" permission_classes = [permissions.AllowAny] @@ -52,6 +58,11 @@ class RegisterView(APIView): }, status=status.HTTP_400_BAD_REQUEST) +@extend_schema( + tags=['Authentication'], + summary='User Login', + description='Authenticate user and receive JWT tokens' +) class LoginView(APIView): """Login endpoint.""" permission_classes = [permissions.AllowAny] @@ -123,6 +134,11 @@ class LoginView(APIView): }, status=status.HTTP_400_BAD_REQUEST) +@extend_schema( + tags=['Authentication'], + summary='Change Password', + description='Change user password' +) class ChangePasswordView(APIView): """Change password endpoint.""" permission_classes = [permissions.IsAuthenticated] @@ -151,6 +167,11 @@ class ChangePasswordView(APIView): }, status=status.HTTP_400_BAD_REQUEST) +@extend_schema( + tags=['Authentication'], + summary='Get Current User', + description='Get information about the currently authenticated user' +) class MeView(APIView): """Get current user information.""" permission_classes = [permissions.IsAuthenticated] diff --git a/backend/igny8_core/auth/views.py b/backend/igny8_core/auth/views.py index 446cb610..01ac8c4c 100644 --- a/backend/igny8_core/auth/views.py +++ b/backend/igny8_core/auth/views.py @@ -10,6 +10,7 @@ from django.contrib.auth import authenticate from django.utils import timezone from django.db import transaction from django_filters.rest_framework import DjangoFilterBackend +from drf_spectacular.utils import extend_schema, extend_schema_view from igny8_core.api.base import AccountModelViewSet from igny8_core.api.authentication import JWTAuthentication, CSRFExemptSessionAuthentication from igny8_core.api.response import success_response, error_response @@ -33,6 +34,10 @@ import jwt # 1. GROUPS - Define user roles and permissions across the system # ============================================================================ +@extend_schema_view( + list=extend_schema(tags=['Authentication']), + retrieve=extend_schema(tags=['Authentication']), +) class GroupsViewSet(viewsets.ViewSet): """ ViewSet for managing user roles and permissions (Groups). @@ -119,6 +124,14 @@ class GroupsViewSet(viewsets.ViewSet): # 2. USERS - Manage global user records and credentials # ============================================================================ +@extend_schema_view( + list=extend_schema(tags=['Authentication']), + create=extend_schema(tags=['Authentication']), + retrieve=extend_schema(tags=['Authentication']), + update=extend_schema(tags=['Authentication']), + partial_update=extend_schema(tags=['Authentication']), + destroy=extend_schema(tags=['Authentication']), +) class UsersViewSet(AccountModelViewSet): """ ViewSet for managing global user records and credentials. @@ -246,6 +259,14 @@ class UsersViewSet(AccountModelViewSet): # 3. ACCOUNTS - Register each unique organization/user space # ============================================================================ +@extend_schema_view( + list=extend_schema(tags=['Authentication']), + create=extend_schema(tags=['Authentication']), + retrieve=extend_schema(tags=['Authentication']), + update=extend_schema(tags=['Authentication']), + partial_update=extend_schema(tags=['Authentication']), + destroy=extend_schema(tags=['Authentication']), +) class AccountsViewSet(AccountModelViewSet): """ ViewSet for managing accounts (unique organization/user spaces). @@ -303,6 +324,14 @@ class AccountsViewSet(AccountModelViewSet): # 4. SUBSCRIPTIONS - Control plan level, limits, and billing per account # ============================================================================ +@extend_schema_view( + list=extend_schema(tags=['Authentication']), + create=extend_schema(tags=['Authentication']), + retrieve=extend_schema(tags=['Authentication']), + update=extend_schema(tags=['Authentication']), + partial_update=extend_schema(tags=['Authentication']), + destroy=extend_schema(tags=['Authentication']), +) class SubscriptionsViewSet(AccountModelViewSet): """ ViewSet for managing subscriptions (plan level, limits, billing per account). @@ -356,6 +385,14 @@ class SubscriptionsViewSet(AccountModelViewSet): # 5. SITE USER ACCESS - Assign users access to specific sites within account # ============================================================================ +@extend_schema_view( + list=extend_schema(tags=['Authentication']), + create=extend_schema(tags=['Authentication']), + retrieve=extend_schema(tags=['Authentication']), + update=extend_schema(tags=['Authentication']), + partial_update=extend_schema(tags=['Authentication']), + destroy=extend_schema(tags=['Authentication']), +) class SiteUserAccessViewSet(AccountModelViewSet): """ ViewSet for managing Site-User access permissions. @@ -394,6 +431,10 @@ class SiteUserAccessViewSet(AccountModelViewSet): # SUPPORTING VIEWSETS (Sites, Sectors, Industries, Plans, Auth) # ============================================================================ +@extend_schema_view( + list=extend_schema(tags=['Authentication']), + retrieve=extend_schema(tags=['Authentication']), +) class PlanViewSet(viewsets.ReadOnlyModelViewSet): """ ViewSet for listing active subscription plans. @@ -420,6 +461,14 @@ class PlanViewSet(viewsets.ReadOnlyModelViewSet): ) +@extend_schema_view( + list=extend_schema(tags=['Authentication']), + create=extend_schema(tags=['Authentication']), + retrieve=extend_schema(tags=['Authentication']), + update=extend_schema(tags=['Authentication']), + partial_update=extend_schema(tags=['Authentication']), + destroy=extend_schema(tags=['Authentication']), +) class SiteViewSet(AccountModelViewSet): """ViewSet for managing Sites.""" serializer_class = SiteSerializer @@ -655,6 +704,14 @@ class SiteViewSet(AccountModelViewSet): ) +@extend_schema_view( + list=extend_schema(tags=['Authentication']), + create=extend_schema(tags=['Authentication']), + retrieve=extend_schema(tags=['Authentication']), + update=extend_schema(tags=['Authentication']), + partial_update=extend_schema(tags=['Authentication']), + destroy=extend_schema(tags=['Authentication']), +) class SectorViewSet(AccountModelViewSet): """ViewSet for managing Sectors.""" serializer_class = SectorSerializer @@ -692,6 +749,10 @@ class SectorViewSet(AccountModelViewSet): ) +@extend_schema_view( + list=extend_schema(tags=['Authentication']), + retrieve=extend_schema(tags=['Authentication']), +) class IndustryViewSet(viewsets.ReadOnlyModelViewSet): """ ViewSet for industry templates. @@ -727,6 +788,10 @@ class IndustryViewSet(viewsets.ReadOnlyModelViewSet): ) +@extend_schema_view( + list=extend_schema(tags=['Authentication']), + retrieve=extend_schema(tags=['Authentication']), +) class SeedKeywordViewSet(viewsets.ReadOnlyModelViewSet): """ ViewSet for SeedKeyword - Global reference data (read-only for non-admins). @@ -776,6 +841,12 @@ class SeedKeywordViewSet(viewsets.ReadOnlyModelViewSet): # AUTHENTICATION ENDPOINTS (Register, Login, Change Password, Me) # ============================================================================ +@extend_schema_view( + register=extend_schema(tags=['Authentication']), + login=extend_schema(tags=['Authentication']), + change_password=extend_schema(tags=['Authentication']), + refresh_token=extend_schema(tags=['Authentication']), +) class AuthViewSet(viewsets.GenericViewSet): """Authentication endpoints. Unified API Standard v1.0 compliant diff --git a/backend/igny8_core/modules/billing/views.py b/backend/igny8_core/modules/billing/views.py index 89353333..b429e637 100644 --- a/backend/igny8_core/modules/billing/views.py +++ b/backend/igny8_core/modules/billing/views.py @@ -9,6 +9,7 @@ from django.db.models import Sum, Count, Q from django.utils import timezone from datetime import timedelta from decimal import Decimal +from drf_spectacular.utils import extend_schema, extend_schema_view from igny8_core.api.base import AccountModelViewSet from igny8_core.api.pagination import CustomPageNumberPagination from igny8_core.api.response import success_response, error_response @@ -23,6 +24,9 @@ from .services import CreditService from .exceptions import InsufficientCreditsError +@extend_schema_view( + list=extend_schema(tags=['Billing']), +) class CreditBalanceViewSet(viewsets.ViewSet): """ ViewSet for credit balance operations @@ -73,6 +77,10 @@ class CreditBalanceViewSet(viewsets.ViewSet): return success_response(data=serializer.data, request=request) +@extend_schema_view( + list=extend_schema(tags=['Billing']), + retrieve=extend_schema(tags=['Billing']), +) class CreditUsageViewSet(viewsets.ReadOnlyModelViewSet): """ ViewSet for credit usage logs @@ -444,6 +452,10 @@ class CreditUsageViewSet(viewsets.ReadOnlyModelViewSet): return success_response(data={'limits': limits_data}, request=request) +@extend_schema_view( + list=extend_schema(tags=['Billing']), + retrieve=extend_schema(tags=['Billing']), +) class CreditTransactionViewSet(viewsets.ReadOnlyModelViewSet): """ ViewSet for credit transaction history diff --git a/backend/igny8_core/modules/planner/views.py b/backend/igny8_core/modules/planner/views.py index e4a3ec57..25297bcc 100644 --- a/backend/igny8_core/modules/planner/views.py +++ b/backend/igny8_core/modules/planner/views.py @@ -8,6 +8,7 @@ from django.http import HttpResponse import csv import json import time +from drf_spectacular.utils import extend_schema, extend_schema_view from igny8_core.api.base import SiteSectorModelViewSet from igny8_core.api.pagination import CustomPageNumberPagination from igny8_core.api.response import success_response, error_response @@ -18,6 +19,14 @@ from .serializers import KeywordSerializer, ContentIdeasSerializer from .cluster_serializers import ClusterSerializer +@extend_schema_view( + list=extend_schema(tags=['Planner']), + create=extend_schema(tags=['Planner']), + retrieve=extend_schema(tags=['Planner']), + update=extend_schema(tags=['Planner']), + partial_update=extend_schema(tags=['Planner']), + destroy=extend_schema(tags=['Planner']), +) class KeywordViewSet(SiteSectorModelViewSet): """ ViewSet for managing keywords with CRUD operations @@ -662,6 +671,14 @@ class KeywordViewSet(SiteSectorModelViewSet): ) +@extend_schema_view( + list=extend_schema(tags=['Planner']), + create=extend_schema(tags=['Planner']), + retrieve=extend_schema(tags=['Planner']), + update=extend_schema(tags=['Planner']), + partial_update=extend_schema(tags=['Planner']), + destroy=extend_schema(tags=['Planner']), +) class ClusterViewSet(SiteSectorModelViewSet): """ ViewSet for managing clusters with CRUD operations @@ -952,6 +969,14 @@ class ClusterViewSet(SiteSectorModelViewSet): ) +@extend_schema_view( + list=extend_schema(tags=['Planner']), + create=extend_schema(tags=['Planner']), + retrieve=extend_schema(tags=['Planner']), + update=extend_schema(tags=['Planner']), + partial_update=extend_schema(tags=['Planner']), + destroy=extend_schema(tags=['Planner']), +) class ContentIdeasViewSet(SiteSectorModelViewSet): """ ViewSet for managing content ideas with CRUD operations diff --git a/backend/igny8_core/modules/system/integration_views.py b/backend/igny8_core/modules/system/integration_views.py index a92a9dc8..facf6e11 100644 --- a/backend/igny8_core/modules/system/integration_views.py +++ b/backend/igny8_core/modules/system/integration_views.py @@ -7,6 +7,7 @@ from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response from django.db import transaction +from drf_spectacular.utils import extend_schema, extend_schema_view from igny8_core.api.base import AccountModelViewSet from igny8_core.api.response import success_response, error_response from igny8_core.api.throttles import DebugScopedRateThrottle @@ -16,6 +17,14 @@ from django.conf import settings logger = logging.getLogger(__name__) +@extend_schema_view( + list=extend_schema(tags=['System']), + retrieve=extend_schema(tags=['System']), + update=extend_schema(tags=['System']), + test_connection=extend_schema(tags=['System']), + task_progress=extend_schema(tags=['System']), + get_image_generation_settings=extend_schema(tags=['System']), +) class IntegrationSettingsViewSet(viewsets.ViewSet): """ ViewSet for managing integration settings (OpenAI, Runware, GSC) diff --git a/backend/igny8_core/modules/system/settings_views.py b/backend/igny8_core/modules/system/settings_views.py index 88c26e10..b78ca87a 100644 --- a/backend/igny8_core/modules/system/settings_views.py +++ b/backend/igny8_core/modules/system/settings_views.py @@ -6,6 +6,7 @@ from rest_framework import viewsets, status, permissions from rest_framework.decorators import action from rest_framework.response import Response from django.db import transaction +from drf_spectacular.utils import extend_schema, extend_schema_view from igny8_core.api.base import AccountModelViewSet from igny8_core.api.response import success_response, error_response from igny8_core.api.authentication import JWTAuthentication, CSRFExemptSessionAuthentication @@ -18,6 +19,14 @@ from .settings_serializers import ( ) +@extend_schema_view( + list=extend_schema(tags=['System']), + create=extend_schema(tags=['System']), + retrieve=extend_schema(tags=['System']), + update=extend_schema(tags=['System']), + partial_update=extend_schema(tags=['System']), + destroy=extend_schema(tags=['System']), +) class SystemSettingsViewSet(AccountModelViewSet): """ ViewSet for managing system-wide settings (admin only for write operations) @@ -61,6 +70,14 @@ class SystemSettingsViewSet(AccountModelViewSet): return success_response(data=serializer.data, request=request) +@extend_schema_view( + list=extend_schema(tags=['System']), + create=extend_schema(tags=['System']), + retrieve=extend_schema(tags=['System']), + update=extend_schema(tags=['System']), + partial_update=extend_schema(tags=['System']), + destroy=extend_schema(tags=['System']), +) class AccountSettingsViewSet(AccountModelViewSet): """ ViewSet for managing account-level settings @@ -113,6 +130,14 @@ class AccountSettingsViewSet(AccountModelViewSet): serializer.save(account=account) +@extend_schema_view( + list=extend_schema(tags=['System']), + create=extend_schema(tags=['System']), + retrieve=extend_schema(tags=['System']), + update=extend_schema(tags=['System']), + partial_update=extend_schema(tags=['System']), + destroy=extend_schema(tags=['System']), +) class UserSettingsViewSet(AccountModelViewSet): """ ViewSet for managing user-level settings @@ -171,6 +196,14 @@ class UserSettingsViewSet(AccountModelViewSet): serializer.save(user=user, account=account) +@extend_schema_view( + list=extend_schema(tags=['System']), + create=extend_schema(tags=['System']), + retrieve=extend_schema(tags=['System']), + update=extend_schema(tags=['System']), + partial_update=extend_schema(tags=['System']), + destroy=extend_schema(tags=['System']), +) class ModuleSettingsViewSet(AccountModelViewSet): """ ViewSet for managing module-specific settings @@ -242,6 +275,14 @@ class ModuleSettingsViewSet(AccountModelViewSet): serializer.save(account=account) +@extend_schema_view( + list=extend_schema(tags=['System']), + create=extend_schema(tags=['System']), + retrieve=extend_schema(tags=['System']), + update=extend_schema(tags=['System']), + partial_update=extend_schema(tags=['System']), + destroy=extend_schema(tags=['System']), +) class AISettingsViewSet(AccountModelViewSet): """ ViewSet for managing AI-specific settings diff --git a/backend/igny8_core/modules/system/views.py b/backend/igny8_core/modules/system/views.py index 6124e5c3..554a750e 100644 --- a/backend/igny8_core/modules/system/views.py +++ b/backend/igny8_core/modules/system/views.py @@ -12,6 +12,7 @@ from django.db import transaction, connection from django.core.cache import cache from django.utils import timezone from django_filters.rest_framework import DjangoFilterBackend +from drf_spectacular.utils import extend_schema, extend_schema_view from igny8_core.api.base import AccountModelViewSet from igny8_core.api.response import success_response, error_response from igny8_core.api.permissions import IsEditorOrAbove, IsAuthenticatedAndActive, IsViewerOrAbove @@ -23,6 +24,14 @@ from .serializers import AIPromptSerializer, AuthorProfileSerializer, StrategySe logger = logging.getLogger(__name__) +@extend_schema_view( + list=extend_schema(tags=['System']), + create=extend_schema(tags=['System']), + retrieve=extend_schema(tags=['System']), + update=extend_schema(tags=['System']), + partial_update=extend_schema(tags=['System']), + destroy=extend_schema(tags=['System']), +) class AIPromptViewSet(AccountModelViewSet): """ ViewSet for managing AI prompts @@ -192,6 +201,14 @@ class AIPromptViewSet(AccountModelViewSet): ) +@extend_schema_view( + list=extend_schema(tags=['System']), + create=extend_schema(tags=['System']), + retrieve=extend_schema(tags=['System']), + update=extend_schema(tags=['System']), + partial_update=extend_schema(tags=['System']), + destroy=extend_schema(tags=['System']), +) class AuthorProfileViewSet(AccountModelViewSet): """ ViewSet for managing Author Profiles @@ -210,6 +227,14 @@ class AuthorProfileViewSet(AccountModelViewSet): filterset_fields = ['is_active', 'language'] +@extend_schema_view( + list=extend_schema(tags=['System']), + create=extend_schema(tags=['System']), + retrieve=extend_schema(tags=['System']), + update=extend_schema(tags=['System']), + partial_update=extend_schema(tags=['System']), + destroy=extend_schema(tags=['System']), +) class StrategyViewSet(AccountModelViewSet): """ ViewSet for managing Strategies diff --git a/backend/igny8_core/modules/writer/views.py b/backend/igny8_core/modules/writer/views.py index a36f47b5..96cde1d8 100644 --- a/backend/igny8_core/modules/writer/views.py +++ b/backend/igny8_core/modules/writer/views.py @@ -4,6 +4,7 @@ from rest_framework.response import Response from django_filters.rest_framework import DjangoFilterBackend from django.db import transaction, models from django.db.models import Q +from drf_spectacular.utils import extend_schema, extend_schema_view from igny8_core.api.base import SiteSectorModelViewSet from igny8_core.api.pagination import CustomPageNumberPagination from igny8_core.api.response import success_response, error_response @@ -13,6 +14,14 @@ from .models import Tasks, Images, Content from .serializers import TasksSerializer, ImagesSerializer, ContentSerializer +@extend_schema_view( + list=extend_schema(tags=['Writer']), + create=extend_schema(tags=['Writer']), + retrieve=extend_schema(tags=['Writer']), + update=extend_schema(tags=['Writer']), + partial_update=extend_schema(tags=['Writer']), + destroy=extend_schema(tags=['Writer']), +) class TasksViewSet(SiteSectorModelViewSet): """ ViewSet for managing tasks with CRUD operations @@ -374,6 +383,14 @@ class TasksViewSet(SiteSectorModelViewSet): ) +@extend_schema_view( + list=extend_schema(tags=['Writer']), + create=extend_schema(tags=['Writer']), + retrieve=extend_schema(tags=['Writer']), + update=extend_schema(tags=['Writer']), + partial_update=extend_schema(tags=['Writer']), + destroy=extend_schema(tags=['Writer']), +) class ImagesViewSet(SiteSectorModelViewSet): """ ViewSet for managing content images @@ -773,6 +790,14 @@ class ImagesViewSet(SiteSectorModelViewSet): request=request ) +@extend_schema_view( + list=extend_schema(tags=['Writer']), + create=extend_schema(tags=['Writer']), + retrieve=extend_schema(tags=['Writer']), + update=extend_schema(tags=['Writer']), + partial_update=extend_schema(tags=['Writer']), + destroy=extend_schema(tags=['Writer']), +) class ContentViewSet(SiteSectorModelViewSet): """ ViewSet for managing task content diff --git a/backend/igny8_core/settings.py b/backend/igny8_core/settings.py index 2a5a0966..9b0bc051 100644 --- a/backend/igny8_core/settings.py +++ b/backend/igny8_core/settings.py @@ -331,7 +331,8 @@ SPECTACULAR_SETTINGS = { # Include request/response examples 'SERVE_PERMISSIONS': ['rest_framework.permissions.AllowAny'], 'SERVE_AUTHENTICATION': None, # Allow unauthenticated access to docs - # Tags for grouping endpoints + + # Tag configuration - prevent auto-generation and use explicit tags 'TAGS': [ {'name': 'Authentication', 'description': 'User authentication and registration'}, {'name': 'Planner', 'description': 'Keywords, clusters, and content ideas'}, @@ -339,6 +340,54 @@ SPECTACULAR_SETTINGS = { {'name': 'System', 'description': 'Settings, prompts, and integrations'}, {'name': 'Billing', 'description': 'Credits, usage, and transactions'}, ], + 'TAGS_ORDER': ['Authentication', 'Planner', 'Writer', 'System', 'Billing'], + # Postprocessing hook to filter out auto-generated tags + 'POSTPROCESSING_HOOKS': ['igny8_core.api.schema_extensions.postprocess_schema_filter_tags'], + + # Swagger UI configuration + 'SWAGGER_UI_SETTINGS': { + 'deepLinking': True, + 'displayOperationId': False, + 'defaultModelsExpandDepth': 1, # Collapse models by default + 'defaultModelExpandDepth': 1, # Collapse model properties by default + 'defaultModelRendering': 'model', # Show models in a cleaner format + 'displayRequestDuration': True, + 'docExpansion': 'none', # Collapse all operations by default + 'filter': True, # Enable filter box + 'showExtensions': True, + 'showCommonExtensions': True, + 'tryItOutEnabled': True, # Enable "Try it out" by default + }, + + # ReDoc configuration + 'REDOC_UI_SETTINGS': { + 'hideDownloadButton': False, + 'hideHostname': False, + 'hideLoading': False, + 'hideSingleRequestSampleTab': False, + 'expandResponses': '200,201', # Expand successful responses + 'jsonSampleExpandLevel': 2, # Expand JSON samples 2 levels + 'hideFab': False, + 'theme': { + 'colors': { + 'primary': { + 'main': '#32329f' + } + } + } + }, + + # Schema presentation improvements + 'SCHEMA_COERCE_PATH_PK': True, + 'SCHEMA_COERCE_METHOD_NAMES': { + 'retrieve': 'get', + 'list': 'list', + 'create': 'post', + 'update': 'put', + 'partial_update': 'patch', + 'destroy': 'delete', + }, + # Custom response format documentation 'EXTENSIONS_INFO': { 'x-code-samples': [