diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/admin.py b/api/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/api/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/api/apps.py b/api/apps.py new file mode 100644 index 0000000..66656fd --- /dev/null +++ b/api/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ApiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'api' diff --git a/api/models.py b/api/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/api/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/api/permissions.py b/api/permissions.py new file mode 100644 index 0000000..7344c64 --- /dev/null +++ b/api/permissions.py @@ -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) \ No newline at end of file diff --git a/api/serializers.py b/api/serializers.py new file mode 100644 index 0000000..5f56331 --- /dev/null +++ b/api/serializers.py @@ -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'] diff --git a/api/tests.py b/api/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/api/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/api/urls.py b/api/urls.py new file mode 100644 index 0000000..8963946 --- /dev/null +++ b/api/urls.py @@ -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="ep230051013@iiti.ac.in"), + 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//', QRVerifyView.as_view(), name='qrverify'), + path('qrverify/scan/', QRVerifyUpdateView.as_view(), name='qrverify_update'), +] diff --git a/api/utils/__init__.py b/api/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/utils/rebate_checker.py b/api/utils/rebate_checker.py new file mode 100644 index 0000000..b2d211d --- /dev/null +++ b/api/utils/rebate_checker.py @@ -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() diff --git a/api/views.py b/api/views.py new file mode 100644 index 0000000..5944a67 --- /dev/null +++ b/api/views.py @@ -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() diff --git a/home/migrations/0001_initial.py b/home/migrations/0001_initial.py index d98fffa..eb701cb 100644 --- a/home/migrations/0001_initial.py +++ b/home/migrations/0001_initial.py @@ -1,8 +1,8 @@ -# Generated by Django 4.1.7 on 2024-01-24 17:20 +# Generated by Django 5.0.8 on 2024-10-04 11:03 -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone +from django.db import migrations, models class Migration(migrations.Migration): @@ -24,38 +24,6 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'About Us', }, ), - migrations.CreateModel( - name='Allocation', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('student_id', models.CharField(blank=True, default=None, help_text='This contains the Allocation Id', max_length=30, null=True, verbose_name='Allocation Id')), - ('high_tea', models.BooleanField(blank=True, default=False, help_text='This contains the info if high tea is taken or not', null=True, verbose_name='High Tea')), - ('jain', models.BooleanField(blank=True, default=False, help_text='This contains the info if jain food is taken or not', null=True, verbose_name='Jain')), - ('first_pref', models.CharField(blank=True, default=None, help_text='This contians the first preference caterer of the student', max_length=10, null=True, verbose_name='First Preference')), - ('second_pref', models.CharField(blank=True, default=None, help_text='This contians the first preference caterer of the student', max_length=10, null=True, verbose_name='Second Preference')), - ('third_pref', models.CharField(blank=True, default=None, help_text='This contians the first preference caterer of the student', max_length=10, null=True, verbose_name='Third Preference')), - ], - options={ - 'verbose_name': 'Allocation Detail', - 'verbose_name_plural': 'Allocation Details', - }, - ), - migrations.CreateModel( - name='AllocationSpring23', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('student_id', models.CharField(blank=True, default=None, help_text='This contains the Allocation Id', max_length=30, null=True, verbose_name='Allocation Id')), - ('caterer_name', models.CharField(blank=True, default='', help_text='The text in this text field contains the caterer name.', max_length=50, null=True, verbose_name='Caterer Name')), - ('high_tea', models.BooleanField(blank=True, default=False, help_text='This contains the info if high tea is taken or not', null=True, verbose_name='High Tea')), - ('first_pref', models.CharField(blank=True, default=None, help_text='This contians the first preference caterer of the student', max_length=10, null=True, verbose_name='First Preference')), - ('second_pref', models.CharField(blank=True, default=None, help_text='This contians the first preference caterer of the student', max_length=10, null=True, verbose_name='Second Preference')), - ('third_pref', models.CharField(blank=True, default=None, help_text='This contians the first preference caterer of the student', max_length=10, null=True, verbose_name='Third Preference')), - ], - options={ - 'verbose_name': 'Allocation Details for Spring 2023', - 'verbose_name_plural': 'Allocation Details for Spring 2023', - }, - ), migrations.CreateModel( name='Cafeteria', fields=[ @@ -179,49 +147,10 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Period Details', }, ), - migrations.CreateModel( - name='PeriodAutumn22', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('Sno', models.IntegerField(default=0, help_text='This contains the serial number of the Period', verbose_name='Sno')), - ('start_date', models.DateField(blank=True, help_text='This contains the start date of this Period for this semester', null=True)), - ('end_date', models.DateField(blank=True, help_text='This contains the end date of this Period of this semester', null=True)), - ], - options={ - 'verbose_name': 'Period Details for Autumn 2022', - 'verbose_name_plural': 'Period Details for Autumn 2022', - }, - ), - migrations.CreateModel( - name='PeriodAutumn23', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('Sno', models.IntegerField(default=0, help_text='This contains the serial number of the Period', verbose_name='Sno')), - ('start_date', models.DateField(blank=True, help_text='This contains the start date of this Period for this semester', null=True)), - ('end_date', models.DateField(blank=True, help_text='This contains the end date of this Period of this semester', null=True)), - ], - options={ - 'verbose_name': 'Period Details for Autumn 2023', - 'verbose_name_plural': 'Period Details for Autumn 2023', - }, - ), - migrations.CreateModel( - name='PeriodSpring23', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('Sno', models.IntegerField(default=0, help_text='This contains the serial number of the Period', verbose_name='Sno')), - ('start_date', models.DateField(blank=True, help_text='This contains the start date of this Period for this semester', null=True)), - ('end_date', models.DateField(blank=True, help_text='This contains the end date of this Period of this semester', null=True)), - ], - options={ - 'verbose_name': 'Period Details for Spring 2023', - 'verbose_name_plural': 'Period Details for Spring 2023', - }, - ), migrations.CreateModel( name='Rule', fields=[ - ('sno', models.AutoField(default=1, primary_key=True, serialize=False)), + ('sno', models.AutoField(primary_key=True, serialize=False)), ('rule', models.TextField(help_text='The text in the text field contains the rule that will show as one of the rules of the rule page.', verbose_name='Rule')), ], options={ @@ -277,66 +206,63 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name='UnregisteredStudent', + name='AllocationForm', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('email', models.CharField(default='', max_length=30, verbose_name='email')), + ('heading', models.CharField(blank=True, default='', max_length=100, null=True, verbose_name='heading')), + ('description', models.TextField(blank=True, default='', null=True, verbose_name='description')), + ('active', models.BooleanField(blank=True, default=False, null=True, verbose_name='active')), + ('start_time', models.DateTimeField(blank=True, default=django.utils.timezone.now, null=True, verbose_name='Start Time')), + ('end_time', models.DateTimeField(blank=True, null=True, verbose_name='End Time')), + ('show_allocated', models.BooleanField(default=False, verbose_name='show_allocated')), ('period', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.period')), ], options={ - 'verbose_name': 'Unregistered Students', - 'verbose_name_plural': 'Unregistered Students', + 'verbose_name': 'Allocation Form', + 'verbose_name_plural': 'Allocation Form', }, ), migrations.CreateModel( - name='TodayRebate', + name='Allocation', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('date', models.DateField(default=django.utils.timezone.now, help_text='Date of the rebate')), - ('Caterer', models.CharField(default='', max_length=30)), - ('start_date', models.DateField(blank=True, help_text='start date of the rebate', null=True)), - ('end_date', models.DateField(blank=True, help_text='end date of the rebate', null=True)), - ('allocation_id', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.allocation')), + ('student_id', models.CharField(blank=True, default=None, help_text='This contains the Allocation Id', max_length=30, null=True, verbose_name='Allocation Id')), + ('high_tea', models.BooleanField(blank=True, default=False, help_text='This contains the info if high tea is taken or not', null=True, verbose_name='High Tea')), + ('jain', models.BooleanField(blank=True, default=False, help_text='This contains the info if jain food is taken or not', null=True, verbose_name='Jain')), + ('first_pref', models.CharField(blank=True, default=None, help_text='This contians the first preference caterer of the student', max_length=10, null=True, verbose_name='First Preference')), + ('second_pref', models.CharField(blank=True, default=None, help_text='This contians the first preference caterer of the student', max_length=10, null=True, verbose_name='Second Preference')), + ('third_pref', models.CharField(blank=True, default=None, help_text='This contians the first preference caterer of the student', max_length=10, null=True, verbose_name='Third Preference')), + ('registration_time', models.DateTimeField(blank=True, default=django.utils.timezone.now, help_text='This contains the date and time of registration', null=True, verbose_name='Registration time')), + ('caterer', models.ForeignKey(default=None, help_text='Contains the allocated caterer of the student', null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.caterer')), + ('period', models.ForeignKey(default=None, help_text='Contains the period of allocation', null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.period')), + ('email', models.ForeignKey(default=0, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.student')), ], options={ - 'verbose_name': "Today's Rebate", - 'verbose_name_plural': "Today's Rebate", + 'verbose_name': 'Allocation Detail', + 'verbose_name_plural': 'Allocation Details', }, ), + migrations.AddField( + model_name='period', + name='semester', + field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='home.semester', verbose_name='Semester'), + ), migrations.CreateModel( - name='StudentBills', + name='CatererBills', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('period1_short', models.IntegerField(default=0, null=True, verbose_name='Period 1 Short')), - ('period1_long', models.IntegerField(default=0, null=True, verbose_name='Period 1 Long')), - ('period1_high_tea', models.BooleanField(default=True, null=True, verbose_name='Period 1 High Tea')), - ('period1_bill', models.IntegerField(default=0, null=True, verbose_name='Period 1 Rebate Amount')), - ('period2_short', models.IntegerField(default=0, null=True, verbose_name='Period 2 Short')), - ('period2_long', models.IntegerField(default=0, null=True, verbose_name='Period 2 Long')), - ('period2_high_tea', models.BooleanField(default=True, null=True, verbose_name='Period 2 High Tea')), - ('period2_bill', models.IntegerField(default=0, null=True, verbose_name='Period 2 Rebate Amount')), - ('period3_short', models.IntegerField(default=0, null=True, verbose_name='Period 3 Short')), - ('period3_long', models.IntegerField(default=0, null=True, verbose_name='Period 3 Long')), - ('period3_high_tea', models.BooleanField(default=True, null=True, verbose_name='Period 3 High Tea')), - ('period3_bill', models.IntegerField(default=0, null=True, verbose_name='Period 3 Rebate Amount')), - ('period4_short', models.IntegerField(default=0, null=True, verbose_name='Period 4 Short')), - ('period4_long', models.IntegerField(default=0, null=True, verbose_name='Period 4 Long')), - ('period4_high_tea', models.BooleanField(default=True, null=True, verbose_name='Period 4 High Tea')), - ('period4_bill', models.IntegerField(default=0, null=True, verbose_name='Period 4 Rebate Amount')), - ('period5_short', models.IntegerField(default=0, null=True, verbose_name='Period 5 Short')), - ('period5_long', models.IntegerField(default=0, null=True, verbose_name='Period 5 Long')), - ('period5_high_tea', models.BooleanField(default=True, null=True, verbose_name='Period 5 High Tea')), - ('period5_bill', models.IntegerField(default=0, null=True, verbose_name='Period 5 Rebate Amount')), - ('period6_short', models.IntegerField(default=0, null=True, verbose_name='Period 6 Short')), - ('period6_long', models.IntegerField(default=0, null=True, verbose_name='Period 6 Long')), - ('period6_high_tea', models.BooleanField(default=True, null=True, verbose_name='Period 6 High Tea')), - ('period6_bill', models.IntegerField(default=0, null=True, verbose_name='Period 6 Rebate Amount')), - ('email', models.ForeignKey(default='', null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.student')), + ('period1_bills', models.IntegerField(default=0, null=True, verbose_name='Period 1 Bill')), + ('period2_bills', models.IntegerField(default=0, null=True, verbose_name='Period 2 Bill')), + ('period3_bills', models.IntegerField(default=0, null=True, verbose_name='Period 3 Bill')), + ('period4_bills', models.IntegerField(default=0, null=True, verbose_name='Period 4 Bill')), + ('period5_bills', models.IntegerField(default=0, null=True, verbose_name='Period 5 Bill')), + ('period6_bills', models.IntegerField(default=0, null=True, verbose_name='Period 6 Bill')), + ('caterer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.caterer')), ('semester', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='home.semester', verbose_name='Semester')), ], options={ - 'verbose_name': 'Rebate Bill', - 'verbose_name_plural': 'Rebate Bills', + 'verbose_name': 'Caterer Bill', + 'verbose_name_plural': 'Caterer Bills', }, ), migrations.CreateModel( @@ -348,7 +274,7 @@ class Migration(migrations.Migration): ('lunch', models.BooleanField(help_text='This contains if the lunch was eaten by the student', verbose_name='lunch')), ('high_tea', models.BooleanField(help_text='This contains if the high tea was eaten by the student', verbose_name='high_tea')), ('dinner', models.BooleanField(help_text='This contains if the dinner was eaten by the student', verbose_name='dinner')), - ('student_id', models.ForeignKey(default=0, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.allocationspring23')), + ('student_id', models.ForeignKey(default=0, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.student')), ], options={ 'verbose_name': 'Scan Details', @@ -390,41 +316,6 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Rebate Bills Spring 2023', }, ), - migrations.CreateModel( - name='RebateAutumn23', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('period1_short', models.IntegerField(default=0, null=True, verbose_name='Period 1 Short')), - ('period1_long', models.IntegerField(default=0, null=True, verbose_name='Period 1 Long')), - ('period1_high_tea', models.BooleanField(default=True, verbose_name='Period 1 High Tea')), - ('period1_bill', models.IntegerField(default=0, null=True, verbose_name='Period 1 Rebate Amount')), - ('period2_short', models.IntegerField(default=0, null=True, verbose_name='Period 2 Short')), - ('period2_long', models.IntegerField(default=0, null=True, verbose_name='Period 2 Long')), - ('period2_high_tea', models.BooleanField(default=True, verbose_name='Period 2 High Tea')), - ('period2_bill', models.IntegerField(default=0, null=True, verbose_name='Period 2 Rebate Amount')), - ('period3_short', models.IntegerField(default=0, null=True, verbose_name='Period 3 Short')), - ('period3_long', models.IntegerField(default=0, null=True, verbose_name='Period 3 Long')), - ('period3_high_tea', models.BooleanField(default=True, verbose_name='Period 3 High Tea')), - ('period3_bill', models.IntegerField(default=0, null=True, verbose_name='Period 3 Rebate Amount')), - ('period4_short', models.IntegerField(default=0, null=True, verbose_name='Period 4 Short')), - ('period4_long', models.IntegerField(default=0, null=True, verbose_name='Period 4 Long')), - ('period4_high_tea', models.BooleanField(default=True, verbose_name='Period 4 High Tea')), - ('period4_bill', models.IntegerField(default=0, null=True, verbose_name='Period 4 Rebate Amount')), - ('period5_short', models.IntegerField(default=0, null=True, verbose_name='Period 5 Short')), - ('period5_long', models.IntegerField(default=0, null=True, verbose_name='Period 5 Long')), - ('period5_high_tea', models.BooleanField(default=True, verbose_name='Period 5 High Tea')), - ('period5_bill', models.IntegerField(default=0, null=True, verbose_name='Period 5 Rebate Amount')), - ('period6_short', models.IntegerField(default=0, null=True, verbose_name='Period 6 Short')), - ('period6_long', models.IntegerField(default=0, null=True, verbose_name='Period 6 Long')), - ('period6_high_tea', models.BooleanField(default=True, verbose_name='Period 6 High Tea')), - ('period6_bill', models.IntegerField(default=0, null=True, verbose_name='Period 6 Rebate Amount')), - ('email', models.ForeignKey(default='', null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.student')), - ], - options={ - 'verbose_name': 'Rebate Bill Autumn 2023', - 'verbose_name_plural': 'Rebate Bills Autumn 2023', - }, - ), migrations.CreateModel( name='RebateAutumn22', fields=[ @@ -476,11 +367,6 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Short Rebate Details', }, ), - migrations.AddField( - model_name='period', - name='semester', - field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='home.semester', verbose_name='Semester'), - ), migrations.CreateModel( name='LongRebate', fields=[ @@ -489,6 +375,7 @@ class Migration(migrations.Migration): ('end_date', models.DateField(blank=True, help_text='end date of the rebate', null=True)), ('days', models.IntegerField(default=0, verbose_name='days')), ('approved', models.BooleanField(default=False, verbose_name='Approved')), + ('reason', models.TextField(blank=True, choices=[('', 'Choose the reason'), ('Incomplete form. Please submit a new rebate application', 'Incomplete form'), ('Signature of approving authority missing. Please submit a new rebate application', 'Signature missing'), ('Attached file is not the rebate form. Please submit a new rebate application with correct attachment', 'Wrong attached document'), ('There is a date mismatch between the one written in the form and the one in the attached form. Please submit a new rebate application', 'There is a date mismatch between the one written in the form and the one in the attached form')], default='')), ('date_applied', models.DateField(default=django.utils.timezone.now, help_text='Date on which the rebate was applied')), ('file', models.FileField(blank=True, default=None, null=True, upload_to='documents/', verbose_name='File')), ('email', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.student')), @@ -499,149 +386,66 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name='CatererBillsSpring23', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('period1_bills', models.IntegerField(default=0, null=True, verbose_name='Period 1 Bill')), - ('period2_bills', models.IntegerField(default=0, null=True, verbose_name='Period 2 Bill')), - ('period3_bills', models.IntegerField(default=0, null=True, verbose_name='Period 3 Bill')), - ('period4_bills', models.IntegerField(default=0, null=True, verbose_name='Period 4 Bill')), - ('period5_bills', models.IntegerField(default=0, null=True, verbose_name='Period 5 Bill')), - ('period6_bills', models.IntegerField(default=0, null=True, verbose_name='Period 6 Bill')), - ('caterer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.caterer')), - ], - options={ - 'verbose_name': 'Caterer Bill Spring 2023', - 'verbose_name_plural': 'Caterer Bills Spring 2023', - }, - ), - migrations.CreateModel( - name='CatererBillsAutumn23', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('period1_bills', models.IntegerField(default=0, null=True, verbose_name='Period 1 Bill')), - ('period2_bills', models.IntegerField(default=0, null=True, verbose_name='Period 2 Bill')), - ('period3_bills', models.IntegerField(default=0, null=True, verbose_name='Period 3 Bill')), - ('period4_bills', models.IntegerField(default=0, null=True, verbose_name='Period 4 Bill')), - ('period5_bills', models.IntegerField(default=0, null=True, verbose_name='Period 5 Bill')), - ('period6_bills', models.IntegerField(default=0, null=True, verbose_name='Period 6 Bill')), - ('caterer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.caterer')), - ], - options={ - 'verbose_name': 'Caterer Bill Autumn 2023', - 'verbose_name_plural': 'Caterer Bills Autumn 2023', - }, - ), - migrations.CreateModel( - name='CatererBillsAutumn22', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('period1_bills', models.IntegerField(default=0, null=True, verbose_name='Period 1 Bill')), - ('period2_bills', models.IntegerField(default=0, null=True, verbose_name='Period 2 Bill')), - ('period3_bills', models.IntegerField(default=0, null=True, verbose_name='Period 3 Bill')), - ('period4_bills', models.IntegerField(default=0, null=True, verbose_name='Period 4 Bill')), - ('period5_bills', models.IntegerField(default=0, null=True, verbose_name='Period 5 Bill')), - ('period6_bills', models.IntegerField(default=0, null=True, verbose_name='Period 6 Bill')), - ('caterer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.caterer')), - ], - options={ - 'verbose_name': 'Caterer Bill Autumn 2022', - 'verbose_name_plural': 'Caterer Bills Autumn 2022', - }, - ), - migrations.CreateModel( - name='CatererBills', + name='StudentBills', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('period1_bills', models.IntegerField(default=0, null=True, verbose_name='Period 1 Bill')), - ('period2_bills', models.IntegerField(default=0, null=True, verbose_name='Period 2 Bill')), - ('period3_bills', models.IntegerField(default=0, null=True, verbose_name='Period 3 Bill')), - ('period4_bills', models.IntegerField(default=0, null=True, verbose_name='Period 4 Bill')), - ('period5_bills', models.IntegerField(default=0, null=True, verbose_name='Period 5 Bill')), - ('period6_bills', models.IntegerField(default=0, null=True, verbose_name='Period 6 Bill')), - ('caterer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.caterer')), + ('period1_short', models.IntegerField(default=0, null=True, verbose_name='Period 1 Short')), + ('period1_long', models.IntegerField(default=0, null=True, verbose_name='Period 1 Long')), + ('period1_high_tea', models.BooleanField(default=True, null=True, verbose_name='Period 1 High Tea')), + ('period1_bill', models.IntegerField(default=0, null=True, verbose_name='Period 1 Rebate Amount')), + ('period2_short', models.IntegerField(default=0, null=True, verbose_name='Period 2 Short')), + ('period2_long', models.IntegerField(default=0, null=True, verbose_name='Period 2 Long')), + ('period2_high_tea', models.BooleanField(default=True, null=True, verbose_name='Period 2 High Tea')), + ('period2_bill', models.IntegerField(default=0, null=True, verbose_name='Period 2 Rebate Amount')), + ('period3_short', models.IntegerField(default=0, null=True, verbose_name='Period 3 Short')), + ('period3_long', models.IntegerField(default=0, null=True, verbose_name='Period 3 Long')), + ('period3_high_tea', models.BooleanField(default=True, null=True, verbose_name='Period 3 High Tea')), + ('period3_bill', models.IntegerField(default=0, null=True, verbose_name='Period 3 Rebate Amount')), + ('period4_short', models.IntegerField(default=0, null=True, verbose_name='Period 4 Short')), + ('period4_long', models.IntegerField(default=0, null=True, verbose_name='Period 4 Long')), + ('period4_high_tea', models.BooleanField(default=True, null=True, verbose_name='Period 4 High Tea')), + ('period4_bill', models.IntegerField(default=0, null=True, verbose_name='Period 4 Rebate Amount')), + ('period5_short', models.IntegerField(default=0, null=True, verbose_name='Period 5 Short')), + ('period5_long', models.IntegerField(default=0, null=True, verbose_name='Period 5 Long')), + ('period5_high_tea', models.BooleanField(default=True, null=True, verbose_name='Period 5 High Tea')), + ('period5_bill', models.IntegerField(default=0, null=True, verbose_name='Period 5 Rebate Amount')), + ('period6_short', models.IntegerField(default=0, null=True, verbose_name='Period 6 Short')), + ('period6_long', models.IntegerField(default=0, null=True, verbose_name='Period 6 Long')), + ('period6_high_tea', models.BooleanField(default=True, null=True, verbose_name='Period 6 High Tea')), + ('period6_bill', models.IntegerField(default=0, null=True, verbose_name='Period 6 Rebate Amount')), + ('email', models.ForeignKey(default='', null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.student')), ('semester', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='home.semester', verbose_name='Semester')), ], options={ - 'verbose_name': 'Caterer Bill', - 'verbose_name_plural': 'Caterer Bills', - }, - ), - migrations.AddField( - model_name='allocationspring23', - name='month', - field=models.ForeignKey(default=0, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.periodspring23'), - ), - migrations.AddField( - model_name='allocationspring23', - name='roll_no', - field=models.ForeignKey(default=0, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.student'), - ), - migrations.CreateModel( - name='AllocationForm', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('heading', models.CharField(blank=True, default='', max_length=100, null=True, verbose_name='heading')), - ('description', models.TextField(blank=True, default='', null=True, verbose_name='description')), - ('active', models.BooleanField(blank=True, default=False, null=True, verbose_name='active')), - ('start_time', models.DateTimeField(blank=True, default=django.utils.timezone.now, null=True, verbose_name='Start Time')), - ('end_time', models.DateTimeField(blank=True, null=True, verbose_name='End Time')), - ('period', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.period')), - ], - options={ - 'verbose_name': 'Allocation Form', - 'verbose_name_plural': 'Allocation Form', + 'verbose_name': 'Rebate Bill', + 'verbose_name_plural': 'Rebate Bills', }, ), migrations.CreateModel( - name='AllocationAutumn23', + name='TodayRebate', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('student_id', models.CharField(blank=True, default=None, help_text='This contains the Allocation Id', max_length=30, null=True, verbose_name='Allocation Id')), - ('caterer_name', models.CharField(blank=True, default='', help_text='The text in this text field contains the caterer name.', max_length=50, null=True, verbose_name='Caterer Name')), - ('high_tea', models.BooleanField(blank=True, default=False, help_text='This contains the info if high tea is taken or not', null=True, verbose_name='High Tea')), - ('first_pref', models.CharField(blank=True, default=None, help_text='This contians the first preference caterer of the student', max_length=10, null=True, verbose_name='First Preference')), - ('second_pref', models.CharField(blank=True, default=None, help_text='This contians the first preference caterer of the student', max_length=10, null=True, verbose_name='Second Preference')), - ('third_pref', models.CharField(blank=True, default=None, help_text='This contians the first preference caterer of the student', max_length=10, null=True, verbose_name='Third Preference')), - ('month', models.ForeignKey(default=0, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.periodautumn23')), - ('roll_no', models.ForeignKey(default=0, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.student')), + ('date', models.DateField(default=django.utils.timezone.now, help_text='Date of the rebate')), + ('Caterer', models.CharField(default='', max_length=30)), + ('start_date', models.DateField(blank=True, help_text='start date of the rebate', null=True)), + ('end_date', models.DateField(blank=True, help_text='end date of the rebate', null=True)), + ('allocation_id', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.allocation')), ], options={ - 'verbose_name': 'Allocation Details for Autumn 2023', - 'verbose_name_plural': 'Allocation Details for Autumn 2023', + 'verbose_name': "Today's Rebate", + 'verbose_name_plural': "Today's Rebate", }, ), migrations.CreateModel( - name='AllocationAutumn22', + name='UnregisteredStudent', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('student_id', models.CharField(blank=True, default=None, help_text='This contains the Allocation Id', max_length=30, null=True, verbose_name='Allocation Id')), - ('caterer_name', models.CharField(blank=True, default='', help_text='The text in this text field contains the caterer name.', max_length=50, null=True, verbose_name='Caterer Name')), - ('high_tea', models.BooleanField(blank=True, default=False, help_text='This contains the info if high tea is taken or not', null=True, verbose_name='High Tea')), - ('first_pref', models.CharField(blank=True, default=None, help_text='This contians the first preference caterer of the student', max_length=10, null=True, verbose_name='First Preference')), - ('second_pref', models.CharField(blank=True, default=None, help_text='This contians the first preference caterer of the student', max_length=10, null=True, verbose_name='Second Preference')), - ('third_pref', models.CharField(blank=True, default=None, help_text='This contians the first preference caterer of the student', max_length=10, null=True, verbose_name='Third Preference')), - ('month', models.ForeignKey(default=0, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.periodautumn22')), - ('roll_no', models.ForeignKey(default=0, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.student')), + ('email', models.CharField(default='', max_length=30, verbose_name='email')), + ('period', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.period')), ], options={ - 'verbose_name': 'Allocation Details for Autumn 2022', - 'verbose_name_plural': 'Allocation Details for Autumn 2022', + 'verbose_name': 'Unregistered Students', + 'verbose_name_plural': 'Unregistered Students', }, ), - migrations.AddField( - model_name='allocation', - name='caterer', - field=models.ForeignKey(default=None, help_text='Contains the allocated caterer of the student', null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.caterer'), - ), - migrations.AddField( - model_name='allocation', - name='email', - field=models.ForeignKey(default=0, null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.student'), - ), - migrations.AddField( - model_name='allocation', - name='period', - field=models.ForeignKey(default=None, help_text='Contains the period of allocation', null=True, on_delete=django.db.models.deletion.SET_NULL, to='home.period'), - ), ] diff --git a/home/migrations/0002_remove_allocationautumn22_month_and_more.py b/home/migrations/0002_remove_allocationautumn22_month_and_more.py deleted file mode 100644 index 93423d9..0000000 --- a/home/migrations/0002_remove_allocationautumn22_month_and_more.py +++ /dev/null @@ -1,125 +0,0 @@ -# Generated by Django 5.0.3 on 2024-08-17 14:53 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("home", "0001_initial"), - ] - - operations = [ - migrations.RemoveField( - model_name="allocationautumn22", - name="month", - ), - migrations.RemoveField( - model_name="allocationautumn22", - name="roll_no", - ), - migrations.RemoveField( - model_name="allocationautumn23", - name="month", - ), - migrations.RemoveField( - model_name="allocationautumn23", - name="roll_no", - ), - migrations.RemoveField( - model_name="allocationspring23", - name="month", - ), - migrations.RemoveField( - model_name="allocationspring23", - name="roll_no", - ), - migrations.AlterField( - model_name="scan", - name="student_id", - field=models.ForeignKey( - default=0, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="home.student", - ), - ), - migrations.RemoveField( - model_name="catererbillsautumn22", - name="caterer", - ), - migrations.RemoveField( - model_name="catererbillsautumn23", - name="caterer", - ), - migrations.RemoveField( - model_name="catererbillsspring23", - name="caterer", - ), - migrations.RemoveField( - model_name="rebateautumn23", - name="email", - ), - migrations.AddField( - model_name="longrebate", - name="reason", - field=models.TextField( - blank=True, - choices=[ - ("", "Choose the reason"), - ( - "Incomplete form. Please submit a new rebate application", - "Incomplete form", - ), - ( - "Signature of approving authority missing. Please submit a new rebate application", - "Signature missing", - ), - ( - "Attached file is not the rebate form. Please submit a new rebate application with correct attachment", - "Wrong attached document", - ), - ( - "There is a date mismatch between the one written in the form and the one in the attached form. Please submit a new rebate application", - "There is a date mismatch between the one written in the form and the one in the attached form", - ), - ], - default="", - ), - ), - migrations.AlterField( - model_name="rule", - name="sno", - field=models.AutoField(primary_key=True, serialize=False), - ), - migrations.DeleteModel( - name="PeriodAutumn22", - ), - migrations.DeleteModel( - name="AllocationAutumn22", - ), - migrations.DeleteModel( - name="PeriodAutumn23", - ), - migrations.DeleteModel( - name="AllocationAutumn23", - ), - migrations.DeleteModel( - name="PeriodSpring23", - ), - migrations.DeleteModel( - name="AllocationSpring23", - ), - migrations.DeleteModel( - name="CatererBillsAutumn22", - ), - migrations.DeleteModel( - name="CatererBillsAutumn23", - ), - migrations.DeleteModel( - name="CatererBillsSpring23", - ), - migrations.DeleteModel( - name="RebateAutumn23", - ), - ] diff --git a/home/migrations/0003_allocationform_show_allocated.py b/home/migrations/0003_allocationform_show_allocated.py deleted file mode 100644 index 1c06dd1..0000000 --- a/home/migrations/0003_allocationform_show_allocated.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.0.3 on 2024-08-24 05:37 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("home", "0002_remove_allocationautumn22_month_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="allocationform", - name="show_allocated", - field=models.BooleanField(default=False, verbose_name="show_allocated"), - ), - ] diff --git a/home/migrations/0004_allocation_registration_time.py b/home/migrations/0004_allocation_registration_time.py deleted file mode 100644 index fea5c31..0000000 --- a/home/migrations/0004_allocation_registration_time.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.0.8 on 2024-09-03 16:13 - -import django.utils.timezone -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('home', '0003_allocationform_show_allocated'), - ] - - operations = [ - migrations.AddField( - model_name='allocation', - name='registration_time', - field=models.DateTimeField(blank=True, default=django.utils.timezone.now, help_text='This contains the time of registration', null=True, verbose_name='Registration time'), - ), - ] diff --git a/home/migrations/0005_alter_allocation_registration_time.py b/home/migrations/0005_alter_allocation_registration_time.py deleted file mode 100644 index a0c8191..0000000 --- a/home/migrations/0005_alter_allocation_registration_time.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 5.0.3 on 2024-09-25 14:43 - -import django.utils.timezone -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("home", "0004_allocation_registration_time"), - ] - - operations = [ - migrations.AlterField( - model_name="allocation", - name="registration_time", - field=models.DateTimeField( - blank=True, - default=django.utils.timezone.now, - help_text="This contains the date and time of registration", - null=True, - verbose_name="Registration time", - ), - ), - ] diff --git a/messWebsite/settings.py b/messWebsite/settings.py index 13686a1..31b80a3 100644 --- a/messWebsite/settings.py +++ b/messWebsite/settings.py @@ -18,7 +18,7 @@ SECRET_KEY = env("SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = env.bool("DEBUG", default=False) ALLOWED_HOSTS = ["diningfee.iiti.ac.in", "127.0.0.1", "103.159.214.171"] CSRF_TRUSTED_ORIGINS = ["http://diningfee.iiti.ac.in", "https://diningfee.iiti.ac.in"] @@ -45,8 +45,14 @@ "cloudinary", "apscheduler", "django_apscheduler", + 'rest_framework', + 'rest_framework.authtoken', + 'drf_yasg', + # Local apps "home.apps.HomeConfig", + "qrscan.apps.QrscanConfig", + 'api', ] MIDDLEWARE = [ @@ -161,6 +167,29 @@ "django.contrib.auth.backends.ModelBackend", "allauth.account.auth_backends.AuthenticationBackend", ) + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.TokenAuthentication', + ], + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.IsAuthenticated', + ], +} + +SWAGGER_SETTINGS = { + 'SECURITY_DEFINITIONS': { + 'Basic': { + 'type': 'basic' + }, + 'Token': { + 'type': 'apiKey', + 'name': 'Authorization', + 'in': 'header' + } + } +} + ACCOUNT_ADAPTER = "home.adapters.account_adapter.CustomAccountAdapter" ACCOUNT_AUTHENTICATION_METHOD = "email" ACCOUNT_EMAIL_REQUIRED = True diff --git a/messWebsite/urls.py b/messWebsite/urls.py index 9d0174a..42dafc5 100644 --- a/messWebsite/urls.py +++ b/messWebsite/urls.py @@ -31,6 +31,8 @@ path("accounts/google/login/", oauth2_login, name="google_login"), path("accounts/google/login/callback/", oauth2_callback, name="google_callback"), path("", include("home.urls")), - path("media//", serve, {"document_root": settings.MEDIA_ROOT}), - path("static//", serve, {"document_root": settings.STATIC_ROOT}), + path("qrscan/", include("qrscan.urls")), + path('api/v1/', include('api.urls')), + path('media//', serve, {'document_root': settings.MEDIA_ROOT}), + path('static//', serve, {'document_root': settings.STATIC_ROOT}), ] diff --git a/qrscan/__init__.py b/qrscan/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/qrscan/admin.py b/qrscan/admin.py new file mode 100644 index 0000000..9fd8137 --- /dev/null +++ b/qrscan/admin.py @@ -0,0 +1,41 @@ +from django.contrib import admin +from .models import MessCard, Meal, MessTiming + +# Admin configuration for MessCard model +@admin.register(MessCard) +class MessCardAdmin(admin.ModelAdmin): + list_display = ['id', 'allocation', 'student'] + list_display_links = ['id', 'allocation', 'student'] + search_fields = ('allocation__student_id', 'allocation__email__email') + list_filter = ('allocation__caterer__name',) + raw_id_fields = ('allocation', 'student') + ordering = ['id'] + list_per_page = 25 + + +# Admin configuration for Meal model +@admin.register(Meal) +class MealAdmin(admin.ModelAdmin): + list_display = ['id', 'mess_card', 'date', 'breakfast', 'lunch', 'dinner'] + list_display_links = ['id', 'mess_card'] + search_fields = ('mess_card__allocation__student_id', 'mess_card__allocation__email__email') + list_filter = ('mess_card__allocation__caterer__name', 'date', 'breakfast', 'lunch', 'dinner') + raw_id_fields = ('mess_card',) + ordering = ['date', 'id'] + list_per_page = 25 + + +# Admin configuration for MessTimings model +@admin.register(MessTiming) +class MessTimingsAdmin(admin.ModelAdmin): + list_display = ['meal_type', 'start_time', 'end_time'] + list_display_links = ['meal_type'] + search_fields = ('meal_type',) + list_filter = ('meal_type',) + ordering = ['meal_type'] + + def start_time(self, obj): + return obj.start_time.strftime('%H:%M') + + def end_time(self, obj): + return obj.end_time.strftime('%H:%M') diff --git a/qrscan/apps.py b/qrscan/apps.py new file mode 100644 index 0000000..032f574 --- /dev/null +++ b/qrscan/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class QrscanConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'qrscan' diff --git a/qrscan/migrations/0001_initial.py b/qrscan/migrations/0001_initial.py new file mode 100644 index 0000000..27846bd --- /dev/null +++ b/qrscan/migrations/0001_initial.py @@ -0,0 +1,40 @@ +# Generated by Django 5.0.8 on 2024-10-04 11:03 + +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('home', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='MessCard', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='This contains the unique id of the mess card', primary_key=True, serialize=False, unique=True)), + ('qr_code', models.ImageField(blank=True, help_text='This contains the qr code image', null=True, upload_to='qr_codes/')), + ('allocation', models.ForeignKey(blank=True, help_text='This contains the allocation details', null=True, on_delete=django.db.models.deletion.CASCADE, to='home.allocation')), + ('student', models.ForeignKey(blank=True, help_text='This contains the student details', null=True, on_delete=django.db.models.deletion.CASCADE, to='home.student')), + ], + ), + migrations.CreateModel( + name='Meal', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField(auto_now_add=True, help_text='This contains the date of the meal')), + ('breakfast', models.BooleanField(default=False, help_text='This contains the breakfast status')), + ('lunch', models.BooleanField(default=False, help_text='This contains the lunch status')), + ('dinner', models.BooleanField(default=False, help_text='This contains the dinner status')), + ('mess_card', models.ForeignKey(blank=True, help_text='This contains the mess card details', null=True, on_delete=django.db.models.deletion.CASCADE, to='qrscan.messcard')), + ], + options={ + 'unique_together': {('mess_card', 'date')}, + }, + ), + ] diff --git a/qrscan/migrations/0002_meal_high_tea.py b/qrscan/migrations/0002_meal_high_tea.py new file mode 100644 index 0000000..854432d --- /dev/null +++ b/qrscan/migrations/0002_meal_high_tea.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.8 on 2024-10-06 12:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('qrscan', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='meal', + name='high_tea', + field=models.BooleanField(default=False, help_text='This contains the high tea status'), + ), + ] diff --git a/qrscan/migrations/0003_messtimings.py b/qrscan/migrations/0003_messtimings.py new file mode 100644 index 0000000..83163d9 --- /dev/null +++ b/qrscan/migrations/0003_messtimings.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.8 on 2024-10-06 13:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('qrscan', '0002_meal_high_tea'), + ] + + operations = [ + migrations.CreateModel( + name='MessTimings', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('meal_type', models.CharField(choices=[('breakfast', 'Breakfast'), ('lunch', 'Lunch'), ('high_tea', 'High Tea'), ('dinner', 'Dinner')], help_text='This contains the meal type', max_length=10)), + ('start_time', models.TimeField(blank=True, help_text='This contains the start time', null=True)), + ('end_time', models.TimeField(blank=True, help_text='This contains the end time', null=True)), + ], + ), + ] diff --git a/qrscan/migrations/0004_messtiming_delete_messtimings.py b/qrscan/migrations/0004_messtiming_delete_messtimings.py new file mode 100644 index 0000000..c48000c --- /dev/null +++ b/qrscan/migrations/0004_messtiming_delete_messtimings.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0.8 on 2024-10-06 13:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('qrscan', '0003_messtimings'), + ] + + operations = [ + migrations.CreateModel( + name='MessTiming', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('meal_type', models.CharField(choices=[('breakfast', 'Breakfast'), ('lunch', 'Lunch'), ('high_tea', 'High Tea'), ('dinner', 'Dinner')], help_text='This contains the meal type', max_length=10, unique=True)), + ('start_time', models.TimeField(blank=True, help_text='This contains the start time', null=True)), + ('end_time', models.TimeField(blank=True, help_text='This contains the end time', null=True)), + ], + ), + migrations.DeleteModel( + name='MessTimings', + ), + ] diff --git a/qrscan/migrations/__init__.py b/qrscan/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/qrscan/models.py b/qrscan/models.py new file mode 100644 index 0000000..e372f9c --- /dev/null +++ b/qrscan/models.py @@ -0,0 +1,144 @@ +import uuid +from io import BytesIO +from PIL import Image +import qrcode + +from django.core.files import File +from django.db import models + +from home.models import Allocation, Student + + +class MessCard(models.Model): + id = models.UUIDField( + primary_key=True, + default=uuid.uuid4, + editable=False, + unique=True, + help_text="This contains the unique id of the mess card" + ) + allocation = models.ForeignKey( + Allocation, + on_delete=models.CASCADE, + help_text="This contains the allocation details", + null=True, + blank=True + ) + student = models.ForeignKey( + Student, + on_delete=models.CASCADE, + help_text="This contains the student details", + ) + qr_code = models.ImageField( + upload_to='qr_codes/', + help_text="This contains the qr code image", + blank=True, + null=True + ) + + def __str__(self): + return f"{self.allocation.student_id} - {self.allocation.email.email}" + + def generate_qr_code(self): + data = str(self.id) + + qr = qrcode.QRCode( + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_H, + box_size=10, + border=4, + ) + qr.add_data(data) + qr.make(fit=True) + + img = qr.make_image(fill="black", back_color="white").convert('RGB') + + try: + logo = Image.open('./static/images/iiti_bw.png') + logo = logo.resize((150, 150)) + pos = ((img.size[0] - logo.size[0]) // 2, (img.size[1] - logo.size[1]) // 2) + img.paste(logo, pos) + except FileNotFoundError: + print("Logo file not found. Proceeding without logo.") + + + buffer = BytesIO() + img.save(buffer, format='PNG') + file_name = f"qr_{self.student.email}_{self.allocation.student_id}.png" + + self.qr_code.save(file_name, File(buffer), save=False) + buffer.close() + + def save(self, *args, **kwargs): + if not self.allocation: + self.allocation = self.student.allocation_set.last() + if not self.pk: + super().save(*args, **kwargs) + if not self.qr_code: + self.generate_qr_code() + super().save(*args, **kwargs) + + +class Meal(models.Model): + mess_card = models.ForeignKey( + MessCard, + on_delete=models.CASCADE, + help_text="This contains the mess card details", + null=True, + blank=True + ) + date = models.DateField( + help_text="This contains the date of the meal", + auto_now_add=True + ) + breakfast = models.BooleanField( + help_text="This contains the breakfast status", + default=False + ) + lunch = models.BooleanField( + help_text="This contains the lunch status", + default=False + ) + high_tea = models.BooleanField( + help_text="This contains the high tea status", + default=False + ) + dinner = models.BooleanField( + help_text="This contains the dinner status", + default=False + ) + + class Meta: + unique_together = ['mess_card', 'date'] + + def __str__(self): + return f"{self.mess_card.allocation.student_id} - {self.date}" + + +class MessTiming(models.Model): + MEAL_TYPES = [ + ('breakfast', 'Breakfast'), + ('lunch', 'Lunch'), + ('high_tea', 'High Tea'), + ('dinner', 'Dinner') + ] + + meal_type = models.CharField( + max_length=10, + choices=MEAL_TYPES, + help_text="This contains the meal type", + unique=True, + ) + start_time = models.TimeField( + help_text="This contains the start time", + null=True, + blank=True + ) + end_time = models.TimeField( + help_text="This contains the end time", + null=True, + blank=True + ) + + def __str__(self): + return f"{self.meal_type} Timings" diff --git a/qrscan/templates/mess_card.html b/qrscan/templates/mess_card.html new file mode 100644 index 0000000..09d24c9 --- /dev/null +++ b/qrscan/templates/mess_card.html @@ -0,0 +1,75 @@ +{% extends 'base.html' %} {% load static %} {%block externalCss%} + + + + + +{% endblock %} {%block body%} +
+
+
+
+
+ User Image +
+
{{ student.name }}
+
+ Roll Number: {{ student.roll_no }}
+ Email: {{ user.email }}
+ Department: {{ student.department }}
+ Hostel and Room: {{ student.hostel }} {{ student.room_no }}
+ {{ mess_card.allocation.period }} : + {{ mess_card.allocation.caterer.name }} - + {{ mess_card.allocation.student_id }}
+
+ QR Code +
+ +
+
+ +
+
+ +
+ +{% endblock %} diff --git a/qrscan/tests.py b/qrscan/tests.py new file mode 100644 index 0000000..e69de29 diff --git a/qrscan/urls.py b/qrscan/urls.py new file mode 100644 index 0000000..d00fbaf --- /dev/null +++ b/qrscan/urls.py @@ -0,0 +1,26 @@ +"""Mess Website URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.1/topics/http/urls/ +Examples: + + Function views (This is used here) + 1. Add an import: from . import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') + + Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') + + Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" + +from django.urls import path + +from . import views + +urlpatterns = [ + path("", views.mess_card, name="mess_card"), +] \ No newline at end of file diff --git a/qrscan/views.py b/qrscan/views.py new file mode 100644 index 0000000..c3c4ad7 --- /dev/null +++ b/qrscan/views.py @@ -0,0 +1,49 @@ +from django.shortcuts import render +from django.contrib.auth.decorators import login_required +from home.models import Student +from .models import MessCard +from allauth.socialaccount.models import SocialAccount + +@login_required +def mess_card(request): + """ + Display the Mess card of the user. + + *Template:* + + :template:`mess_card.html` + """ + + student = Student.objects.filter(email__iexact=str(request.user.email)).last() + socialaccount_obj = SocialAccount.objects.filter( + provider="google", user_id=request.user.id + ) + allocation = student.allocation_set.last() + if(not allocation): + raise ValueError("Allocation not found!") + mess_card, _ = MessCard.objects.get_or_create(student=student) + if(not mess_card.allocation): + setattr(mess_card, allocation) + mess_card.save() + elif(mess_card.allocation != allocation): + setattr(mess_card, allocation) + mess_card.save() + + picture = "not available" + try: + if socialaccount_obj: + picture = socialaccount_obj[0].extra_data["picture"] + else: + picture = "not available" + except (IndexError, KeyError): + picture = "not available" + + context = { + "student": student, + "picture": picture, + "user": request.user, + "mess_card": mess_card, + } + + return render(request, "mess_card.html", context=context) + diff --git a/requirements.txt b/requirements.txt index 6d2c16f..6cc8ea5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,13 @@ -Django==5.0.8 -django_allauth==0.54.0 -django_environ==0.10.0 -django_import_export==3.2.0 -gunicorn==22.0.0 -whitenoise==6.4.0 -django-cloudinary-storage==0.3.0 -django-admin-logs==1.0.2 -Pillow -django-apscheduler -# django-cloudinary-storage==0.3.0 +APScheduler==3.10.4 +cloudinary==1.41.0 +Django==5.0.8 +django_allauth==0.54.0 +django_apscheduler==0.7.0 +django_environ==0.10.0 +django_import_export==3.2.0 +djangorestframework==3.15.2 +drf_yasg==1.21.7 +pandas==2.2.3 +Pillow==10.4.0 +qrcode==8.0 +Requests==2.32.3 diff --git a/static/css/mess-card.css b/static/css/mess-card.css new file mode 100644 index 0000000..0b2282e --- /dev/null +++ b/static/css/mess-card.css @@ -0,0 +1,80 @@ +main { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; + background-color: #f0f0f0; + font-family: "Arial", sans-serif; +} +.id-card { + width: 400px; + height: 500px; + min-height: max-content; + background-color: white; + border-radius: 15px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + text-align: center; + padding: 20px; + position: relative; + overflow: hidden; +} +.id-card .header { + background-color: #2579a6; + border-top-left-radius: 15px; + border-top-right-radius: 15px; + height: 100px; + position: relative; + clip-path: ellipse(70% 100% at 50% 0%); +} +.id-card { + margin-top: -50px; +} +.id-card .photo { + margin-top: 10px; +} +.id-card .name { + font-size: 22px; + font-weight: bold; + margin-top: 10px; +} +.id-card .divider { + width: 50px; + height: 3px; + background-color: #2579a6; + margin: 10px auto; +} +.id-card .id-number { + font-size: 14px; + margin-top: 10px; +} +.id-card .qr-code { + margin-top: 20px; +} +.id-card .website { + font-size: 12px; + color: #2579a6; + margin-top: 10px; +} +.id-card .footer { + background-color: #2579a6; + border-bottom-left-radius: 15px; + border-bottom-right-radius: 15px; + height: 60px; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + clip-path: ellipse(70% 100% at 50% 100%); +} + +.wrapper { + display: flex; + justify-content: center; + flex-direction: column; +} + +.card-subtitle { + font-size: 28px; + margin-top: 10px; +} diff --git a/static/images/iiti_bw.png b/static/images/iiti_bw.png new file mode 100644 index 0000000..9fa5ac4 Binary files /dev/null and b/static/images/iiti_bw.png differ diff --git a/templates/base.html b/templates/base.html index cfc0ef1..e02deeb 100644 --- a/templates/base.html +++ b/templates/base.html @@ -95,6 +95,7 @@ diff --git a/templates/profile.html b/templates/profile.html index 1b93e59..23c1d5a 100644 --- a/templates/profile.html +++ b/templates/profile.html @@ -18,6 +18,7 @@
{{ student.name }}
Roll Number: {{ student.roll_no }}
Email: {{ user.email }}
Department: {{ student.department }}
+
{% if allocation_info|length > 0 %}
Your Allocation Details: