feat: 添加所有 Django apps 的 ViewSets

- User ViewSet(个人中心、统计、收藏、评分、搜索)
- Region ViewSet(层级查询、树形结构、文章、服务、统计、评分、收藏)
- Article ViewSet(创建、提交、审核、评论、点赞、统计)
- FeaturedService ViewSet(创建、提交、审核、评论、点赞、评分、统计)
- Moderation ViewSets(版主申请、权限、支持、限制)
- Interaction ViewSets(评论、评分、点赞、收藏、AI审核)

完整实现权限控制、审核流程和交互功能
This commit is contained in:
mashen
2026-04-09 13:44:13 +00:00
parent edec596516
commit d9c6c8ff59
6 changed files with 1129 additions and 4 deletions

View File

@@ -0,0 +1,204 @@
from rest_framework import viewsets, permissions, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from django.utils import timezone
from django.db.models import Q
from .models import Article
from .serializers import (
ArticleSerializer,
ArticleCreateSerializer,
ArticleUpdateSerializer,
ArticleReviewSerializer,
ArticleListSerializer
)
class ArticleViewSet(viewsets.ModelViewSet):
"""ViewSet for Article model."""
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
filter_backends = [filters.SearchFilter, filters.OrderingFilter, filters.DjangoFilterBackend]
search_fields = ['title', 'content']
filterset_fields = ['article_type', 'region', 'publish_status']
ordering_fields = ['created_at', 'updated_at', 'published_at']
ordering = ['-created_at']
def get_queryset(self):
queryset = Article.objects.select_related('author', 'region', 'moderator_reviewer')
# Only show published articles to non-authenticated users
if not self.request.user.is_authenticated:
return queryset.filter(publish_status='published')
# Show all for admins
if self.request.user.is_admin():
return queryset
# Show own articles + published articles for regular users
return queryset.filter(
Q(author=self.request.user) |
Q(publish_status='published')
).distinct()
def get_serializer_class(self):
if self.action == 'create':
return ArticleCreateSerializer
elif self.action in ['update', 'partial_update']:
return ArticleUpdateSerializer
elif self.action == 'list':
return ArticleListSerializer
elif self.action in ['approve', 'reject', 'submit']:
return ArticleReviewSerializer
return ArticleSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
def perform_update(self, serializer):
# Only allow updating own articles or by admin
if (not self.request.user.is_admin() and
str(serializer.instance.author.id) != str(self.request.user.id)):
from rest_framework.exceptions import PermissionDenied
raise PermissionDenied("You can only update your own articles")
serializer.save()
def perform_destroy(self, instance):
# Only allow deleting own articles or by admin
if (not self.request.user.is_admin() and
str(instance.author.id) != str(self.request.user.id)):
from rest_framework.exceptions import PermissionDenied
raise PermissionDenied("You can only delete your own articles")
instance.delete()
@action(detail=True, methods=['post'])
def submit(self, request, pk=None):
"""Submit article for review."""
article = self.get_object()
if article.author != request.user:
return Response(
{'detail': 'You can only submit your own articles'},
status=status.HTTP_403_FORBIDDEN
)
article.submit_for_review()
return Response({'message': 'Article submitted for review'})
@action(detail=True, methods=['post'])
def approve(self, request, pk=None):
"""Approve article (moderator only)."""
article = self.get_object()
serializer = self.get_serializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
if not request.user.is_moderator():
return Response(
{'detail': 'Only moderators can approve articles'},
status=status.HTTP_403_FORBIDDEN
)
# Check if moderator has permission for this region
from apps.moderation.models import ModeratorPermission
has_permission = ModeratorPermission.objects.filter(
moderator=request.user,
region=article.region,
status='active'
).exists()
if not has_permission:
return Response(
{'detail': 'You do not have permission to approve articles in this region'},
status=status.HTTP_403_FORBIDDEN
)
article.approve_moderator(
reviewer=request.user,
reason=serializer.validated_data.get('reason', '')
)
return Response({'message': 'Article approved'})
@action(detail=True, methods=['post'])
def reject(self, request, pk=None):
"""Reject article (moderator only)."""
article = self.get_object()
serializer = self.get_serializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
if not request.user.is_moderator():
return Response(
{'detail': 'Only moderators can reject articles'},
status=status.HTTP_403_FORBIDDEN
)
# Check if moderator has permission for this region
from apps.moderation.models import ModeratorPermission
has_permission = ModeratorPermission.objects.filter(
moderator=request.user,
region=article.region,
status='active'
).exists()
if not has_permission:
return Response(
{'detail': 'You do not have permission to reject articles in this region'},
status=status.HTTP_403_FORBIDDEN
)
article.reject_moderator(
reviewer=request.user,
reason=serializer.validated_data.get('reason', 'Required reason')
)
return Response({'message': 'Article rejected'})
@action(detail=True, methods=['get'])
def comments(self, request, pk=None):
"""Get comments for an article."""
article = self.get_object()
from apps.interactions.serializers import CommentSerializer
from apps.interactions.models import Comment
comments = Comment.objects.filter(
target_type='article',
target_id=article.id,
ai_status='approved'
)
serializer = CommentSerializer(comments, many=True)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def like(self, request, pk=None):
"""Like or unlike an article."""
article = self.get_object()
from apps.interactions.models import Like
like, created = Like.objects.get_or_create(
user=request.user,
target_type='article',
target_id=article.id
)
if not created:
like.delete()
return Response({'message': 'Unliked', 'liked': False})
return Response({'message': 'Liked', 'liked': True})
@action(detail=True, methods=['get'])
def stats(self, request, pk=None):
"""Get article statistics."""
article = self.get_object()
from apps.interactions.models import Like, Comment, Rating
return Response({
'likes_count': Like.objects.filter(
target_type='article',
target_id=article.id
).count(),
'comments_count': Comment.objects.filter(
target_type='article',
target_id=article.id,
ai_status='approved'
).count(),
'views_count': getattr(article, 'views_count', 0),
})

