Add product review model, serializer, and API endpoint
Introduces a Review model for product reviews, including rating and comment fields. Adds a public serializer and a ModelViewSet for reviews with search and ordering capabilities. Also updates the frontend API client to use the correct token refresh endpoint and improves FormData handling.
This commit is contained in:
@@ -616,4 +616,22 @@ class Invoice(models.Model):
|
||||
|
||||
# Save directly to FileField
|
||||
self.pdf_file.save(f"{self.invoice_number}.pdf", ContentFile(pdf_bytes))
|
||||
self.save()
|
||||
self.save()
|
||||
|
||||
|
||||
class Review(models.Model):
|
||||
product = models.ForeignKey(Product, related_name="reviews", on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="reviews"
|
||||
)
|
||||
|
||||
rating = models.PositiveIntegerField(
|
||||
validators=[MinValueValidator(1), MaxValueValidator(5)]
|
||||
)
|
||||
comment = models.TextField(blank=True)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Review for {self.product.name} by {self.user.username}"
|
||||
@@ -2,7 +2,7 @@ from rest_framework import serializers
|
||||
|
||||
from thirdparty.stripe.client import StripeClient
|
||||
|
||||
from .models import Refund, Order, Invoice
|
||||
from .models import Refund, Order, Invoice, Review
|
||||
|
||||
|
||||
class RefundCreatePublicSerializer(serializers.Serializer):
|
||||
@@ -465,3 +465,9 @@ class OrderReadSerializer(serializers.ModelSerializer):
|
||||
return list(obj.discount.values_list("code", flat=True))
|
||||
|
||||
|
||||
|
||||
class ReviewSerializerPublic(serializers.Serializer):
|
||||
|
||||
class Meta:
|
||||
model = Review
|
||||
fields = "__all__"
|
||||
@@ -4,8 +4,8 @@ from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
import base64
|
||||
|
||||
from .models import Refund
|
||||
from .serializers import RefundCreatePublicSerializer
|
||||
from .models import Refund, Review
|
||||
from .serializers import RefundCreatePublicSerializer, ReviewSerializerPublic
|
||||
|
||||
|
||||
from django.db import transaction
|
||||
@@ -414,4 +414,16 @@ class RefundPublicView(APIView):
|
||||
"base64": pdf_b64,
|
||||
},
|
||||
}
|
||||
return Response(resp, status=status.HTTP_201_CREATED)
|
||||
return Response(resp, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
class ReviewPublicViewSet(viewsets.ModelViewSet):
|
||||
queryset = Review.objects.all()
|
||||
serializer_class = ReviewSerializerPublic
|
||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
|
||||
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
|
||||
search_fields = ["product__name", "user__username", "comment"]
|
||||
ordering_fields = ["rating", "created_at"]
|
||||
ordering = ["-created_at"]
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ privateApi.interceptors.response.use(
|
||||
original._retry = true;
|
||||
|
||||
try {
|
||||
await privateApi.post("/auth/refresh/");
|
||||
await privateApi.post("/api/account/token/refresh/");
|
||||
return privateApi(original);
|
||||
} catch {
|
||||
// optional: logout
|
||||
@@ -37,6 +37,11 @@ privateApi.interceptors.response.use(
|
||||
export const privateMutator = async <T>(
|
||||
config: AxiosRequestConfig
|
||||
): Promise<T> => {
|
||||
// If sending FormData, remove Content-Type header to let axios set it with boundary
|
||||
if (config.data instanceof FormData) {
|
||||
delete config.headers?.['Content-Type'];
|
||||
}
|
||||
|
||||
const response = await privateApi.request<T>(config);
|
||||
return response.data;
|
||||
};
|
||||
Reference in New Issue
Block a user