Skip to content

Commit

Permalink
Qr Code (#86)
Browse files Browse the repository at this point in the history
* 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
Princekumarofficial authored Oct 7, 2024
1 parent fa72158 commit e5638f8
Show file tree
Hide file tree
Showing 36 changed files with 1,030 additions and 478 deletions.
Empty file added api/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions api/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions api/apps.py
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'
3 changes: 3 additions & 0 deletions api/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.db import models

# Create your models here.
12 changes: 12 additions & 0 deletions api/permissions.py
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)
74 changes: 74 additions & 0 deletions api/serializers.py
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']
3 changes: 3 additions & 0 deletions api/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
38 changes: 38 additions & 0 deletions api/urls.py
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 added api/utils/__init__.py
Empty file.
26 changes: 26 additions & 0 deletions api/utils/rebate_checker.py
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()
207 changes: 207 additions & 0 deletions api/views.py
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()
Loading

0 comments on commit e5638f8

Please sign in to comment.