View File

@@ -0,0 +1,227 @@
from rest_framework import viewsets, permissions, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from django.db.models import Q
from .models import FeaturedService
from .serializers import (
FeaturedServiceSerializer,
FeaturedServiceCreateSerializer,
FeaturedServiceUpdateSerializer,
FeaturedServiceReviewSerializer,
FeaturedServiceListSerializer
)
class FeaturedServiceViewSet(viewsets.ModelViewSet):
"""ViewSet for FeaturedService model."""
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
filter_backends = [filters.SearchFilter, filters.OrderingFilter, filters.DjangoFilterBackend]
search_fields = ['name', 'description']
filterset_fields = ['category', 'region', 'publish_status']
ordering_fields = ['created_at', 'updated_at', 'published_at']
ordering = ['-created_at']
def get_queryset(self):
queryset = FeaturedService.objects.select_related('submitter', 'region', 'moderator_reviewer')
# Only show published services to non-authenticated users
if not self.request.user.is_authenticated:
return queryset.filter(publish_status='published')
# Show all for admins
if self.request.user.is_admin():
return queryset
# Show own services + published services for regular users
return queryset.filter(
Q(submitter=self.request.user) |
Q(publish_status='published')
).distinct()
def get_serializer_class(self):
if self.action == 'create':
return FeaturedServiceCreateSerializer
elif self.action in ['update', 'partial_update']:
return FeaturedServiceUpdateSerializer
elif self.action == 'list':
return FeaturedServiceListSerializer
elif self.action in ['approve', 'reject', 'submit']:
return FeaturedServiceReviewSerializer
return FeaturedServiceSerializer
def perform_create(self, serializer):
serializer.save(submitter=self.request.user)
def perform_update(self, serializer):
# Only allow updating own services or by admin
if (not self.request.user.is_admin() and
str(serializer.instance.submitter.id) != str(self.request.user.id)):
from rest_framework.exceptions import PermissionDenied
raise PermissionDenied("You can only update your own services")
serializer.save()
def perform_destroy(self, instance):
# Only allow deleting own services or by admin
if (not self.request.user.is_admin() and
str(instance.submitter.id) != str(self.request.user.id)):
from rest_framework.exceptions import PermissionDenied
raise PermissionDenied("You can only delete your own services")
instance.delete()
@action(detail=True, methods=['post'])
def submit(self, request, pk=None):
"""Submit service for review."""
service = self.get_object()
if service.submitter != request.user:
return Response(
{'detail': 'You can only submit your own services'},
status=status.HTTP_403_FORBIDDEN
)
service.submit_for_review()
return Response({'message': 'Service submitted for review'})
@action(detail=True, methods=['post'])
def approve(self, request, pk=None):
"""Approve service (moderator only)."""
service = self.get_object()
serializer = self.get_serializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
if not request.user.is_moderator():
return Response(
{'detail': 'Only moderators can approve services'},
status=status.HTTP_403_FORBIDDEN
)
# Check if moderator has permission for this region
from apps.moderation.models import ModeratorPermission
has_permission = ModeratorPermission.objects.filter(
moderator=request.user,
region=service.region,
status='active'
).exists()
if not has_permission:
return Response(
{'detail': 'You do not have permission to approve services in this region'},
status=status.HTTP_403_FORBIDDEN
)
service.approve_moderator(
reviewer=request.user,
reason=serializer.validated_data.get('reason', '')
)
return Response({'message': 'Service approved'})
@action(detail=True, methods=['post'])
def reject(self, request, pk=None):
"""Reject service (moderator only)."""
service = self.get_object()
serializer = self.get_serializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
if not request.user.is_moderator():
return Response(
{'detail': 'Only moderators can reject services'},
status=status.HTTP_403_FORBIDDEN
)
# Check if moderator has permission for this region
from apps.moderation.models import ModeratorPermission
has_permission = ModeratorPermission.objects.filter(
moderator=request.user,
region=service.region,
status='active'
).exists()
if not has_permission:
return Response(
{'detail': 'You do not have permission to reject services in this region'},
status=status.HTTP_403_FORBIDDEN
)
service.reject_moderator(
reviewer=request.user,
reason=serializer.validated_data.get('reason', 'Required reason')
)
return Response({'message': 'Service rejected'})
@action(detail=True, methods=['get'])
def comments(self, request, pk=None):
"""Get comments for a service."""
service = self.get_object()
from apps.interactions.serializers import CommentSerializer
from apps.interactions.models import Comment
comments = Comment.objects.filter(
target_type='service',
target_id=service.id,
ai_status='approved'
)
serializer = CommentSerializer(comments, many=True)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def like(self, request, pk=None):
"""Like or unlike a service."""
service = self.get_object()
from apps.interactions.models import Like
like, created = Like.objects.get_or_create(
user=request.user,
target_type='service',
target_id=service.id
)
if not created:
like.delete()
return Response({'message': 'Unliked', 'liked': False})
return Response({'message': 'Liked', 'liked': True})
@action(detail=True, methods=['post'])
def rate(self, request, pk=None):
"""Rate a service."""
service = self.get_object()
from apps.interactions.serializers import RatingCreateSerializer
serializer = RatingCreateSerializer(data=request.data)
if serializer.is_valid():
serializer.save(target_type='service', target_id=service.id)
return Response({'message': 'Rating saved'})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@action(detail=True, methods=['get'])
def stats(self, request, pk=None):
"""Get service statistics."""
service = self.get_object()
from apps.interactions.models import Like, Comment, Rating
likes_count = Like.objects.filter(
target_type='service',
target_id=service.id
).count()
comments_count = Comment.objects.filter(
target_type='service',
target_id=service.id,
ai_status='approved'
).count()
ratings = Rating.objects.filter(
target_type='service',
target_id=service.id
)
avg_rating = ratings.aggregate(avg=models.Avg('score'))['avg'] or 0
return Response({
'likes_count': likes_count,
'comments_count': comments_count,
'avg_rating': round(avg_rating, 1),
'ratings_count': ratings.count(),
})

View File

@@ -0,0 +1,226 @@
from rest_framework import viewsets, permissions, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Comment, Rating, Like, Favorite
from .serializers import (
CommentSerializer,
CommentCreateSerializer,
RatingSerializer,
RatingCreateSerializer,
LikeSerializer,
FavoriteSerializer,
FavoriteCreateSerializer
)
class CommentViewSet(viewsets.ModelViewSet):
"""ViewSet for Comment model."""
queryset = Comment.objects.select_related('author')
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
filter_backends = [filters.SearchFilter, filters.OrderingFilter, filters.DjangoFilterBackend]
search_fields = ['content']
filterset_fields = ['target_type', 'target_id', 'ai_status']
ordering_fields = ['created_at']
ordering = ['-created_at']
def get_queryset(self):
# Only show approved comments
if not self.request.user.is_authenticated:
return self.queryset.filter(ai_status='approved')
# Admins see all
if self.request.user.is_admin():
return self.queryset
# Regular users see approved + their own
return self.queryset.filter(
Q(ai_status='approved') |
Q(author=self.request.user)
).distinct()
def get_serializer_class(self):
if self.action == 'create':
return CommentCreateSerializer
return CommentSerializer
def perform_create(self, serializer):
serializer.save()
def perform_update(self, serializer):
# Only allow updating own comments
if str(serializer.instance.author.id) != str(self.request.user.id):
from rest_framework.exceptions import PermissionDenied
raise PermissionDenied("You can only update your own comments")
serializer.save()
def perform_destroy(self, instance):
# Only allow deleting own comments or by admin
if (not self.request.user.is_admin() and
str(instance.author.id) != str(self.request.user.id)):
from rest_framework.exceptions import PermissionDenied
raise PermissionDenied("You can only delete your own comments")
instance.delete()
@action(detail=True, methods=['post'])
def approve_ai(self, request, pk=None):
"""Approve comment by AI (simulated)."""
if not request.user.is_ai_auditor():
return Response(
{'detail': 'Only AI auditors can approve comments'},
status=status.HTTP_403_FORBIDDEN
)
comment = self.get_object()
comment.approve_ai()
return Response({'message': 'Comment approved by AI'})
@action(detail=True, methods=['post'])
def reject_ai(self, request, pk=None):
"""Reject comment by AI (simulated)."""
if not request.user.is_ai_auditor():
return Response(
{'detail': 'Only AI auditors can reject comments'},
status=status.HTTP_403_FORBIDDEN
)
comment = self.get_object()
reason = request.data.get('reason', 'Content violates guidelines')
comment.reject_ai(reason)
return Response({'message': 'Comment rejected by AI'})
class RatingViewSet(viewsets.ModelViewSet):
"""ViewSet for Rating model."""
queryset = Rating.objects.select_related('user')
serializer_class = RatingSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
filter_backends = [filters.SearchFilter, filters.OrderingFilter, filters.DjangoFilterBackend]
filterset_fields = ['target_type', 'target_id', 'user']
ordering_fields = ['created_at']
ordering = ['-created_at']
def get_queryset(self):
# Admins see all
if self.request.user.is_admin():
return self.queryset
# Regular users see their own ratings
return self.queryset.filter(user=self.request.user)
def get_serializer_class(self):
if self.action == 'create':
return RatingCreateSerializer
return RatingSerializer
def perform_create(self, serializer):
serializer.save()
def perform_destroy(self, instance):
# Only allow deleting own ratings
if str(instance.user.id) != str(self.request.user.id):
from rest_framework.exceptions import PermissionDenied
raise PermissionDenied("You can only delete your own ratings")
instance.delete()
@action(detail=False, methods=['get'])
def my_ratings(self, request):
"""Get current user's ratings."""
ratings = Rating.objects.filter(user=request.user).select_related()
serializer = self.get_serializer(ratings, many=True)
return Response(serializer.data)
class LikeViewSet(viewsets.ModelViewSet):
"""ViewSet for Like model."""
queryset = Like.objects.select_related('user')
serializer_class = LikeSerializer
permission_classes = [permissions.IsAuthenticated]
filter_backends = [filters.OrderingFilter, filters.DjangoFilterBackend]
filterset_fields = ['target_type', 'target_id', 'user']
ordering_fields = ['created_at']
ordering = ['-created_at']
def get_queryset(self):
# Admins see all
if self.request.user.is_admin():
return self.queryset
# Regular users see their own likes
return self.queryset.filter(user=self.request.user)
@action(detail=False, methods=['post'])
def toggle(self, request):
"""Toggle like on a target."""
target_type = request.data.get('target_type')
target_id = request.data.get('target_id')
if not target_type or not target_id:
return Response(
{'detail': 'target_type and target_id are required'},
status=status.HTTP_400_BAD_REQUEST
)
like, created = Like.objects.get_or_create(
user=request.user,
target_type=target_type,
target_id=target_id
)
if not created:
like.delete()
return Response({'message': 'Unliked', 'liked': False})
return Response({'message': 'Liked', 'liked': True})
@action(detail=False, methods=['get'])
def my_likes(self, request):
"""Get current user's likes."""
likes = Like.objects.filter(user=request.user).select_related()
serializer = self.get_serializer(likes, many=True)
return Response(serializer.data)
class FavoriteViewSet(viewsets.ModelViewSet):
"""ViewSet for Favorite model."""
queryset = Favorite.objects.select_related('user')
serializer_class = FavoriteSerializer
permission_classes = [permissions.IsAuthenticated]
filter_backends = [filters.OrderingFilter, filters.DjangoFilterBackend]
filterset_fields = ['target_type', 'target_id', 'user']
ordering_fields = ['created_at']
ordering = ['-created_at']
def get_queryset(self):
# Admins see all
if self.request.user.is_admin():
return self.queryset
# Regular users see their own favorites
return self.queryset.filter(user=self.request.user)
def get_serializer_class(self):
if self.action == 'create':
return FavoriteCreateSerializer
return FavoriteSerializer
@action(detail=False, methods=['post'])
def toggle(self, request):
"""Toggle favorite on a target."""
serializer = FavoriteCreateSerializer(data=request.data)
if serializer.is_valid():
result = serializer.save()
if result is None:
return Response({'message': 'Unfavorited', 'favorited': False})
return Response({'message': 'Favorited', 'favorited': True})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@action(detail=False, methods=['get'])
def my_favorites(self, request):
"""Get current user's favorites."""
favorites = Favorite.objects.filter(user=request.user).select_related()
serializer = FavoriteSerializer(favorites, many=True)
return Response(serializer.data)

