Implement unified API standard across backend viewsets and serializers, enhancing error handling and response formatting. Update AccountModelViewSet to standardize CRUD operations with success and error responses. Refactor various viewsets to inherit from AccountModelViewSet, ensuring compliance with the new standard. Improve frontend components to handle API responses consistently and update configuration for better user experience.

This commit is contained in:
IGNY8 VPS (Salman)
2025-11-15 23:04:31 +00:00
parent 5a3706d997
commit 0ec594363c
11 changed files with 593 additions and 113 deletions

View File

@@ -13,7 +13,8 @@ class KeywordSerializer(serializers.ModelSerializer):
intent = serializers.CharField(read_only=True) # From seed_keyword.intent
# SeedKeyword relationship
seed_keyword_id = serializers.IntegerField(write_only=True, required=True)
# Required for create, optional for update (can change seed_keyword or just update other fields)
seed_keyword_id = serializers.IntegerField(write_only=True, required=False)
seed_keyword = SeedKeywordSerializer(read_only=True)
# Overrides
@@ -50,9 +51,19 @@ class KeywordSerializer(serializers.ModelSerializer):
]
read_only_fields = ['id', 'created_at', 'updated_at', 'account_id', 'keyword', 'volume', 'difficulty', 'intent']
def validate(self, attrs):
"""Validate that seed_keyword_id is provided for create operations"""
# For create operations, seed_keyword_id is required
if self.instance is None and 'seed_keyword_id' not in attrs:
raise serializers.ValidationError({'seed_keyword_id': 'This field is required when creating a keyword.'})
return attrs
def create(self, validated_data):
"""Create Keywords instance with seed_keyword"""
seed_keyword_id = validated_data.pop('seed_keyword_id')
seed_keyword_id = validated_data.pop('seed_keyword_id', None)
if not seed_keyword_id:
raise serializers.ValidationError({'seed_keyword_id': 'This field is required when creating a keyword.'})
try:
seed_keyword = SeedKeyword.objects.get(id=seed_keyword_id)
except SeedKeyword.DoesNotExist:
@@ -63,6 +74,7 @@ class KeywordSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data):
"""Update Keywords instance with seed_keyword"""
# seed_keyword_id is optional for updates - only update if provided
if 'seed_keyword_id' in validated_data:
seed_keyword_id = validated_data.pop('seed_keyword_id')
try:

View File

