Skip to content

Commit 2315cc4

Browse files
feat: profile page apis (#160)
Co-authored-by: moonlitgrace <[email protected]>
1 parent 21adb64 commit 2315cc4

File tree

6 files changed

+177
-10
lines changed

6 files changed

+177
-10
lines changed

backend/apps/api/serializers/user/profile.py renamed to backend/apps/api/serializers/user/profile/__init__.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22

33
from rest_framework import serializers
44

5+
from apps.api.serializers.user import UserSerializer
56
from apps.user.models import Profile
67

7-
from ...serializers.user import UserSerializer
8-
98

109
class ProfileSerializer(serializers.ModelSerializer):
1110
user = UserSerializer(read_only=True)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import copy
2+
3+
from rest_framework import serializers
4+
5+
from apps.api.serializers.comment import CommentDetailSerializer
6+
from apps.api.serializers.post import PostSerializer
7+
8+
9+
class DownvotedSerializer(serializers.Serializer):
10+
type = serializers.CharField()
11+
data = serializers.SerializerMethodField()
12+
13+
def get_data(self, obj) -> dict:
14+
obj_type = obj.type
15+
obj_copy = copy.copy(obj)
16+
17+
delattr(obj_copy, "type")
18+
19+
if obj_type == "post":
20+
return PostSerializer(obj_copy, context=self.context).data
21+
return CommentDetailSerializer(obj_copy, context=self.context).data
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import copy
2+
3+
from rest_framework import serializers
4+
5+
from apps.api.serializers.comment import CommentDetailSerializer
6+
from apps.api.serializers.post import PostSerializer
7+
8+
9+
class OverviewSerializer(serializers.Serializer):
10+
type = serializers.CharField()
11+
data = serializers.SerializerMethodField()
12+
13+
def get_data(self, obj) -> dict:
14+
obj_type = obj.type
15+
obj_copy = copy.copy(obj)
16+
17+
delattr(obj_copy, "type")
18+
19+
if obj_type == "post":
20+
return PostSerializer(obj_copy, context=self.context).data
21+
return CommentDetailSerializer(obj_copy, context=self.context).data
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import copy
2+
3+
from rest_framework import serializers
4+
5+
from apps.api.serializers.comment import CommentDetailSerializer
6+
from apps.api.serializers.post import PostSerializer
7+
8+
9+
class UpvotedSerializer(serializers.Serializer):
10+
type = serializers.CharField()
11+
data = serializers.SerializerMethodField()
12+
13+
def get_data(self, obj) -> dict:
14+
obj_type = obj.type
15+
obj_copy = copy.copy(obj)
16+
17+
delattr(obj_copy, "type")
18+
19+
if obj_type == "post":
20+
return PostSerializer(obj_copy, context=self.context).data
21+
return CommentDetailSerializer(obj_copy, context=self.context).data

backend/apps/api/viewsets/user/__init__.py

+112-8
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1+
from http import HTTPMethod
2+
from itertools import chain
3+
14
from django.conf import settings
5+
from django.db.models import CharField, QuerySet, Value
26
from drf_spectacular.utils import extend_schema
3-
from rest_framework import exceptions, filters, permissions, viewsets
7+
from rest_framework import exceptions, filters, permissions, response, viewsets
8+
from rest_framework.decorators import action
49

10+
from apps.api.serializers.comment import CommentDetailSerializer
11+
from apps.api.serializers.post import PostSerializer
12+
from apps.api.serializers.user.profile import ProfileSerializer
13+
from apps.api.serializers.user.profile.downvoted import DownvotedSerializer
14+
from apps.api.serializers.user.profile.overview import OverviewSerializer
15+
from apps.api.serializers.user.profile.upvoted import UpvotedSerializer
516
from apps.user.models import Profile
617

7-
from ...serializers.user.profile import ProfileSerializer
8-
918

10-
@extend_schema(tags=['user & profiles'])
19+
@extend_schema(tags=["user & profiles"])
1120
class ProfileViewSet(viewsets.ReadOnlyModelViewSet):
1221
"""
1322
ViewSet for performing read-only operations on the Profile model.
@@ -19,10 +28,105 @@ class ProfileViewSet(viewsets.ReadOnlyModelViewSet):
1928
queryset = Profile.objects.all()
2029
serializer_class = ProfileSerializer
2130
filter_backends = (filters.SearchFilter,)
22-
search_fields = ('username',)
31+
search_fields = ("username",)
32+
33+
serializer_classes = {
34+
"overview": OverviewSerializer,
35+
"posts": PostSerializer,
36+
"comments": CommentDetailSerializer,
37+
"upvoted": UpvotedSerializer,
38+
"downvoted": DownvotedSerializer,
39+
}
40+
41+
def get_serializer_class(self):
42+
if self.action in self.serializer_classes:
43+
return self.serializer_classes[self.action]
44+
return self.serializer_class
45+
46+
def get_queryset(self) -> QuerySet[Profile]:
47+
return super().get_queryset()
48+
49+
def get_object(self) -> Profile:
50+
qs = self.get_queryset()
51+
filter_kwargs = {f"{self.lookup_field}": self.kwargs[self.lookup_field]}
52+
obj = qs.filter(**filter_kwargs).first()
53+
54+
if not obj:
55+
raise exceptions.NotFound(
56+
f"Profile with ID {self.kwargs[self.lookup_field]} not found."
57+
)
58+
return obj
59+
60+
@extend_schema(responses=OverviewSerializer(many=True))
61+
@action(detail=True, methods=[HTTPMethod.GET])
62+
def overview(self, request, pk=None):
63+
"""Returns a mixed list of posts and comments by the user, ordered by date."""
64+
profile = self.get_object()
65+
posts = profile.posts.all().annotate(type=Value("post", CharField()))
66+
comments = profile.comments.with_annotated_ratio().annotate(
67+
type=Value("comment", CharField())
68+
)
69+
70+
combined_data = sorted(chain(posts, comments), key=lambda obj: obj.created_at, reverse=True)
71+
serialized_data = self.get_serializer(
72+
combined_data, context={'request': request}, many=True
73+
).data
74+
return response.Response(serialized_data)
75+
76+
@extend_schema(responses=PostSerializer(many=True))
77+
@action(detail=True, methods=[HTTPMethod.GET])
78+
def posts(self, request, pk=None):
79+
"""Returns a list of posts by the user, ordered by date."""
80+
profile = self.get_object()
81+
posts = profile.posts.all().order_by("-created_at")
82+
serialized_data = self.get_serializer(posts, context={"request": request}, many=True).data
83+
return response.Response(serialized_data)
84+
85+
@extend_schema(responses=CommentDetailSerializer(many=True))
86+
@action(detail=True, methods=[HTTPMethod.GET])
87+
def comments(self, request, pk=None):
88+
"""Returns a list of comments by the user, ordered by date."""
89+
profile = self.get_object()
90+
comments = profile.comments.with_annotated_ratio().order_by("-created_at")
91+
serialized_data = self.get_serializer(
92+
comments, context={"request": request}, many=True
93+
).data
94+
return response.Response(serialized_data)
95+
96+
@extend_schema(responses=UpvotedSerializer(many=True))
97+
@action(detail=True, methods=[HTTPMethod.GET])
98+
def upvoted(self, request, pk=None):
99+
"""Returns a mixed list of upvoted posts and comments by the user, ordered by date."""
100+
profile = self.get_object()
101+
posts = profile.upvoted_posts.all().annotate(type=Value("post", CharField()))
102+
comments = profile.upvoted_comments.with_annotated_ratio().annotate(
103+
type=Value("comment", CharField())
104+
)
105+
106+
combined_data = sorted(chain(posts, comments), key=lambda obj: obj.created_at, reverse=True)
107+
serialized_data = self.get_serializer(
108+
combined_data, context={"request": request}, many=True
109+
).data
110+
return response.Response(serialized_data)
111+
112+
@extend_schema(responses=DownvotedSerializer(many=True))
113+
@action(detail=True, methods=[HTTPMethod.GET])
114+
def downvoted(self, request, pk=None):
115+
"""Returns a mixed list of downvoted posts and comments by the user, ordered by date."""
116+
profile = self.get_object()
117+
posts = profile.downvoted_posts.all().annotate(type=Value("post", CharField()))
118+
comments = profile.downvoted_comments.with_annotated_ratio().annotate(
119+
type=Value("comment", CharField())
120+
)
121+
122+
combined_data = sorted(chain(posts, comments), key=lambda obj: obj.created_at, reverse=True)
123+
serialized_data = self.get_serializer(
124+
combined_data, context={"request": request}, many=True
125+
).data
126+
return response.Response(serialized_data)
23127

24128

25-
@extend_schema(tags=['user & profiles'])
129+
@extend_schema(tags=["user & profiles"])
26130
class MyProfilesViewSet(viewsets.ModelViewSet):
27131
"""
28132
ViewSet to manage profiles associated with the authenticated user.
@@ -39,7 +143,7 @@ def get_queryset(self): # pyright: ignore
39143
Restrict queryset to profiles owned by the currently authenticated user.
40144
"""
41145
# during schema generation
42-
if getattr(self, 'swagger_fake_view', False):
146+
if getattr(self, "swagger_fake_view", False):
43147
return Profile.objects.none()
44148
user = self.request.user
45149
return user.profiles.all() # pyright: ignore
@@ -54,6 +158,6 @@ def perform_create(self, serializer):
54158
user = self.request.user
55159
if user.profiles.count() >= settings.PROFILE_LIMIT: # pyright: ignore
56160
raise exceptions.ValidationError(
57-
f'A user cannot have more than {settings.PROFILE_LIMIT} profiles.'
161+
f"A user cannot have more than {settings.PROFILE_LIMIT} profiles."
58162
)
59163
serializer.save(user=user)

backend/core/settings.py

+1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
'SERVE_INCLUDE_SCHEMA': False,
103103
'SCHEMA_PATH_PREFIX': r'/api/v[1-9]/',
104104
'SCHEMA_PATH_PREFIX_TRIM': True,
105+
'SERVERS': [{'url': '/api/v1', 'description': 'v1 API version'}],
105106
# sidecar config
106107
'SWAGGER_UI_DIST': 'SIDECAR',
107108
'SWAGGER_UI_FAVICON_HREF': 'SIDECAR',

0 commit comments

Comments
 (0)