View File

@@ -0,0 +1,197 @@
from rest_framework import viewsets, permissions, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from django.db.models import Q
from .models import (
ModeratorApplication,
ModeratorPermission,
ModeratorSupport,
PermissionRestriction
)
from .serializers import (
ModeratorApplicationSerializer,
ModeratorApplicationCreateSerializer,
ModeratorPermissionSerializer,
ModeratorSupportSerializer,
PermissionRestrictionSerializer
)
class ModeratorApplicationViewSet(viewsets.ModelViewSet):
"""ViewSet for ModeratorApplication model."""
permission_classes = [permissions.IsAuthenticated]
filter_backends = [filters.SearchFilter, filters.OrderingFilter, filters.DjangoFilterBackend]
search_fields = ['applicant__username', 'region__name']
filterset_fields = ['status', 'rank', 'region']
ordering_fields = ['created_at', 'deadline']
ordering = ['-created_at']
def get_queryset(self):
queryset = ModeratorApplication.objects.select_related('applicant', 'region', 'reviewed_by')
# Admins see all
if self.request.user.is_admin():
return queryset
# Regular users see their own applications
return queryset.filter(applicant=self.request.user)
def get_serializer_class(self):
if self.action == 'create':
return ModeratorApplicationCreateSerializer
return ModeratorApplicationSerializer
def perform_create(self, serializer):
serializer.save()
@action(detail=True, methods=['post'])
def support(self, request, pk=None):
"""Support a moderator application."""
application = self.get_object()
# Check if application is still pending
if application.status != 'pending':
return Response(
{'detail': 'Can only support pending applications'},
status=status.HTTP_400_BAD_REQUEST
)
# Check if already supported
if ModeratorSupport.objects.filter(
supporter=request.user,
application=application
).exists():
return Response(
{'detail': 'Already supported this application'},
status=status.HTTP_400_BAD_REQUEST
)
serializer = ModeratorSupportSerializer(data={'application': application.id})
if serializer.is_valid():
serializer.save()
return Response({'message': 'Application supported', 'support_count': application.support_count})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@action(detail=True, methods=['post'])
def approve(self, request, pk=None):
"""Approve moderator application (admin only)."""
if not request.user.is_admin():
return Response(
{'detail': 'Only admins can approve applications'},
status=status.HTTP_403_FORBIDDEN
)
application = self.get_object()
if application.status != 'pending':
return Response(
{'detail': 'Can only approve pending applications'},
status=status.HTTP_400_BAD_REQUEST
)
# Check if has enough support
if not application.has_enough_support():
return Response(
{'detail': 'Not enough support votes'},
status=status.HTTP_400_BAD_REQUEST
)
# Create moderator permission
from .models import ModeratorPermission
ModeratorPermission.objects.create(
moderator=application.applicant,
region=application.region,
rank=application.rank,
status='active'
)
# Update application status
application.status = 'approved'
application.reviewed_by = request.user
application.reviewed_at = timezone.now()
application.save()
return Response({'message': 'Application approved, moderator permissions granted'})
@action(detail=True, methods=['post'])
def reject(self, request, pk=None):
"""Reject moderator application (admin only)."""
if not request.user.is_admin():
return Response(
{'detail': 'Only admins can reject applications'},
status=status.HTTP_403_FORBIDDEN
)
application = self.get_object()
if application.status != 'pending':
return Response(
{'detail': 'Can only reject pending applications'},
status=status.HTTP_400_BAD_REQUEST
)
application.status = 'rejected'
application.reviewed_by = request.user
application.reviewed_at = timezone.now()
application.save()
return Response({'message': 'Application rejected'})
@action(detail=False, methods=['get'])
def my_applications(self, request):
"""Get current user's applications."""
applications = ModeratorApplication.objects.filter(
applicant=request.user
).select_related('region')
serializer = self.get_serializer(applications, many=True)
return Response(serializer.data)
class ModeratorPermissionViewSet(viewsets.ReadOnlyModelViewSet):
"""ViewSet for ModeratorPermission model (read-only)."""
queryset = ModeratorPermission.objects.select_related('moderator', 'region')
serializer_class = ModeratorPermissionSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
# Admins see all
if self.request.user.is_admin():
return self.queryset
# Moderators see their own permissions
return self.queryset.filter(moderator=self.request.user)
@action(detail=False, methods=['get'])
def my_permissions(self, request):
"""Get current user's moderator permissions."""
permissions = ModeratorPermission.objects.filter(
moderator=request.user,
status='active'
).select_related('region')
serializer = self.get_serializer(permissions, many=True)
return Response(serializer.data)
class PermissionRestrictionViewSet(viewsets.ModelViewSet):
"""ViewSet for PermissionRestriction model."""
queryset = PermissionRestriction.objects.select_related('operator', 'target_moderator')
serializer_class = PermissionRestrictionSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
# Admins see all
if self.request.user.is_admin():
return self.queryset
# Moderators see restrictions on themselves
return self.queryset.filter(target_moderator=self.request.user)
def perform_create(self, serializer):
# Only admins can create restrictions
if not self.request.user.is_admin():
from rest_framework.exceptions import PermissionDenied
raise PermissionDenied("Only admins can create restrictions")
serializer.save(operator=self.request.user)