@@ -1,12 +1,16 @@
"""
ViewSets for Settings Models
Unified API Standard v1.0 compliant
"""
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 igny8_core.api.base import AccountModelViewSet
from igny8_core.api.response import success_response, error_response
from igny8_core.api.authentication import JWTAuthentication, CSRFExemptSessionAuthentication
from igny8_core.api.pagination import CustomPageNumberPagination
from igny8_core.api.throttles import DebugScopedRateThrottle
from .settings_models import SystemSettings, AccountSettings, UserSettings, ModuleSettings, AISettings
from .settings_serializers import (
SystemSettingsSerializer, AccountSettingsSerializer, UserSettingsSerializer,
@@ -14,14 +18,18 @@ from .settings_serializers import (
)
class SystemSettingsViewSet(viewsets.ModelViewSet):
class SystemSettingsViewSet(AccountModelViewSet):
"""
ViewSet for managing system-wide settings (admin only for write operations)
Unified API Standard v1.0 compliant
"""
queryset = SystemSettings.objects.all()
serializer_class = SystemSettingsSerializer
permission_classes = [permissions.IsAuthenticated] # Require authentication
authentication_classes = [JWTAuthentication, CSRFExemptSessionAuthentication]
pagination_class = CustomPageNumberPagination
throttle_scope = 'system'
throttle_classes = [DebugScopedRateThrottle]
def get_permissions(self):
"""Admin only for write operations, read for authenticated users"""
@@ -43,23 +51,28 @@ class SystemSettingsViewSet(viewsets.ModelViewSet):
try:
setting = SystemSettings.objects.get(key=pk)
except SystemSettings.DoesNotExist:
return Response(
{'error': 'Setting not found'},
status=status.HTTP_404_NOT_FOUND
return error_response(
error='Setting not found',
status_code=status.HTTP_404_NOT_FOUND,
request=request
)
serializer = self.get_serializer(setting)
return Response(serializer.data)
return success_response(data=serializer.data, request=request)
class AccountSettingsViewSet(AccountModelViewSet):
"""
ViewSet for managing account-level settings
Unified API Standard v1.0 compliant
"""
queryset = AccountSettings.objects.all()
serializer_class = AccountSettingsSerializer
permission_classes = [permissions.IsAuthenticated]
authentication_classes = [JWTAuthentication, CSRFExemptSessionAuthentication]
pagination_class = CustomPageNumberPagination
throttle_scope = 'system'
throttle_classes = [DebugScopedRateThrottle]
def get_queryset(self):
"""Get settings for current account"""
@@ -76,13 +89,14 @@ class AccountSettingsViewSet(AccountModelViewSet):
try:
setting = queryset.get(key=pk)
except AccountSettings.DoesNotExist:
return Response(
{'error': 'Setting not found'},
status=status.HTTP_404_NOT_FOUND
return error_response(
error='Setting not found',
status_code=status.HTTP_404_NOT_FOUND,
request=request
)
serializer = self.get_serializer(setting)
return Response(serializer.data)
return success_response(data=serializer.data, request=request)
def perform_create(self, serializer):
"""Set account automatically"""
@@ -99,14 +113,18 @@ class AccountSettingsViewSet(AccountModelViewSet):
serializer.save(account=account)
class UserSettingsViewSet(viewsets.ModelViewSet):
class UserSettingsViewSet(AccountModelViewSet):
"""
ViewSet for managing user-level settings
Unified API Standard v1.0 compliant
"""
queryset = UserSettings.objects.all()
serializer_class = UserSettingsSerializer
permission_classes = [permissions.IsAuthenticated]
authentication_classes = [JWTAuthentication, CSRFExemptSessionAuthentication]
pagination_class = CustomPageNumberPagination
throttle_scope = 'system'
throttle_classes = [DebugScopedRateThrottle]
def get_queryset(self):
"""Get settings for current user and account"""
@@ -130,13 +148,14 @@ class UserSettingsViewSet(viewsets.ModelViewSet):
try:
setting = queryset.get(key=pk)
except UserSettings.DoesNotExist:
return Response(
{'error': 'Setting not found'},
status=status.HTTP_404_NOT_FOUND
return error_response(
error='Setting not found',
status_code=status.HTTP_404_NOT_FOUND,
request=request
)
serializer = self.get_serializer(setting)
return Response(serializer.data)
return success_response(data=serializer.data, request=request)
def perform_create(self, serializer):
"""Set user and account automatically"""
@@ -155,11 +174,15 @@ class UserSettingsViewSet(viewsets.ModelViewSet):
class ModuleSettingsViewSet(AccountModelViewSet):
"""
ViewSet for managing module-specific settings
Unified API Standard v1.0 compliant
"""
queryset = ModuleSettings.objects.all()
serializer_class = ModuleSettingsSerializer
permission_classes = [permissions.IsAuthenticated]
authentication_classes = [JWTAuthentication, CSRFExemptSessionAuthentication]
pagination_class = CustomPageNumberPagination
throttle_scope = 'system'
throttle_classes = [DebugScopedRateThrottle]
def get_queryset(self):
"""Get settings for current account, optionally filtered by module"""
@@ -174,7 +197,7 @@ class ModuleSettingsViewSet(AccountModelViewSet):
"""Get all settings for a specific module"""
queryset = self.get_queryset().filter(module_name=module_name)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
return success_response(data=serializer.data, request=request)
def retrieve(self, request, pk=None):
"""Get setting by key (pk can be key string)"""
@@ -189,18 +212,20 @@ class ModuleSettingsViewSet(AccountModelViewSet):
try:
setting = queryset.get(module_name=module_name, key=pk)
except ModuleSettings.DoesNotExist:
return Response(
{'error': 'Setting not found'},
status=status.HTTP_404_NOT_FOUND
return error_response(
error='Setting not found',
status_code=status.HTTP_404_NOT_FOUND,
request=request
)
else:
return Response(
{'error': 'Setting not found'},
status=status.HTTP_404_NOT_FOUND
return error_response(
error='Setting not found',
status_code=status.HTTP_404_NOT_FOUND,
request=request
)
serializer = self.get_serializer(setting)
return Response(serializer.data)
return success_response(data=serializer.data, request=request)
def perform_create(self, serializer):
"""Set account automatically"""
@@ -220,11 +245,15 @@ class ModuleSettingsViewSet(AccountModelViewSet):
class AISettingsViewSet(AccountModelViewSet):
"""
ViewSet for managing AI-specific settings
Unified API Standard v1.0 compliant
"""
queryset = AISettings.objects.all()
serializer_class = AISettingsSerializer
permission_classes = [permissions.IsAuthenticated]
authentication_classes = [JWTAuthentication, CSRFExemptSessionAuthentication]
pagination_class = CustomPageNumberPagination
throttle_scope = 'system'
throttle_classes = [DebugScopedRateThrottle]
def get_queryset(self):
"""Get AI settings for current account"""
@@ -241,13 +270,14 @@ class AISettingsViewSet(AccountModelViewSet):
try:
setting = queryset.get(integration_type=pk)
except AISettings.DoesNotExist:
return Response(
{'error': 'AI Setting not found'},
status=status.HTTP_404_NOT_FOUND
return error_response(
error='AI Setting not found',
status_code=status.HTTP_404_NOT_FOUND,
request=request
)
serializer = self.get_serializer(setting)
return Response(serializer.data)
return success_response(data=serializer.data, request=request)
def perform_create(self, serializer):
"""Set account automatically"""

View File

@@ -375,9 +375,13 @@ class TasksViewSet(SiteSectorModelViewSet):
class ImagesViewSet(SiteSectorModelViewSet):
"""
ViewSet for managing content images
Unified API Standard v1.0 compliant
"""
queryset = Images.objects.all()
serializer_class = ImagesSerializer
pagination_class = CustomPageNumberPagination
throttle_scope = 'writer'
throttle_classes = [DebugScopedRateThrottle]
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
ordering_fields = ['created_at', 'position', 'id']
@@ -385,12 +389,37 @@ class ImagesViewSet(SiteSectorModelViewSet):
filterset_fields = ['task_id', 'content_id', 'image_type', 'status']
def perform_create(self, serializer):
"""Override to automatically set account"""
account = getattr(self.request, 'account', None)
if account:
serializer.save(account=account)
else:
serializer.save()
"""Override to automatically set account, site, and sector"""
from rest_framework.exceptions import ValidationError
# Get site and sector from request (set by middleware) or user's active context
site = getattr(self.request, 'site', None)
sector = getattr(self.request, 'sector', None)
if not site:
# Fallback to user's active site if not set by middleware
user = getattr(self.request, 'user', None)
if user and user.is_authenticated and hasattr(user, 'active_site'):
site = user.active_site
if not sector and site:
# Fallback to default sector for the site if not set by middleware
from igny8_core.auth.models import Sector
sector = site.sectors.filter(is_default=True).first()
# Site and sector are required - raise ValidationError if not available
# Use dict format for ValidationError to ensure proper error structure
if not site:
raise ValidationError({"site": ["Site is required for image creation. Please select a site."]})
if not sector:
raise ValidationError({"sector": ["Sector is required for image creation. Please select a sector."]})
# Add site and sector to validated_data so base class can validate access
serializer.validated_data['site'] = site
serializer.validated_data['sector'] = sector
# Call parent to set account and validate access
super().perform_create(serializer)
@action(detail=True, methods=['get'], url_path='file', url_name='image_file')
def serve_image_file(self, request, pk=None):