added frontend for social + feed partiali working
This commit is contained in:
@@ -1,14 +1,17 @@
|
||||
from django.db.models import Count, Q
|
||||
from rest_framework import status, viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import PermissionDenied, ValidationError
|
||||
from rest_framework.parsers import MultiPartParser
|
||||
from rest_framework.response import Response
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
|
||||
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from social.hubs.models import Tags
|
||||
from .models import Post, PostVote
|
||||
from vontor_cz.pagination import CreatedCursorPagination
|
||||
from .models import Post, PostContent, PostVote
|
||||
from .permissions import CanDeletePost, IsPostAuthorOnly
|
||||
from .serializers import PostSerializer, PostVoteSerializer, TagAttachSerializer
|
||||
from .serializers import PostSerializer, PostContentSerializer, PostVoteSerializer, TagAttachSerializer
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -61,7 +64,12 @@ class PostViewSet(viewsets.ModelViewSet):
|
||||
return [IsAuthenticated()]
|
||||
|
||||
def get_queryset(self):
|
||||
qs = Post.objects.select_related('author', 'hub').prefetch_related('tags', 'contents')
|
||||
qs = (
|
||||
Post.objects
|
||||
.select_related('author', 'hub')
|
||||
.prefetch_related('tags', 'contents', 'votes')
|
||||
.annotate(reply_count=Count('replies', distinct=True))
|
||||
)
|
||||
hub_id = self.request.query_params.get('hub')
|
||||
if hub_id:
|
||||
qs = qs.filter(hub_id=hub_id)
|
||||
@@ -70,6 +78,33 @@ class PostViewSet(viewsets.ModelViewSet):
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(author=self.request.user)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Media upload action
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@extend_schema(
|
||||
tags=["posts"],
|
||||
summary="Upload media to a post",
|
||||
description="Attach an image or video file to a post. Only the post author can upload.",
|
||||
request={'multipart/form-data': {
|
||||
'type': 'object',
|
||||
'properties': {'file': {'type': 'string', 'format': 'binary'}},
|
||||
'required': ['file'],
|
||||
}},
|
||||
responses={201: PostContentSerializer},
|
||||
)
|
||||
@action(detail=True, methods=['post'], url_path='media', parser_classes=[MultiPartParser])
|
||||
def upload_media(self, request, pk=None):
|
||||
post = self.get_object()
|
||||
if post.author != request.user:
|
||||
raise PermissionDenied('Only the post author can upload media.')
|
||||
file = request.FILES.get('file')
|
||||
if not file:
|
||||
raise ValidationError({'file': 'No file provided.'})
|
||||
content = PostContent(post=post, file=file)
|
||||
content.save()
|
||||
return Response(PostContentSerializer(content).data, status=status.HTTP_201_CREATED)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Tag attachment actions
|
||||
# ------------------------------------------------------------------
|
||||
@@ -132,6 +167,58 @@ class PostViewSet(viewsets.ModelViewSet):
|
||||
post.tags.remove(tag)
|
||||
return Response(PostSerializer(post, context={'request': request}).data)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Feed action (cursor-paginated aggregated feed for the user)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@extend_schema(
|
||||
tags=["posts"],
|
||||
summary="Get the user's post feed",
|
||||
description=(
|
||||
"Returns a cursor-paginated stream of top-level posts (excluding replies) "
|
||||
"aggregated from the user's joined hubs, public hubs, and hub-less posts. "
|
||||
"Pass `feed_strategy` to switch between ranking algorithms (currently only "
|
||||
"`recent` is implemented; reserved for future custom algorithms)."
|
||||
),
|
||||
parameters=[
|
||||
OpenApiParameter(name='feed_strategy', required=False, type=str,
|
||||
description="Algorithm key, default `recent`."),
|
||||
OpenApiParameter(name='cursor', required=False, type=str,
|
||||
description="Opaque pagination cursor."),
|
||||
],
|
||||
responses={200: PostSerializer(many=True)},
|
||||
)
|
||||
@action(detail=False, methods=['get'], url_path='feed')
|
||||
def feed(self, request):
|
||||
user = request.user
|
||||
strategy = request.query_params.get('feed_strategy', 'recent')
|
||||
|
||||
base_qs = (
|
||||
Post.objects
|
||||
.select_related('author', 'hub')
|
||||
.prefetch_related('tags', 'contents', 'votes')
|
||||
.annotate(reply_count=Count('replies', distinct=True))
|
||||
.filter(reply_to__isnull=True)
|
||||
)
|
||||
|
||||
joined_hub_ids = list(user.hubs.values_list('id', flat=True)) if user.is_authenticated else []
|
||||
visibility_filter = (
|
||||
Q(hub__isnull=True)
|
||||
| Q(hub__is_public=True)
|
||||
| Q(hub_id__in=joined_hub_ids)
|
||||
)
|
||||
qs = base_qs.filter(visibility_filter).distinct()
|
||||
|
||||
if strategy == 'recent':
|
||||
qs = qs.order_by('-created_at')
|
||||
else:
|
||||
qs = qs.order_by('-created_at')
|
||||
|
||||
paginator = CreatedCursorPagination()
|
||||
page = paginator.paginate_queryset(qs, request, view=self)
|
||||
ser = PostSerializer(page, many=True, context={'request': request})
|
||||
return paginator.get_paginated_response(ser.data)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Vote action
|
||||
# ------------------------------------------------------------------
|
||||
@@ -154,4 +241,3 @@ class PostViewSet(viewsets.ModelViewSet):
|
||||
defaults={'vote': ser.validated_data['vote']},
|
||||
)
|
||||
return Response(PostVoteSerializer(vote_obj).data)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user