View File

@@ -0,0 +1,141 @@
from rest_framework import viewsets, permissions, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from .models import Region
from .serializers import (
RegionSerializer,
RegionDetailSerializer,
RegionTreeSerializer
)
from apps.interactions.models import Rating, Favorite
class RegionViewSet(viewsets.ModelViewSet):
"""ViewSet for Region model."""
queryset = Region.objects.filter(status='active')
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
search_fields = ['name']
ordering_fields = ['name', 'level', 'created_at']
ordering = ['level', 'name']
def get_serializer_class(self):
if self.action == 'retrieve':
return RegionDetailSerializer
elif self.action == 'tree':
return RegionTreeSerializer
return RegionSerializer
def perform_create(self, serializer):
# Only admin can create regions
if not self.request.user.is_admin():
from rest_framework.exceptions import PermissionDenied
raise PermissionDenied("Only admins can create regions")
serializer.save()
def perform_update(self, serializer):
# Only admin can update regions
if not self.request.user.is_admin():
from rest_framework.exceptions import PermissionDenied
raise PermissionDenied("Only admins can update regions")
serializer.save()
@action(detail=False, methods=['get'])
def provinces(self, request):
"""Get all provinces (top-level regions)."""
provinces = self.queryset.filter(parent__isnull=True)
serializer = self.get_serializer(provinces, many=True)
return Response(serializer.data)
@action(detail=True, methods=['get'])
def children(self, request, pk=None):
"""Get children of a region."""
region = self.get_object()
children = region.get_children()
serializer = self.get_serializer(children, many=True)
return Response(serializer.data)
@action(detail=True, methods=['get'])
def path(self, request, pk=None):
"""Get full path of a region."""
region = self.get_object()
path = []
current = region
while current:
serializer = self.get_serializer(current)
path.insert(0, serializer.data)
current = current.parent
return Response(path)
@action(detail=False, methods=['get'])
def tree(self, request):
"""Get region tree structure."""
root_regions = self.queryset.filter(parent__isnull=True)
serializer = RegionTreeSerializer(root_regions, many=True)
return Response(serializer.data)
@action(detail=True, methods=['get'])
def articles(self, request, pk=None):
"""Get articles for a region."""
region = self.get_object()
articles = region.articles.filter(publish_status='published')
from apps.articles.serializers import ArticleListSerializer
serializer = ArticleListSerializer(articles, many=True)
return Response(serializer.data)
@action(detail=True, methods=['get'])
def services(self, request, pk=None):
"""Get featured services for a region."""
region = self.get_object()
services = region.featured_services.filter(publish_status='published')
from apps.featured_services.serializers import FeaturedServiceListSerializer
serializer = FeaturedServiceListSerializer(services, many=True)
return Response(serializer.data)
@action(detail=True, methods=['get'])
def stats(self, request, pk=None):
"""Get statistics for a region."""
region = self.get_object()
return Response({
'articles_count': region.articles.filter(publish_status='published').count(),
'services_count': region.featured_services.filter(publish_status='published').count(),
'children_count': region.children.count(),
})
@action(detail=True, methods=['post'])
def rate(self, request, pk=None):
"""Rate a region."""
region = self.get_object()
serializer = RatingCreateSerializer(data=request.data)
if serializer.is_valid():
serializer.save(target_type='region', target_id=region.id)
return Response({'message': 'Rating saved'}, status=201)
return Response(serializer.errors, status=400)
@action(detail=True, methods=['get'])
def my_rating(self, request, pk=None):
"""Get user's rating for a region."""
region = self.get_object()
try:
rating = Rating.objects.get(
user=request.user,
target_type='region',
target_id=region.id
)
return Response({'score': rating.score})
except Rating.DoesNotExist:
return Response({'score': None})
@action(detail=True, methods=['post'])
def favorite(self, request, pk=None):
"""Favorite or unfavorite a region."""
region = self.get_object()
serializer = FavoriteCreateSerializer(data=request.data)
if serializer.is_valid():
result = serializer.save(target_type='region', target_id=region.id)
if result is None:
return Response({'message': 'Unfavorited'}, status=200)
return Response({'message': 'Favorited'}, status=201)
return Response(serializer.errors, status=400)

