Skip to content

Commit 5a20ebd

Browse files
committed
feat(blogs): add post viewset, actions for reports, update managers, routers, etc
1 parent 8f98c19 commit 5a20ebd

File tree

7 files changed

+164
-22
lines changed

7 files changed

+164
-22
lines changed

apps/blogs/managers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,7 @@ def get_search(self, search_term):
3030
| Q(content__icontains=search_term)
3131
| Q(tags__name__icontains=search_term)
3232
)
33+
34+
35+
class PostReportManager(BaseManager):
36+
"""Manager for Post Model."""

apps/blogs/models.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from apps.utilities.models import BaseModel
77
from apps.utilities.mixins import SlugMixin
8-
from .managers import TagManager, PostManager
8+
from .managers import TagManager, PostManager, PostReportManager
99
from .choices import PriorityChoices, StatusChoices
1010

1111
User = settings.AUTH_USER_MODEL
@@ -73,6 +73,8 @@ class PostReport(BaseModel):
7373
default=StatusChoices.PENDING,
7474
)
7575

76+
objects = PostReportManager()
77+
7678
class Meta:
7779
ordering = ["pk"]
7880
verbose_name = "post report"

apps/blogs/routers.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""Routers for Blogs App."""
2+
3+
from django.urls import include
4+
from django.urls import path
5+
from rest_framework.routers import DefaultRouter
6+
7+
from .viewsets import PostViewSet
8+
9+
router = DefaultRouter()
10+
router.register(r"posts", PostViewSet, basename="post")
11+
12+
urlpatterns = [
13+
path("api/v1/", include(router.urls)),
14+
]

apps/blogs/serializers.py

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
"""Serializers for Blogs App."""
22

3-
from rest_framework.serializers import ModelSerializer, CharField
3+
from rest_framework.serializers import ModelSerializer, CharField, StringRelatedField
44

5+
from apps.utilities.mixins import ReadOnlyFieldsMixin
56
from .models import Post, Tag, PostReport
67

78

8-
class TagReadSerializer(ModelSerializer):
9+
class TagReadSerializer(ReadOnlyFieldsMixin, ModelSerializer):
910
"""Serializer for Category model (List/retrieve)."""
1011

1112
class Meta:
@@ -15,10 +16,6 @@ class Meta:
1516
"name",
1617
"slug",
1718
]
18-
read_only_fields = [
19-
"id",
20-
"slug",
21-
]
2219

2320

2421
class TagWriteSerializer(ModelSerializer):
@@ -29,10 +26,11 @@ class Meta:
2926
fields = ["name"]
3027

3128

32-
class PostReadSerializer(ModelSerializer):
29+
class PostReadSerializer(ReadOnlyFieldsMixin, ModelSerializer):
3330
"""Serializer for Post model (List/retrieve)."""
3431

3532
tags = TagReadSerializer(many=True)
33+
author_id = StringRelatedField()
3634

3735
class Meta:
3836
model = Post
@@ -46,11 +44,6 @@ class Meta:
4644
"created_at",
4745
"updated_at",
4846
]
49-
read_only_fields = [
50-
"slug",
51-
"created_at",
52-
"updated_at",
53-
]
5447

5548

5649
class PostWriteSerializer(ModelSerializer):
@@ -65,7 +58,7 @@ class Meta:
6558
]
6659

6760

68-
class PostReportReadSerializer(ModelSerializer):
61+
class PostReportReadSerializer(ReadOnlyFieldsMixin, ModelSerializer):
6962
"""Serializer for PostReport model (List)."""
7063

7164
priority = CharField(source="get_priority_display")

apps/blogs/viewsets.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
"""ViewSets for Blogs App."""
2+
3+
from django.db import transaction
4+
from django.utils.decorators import method_decorator
5+
from django.views.decorators.cache import cache_page
6+
from django.views.decorators.vary import vary_on_headers
7+
from rest_framework.viewsets import ModelViewSet
8+
from rest_framework.permissions import AllowAny
9+
from rest_framework.decorators import action
10+
from rest_framework.response import Response
11+
from rest_framework import status
12+
13+
from apps.users.permissions import IsMarketing, IsClient
14+
from apps.utilities.mixins import ListCacheMixin, LogicalDeleteMixin
15+
from .models import Post, PostReport
16+
from .serializers import (
17+
PostReadSerializer,
18+
PostWriteSerializer,
19+
PostReportReadSerializer,
20+
PostReportWriteSerializer,
21+
)
22+
from .choices import PriorityChoices
23+
24+
25+
class PostViewSet(ListCacheMixin, LogicalDeleteMixin, ModelViewSet):
26+
"""
27+
ViewSet for managing Country instances.
28+
29+
Endpoints:
30+
- GET /api/v1/posts/
31+
- POST /api/v1/posts/
32+
- GET /api/v1/posts/{id}/
33+
- PUT /api/v1/posts/{id}/
34+
- PATCH /api/v1/posts/{id}/
35+
- DELETE /api/v1/posts/{id}/
36+
"""
37+
38+
permission_classes = [IsMarketing]
39+
serializer_class = PostWriteSerializer
40+
search_fields = ["title"]
41+
# filterset_class = PostFilter
42+
43+
def get_queryset(self):
44+
return (
45+
Post.objects.get_available()
46+
.select_related("author_id")
47+
.prefetch_related("tags")
48+
)
49+
50+
def get_serializer_class(self):
51+
if self.action in ["list", "retrieve"]:
52+
return PostReadSerializer
53+
return super().get_serializer_class()
54+
55+
def get_permissions(self):
56+
if self.action in ["list", "retrieve"]:
57+
return [AllowAny()]
58+
return super().get_permissions()
59+
60+
@action(
61+
detail=True,
62+
methods=["post"],
63+
permission_classes=[IsClient],
64+
url_path="report",
65+
)
66+
def create_report(self, request, *args, **kwargs):
67+
"""
68+
Action report a specific post by ID.
69+
70+
Endpoints:
71+
- GET api/v1/posts/{id}/report/
72+
"""
73+
post = self.get_object()
74+
serializer = PostReportWriteSerializer(data=request.data)
75+
if serializer.is_valid():
76+
if PostReport.objects.filter(post_id=post, user_id=request.user).exists():
77+
return Response(
78+
{"detail": "You have already reported this post."},
79+
status=status.HTTP_400_BAD_REQUEST,
80+
)
81+
82+
with transaction.atomic():
83+
# Update points of the post
84+
post.points = post.points - 1
85+
if post.points <= 5:
86+
post.is_available = False
87+
post.save()
88+
89+
# Determine priority for the new report
90+
if post.points <= 25:
91+
priority = PriorityChoices.URGENT
92+
elif post.points <= 50:
93+
priority = PriorityChoices.HIGH
94+
elif post.points <= 75:
95+
priority = PriorityChoices.MEDIUM
96+
else:
97+
priority = PriorityChoices.LOW
98+
99+
serializer.save(user_id=request.user, post_id=post, priority=priority)
100+
return Response(
101+
{"detail": "Your report has been submitted successfully."},
102+
status=status.HTTP_201_CREATED,
103+
)
104+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
105+
106+
@action(
107+
detail=False,
108+
methods=["get"],
109+
permission_classes=[IsMarketing],
110+
url_path="reports",
111+
)
112+
@method_decorator(cache_page(60 * 60 * 2))
113+
@method_decorator(vary_on_headers("User-Agent"))
114+
def get_reports(self, request, *args, **kwargs):
115+
"""
116+
Action retrieve a list of all post reports.
117+
118+
Endpoints:
119+
- GET api/v1/posts/reports/
120+
"""
121+
reports = PostReport.objects.get_available()
122+
if reports.exists():
123+
serializer = PostReportReadSerializer(reports, many=True)
124+
return Response(serializer.data)
125+
return Response(
126+
{"detail": "No reports found."},
127+
status=status.HTTP_404_NOT_FOUND,
128+
)

config/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,15 @@
3232
# ! TODO: Remove urls
3333
# path("", include("apps.restaurants.urls")),
3434
# path("", include("apps.promotions.urls")),
35+
# path("", include("apps.blogs.urls")),
3536
# Apps urls
3637
path("", include("apps.restaurants.routers")),
3738
path("", include("apps.promotions.routers")),
3839
path("", include("apps.locations.routers")),
40+
path("", include("apps.blogs.routers")),
3941
path("", include("apps.home.urls")),
4042
path("", include("apps.drivers.urls")),
4143
path("", include("apps.orders.urls")),
42-
path("", include("apps.blogs.urls")),
4344
path("", include("apps.users.urls")),
4445
]
4546

roadmap.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,15 @@ Pending implementation
9898

9999
## Blogs [OK]
100100

101-
- [ ] `GET /posts` Obtener una lista de todos los posts (AllowAny).
102-
- [ ] `POST /posts` Crear un nuevo post (IsMarketing).
103-
- [ ] `GET /posts/{id}` Obtener los detalles de un post específico por su ID (AllowAny).
104-
- [ ] `PATCH /posts/{id}` Actualizar un post específico por su ID (IsMarketing).
105-
- [ ] `DELETE /posts/{id}` Eliminar un post específico por su ID (IsMarketing).
101+
- [x] `GET /posts` Obtener una lista de todos los posts (AllowAny).
102+
- [x] `POST /posts` Crear un nuevo post (IsMarketing).
103+
- [x] `GET /posts/{id}` Obtener los detalles de un post específico por su ID (AllowAny).
104+
- [x] `PATCH /posts/{id}` Actualizar un post específico por su ID (IsMarketing).
105+
- [x] `DELETE /posts/{id}` Eliminar un post específico por su ID (IsMarketing).
106+
106107
- [ ] `POST /posts/{id}/report` Reportar un post específico por su ID (IsClient).
107108
- [ ] `GET /posts/tags` Obtener una lista de todas las etiquetas (IsMarketing).
108-
- [ ] `GET /posts/tags` Crear una nueva etiqueta (IsMarketing).
109-
- [ ] `GET /posts/search/` Buscar posts basados en una consulta de búsqueda (AllowAny).
109+
- [ ] `POST /posts/tags` Crear una nueva etiqueta (IsMarketing).
110110

111111
TODO: recents and featured endpoint unificarlos con /posts agregando un filtro
112112

0 commit comments

Comments
 (0)