Skip to content

Commit

Permalink
Merge pull request #30 from djangocameroon/refactor/serializers
Browse files Browse the repository at this point in the history
Feat: updates to serializers
  • Loading branch information
Edmond22-prog authored Aug 20, 2024
2 parents 049f8ab + 4c30df1 commit 9a16c89
Show file tree
Hide file tree
Showing 12 changed files with 208 additions and 44 deletions.
1 change: 1 addition & 0 deletions apps/events/serializers/event_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

class EventSerializer(serializers.ModelSerializer):
speakers = serializers.SerializerMethodField()
tags = serializers.ListField(child=serializers.CharField(), required=False)

class Meta:
model = Event
Expand Down
56 changes: 29 additions & 27 deletions apps/events/views/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,51 @@
class EventViewSet(ModelViewSet, APIResponseMixin):
queryset = Event.objects.all().select_related('created_by', 'updated_by')
authentication_classes = [OAuth2Authentication]
serializer_class = EventSerializer
http_method_names = ["get", "post", "put", "delete"]
parser_classes = [JSONParser]

def get_serializer_class(self):
if self.action in ["list"]:
return EventSerializer
return EventSerializer

def get_permissions(self):
if self.action in ["list", "retrieve"]:
permission_classes = [AllowAny]
else:
permission_classes = [IsAuthenticated]
return [permission() for permission in permission_classes]

@extend_schema(
summary="Get all events",
operation_id="get_events",
description="Get all events.",
responses={
200: OpenApiResponse(
response=EventSerializer(many=True),
description=_("List of events"),
)
},
tags=["Events"],
)
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
return self.paginated_response(
request=request,
queryset=queryset,
serializer_class=EventSerializer,
message=_("List of events"),
status_code=status.HTTP_200_OK,
)