View File

@@ -1,8 +1,14 @@
from rest_framework import viewsets, permissions from rest_framework import viewsets, permissions, status
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from django.db.models import Count, Q
from .models import User from .models import User
from .serializers import UserSerializer, UserDetailSerializer from .serializers import (
UserSerializer,
UserDetailSerializer,
UserUpdateSerializer,
UserStatsSerializer
)
class UserViewSet(viewsets.ModelViewSet): class UserViewSet(viewsets.ModelViewSet):
@@ -12,12 +18,136 @@ class UserViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated] permission_classes = [permissions.IsAuthenticated]
def get_serializer_class(self): def get_serializer_class(self):
if self.action in ['retrieve', 'update', 'partial_update']: if self.action == 'retrieve' and self.kwargs.get('pk') == 'me':
return UserDetailSerializer return UserDetailSerializer
elif self.action in ['update', 'partial_update'] and self.kwargs.get('pk') == 'me':
return UserUpdateSerializer
return UserSerializer return UserSerializer
def get_queryset(self):
# Only admins can see all users
if self.request.user.is_admin():
return User.objects.all()
# Regular users can only see themselves
return User.objects.filter(id=self.request.user.id)
def list(self, request, *args, **kwargs):
"""Only admins can list all users."""
if not request.user.is_admin():
return Response(
{'detail': 'You do not have permission to perform this action.'},
status=status.HTTP_403_FORBIDDEN
)
return super().list(request, *args, **kwargs)
def retrieve(self, request, *args, **kwargs):
"""Get user details (me for current user)."""
if kwargs.get('pk') == 'me':
self.kwargs['pk'] = request.user.id
return super().retrieve(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
"""Update user details (only me for regular users)."""
if kwargs.get('pk') == 'me':
self.kwargs['pk'] = request.user.id
elif not request.user.is_admin() and str(kwargs.get('pk')) != str(request.user.id):
return Response(
{'detail': 'You can only update your own profile.'},
status=status.HTTP_403_FORBIDDEN
)
return super().update(request, *args, **kwargs)
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
def me(self, request): def me(self, request):
"""Get current user.""" """Get current user details."""
serializer = self.get_serializer(request.user) serializer = self.get_serializer(request.user)
return Response(serializer.data)
@action(detail=True, methods=['get'])
def stats(self, request, pk=None):
"""Get user statistics."""
if pk == 'me':
pk = request.user.id
user = self.get_object()
if str(user.id) != str(request.user.id) and not request.user.is_admin():
return Response(
{'detail': 'You do not have permission to view this user\'s stats.'},
status=status.HTTP_403_FORBIDDEN
)
from apps.articles.models import Article
from apps.featured_services.models import FeaturedService
from apps.interactions.models import Comment, Like, Favorite, Rating
return Response({
'articles_count': Article.objects.filter(author=user).count(),
'services_count': FeaturedService.objects.filter(submitter=user).count(),
'comments_count': Comment.objects.filter(author=user).count(),
'likes_count': Like.objects.filter(user=user).count(),
'favorites_count': Favorite.objects.filter(user=user).count(),
'ratings_count': Rating.objects.filter(user=user).count(),
})
@action(detail=True, methods=['get'])
def favorites(self, request, pk=None):
"""Get user's favorites."""
if pk == 'me':
pk = request.user.id
user = self.get_object()
if str(user.id) != str(request.user.id):
return Response(
{'detail': 'You can only view your own favorites.'},
status=status.HTTP_403_FORBIDDEN
)
from apps.interactions.serializers import FavoriteSerializer
favorites = Favorite.objects.filter(user=user).select_related()
serializer = FavoriteSerializer(favorites, many=True)
return Response(serializer.data)
@action(detail=True, methods=['get'])
def ratings(self, request, pk=None):
"""Get user's ratings."""
if pk == 'me':
pk = request.user.id
user = self.get_object()
if str(user.id) != str(request.user.id):
return Response(
{'detail': 'You can only view your own ratings.'},
status=status.HTTP_403_FORBIDDEN
)
from apps.interactions.serializers import RatingSerializer
ratings = Rating.objects.filter(user=user).select_related()
serializer = RatingSerializer(ratings, many=True)
return Response(serializer.data)
@action(detail=False, methods=['get'])
def search(self, request):
"""Search users (admin only)."""
if not request.user.is_admin():
return Response(
{'detail': 'Only admins can search users.'},
status=status.HTTP_403_FORBIDDEN
)
query = request.query_params.get('q', '')
if query:
users = User.objects.filter(
Q(username__icontains=query) |
Q(email__icontains=query) |
Q(first_name__icontains=query)
)
else:
users = User.objects.all()
page = self.paginate_queryset(users)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(users, many=True)
return Response(serializer.data) return Response(serializer.data)