-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* qr code user view * Static files fix * enhance * update * remove unncessary code * Basic QR code app * qr scan with revbate check * Added comments and docstrings and improved the logic * get mess cards added auto create mess card * Update migrations * Fixed bugs * Some fixes * More details added
- Loading branch information
1 parent
fa72158
commit e5638f8
Showing
36 changed files
with
1,030 additions
and
478 deletions.
There are no files selected for viewing
Empty file.
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,3 @@ | ||
from django.contrib import admin | ||
|
||
# Register your models here. |
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 django.apps import AppConfig | ||
|
||
|
||
class ApiConfig(AppConfig): | ||
default_auto_field = 'django.db.models.BigAutoField' | ||
name = 'api' |
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,3 @@ | ||
from django.db import models | ||
|
||
# Create your models here. |
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,12 @@ | ||
from rest_framework.permissions import BasePermission | ||
|
||
class IsStaff(BasePermission): | ||
""" | ||
Custom permission to only allow access to staff users. | ||
""" | ||
|
||
def has_permission(self, request, view): | ||
""" | ||
Check if the user is authenticated and is a staff member. | ||
""" | ||
return bool(request.user and request.user.is_staff) |
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,74 @@ | ||
from rest_framework import serializers | ||
from home.adapters.account_adapter import User | ||
from home.models import Allocation, Period, Student, Caterer | ||
from qrscan.models import MessCard, Meal | ||
|
||
class UserSerializer(serializers.ModelSerializer): | ||
"""Serializer for User model.""" | ||
class Meta: | ||
model = User | ||
fields = ['id', 'email', 'is_staff', 'is_superuser', 'username', 'last_login', 'date_joined', 'first_name', 'last_name'] | ||
|
||
|
||
class StudentSerializer(serializers.ModelSerializer): | ||
"""Serializer for Student model.""" | ||
class Meta: | ||
model = Student | ||
fields = ['id', 'email', 'name'] | ||
|
||
|
||
class CatererSerializer(serializers.ModelSerializer): | ||
"""Serializer for Caterer model.""" | ||
class Meta: | ||
model = Caterer | ||
fields = ['id', 'name'] | ||
|
||
|
||
class AllocationSerializer(serializers.ModelSerializer): | ||
"""Serializer for Allocation model.""" | ||
caterer = CatererSerializer(read_only=True) | ||
|
||
class Meta: | ||
model = Allocation | ||
fields = ['id', 'student_id', 'email', 'caterer'] | ||
|
||
|
||
class AllocationDetailSerializer(serializers.ModelSerializer): | ||
"""Detailed serializer for Allocation model.""" | ||
student = StudentSerializer(read_only=True) | ||
caterer = CatererSerializer(read_only=True) | ||
|
||
class Meta: | ||
model = Allocation | ||
fields = ['id', 'period_id', 'student_id', 'student', 'caterer'] | ||
|
||
|
||
class PeriodSerializer(serializers.ModelSerializer): | ||
"""Serializer for Period model.""" | ||
allocation_set = AllocationSerializer(many=True, read_only=True) | ||
|
||
class Meta: | ||
model = Period | ||
fields = '__all__' | ||
|
||
|
||
class QRVerifySerializer(serializers.ModelSerializer): | ||
"""Serializer for QR verification.""" | ||
student = StudentSerializer(read_only=True) | ||
allocation = AllocationSerializer(read_only=True) | ||
|
||
class Meta: | ||
model = MessCard | ||
fields = ['id', 'allocation', 'student', 'qr_code'] | ||
|
||
|
||
class QRVerifyPostSerializer(serializers.Serializer): | ||
"""Serializer for QR verification POST request.""" | ||
id = serializers.UUIDField(required=True) | ||
|
||
|
||
class MealSerializer(serializers.ModelSerializer): | ||
"""Serializer for Meal model.""" | ||
class Meta: | ||
model = Meal | ||
fields = ['id', 'mess_card', 'date', 'breakfast', 'lunch', 'dinner'] |
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,3 @@ | ||
from django.test import TestCase | ||
|
||
# Create your tests here. |
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,38 @@ | ||
from django.urls import path, include | ||
from rest_framework.routers import DefaultRouter | ||
from .views import AllocationViewSet, PeriodViewSet, QRVerifyView, MealViewSet, LogoutView, IsAuthenticated, UserDetail, QRVerifyUpdateView | ||
from rest_framework.authtoken.views import obtain_auth_token | ||
from rest_framework import permissions | ||
from drf_yasg.views import get_schema_view | ||
from drf_yasg import openapi | ||
|
||
|
||
schema_view = get_schema_view( | ||
openapi.Info( | ||
title="Mess Website API", | ||
default_version='v1', | ||
description="API for the Mess Website", | ||
# terms_of_service="https://www.google.com/policies/terms/", | ||
contact=openapi.Contact(email="[email protected]"), | ||
license=openapi.License(name="BSD License"), | ||
), | ||
public=True, | ||
permission_classes=(permissions.AllowAny,), | ||
) | ||
|
||
router = DefaultRouter() | ||
router.register(r'allocation', AllocationViewSet) | ||
router.register(r'period', PeriodViewSet) | ||
router.register(r'meal', MealViewSet) | ||
|
||
urlpatterns = [ | ||
path('', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), | ||
path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), | ||
path('', include(router.urls)), | ||
path('auth/user/', UserDetail.as_view(), name='user_detail'), | ||
path('auth/login/', obtain_auth_token, name='api_token_auth'), | ||
path('auth/logout/', LogoutView.as_view(), name='api_token_logout'), | ||
path('auth/is_authenticated/', IsAuthenticated.as_view(), name='is_authenticated'), | ||
path('qrverify/<int:id>/', QRVerifyView.as_view(), name='qrverify'), | ||
path('qrverify/scan/', QRVerifyUpdateView.as_view(), name='qrverify_update'), | ||
] |
Empty file.
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,26 @@ | ||
from django.utils import timezone | ||
from home.models.students import Student, LongRebate, Rebate, Allocation | ||
|
||
|
||
def is_student_on_rebate(student: Student, allocation: Allocation): | ||
""" | ||
This function checks if the student is on rebate or not | ||
""" | ||
today = timezone.localtime().date() | ||
|
||
# Check if there is a TodayRebate for the given allocation | ||
if Rebate.objects.filter( | ||
allocation_id=allocation, | ||
start_date__lte=today, | ||
end_date__gte=today, | ||
approved=True | ||
).exists(): | ||
return True | ||
|
||
# Check if there is an approved LongRebate for the student within the date range | ||
return LongRebate.objects.filter( | ||
email=student, | ||
start_date__lte=today, | ||
end_date__gte=today, | ||
approved=True | ||
).exists() |
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,207 @@ | ||
from django.utils import timezone | ||
from rest_framework import viewsets, generics, status | ||
from rest_framework.authtoken.models import Token | ||
from rest_framework.response import Response | ||
from rest_framework.views import APIView | ||
from drf_yasg import openapi | ||
from drf_yasg.utils import swagger_auto_schema | ||
|
||
from home.models import Allocation, Period | ||
from qrscan.models import MessCard, Meal, MessTiming | ||
from .permissions import IsStaff | ||
from .serializers import ( | ||
AllocationSerializer, PeriodSerializer, AllocationDetailSerializer, | ||
QRVerifySerializer, MealSerializer, UserSerializer, QRVerifyPostSerializer | ||
) | ||
from .utils.rebate_checker import is_student_on_rebate | ||
|
||
class LogoutView(APIView): | ||
""" | ||
View to handle user logout. | ||
""" | ||
permission_classes = [IsStaff] | ||
|
||
def post(self, request): | ||
try: | ||
token = Token.objects.get(user=request.user) | ||
token.delete() | ||
return Response({"detail": "Successfully logged out."}, status=status.HTTP_200_OK) | ||
except Token.DoesNotExist: | ||
return Response({"detail": "Invalid token."}, status=status.HTTP_400_BAD_REQUEST) | ||
|
||
|
||
class IsAuthenticated(APIView): | ||
""" | ||
View to check if the user is authenticated. | ||
""" | ||
permission_classes = [IsStaff] | ||
|
||
def get(self, request): | ||
if request.user.is_staff: | ||
return Response({"detail": "Authenticated"}, status=status.HTTP_200_OK) | ||
return Response(status=status.HTTP_403_FORBIDDEN) | ||
|
||
|
||
class UserDetail(APIView): | ||
""" | ||
View to get user details. | ||
""" | ||
def get(self, request): | ||
serializer = UserSerializer(request.user) | ||
return Response(serializer.data) | ||
|
||
|
||
class AllocationViewSet(viewsets.ReadOnlyModelViewSet): | ||
""" | ||
ViewSet for viewing allocations. | ||
""" | ||
permission_classes = [IsStaff] | ||
serializer_class = AllocationSerializer | ||
queryset = Allocation.objects.all() | ||
|
||
def get_serializer_class(self): | ||
if self.action == 'list': | ||
return AllocationSerializer | ||
elif self.action == 'retrieve': | ||
return AllocationDetailSerializer | ||
return super().get_serializer_class() | ||
|
||
def get_queryset(self): | ||
period_id = self.request.query_params.get('period_id') | ||
if period_id: | ||
return Allocation.objects.filter(period_id=period_id) | ||
return Allocation.objects.all() | ||
|
||
|
||
class PeriodViewSet(viewsets.ReadOnlyModelViewSet): | ||
""" | ||
ViewSet for viewing periods. | ||
""" | ||
permission_classes = [IsStaff] | ||
serializer_class = PeriodSerializer | ||
queryset = Period.objects.all() | ||
|
||
def get_queryset(self): | ||
return Period.objects.order_by('-start_date')[:1] | ||
|
||
|
||
class QRVerifyView(generics.RetrieveAPIView): | ||
""" | ||
View to verify QR codes. | ||
""" | ||
permission_classes = [IsStaff] | ||
serializer_class = QRVerifySerializer | ||
queryset = MessCard.objects.all() | ||
lookup_field = 'id' | ||
|
||
|
||
class QRVerifyUpdateView(APIView): | ||
""" | ||
View to update QR verification. | ||
""" | ||
permission_classes = [IsStaff] | ||
|
||
def _get_meal_type(self, time): | ||
meal_types = ['breakfast', 'lunch', 'high_tea', 'dinner'] | ||
for meal_type in meal_types: | ||
try: | ||
meal_timing = MessTiming.objects.get(meal_type=meal_type) | ||
if meal_timing.start_time <= time <= meal_timing.end_time: | ||
return meal_type | ||
except MessTiming.DoesNotExist: | ||
raise ValueError(f"{meal_type.capitalize()} timings not found.") | ||
return None | ||
|
||
def _filter_valid_cards(self, cards, meal_type): | ||
valid_cards = [] | ||
for card in cards: | ||
if is_student_on_rebate(card.student, card.allocation): | ||
continue | ||
|
||
if self._is_meal_recorded(card, meal_type): | ||
continue | ||
|
||
valid_cards.append(card) | ||
return valid_cards | ||
|
||
def _is_meal_recorded(self, card, meal_type): | ||
today = timezone.localtime().date() | ||
if meal_type == 'breakfast' and card.meal_set.filter(date=today, breakfast=True).exists(): | ||
return True | ||
elif meal_type == 'lunch' and card.meal_set.filter(date=today, lunch=True).exists(): | ||
return True | ||
elif meal_type == 'high_tea' and card.meal_set.filter(date=today, high_tea=True).exists(): | ||
return True | ||
elif meal_type == 'dinner' and card.meal_set.filter(date=today, dinner=True).exists(): | ||
return True | ||
return False | ||
|
||
def get(self, request): | ||
cards = MessCard.objects.filter(allocation__caterer__name=request.user.username) | ||
time = timezone.localtime().time() | ||
|
||
if not cards.exists(): | ||
return Response({"detail": "No cards found."}, status=status.HTTP_404_NOT_FOUND) | ||
|
||
meal_type = self._get_meal_type(time) | ||
valid_cards = self._filter_valid_cards(cards, meal_type) | ||
|
||
data = QRVerifySerializer(valid_cards, many=True).data | ||
return Response(data, status=status.HTTP_200_OK) | ||
|
||
@swagger_auto_schema( | ||
request_body=openapi.Schema( | ||
type=openapi.TYPE_OBJECT, | ||
properties={ | ||
'id': openapi.Schema(type=openapi.TYPE_STRING, description='Card ID') | ||
}, | ||
required=['id'], | ||
), | ||
responses={ | ||
200: 'Meal recorded successfully', | ||
400: 'Invalid card id or bad request', | ||
409: 'Meal already recorded.' | ||
} | ||
) | ||
def post(self, request): | ||
data = request.data | ||
serializer = QRVerifyPostSerializer(data={"id": data['id']}) | ||
|
||
if serializer.is_valid(): | ||
card_id = serializer.validated_data['id'] | ||
try: | ||
card = MessCard.objects.get(id=card_id) | ||
card_return_data = QRVerifySerializer(card).data | ||
date = timezone.localtime().date() | ||
time = timezone.localtime().time() | ||
meal, _ = Meal.objects.get_or_create(mess_card=card, date=date) | ||
|
||
if card.allocation.period.end_date <= date: | ||
return Response({"success": False, "detail": "Not registered for current Period.", "mess_card": card_return_data}, status=status.HTTP_403_FORBIDDEN) | ||
|
||
if card.allocation.caterer.name != request.user.username: | ||
return Response({"success": False, "detail": "Wrong caterer.", "mess_card": card_return_data}, status=status.HTTP_403_FORBIDDEN) | ||
|
||
if is_student_on_rebate(card.student, card.allocation): | ||
return Response({"success": False, "detail": "Student is on rebate.", "mess_card": card_return_data}, status=status.HTTP_403_FORBIDDEN) | ||
|
||
meal_type = self._get_meal_type(time) | ||
|
||
if getattr(meal, meal_type): | ||
return Response({"success": False, "detail": "Meal Already Recorded", "mess_card": card_return_data}, status=status.HTTP_409_CONFLICT) | ||
|
||
setattr(meal, meal_type, True) | ||
meal.save() | ||
return Response({"success": True, "detail": "Meal recorded successfully", "mess_card": card_return_data}, status=status.HTTP_200_OK) | ||
except MessCard.DoesNotExist: | ||
return Response({"success": False, "detail": "Invalid card id."}, status=status.HTTP_400_BAD_REQUEST) | ||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) | ||
|
||
|
||
class MealViewSet(viewsets.ModelViewSet): | ||
""" | ||
ViewSet for managing meals. | ||
""" | ||
permission_classes = [IsStaff] | ||
serializer_class = MealSerializer | ||
queryset = Meal.objects.all() |
Oops, something went wrong.