forked from ArmanCreativeSolutions/django-challenge
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from osmanmakhtoom/feat/backend-stadiums-app
Feat/backend stadiums app
- Loading branch information
Showing
28 changed files
with
407 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
from django.contrib import admin | ||
|
||
from apps.stadiums.models import Stadium, Match, Seat, Ticket | ||
from apps.stadiums.tasks import update_cache_match_info_task, update_cache_seats_task | ||
|
||
|
||
@admin.register(Stadium) | ||
class StadiumAdmin(admin.ModelAdmin): | ||
list_display = ('name', 'location', 'description', 'is_active', 'created_at', 'updated_at') | ||
list_filter = ('is_active', 'location') | ||
search_fields = ('name', 'description', 'location', 'uuid') | ||
date_hierarchy = 'created_at' | ||
|
||
|
||
@admin.register(Match) | ||
class MatchAdmin(admin.ModelAdmin): | ||
list_display = ('stadium', 'home_team', 'away_team', 'is_active', 'created_at', 'updated_at') | ||
list_filter = ('is_active', 'home_team', 'away_team') | ||
search_fields = ('stadium__name', 'home_team', 'away_team', 'uuid') | ||
date_hierarchy = 'created_at' | ||
|
||
def save_model(self, request, obj, form, change): | ||
super().save_model(request, obj, form, change) | ||
update_cache_match_info_task.delay() | ||
|
||
|
||
@admin.register(Seat) | ||
class SeatAdmin(admin.ModelAdmin): | ||
list_display = ('seat_number', 'match', 'is_reserved', 'price', 'is_active', 'created_at', 'updated_at') | ||
list_filter = ('is_reserved', 'match', 'price') | ||
search_fields = ('seat_number', 'match__home_team', 'match__away_team', 'price', 'uuid') | ||
date_hierarchy = 'created_at' | ||
|
||
def save_model(self, request, obj, form, change): | ||
super().save_model(request, obj, form, change) | ||
update_cache_seats_task.delay() | ||
|
||
|
||
@admin.register(Ticket) | ||
class TicketAdmin(admin.ModelAdmin): | ||
list_display = ('user', 'seat', 'is_active', 'created_at', 'updated_at') | ||
list_filter = ('is_active', 'seat', 'user') | ||
search_fields = ('user__email', 'seat__price', 'uuid') | ||
date_hierarchy = 'created_at' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from apps.stadiums.api.serializers.v1.stadium_serializer import StadiumSerializer | ||
from apps.stadiums.api.serializers.v1.match_serializer import MatchSerializer | ||
from apps.stadiums.api.serializers.v1.seat_serializer import SeatSerializer | ||
from apps.stadiums.api.serializers.v1.ticket_serializer import TicketSerializer | ||
|
||
__all__ = ['StadiumSerializer', 'MatchSerializer', 'SeatSerializer', 'TicketSerializer'] |
19 changes: 19 additions & 0 deletions
19
backend/apps/stadiums/api/serializers/v1/match_serializer.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from django.core.exceptions import ImproperlyConfigured | ||
|
||
from rest_framework import serializers | ||
|
||
from apps.stadiums.models import Match | ||
|
||
|
||
class MatchSerializer(serializers.ModelSerializer): | ||
stadium = serializers.SlugRelatedField(slug_field='uuid', read_only=True) | ||
|
||
class Meta: | ||
model = Match | ||
exclude = ('id',) | ||
|
||
def create(self, validated_data): | ||
raise ImproperlyConfigured('You cant\'t create Match instances.') | ||
|
||
def update(self, instance, validated_data): | ||
raise ImproperlyConfigured('You cant\'t update Match instances.') |
19 changes: 19 additions & 0 deletions
19
backend/apps/stadiums/api/serializers/v1/seat_serializer.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from django.core.exceptions import ImproperlyConfigured | ||
|
||
from rest_framework import serializers | ||
|
||
from apps.stadiums.models import Seat | ||
|
||
|
||
class SeatSerializer(serializers.ModelSerializer): | ||
match = serializers.SlugRelatedField(slug_field='uuid', read_only=True) | ||
|
||
class Meta: | ||
model = Seat | ||
exclude = ('id',) | ||
|
||
def create(self, validated_data): | ||
raise ImproperlyConfigured('You cant\'t create Seat instances.') | ||
|
||
def update(self, instance, validated_data): | ||
raise ImproperlyConfigured('You cant\'t update Seat instances.') |
17 changes: 17 additions & 0 deletions
17
backend/apps/stadiums/api/serializers/v1/stadium_serializer.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
from django.core.exceptions import ImproperlyConfigured | ||
|
||
from rest_framework import serializers | ||
|
||
from apps.stadiums.models import Stadium | ||
|
||
|
||
class StadiumSerializer(serializers.ModelSerializer): | ||
class Meta: | ||
model = Stadium | ||
exclude = ('id',) | ||
|
||
def create(self, validated_data): | ||
raise ImproperlyConfigured('You cant\'t create Stadium instances.') | ||
|
||
def update(self, instance, validated_data): | ||
raise ImproperlyConfigured('You cant\'t update Stadium instances.') |
29 changes: 29 additions & 0 deletions
29
backend/apps/stadiums/api/serializers/v1/ticket_serializer.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from django.core.exceptions import ImproperlyConfigured | ||
|
||
from rest_framework import serializers | ||
|
||
from apps.stadiums.models import Ticket | ||
from apps.stadiums.tasks import reserve_seat_task | ||
|
||
|
||
class TicketSerializer(serializers.ModelSerializer): | ||
user = serializers.SlugRelatedField(slug_field='uuid', read_only=True) | ||
seat = serializers.SlugRelatedField(slug_field='uuid', read_only=True) | ||
|
||
class Meta: | ||
model = Ticket | ||
exclude = ('id',) | ||
read_only_fields = ('uuid', 'is_active', 'created_at', 'updated_at') | ||
extra_kwargs = { | ||
'user': {'required': False}, | ||
} | ||
|
||
def create(self, validated_data): | ||
user_uuid = self.context['request'].user.uuid | ||
seat_uuid = validated_data['seat'].uuid | ||
task = reserve_seat_task.delay(user_uuid, seat_uuid) | ||
result = task.get(timeout=10) | ||
return result | ||
|
||
def update(self, instance, validated_data): | ||
raise ImproperlyConfigured('You cant\'t update Ticket instances.') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from django.urls import path | ||
|
||
from rest_framework.routers import DefaultRouter | ||
|
||
from apps.stadiums.api.views.v1 import ( | ||
StadiumView as StadiumViewV1, | ||
MatchView as MatchViewV1, | ||
SeatView as SeatViewV1, | ||
TicketViewSet as TicketViewSetV1, | ||
) | ||
|
||
router = DefaultRouter(trailing_slash=False) | ||
router.register('v1/tickets', TicketViewSetV1, basename='tickets') | ||
|
||
urlpatterns = router.urls + [ | ||
path('v1/stadiums', StadiumViewV1.as_view(), name='stadiums'), | ||
path('v1/matches', MatchViewV1.as_view(), name='matches'), | ||
path('v1/seats', SeatViewV1.as_view(), name='seats'), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from apps.stadiums.api.views.v1.stadium_view import StadiumView | ||
from apps.stadiums.api.views.v1.match_view import MatchView | ||
from apps.stadiums.api.views.v1.seat_view import SeatView | ||
from apps.stadiums.api.views.v1.ticket_viewset import TicketViewSet | ||
|
||
__all__ = ['StadiumView', 'MatchView', 'SeatView', 'TicketViewSet'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
from django.conf import settings | ||
|
||
from rest_framework.permissions import IsAuthenticatedOrReadOnly | ||
from rest_framework.generics import ListAPIView, RetrieveAPIView | ||
|
||
from apps.stadiums.api.serializers.v1 import MatchSerializer | ||
from apps.stadiums.models import Match | ||
from apps.utils.helpers import CacheManager | ||
|
||
|
||
class MatchView(ListAPIView, RetrieveAPIView): | ||
serializer_class = MatchSerializer | ||
permission_classes = (IsAuthenticatedOrReadOnly,) | ||
lookup_field = 'uuid' | ||
|
||
def get_queryset(self): | ||
""" | ||
Return cached queryset if is not expired else hit to database cache it and return. | ||
This approach do not any break in system because we do update cached queryset every time updated table data | ||
""" | ||
cached_queryset = CacheManager(settings.MATCHES_QUERYSET_CACHE_KEY) | ||
if not cached_queryset.is_expired: | ||
return cached_queryset.value | ||
matches = Match.objects.filter_active().select_related('stadium') | ||
cached_queryset.period = settings.MATCHES_QUERYSET_CACHE_TIMEOUT | ||
cached_queryset.value = matches | ||
|
||
return matches |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
from django.conf import settings | ||
|
||
from rest_framework.permissions import IsAuthenticatedOrReadOnly | ||
from rest_framework.generics import ListAPIView, RetrieveAPIView | ||
|
||
from apps.stadiums.api.serializers.v1 import SeatSerializer | ||
from apps.stadiums.models import Seat | ||
from apps.utils.helpers import CacheManager | ||
|
||
|
||
class SeatView(ListAPIView, RetrieveAPIView): | ||
serializer_class = SeatSerializer | ||
permission_classes = (IsAuthenticatedOrReadOnly,) | ||
lookup_field = 'uuid' | ||
|
||
def get_queryset(self): | ||
""" | ||
Return cached queryset if is not expired else hit to database cache it and return. | ||
This approach do not any break in system because we do update cached queryset every time updated table data | ||
""" | ||
cached_queryset = CacheManager(settings.AVAILABLE_SEATS_CACHE_KEY) | ||
if not cached_queryset.is_expired: | ||
return cached_queryset.value | ||
seats = Seat.objects.filter_active(is_reserved=False).select_related('match') | ||
cached_queryset.period = settings.AVAILABLE_SEATS_CACHE_TIMEOUT | ||
cached_queryset.value = seats | ||
|
||
return seats |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from django.views.decorators.cache import cache_page | ||
from django.utils.decorators import method_decorator | ||
|
||
from rest_framework.permissions import IsAuthenticatedOrReadOnly | ||
from rest_framework.generics import ListAPIView, RetrieveAPIView | ||
|
||
from apps.stadiums.api.serializers.v1 import StadiumSerializer | ||
from apps.stadiums.models import Stadium | ||
|
||
|
||
@method_decorator(cache_page(60 * 10), name='dispatch') | ||
class StadiumView(ListAPIView, RetrieveAPIView): | ||
queryset = Stadium.objects.filter_active() | ||
serializer_class = StadiumSerializer | ||
permission_classes = (IsAuthenticatedOrReadOnly,) | ||
lookup_field = 'uuid' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
from django.utils.decorators import method_decorator | ||
from django.views.decorators.cache import cache_page | ||
|
||
from rest_framework.permissions import IsAuthenticated | ||
from rest_framework.viewsets import ModelViewSet | ||
|
||
from apps.stadiums.api.serializers.v1 import TicketSerializer | ||
from apps.stadiums.models import Ticket | ||
|
||
|
||
@method_decorator(cache_page(60 * 10), name='dispatch') | ||
class TicketViewSet(ModelViewSet): | ||
serializer_class = TicketSerializer | ||
permission_classes = (IsAuthenticated,) | ||
lookup_field = 'uuid' | ||
|
||
def get_queryset(self): | ||
return Ticket.objects.filter(user=self.request.user).select_related('user', 'seat') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from apps.stadiums.models.stadium import Stadium | ||
from apps.stadiums.models.match import Match | ||
from apps.stadiums.models.seat import Seat | ||
from apps.stadiums.models.ticket import Ticket | ||
|
||
__all__ = ['Stadium', 'Match', 'Seat', 'Ticket'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from django.db import models | ||
|
||
from apps.stadiums.models import Stadium | ||
|
||
from apps.utils.model_mixins import IsActiveMixin, TimestampedMixin, UUIDMixin | ||
from apps.utils.managers import FilterActiveManager | ||
|
||
|
||
class Match(TimestampedMixin, IsActiveMixin, UUIDMixin): | ||
stadium = models.ForeignKey(Stadium, on_delete=models.PROTECT, related_name='matches') | ||
home_team = models.CharField(max_length=255) | ||
away_team = models.CharField(max_length=255) | ||
match_date = models.DateTimeField() | ||
|
||
objects = FilterActiveManager() | ||
|
||
def __str__(self): | ||
return f'{self.home_team} vs {self.away_team} @ {self.stadium.name} on {self.match_date}' | ||
|
||
def __repr__(self): | ||
return f'<Match: home={self.home_team}, away={self.away_team}, date={self.match_date}, stadium={self.stadium.name}>' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from django.db import models | ||
|
||
from apps.utils.model_mixins import IsActiveMixin, TimestampedMixin, UUIDMixin | ||
from apps.utils.managers import FilterActiveManager | ||
|
||
from apps.stadiums.models import Match | ||
|
||
|
||
class Seat(TimestampedMixin, IsActiveMixin, UUIDMixin): | ||
match = models.ForeignKey(Match, on_delete=models.PROTECT, related_name='seats') | ||
seat_number = models.CharField(max_length=10) | ||
is_reserved = models.BooleanField(default=False) | ||
price = models.IntegerField(default=0) | ||
|
||
objects = FilterActiveManager() | ||
|
||
def __str__(self): | ||
return f'{self.seat_number} for {self.match}' | ||
|
||
def __repr__(self): | ||
return f'<Seat: number={self.seat_number}, match={self.match}>' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from django.db import models | ||
|
||
from apps.utils.model_mixins import IsActiveMixin, TimestampedMixin, UUIDMixin | ||
from apps.utils.managers import FilterActiveManager | ||
|
||
|
||
class Stadium(TimestampedMixin, IsActiveMixin, UUIDMixin): | ||
name = models.CharField(max_length=255) | ||
location = models.CharField(max_length=255) | ||
description = models.TextField(blank=True, null=True) | ||
capacity = models.IntegerField(default=0) | ||
|
||
objects = FilterActiveManager() | ||
|
||
def __str__(self): | ||
return self.name | ||
|
||
def __repr__(self): | ||
return f'<Stadium: name={self.name}, capacity={self.capacity}>' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from django.contrib.auth import get_user_model | ||
from django.db import models | ||
|
||
from apps.utils.model_mixins import IsActiveMixin, TimestampedMixin, UUIDMixin | ||
from apps.utils.managers import FilterActiveManager | ||
|
||
from apps.stadiums.models import Seat | ||
|
||
|
||
class Ticket(TimestampedMixin, IsActiveMixin, UUIDMixin): | ||
seat = models.ForeignKey(Seat, on_delete=models.PROTECT, related_name='tickets') | ||
user = models.ForeignKey(get_user_model(), on_delete=models.PROTECT, related_name='tickets') | ||
|
||
objects = FilterActiveManager() | ||
|
||
def __str__(self): | ||
return f'Ticket for {self.user.uuid} - {self.seat}' | ||
|
||
def __repr__(self): | ||
return f'<Ticket: user={self.user.uuid}, {self.seat}, {self.created_at}>' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from apps.stadiums.tasks.reserve_seat_task import reserve_seat_task | ||
from apps.stadiums.tasks.update_cache_match_info_task import update_cache_match_info_task | ||
from apps.stadiums.tasks.update_cache_seats_task import update_cache_seats_task | ||
|
||
__all__ = ['reserve_seat_task', 'update_cache_match_info_task', 'update_cache_seats_task'] |
Oops, something went wrong.