@extend_schema(
summary="Create an event",
operation_id="create_event",
description="Create an event.",
request=CreateEventInputSerializer,
responses={
201: OpenApiResponse(
response=EventSerializer(),
response=EventSerializer,
description=_("Event created successfully")
)
},
Expand All @@ -48,34 +74,10 @@ def create(self, request, *args, **kwargs):
create_event_serializer = CreateEventInputSerializer(data=request.data)
create_event_serializer.is_valid(raise_exception=True)
event = create_event_serializer.save(created_by=request.user, updated_by=request.user)
response_serializer = EventSerializer(event)
return self.success(
message=_("Event created successfully"),
data=EventSerializer(event).data,
status_code=status.HTTP_201_CREATED,
data=response_serializer.data,
)

@extend_schema(
summary="Get all events",
operation_id="get_events",
description="Get all events.",
responses={
200: OpenApiResponse(
response=EventSerializer(many=True),
description=_("List of events")
)
},
tags=["Events"],
)
def list(self, request, *args, **kwargs):
events = self.get_queryset().select_related(
'created_by', 'updated_by'
)
serializer = EventSerializer(events, many=True)
return self.success(
message=_("List of events"),
status_code=status.HTTP_200_OK,
data=serializer.data,
)

@extend_schema(
Expand Down
16 changes: 14 additions & 2 deletions apps/events/views/reservation.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@
class ReservationViewSet(ModelViewSet, APIResponseMixin):
queryset = Reservation.objects.all()
authentication_classes = [OAuth2Authentication]
serializer_class = ReservationSerializer
http_method_names = ["get", "post", "put", "delete"]
parser_classes = [JSONParser]

def get_serializer_class(self):
if self.action in ["list", "retrieve"]:
return ReservationSerializer
if self.action == "create":
return CreateReservationSerializer

def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
Expand All @@ -40,7 +45,14 @@ def get_permissions(self):
tags=["Reservations"],
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
reservations = self.get_queryset()
return self.paginated_response(
request=request,
queryset=reservations,
serializer_class=ReservationSerializer,
message=_("Reservations listed successfully"),
status_code=status.HTTP_200_OK,
)

@extend_schema(
summary="Get reservation details",
Expand Down
11 changes: 9 additions & 2 deletions apps/events/views/speaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
SpeakerSerializer,
SpeakerWithLastUpdatedBySerializer,
)
from apps.users.serializers.general_serializers import PaginatedResponseSerializer
from mixins.api_response_mixin import APIResponseMixin


Expand All @@ -18,11 +19,15 @@ class SpeakerViewSet(ModelViewSet, APIResponseMixin):
ViewSet for managing speakers.
"""
queryset = Speaker.objects.all()
serializer_class = SpeakerSerializer
authentication_classes = [OAuth2Authentication]
parser_classes = [JSONParser]
http_method_names = ["get", "post", "put", "delete"]

def get_serializer_class(self):
if self.action in ["list", "retrieve"]:
return SpeakerSerializer
return SpeakerWithLastUpdatedBySerializer

def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
Expand Down Expand Up @@ -57,7 +62,9 @@ def create(self, request, *args, **kwargs):
summary="List all speakers",
operation_id="list_speakers",
description="List all speakers.",
responses={200: SpeakerSerializer(many=True)},
responses={
status.HTTP_200_OK: PaginatedResponseSerializer(data_serializer_class=SpeakerSerializer),
}
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
Expand Down
26 changes: 26 additions & 0 deletions apps/users/pagination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response


class CustomPagination(PageNumberPagination):
page_size_query_param = 'page_size'
page_query_param = 'page'.lower()
max_page_size = 100

def get_paginated_response(self, data):
return Response({
'status': True,
'message': 'Data retrieved successfully',
'status_code': 200,
'page': self.page.number,
'page_size': self.page.paginator.per_page,
'total': self.page.paginator.count,
'pagination': {
'next': self.get_next_link(),
'previous': self.get_previous_link(),
'count': self.page.paginator.count,
'current_page': self.page.number,
'total_pages': self.page.paginator.num_pages
},
'data': data
})
Empty file removed apps/users/paginators.py
Empty file.
43 changes: 38 additions & 5 deletions apps/users/serializers/general_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,49 @@
User = get_user_model()


class PaginationSerializer(serializers.Serializer):
next = serializers.CharField()
previous = serializers.CharField()
count = serializers.IntegerField()
current_page = serializers.IntegerField()
total_pages = serializers.IntegerField()


class SuccessResponseSerializer(serializers.Serializer):
status = serializers.BooleanField(default=True)
message = serializers.CharField(max_length=255)
data = serializers.JSONField(required=False)
message = serializers.CharField(max_length=200)
status_code = serializers.IntegerField(default=200)

def __init__(self, *args, **kwargs):
data_serializer_class = kwargs.pop('data_serializer_class', None)
many = kwargs.pop('many', False)
super().__init__(*args, **kwargs)
if data_serializer_class:
self.fields['data'] = data_serializer_class(many=many)


class PaginatedResponseSerializer(SuccessResponseSerializer):
next = serializers.CharField(required=False, allow_null=True)
previous = serializers.CharField(required=False, allow_null=True)
count = serializers.IntegerField()
page = serializers.IntegerField()
page_size = serializers.IntegerField()
total_pages = serializers.IntegerField()

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)


class ErrorResponseSerializer(serializers.Serializer):
status = serializers.BooleanField(default=True)
message = serializers.CharField(max_length=255)
errors = serializers.JSONField(required=False)
status = serializers.BooleanField(default=False)
message = serializers.CharField(default="An error occurred")
status_code = serializers.IntegerField(default=400)

def __init__(self, *args, **kwargs):
default_message = kwargs.pop('default_message', None)
super().__init__(*args, **kwargs)
if default_message:
self.fields['message'].default = default_message


class UserSerializer(serializers.ModelSerializer):
Expand Down
37 changes: 37 additions & 0 deletions apps/users/views/general_viewsets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from rest_framework import status
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet


class BaseModelViewSet(ModelViewSet):
"""
A base viewset that you can extend to add custom pagination handling.
"""

def paginated_response(
self, queryset, request,
serializer_class, message="Success",
status_code=status.HTTP_200_OK,
):
"""
Custom paginated response method.
"""
page = self.paginate_queryset(queryset)
serializer = serializer_class(page, many=True)
response_data = {
"status": True,
"message": message,
"status_code": status_code,
"page": self.paginator.page.number,
"page_size": self.paginator.page_size,
"total": self.paginator.page.paginator.count,
"pagination": {
"next": self.paginator.get_next_link(),
"previous": self.paginator.get_previous_link(),
"count": self.paginator.page.paginator.count,
"current_page": self.paginator.page.number,
"total_pages": self.paginator.page.paginator.num_pages,
},
"results": serializer.data
}
return Response(response_data, status=status_code)
46 changes: 45 additions & 1 deletion mixins/api_response_mixin.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from typing import Any, Optional, Union, List

from rest_framework import status
from rest_framework.pagination import PageNumberPagination
from rest_framework.request import Request
from rest_framework.response import Response


class APIResponseMixin:
"""
A mixin to standardize API responses, providing both success and error response methods.
A mixin to standardize API responses, providing both success and error response methods,
including pagination support.
"""

def success(
Expand Down Expand Up @@ -46,3 +49,44 @@ def error(
"errors": [errors] if isinstance(errors, str) else errors
}
return Response(response_data, status=status_code)

def paginated_response(
self, request: Request, queryset: Any, serializer_class: Any,
message: str = "Success", page_size: int = 10,
status_code: int = status.HTTP_200_OK,
) -> Response:
"""
Returns a paginated response with next and previous URLs.
:param request: The DRF request object.
:param queryset: The queryset to paginate.
:param serializer_class: The serializer class to use for the data.
:param message: A string message describing the success.
:param page_size: Number of items per page.
:param status_code: HTTP status code, default is 200 OK.
:return: DRF Response object with standardized pagination format.
"""
paginator = PageNumberPagination()
paginator.page_size = page_size

if not queryset.ordered:
queryset = queryset.order_by('created_at')

paginated_queryset = paginator.paginate_queryset(queryset, request)

data = serializer_class(paginated_queryset, many=True).data

response_data = {
"status": True,
"message": message,
"data": data,
"status_code": status_code,
"pagination": {
"next": paginator.get_next_link(),
"previous": paginator.get_previous_link(),
"count": paginator.page.paginator.count,
"current_page": paginator.page.number,
"total_pages": paginator.page.paginator.num_pages
}
}
return Response(response_data, status=status.HTTP_200_OK)
3 changes: 1 addition & 2 deletions templates/docs/redoc.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@
</style>
</head>
<body>
<div id="redoc-container"></div>
<script src="https://cdn.jsdelivr.net/npm/redoc@2.1.5/bundles/redoc.standalone.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/redoc-try-it-out/dist/try-it-out.min.js"></script>
<script>
RedocTryItOut.init(
"{% url 'schema' %}",
{title: "Pet Store"},
document.getElementById("redoc_container")
document.getElementById("redoc-container")
)
</script>
</body>
Expand Down
11 changes: 7 additions & 4 deletions utils/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@


def authenticate_user(self, data):
user = User.objects.get(Q(username=data['email_or_username']) | Q(email=data['email_or_username']))
if user and user.check_password(data['password']):
return user
return None
try:
user = User.objects.get(Q(username=data['email_or_username']) | Q(email=data['email_or_username']))
if user and user.check_password(data['password']):
return user
return None
except User.DoesNotExist:
raise ValueError("Authentication credentials invalid")


class EmailOrUsernameBackend(BaseBackend):
Expand Down
2 changes: 1 addition & 1 deletion website_api/settings/extra.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle",
],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"DEFAULT_PAGINATION_CLASS": "apps.users.pagination.CustomPagination",
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
"PAGE_SIZE": 100,
"NON_FIELD_ERRORS_KEY": "message",
Expand Down

0 comments on commit 9a16c89

Please sign in to comment.