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:
@@ -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:
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user