Upload 34 files
Browse files- .gitattributes +1 -0
- Dockerfile +16 -0
- ExamscheduleViewer/__init__.py +0 -0
- ExamscheduleViewer/__pycache__/__init__.cpython-312.pyc +0 -0
- ExamscheduleViewer/__pycache__/settings.cpython-312.pyc +0 -0
- ExamscheduleViewer/__pycache__/urls.cpython-312.pyc +0 -0
- ExamscheduleViewer/__pycache__/wsgi.cpython-312.pyc +0 -0
- ExamscheduleViewer/asgi.py +16 -0
- ExamscheduleViewer/settings.py +124 -0
- ExamscheduleViewer/urls.py +23 -0
- ExamscheduleViewer/wsgi.py +16 -0
- db.sqlite3 +3 -0
- genrateExamSchedule/__init__.py +0 -0
- genrateExamSchedule/__pycache__/__init__.cpython-312.pyc +0 -0
- genrateExamSchedule/__pycache__/admin.cpython-312.pyc +0 -0
- genrateExamSchedule/__pycache__/apps.cpython-312.pyc +0 -0
- genrateExamSchedule/__pycache__/models.cpython-312.pyc +0 -0
- genrateExamSchedule/__pycache__/urls.cpython-312.pyc +0 -0
- genrateExamSchedule/__pycache__/views.cpython-312.pyc +0 -0
- genrateExamSchedule/admin.py +26 -0
- genrateExamSchedule/apps.py +6 -0
- genrateExamSchedule/migrations/0001_initial.py +30 -0
- genrateExamSchedule/migrations/__init__.py +0 -0
- genrateExamSchedule/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
- genrateExamSchedule/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
- genrateExamSchedule/models.py +21 -0
- genrateExamSchedule/tests.py +3 -0
- genrateExamSchedule/urls.py +14 -0
- genrateExamSchedule/views.py +408 -0
- manage.py +22 -0
- requirements.txt +0 -0
- templates/index.html +80 -0
- templates/login.html +61 -0
- templates/upload_schedule.html +81 -0
- templates/view_schedule.html +124 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
db.sqlite3 filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
|
| 2 |
+
# you will also find guides on how best to write your Dockerfile
|
| 3 |
+
|
| 4 |
+
FROM python:3.9
|
| 5 |
+
|
| 6 |
+
RUN useradd -m -u 1000 user
|
| 7 |
+
USER user
|
| 8 |
+
ENV PATH="/home/user/.local/bin:$PATH"
|
| 9 |
+
|
| 10 |
+
WORKDIR /app
|
| 11 |
+
|
| 12 |
+
COPY --chown=user ./requirements.txt requirements.txt
|
| 13 |
+
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
| 14 |
+
|
| 15 |
+
COPY --chown=user . /app
|
| 16 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
ExamscheduleViewer/__init__.py
ADDED
|
File without changes
|
ExamscheduleViewer/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (149 Bytes). View file
|
|
|
ExamscheduleViewer/__pycache__/settings.cpython-312.pyc
ADDED
|
Binary file (2.6 kB). View file
|
|
|
ExamscheduleViewer/__pycache__/urls.cpython-312.pyc
ADDED
|
Binary file (1.12 kB). View file
|
|
|
ExamscheduleViewer/__pycache__/wsgi.cpython-312.pyc
ADDED
|
Binary file (659 Bytes). View file
|
|
|
ExamscheduleViewer/asgi.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
ASGI config for ExamscheduleViewer project.
|
| 3 |
+
|
| 4 |
+
It exposes the ASGI callable as a module-level variable named ``application``.
|
| 5 |
+
|
| 6 |
+
For more information on this file, see
|
| 7 |
+
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import os
|
| 11 |
+
|
| 12 |
+
from django.core.asgi import get_asgi_application
|
| 13 |
+
|
| 14 |
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ExamscheduleViewer.settings')
|
| 15 |
+
|
| 16 |
+
application = get_asgi_application()
|
ExamscheduleViewer/settings.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Django settings for ExamscheduleViewer project.
|
| 3 |
+
|
| 4 |
+
Generated by 'django-admin startproject' using Django 5.0.4.
|
| 5 |
+
|
| 6 |
+
For more information on this file, see
|
| 7 |
+
https://docs.djangoproject.com/en/5.0/topics/settings/
|
| 8 |
+
|
| 9 |
+
For the full list of settings and their values, see
|
| 10 |
+
https://docs.djangoproject.com/en/5.0/ref/settings/
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
| 16 |
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
# Quick-start development settings - unsuitable for production
|
| 20 |
+
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
| 21 |
+
|
| 22 |
+
# SECURITY WARNING: keep the secret key used in production secret!
|
| 23 |
+
SECRET_KEY = 'django-insecure-**@-tuvqyk^ildsh4(g7t^dqa5-9t$n)i6a3+k7wo$9as-r+@d'
|
| 24 |
+
|
| 25 |
+
# SECURITY WARNING: don't run with debug turned on in production!
|
| 26 |
+
DEBUG = True
|
| 27 |
+
|
| 28 |
+
ALLOWED_HOSTS = ["*"]
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
# Application definition
|
| 32 |
+
|
| 33 |
+
INSTALLED_APPS = [
|
| 34 |
+
'django.contrib.admin',
|
| 35 |
+
'django.contrib.auth',
|
| 36 |
+
'django.contrib.contenttypes',
|
| 37 |
+
'django.contrib.sessions',
|
| 38 |
+
'django.contrib.messages',
|
| 39 |
+
'django.contrib.staticfiles',
|
| 40 |
+
'genrateExamSchedule',
|
| 41 |
+
]
|
| 42 |
+
|
| 43 |
+
MIDDLEWARE = [
|
| 44 |
+
'django.middleware.security.SecurityMiddleware',
|
| 45 |
+
'django.contrib.sessions.middleware.SessionMiddleware',
|
| 46 |
+
'django.middleware.common.CommonMiddleware',
|
| 47 |
+
'django.middleware.csrf.CsrfViewMiddleware',
|
| 48 |
+
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
| 49 |
+
'django.contrib.messages.middleware.MessageMiddleware',
|
| 50 |
+
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
| 51 |
+
]
|
| 52 |
+
|
| 53 |
+
ROOT_URLCONF = 'ExamscheduleViewer.urls'
|
| 54 |
+
|
| 55 |
+
TEMPLATES = [
|
| 56 |
+
{
|
| 57 |
+
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
| 58 |
+
'DIRS': ['templates'],
|
| 59 |
+
'APP_DIRS': True,
|
| 60 |
+
'OPTIONS': {
|
| 61 |
+
'context_processors': [
|
| 62 |
+
'django.template.context_processors.debug',
|
| 63 |
+
'django.template.context_processors.request',
|
| 64 |
+
'django.contrib.auth.context_processors.auth',
|
| 65 |
+
'django.contrib.messages.context_processors.messages',
|
| 66 |
+
],
|
| 67 |
+
},
|
| 68 |
+
},
|
| 69 |
+
]
|
| 70 |
+
|
| 71 |
+
WSGI_APPLICATION = 'ExamscheduleViewer.wsgi.application'
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
# Database
|
| 75 |
+
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
| 76 |
+
|
| 77 |
+
DATABASES = {
|
| 78 |
+
'default': {
|
| 79 |
+
'ENGINE': 'django.db.backends.sqlite3',
|
| 80 |
+
'NAME': BASE_DIR / 'db.sqlite3',
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
# Password validation
|
| 86 |
+
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
| 87 |
+
|
| 88 |
+
AUTH_PASSWORD_VALIDATORS = [
|
| 89 |
+
{
|
| 90 |
+
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
| 100 |
+
},
|
| 101 |
+
]
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
# Internationalization
|
| 105 |
+
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
| 106 |
+
|
| 107 |
+
LANGUAGE_CODE = 'en-us'
|
| 108 |
+
|
| 109 |
+
TIME_ZONE = 'UTC'
|
| 110 |
+
|
| 111 |
+
USE_I18N = True
|
| 112 |
+
|
| 113 |
+
USE_TZ = True
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
# Static files (CSS, JavaScript, Images)
|
| 117 |
+
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
| 118 |
+
|
| 119 |
+
STATIC_URL = 'static/'
|
| 120 |
+
|
| 121 |
+
# Default primary key field type
|
| 122 |
+
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
| 123 |
+
|
| 124 |
+
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
ExamscheduleViewer/urls.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
URL configuration for ExamscheduleViewer project.
|
| 3 |
+
|
| 4 |
+
The `urlpatterns` list routes URLs to views. For more information please see:
|
| 5 |
+
https://docs.djangoproject.com/en/5.0/topics/http/urls/
|
| 6 |
+
Examples:
|
| 7 |
+
Function views
|
| 8 |
+
1. Add an import: from my_app import views
|
| 9 |
+
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
| 10 |
+
Class-based views
|
| 11 |
+
1. Add an import: from other_app.views import Home
|
| 12 |
+
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
| 13 |
+
Including another URLconf
|
| 14 |
+
1. Import the include() function: from django.urls import include, path
|
| 15 |
+
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
| 16 |
+
"""
|
| 17 |
+
from django.contrib import admin
|
| 18 |
+
from django.urls import path, include
|
| 19 |
+
|
| 20 |
+
urlpatterns = [
|
| 21 |
+
path('admin/', admin.site.urls),
|
| 22 |
+
path('', include('genrateExamSchedule.urls')),
|
| 23 |
+
]
|
ExamscheduleViewer/wsgi.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
WSGI config for ExamscheduleViewer project.
|
| 3 |
+
|
| 4 |
+
It exposes the WSGI callable as a module-level variable named ``application``.
|
| 5 |
+
|
| 6 |
+
For more information on this file, see
|
| 7 |
+
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import os
|
| 11 |
+
|
| 12 |
+
from django.core.wsgi import get_wsgi_application
|
| 13 |
+
|
| 14 |
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ExamscheduleViewer.settings')
|
| 15 |
+
|
| 16 |
+
application = get_wsgi_application()
|
db.sqlite3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e9c9c2909f8ec33b1ba647b80aab25ee6e74ddb8f7d38e4dc5a011a168084dd3
|
| 3 |
+
size 466944
|
genrateExamSchedule/__init__.py
ADDED
|
File without changes
|
genrateExamSchedule/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (150 Bytes). View file
|
|
|
genrateExamSchedule/__pycache__/admin.cpython-312.pyc
ADDED
|
Binary file (1.75 kB). View file
|
|
|
genrateExamSchedule/__pycache__/apps.cpython-312.pyc
ADDED
|
Binary file (482 Bytes). View file
|
|
|
genrateExamSchedule/__pycache__/models.cpython-312.pyc
ADDED
|
Binary file (1.56 kB). View file
|
|
|
genrateExamSchedule/__pycache__/urls.cpython-312.pyc
ADDED
|
Binary file (862 Bytes). View file
|
|
|
genrateExamSchedule/__pycache__/views.cpython-312.pyc
ADDED
|
Binary file (18.4 kB). View file
|
|
|
genrateExamSchedule/admin.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.contrib import admin
|
| 2 |
+
from .models import ExamSchedule
|
| 3 |
+
from django.http import HttpResponse
|
| 4 |
+
# Register your models here.
|
| 5 |
+
|
| 6 |
+
class ExamScheduleAdmin(admin.ModelAdmin):
|
| 7 |
+
list_display = ('student_name', 'student_id', 'exam_date', 'room_number', 'subject_name', 'subject_code', 'seat_Number')
|
| 8 |
+
search_fields = ('student_name', 'student_id', 'subject_name')
|
| 9 |
+
list_filter = ('exam_date', 'room_number')
|
| 10 |
+
ordering = ('exam_date', 'student_name')
|
| 11 |
+
list_per_page = 500
|
| 12 |
+
actions = ['export_as_csv']
|
| 13 |
+
|
| 14 |
+
def export_as_csv(self, request, queryset):
|
| 15 |
+
import csv
|
| 16 |
+
response = HttpResponse(content_type='text/csv')
|
| 17 |
+
response['Content-Disposition'] = 'attachment; filename="exam_schedule.csv"'
|
| 18 |
+
writer = csv.writer(response)
|
| 19 |
+
writer.writerow(['student_name', 'student_id', 'exam_date', 'room_number', 'subject_name', 'subject_code', 'seat_Number'])
|
| 20 |
+
for schedule in queryset:
|
| 21 |
+
writer.writerow([schedule.student_name, schedule.student_id, schedule.exam_date, schedule.room_number, schedule.subject_name, schedule.subject_code, schedule.seat_Number])
|
| 22 |
+
return response
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
admin.site.register(ExamSchedule, ExamScheduleAdmin)
|
| 26 |
+
|
genrateExamSchedule/apps.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.apps import AppConfig
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class GenrateexamscheduleConfig(AppConfig):
|
| 5 |
+
default_auto_field = 'django.db.models.BigAutoField'
|
| 6 |
+
name = 'genrateExamSchedule'
|
genrateExamSchedule/migrations/0001_initial.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.0.4 on 2025-04-08 04:29
|
| 2 |
+
|
| 3 |
+
from django.db import migrations, models
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class Migration(migrations.Migration):
|
| 7 |
+
|
| 8 |
+
initial = True
|
| 9 |
+
|
| 10 |
+
dependencies = [
|
| 11 |
+
]
|
| 12 |
+
|
| 13 |
+
operations = [
|
| 14 |
+
migrations.CreateModel(
|
| 15 |
+
name='ExamSchedule',
|
| 16 |
+
fields=[
|
| 17 |
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
| 18 |
+
('student_name', models.CharField(max_length=100)),
|
| 19 |
+
('student_id', models.CharField(max_length=20)),
|
| 20 |
+
('exam_date', models.DateField()),
|
| 21 |
+
('room_number', models.CharField(max_length=10)),
|
| 22 |
+
('subject_name', models.CharField(max_length=100)),
|
| 23 |
+
('subject_code', models.CharField(max_length=20)),
|
| 24 |
+
('seat_Number', models.CharField(max_length=10)),
|
| 25 |
+
],
|
| 26 |
+
options={
|
| 27 |
+
'unique_together': {('exam_date', 'student_id', 'subject_name')},
|
| 28 |
+
},
|
| 29 |
+
),
|
| 30 |
+
]
|
genrateExamSchedule/migrations/__init__.py
ADDED
|
File without changes
|
genrateExamSchedule/migrations/__pycache__/0001_initial.cpython-312.pyc
ADDED
|
Binary file (1.41 kB). View file
|
|
|
genrateExamSchedule/migrations/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (161 Bytes). View file
|
|
|
genrateExamSchedule/models.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.db import models
|
| 2 |
+
|
| 3 |
+
# Create your models here.
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class ExamSchedule(models.Model):
|
| 7 |
+
student_name = models.CharField(max_length=100)
|
| 8 |
+
student_id = models.CharField(max_length=20)
|
| 9 |
+
exam_date = models.DateField()
|
| 10 |
+
room_number = models.CharField(max_length=10)
|
| 11 |
+
subject_name = models.CharField(max_length=100)
|
| 12 |
+
subject_code = models.CharField(max_length=20)
|
| 13 |
+
seat_Number = models.CharField(max_length=10)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def __str__(self):
|
| 17 |
+
return f"{self.student_name} - {self.exam_date} - {self.room_number} - {self.subject_name} - {self.subject_code} - {self.seat_Number}"
|
| 18 |
+
|
| 19 |
+
class Meta:
|
| 20 |
+
unique_together = ('exam_date','student_id', 'subject_name')
|
| 21 |
+
|
genrateExamSchedule/tests.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.test import TestCase
|
| 2 |
+
|
| 3 |
+
# Create your tests here.
|
genrateExamSchedule/urls.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from django.contrib import admin
|
| 3 |
+
from django.urls import path, include
|
| 4 |
+
from . import views
|
| 5 |
+
|
| 6 |
+
urlpatterns = [
|
| 7 |
+
path('', views.index, name='index'),
|
| 8 |
+
path('login/', views.login_view, name='login_view'),
|
| 9 |
+
path('logout/', views.logout_view, name='logout_view'),
|
| 10 |
+
path('generate_schedule/', views.generate_schedule, name='generate_schedule'),
|
| 11 |
+
path('uplaod_schedule/', views.upload_schedule, name='upload_schedule'),
|
| 12 |
+
path('view_schedule/', views.view_schedule, name='view_schedule'),
|
| 13 |
+
|
| 14 |
+
]
|
genrateExamSchedule/views.py
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.shortcuts import render,redirect
|
| 2 |
+
from django.http import HttpResponse
|
| 3 |
+
import pandas as pd
|
| 4 |
+
import numpy as np
|
| 5 |
+
from collections import defaultdict
|
| 6 |
+
import random
|
| 7 |
+
import ast
|
| 8 |
+
import os
|
| 9 |
+
import io
|
| 10 |
+
from .models import ExamSchedule
|
| 11 |
+
import openpyxl
|
| 12 |
+
from datetime import datetime, timedelta
|
| 13 |
+
from django.contrib.auth.decorators import login_required
|
| 14 |
+
from django.contrib.auth import login, authenticate
|
| 15 |
+
from django.contrib.auth.models import User
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def login_view(request):
|
| 19 |
+
if request.method == 'POST':
|
| 20 |
+
username = request.POST.get('username')
|
| 21 |
+
password = request.POST.get('password')
|
| 22 |
+
if username == 'admin' and password == 'admin':
|
| 23 |
+
request.session['is_login'] = True
|
| 24 |
+
|
| 25 |
+
return redirect('index')
|
| 26 |
+
else:
|
| 27 |
+
request.session['is_login'] = False
|
| 28 |
+
return render(request, 'login.html', {'error': 'Invalid username or password'})
|
| 29 |
+
|
| 30 |
+
return render(request,'login.html')
|
| 31 |
+
|
| 32 |
+
def logout_view(request):
|
| 33 |
+
request.session['is_login'] = False
|
| 34 |
+
return redirect('login_view')
|
| 35 |
+
class ExamSeatingPlanGenerator:
|
| 36 |
+
def __init__(self):
|
| 37 |
+
self.subject_data = None
|
| 38 |
+
self.room_data = None
|
| 39 |
+
self.student_data = None
|
| 40 |
+
self.seating_plan = []
|
| 41 |
+
self.unassigned_students = []
|
| 42 |
+
|
| 43 |
+
def load_data_from_excel(self, room_file, exam_schedule_file, student_file):
|
| 44 |
+
"""Load input data from Excel files"""
|
| 45 |
+
# Load room data
|
| 46 |
+
self.room_data = pd.read_excel(room_file)
|
| 47 |
+
# Rename columns to match the expected column names in the algorithm
|
| 48 |
+
self.room_data = self.room_data.rename(columns={
|
| 49 |
+
'Room Number': 'RoomNo',
|
| 50 |
+
'Capacity': 'RoomCapacity'
|
| 51 |
+
})
|
| 52 |
+
|
| 53 |
+
# Load exam schedule data
|
| 54 |
+
self.subject_data = pd.read_excel(exam_schedule_file)
|
| 55 |
+
|
| 56 |
+
# Process the complex columns which contain lists
|
| 57 |
+
self._process_complex_exam_schedule()
|
| 58 |
+
|
| 59 |
+
# Load student data
|
| 60 |
+
self.student_data = pd.read_excel(student_file)
|
| 61 |
+
# Rename columns to match the expected column names in the algorithm
|
| 62 |
+
self.student_data = self.student_data.rename(columns={
|
| 63 |
+
'studentId': 'student Id',
|
| 64 |
+
'Name': 'student name',
|
| 65 |
+
'Subject Name': 'subject name'
|
| 66 |
+
})
|
| 67 |
+
|
| 68 |
+
print(f"Loaded {len(self.subject_data)} subjects, {len(self.room_data)} rooms, {len(self.student_data)} students")
|
| 69 |
+
|
| 70 |
+
def _process_complex_exam_schedule(self):
|
| 71 |
+
"""Process the complex exam schedule data where room numbers, max students allowed, and sides are lists"""
|
| 72 |
+
# Create expanded subject data
|
| 73 |
+
expanded_subjects = []
|
| 74 |
+
|
| 75 |
+
for _, row in self.subject_data.iterrows():
|
| 76 |
+
# Extract exam date (convert to string if it's a datetime)
|
| 77 |
+
exam_date = row.get('Date', None)
|
| 78 |
+
if pd.isna(exam_date):
|
| 79 |
+
exam_date = row.get('Exam Date', None)
|
| 80 |
+
if isinstance(exam_date, pd.Timestamp):
|
| 81 |
+
exam_date = exam_date.strftime('%Y-%m-%d')
|
| 82 |
+
|
| 83 |
+
# Extract subject code and name
|
| 84 |
+
subject_code = row.get('Subject Code', row.get('subject Code', None))
|
| 85 |
+
subject_name = row.get('Subject Name', row.get('subject Name', None))
|
| 86 |
+
|
| 87 |
+
# Get the list of room numbers
|
| 88 |
+
room_numbers_str = str(row.get('list of Room Numbers', row.get('Room Numbers', [])))
|
| 89 |
+
max_students_str = str(row.get('MaxStudents Allowed in rooms', row.get('MaxStudentsAllowed', [])))
|
| 90 |
+
assigned_sides_str = str(row.get('Assigned Sides', []))
|
| 91 |
+
|
| 92 |
+
# Parse the lists
|
| 93 |
+
try:
|
| 94 |
+
# Try to parse as a Python list literal
|
| 95 |
+
room_numbers = self._parse_list_from_string(room_numbers_str)
|
| 96 |
+
max_students = self._parse_list_from_string(max_students_str)
|
| 97 |
+
assigned_sides = self._parse_list_from_string(assigned_sides_str)
|
| 98 |
+
|
| 99 |
+
# Ensure each list has the same length or handle appropriately
|
| 100 |
+
num_rooms = len(room_numbers)
|
| 101 |
+
if len(max_students) != num_rooms:
|
| 102 |
+
print(f"Warning: Mismatch in list lengths for {subject_code}. Rooms: {num_rooms}, Max Students: {len(max_students)}")
|
| 103 |
+
max_students = max_students * num_rooms if len(max_students) == 1 else max_students[:num_rooms]
|
| 104 |
+
|
| 105 |
+
if len(assigned_sides) != num_rooms:
|
| 106 |
+
print(f"Warning: Mismatch in list lengths for {subject_code}. Rooms: {num_rooms}, Sides: {len(assigned_sides)}")
|
| 107 |
+
assigned_sides = assigned_sides * num_rooms if len(assigned_sides) == 1 else assigned_sides[:num_rooms]
|
| 108 |
+
|
| 109 |
+
# Create an entry for each room
|
| 110 |
+
for i in range(num_rooms):
|
| 111 |
+
expanded_subjects.append({
|
| 112 |
+
'Exam Date': exam_date,
|
| 113 |
+
'Subject Code': subject_code,
|
| 114 |
+
'Subject Name': subject_name,
|
| 115 |
+
'Room Number': str(room_numbers[i]),
|
| 116 |
+
'MaxStudents Allowed': int(max_students[i]) if i < len(max_students) else 0,
|
| 117 |
+
'Assigned Side': assigned_sides[i] if i < len(assigned_sides) else 'L'
|
| 118 |
+
})
|
| 119 |
+
except Exception as e:
|
| 120 |
+
print(f"Error processing row for {subject_code}: {e}")
|
| 121 |
+
# Add a default entry to avoid data loss
|
| 122 |
+
expanded_subjects.append({
|
| 123 |
+
'Exam Date': exam_date,
|
| 124 |
+
'Subject Code': subject_code,
|
| 125 |
+
'Subject Name': subject_name,
|
| 126 |
+
'Room Number': str(room_numbers_str).strip('[]'),
|
| 127 |
+
'MaxStudents Allowed': 0,
|
| 128 |
+
'Assigned Side': 'L'
|
| 129 |
+
})
|
| 130 |
+
|
| 131 |
+
# Replace the subject data with the expanded version
|
| 132 |
+
self.subject_data = pd.DataFrame(expanded_subjects)
|
| 133 |
+
|
| 134 |
+
def _parse_list_from_string(self, list_str):
|
| 135 |
+
"""Parse a list from a string representation"""
|
| 136 |
+
# Remove any non-standard formatting
|
| 137 |
+
list_str = list_str.strip()
|
| 138 |
+
|
| 139 |
+
# Try different parsing methods
|
| 140 |
+
try:
|
| 141 |
+
# Try to use ast.literal_eval for safe parsing
|
| 142 |
+
return ast.literal_eval(list_str)
|
| 143 |
+
except:
|
| 144 |
+
# Try to handle comma-separated values in brackets
|
| 145 |
+
if list_str.startswith('[') and list_str.endswith(']'):
|
| 146 |
+
items = list_str[1:-1].split(',')
|
| 147 |
+
return [item.strip() for item in items]
|
| 148 |
+
# Try to handle comma-separated values without brackets
|
| 149 |
+
else:
|
| 150 |
+
return [item.strip() for item in list_str.split(',')]
|
| 151 |
+
|
| 152 |
+
def generate_seating_plan(self):
|
| 153 |
+
"""Generate the full seating plan according to requirements"""
|
| 154 |
+
# Group students by subject
|
| 155 |
+
subject_students = defaultdict(list)
|
| 156 |
+
for _, student in self.student_data.iterrows():
|
| 157 |
+
subject_students[student['subject name']].append({
|
| 158 |
+
'name': student['student name'],
|
| 159 |
+
'id': student['student Id']
|
| 160 |
+
})
|
| 161 |
+
|
| 162 |
+
# Shuffle students within each subject for randomized assignment
|
| 163 |
+
for subject in subject_students:
|
| 164 |
+
random.shuffle(subject_students[subject])
|
| 165 |
+
|
| 166 |
+
# Track seat numbers for each side (L/R) in each room
|
| 167 |
+
seat_counters = {str(room): {'L': 1, 'R': 1} for room in self.room_data['RoomNo']}
|
| 168 |
+
|
| 169 |
+
# Group subject data by subject code, date
|
| 170 |
+
subject_groups = self.subject_data.groupby(['Exam Date', 'Subject Code', 'Subject Name'])
|
| 171 |
+
|
| 172 |
+
for (exam_date, subject_code, subject_name), group in subject_groups:
|
| 173 |
+
# Get students for this subject
|
| 174 |
+
students = subject_students.get(subject_name, [])
|
| 175 |
+
if not students:
|
| 176 |
+
print(f"Warning: No students found for subject {subject_name}")
|
| 177 |
+
continue
|
| 178 |
+
|
| 179 |
+
students_assigned = 0
|
| 180 |
+
student_idx = 0
|
| 181 |
+
|
| 182 |
+
# Process each room for this subject
|
| 183 |
+
for _, room_row in group.iterrows():
|
| 184 |
+
room_no = str(room_row['Room Number'])
|
| 185 |
+
max_students = int(room_row['MaxStudents Allowed'])
|
| 186 |
+
assigned_side = room_row['Assigned Side']
|
| 187 |
+
|
| 188 |
+
# Get room capacity
|
| 189 |
+
room_info = self.room_data[self.room_data['RoomNo'].astype(str) == room_no]
|
| 190 |
+
if len(room_info) == 0:
|
| 191 |
+
print(f"Warning: Room {room_no} not found in room data")
|
| 192 |
+
continue
|
| 193 |
+
|
| 194 |
+
bench_capacity = int(room_info.iloc[0]['RoomCapacity'])
|
| 195 |
+
total_seat_capacity = bench_capacity * 2
|
| 196 |
+
|
| 197 |
+
# Calculate how many students to assign to this room
|
| 198 |
+
students_to_assign = min(max_students, len(students) - student_idx)
|
| 199 |
+
if students_to_assign <= 0:
|
| 200 |
+
continue # Skip if no more students to assign
|
| 201 |
+
|
| 202 |
+
# Calculate number of benches needed on this side
|
| 203 |
+
benches_needed = (students_to_assign + 1) // 2 # Ceiling division
|
| 204 |
+
|
| 205 |
+
# Assign seats to students
|
| 206 |
+
for bench in range(benches_needed):
|
| 207 |
+
# First student in bench
|
| 208 |
+
if student_idx < len(students):
|
| 209 |
+
student = students[student_idx]
|
| 210 |
+
seat_number = f"{assigned_side}{seat_counters[room_no][assigned_side]}"
|
| 211 |
+
|
| 212 |
+
self.seating_plan.append({
|
| 213 |
+
'Exam Date': exam_date,
|
| 214 |
+
'Room Number': room_no,
|
| 215 |
+
'Bench Number': bench + 1,
|
| 216 |
+
'Seat Position': assigned_side,
|
| 217 |
+
'Seat Number': seat_number,
|
| 218 |
+
'Student ID': student['id'],
|
| 219 |
+
'Student Name': student['name'],
|
| 220 |
+
'Subject Code': subject_code,
|
| 221 |
+
'Subject Name': subject_name
|
| 222 |
+
})
|
| 223 |
+
|
| 224 |
+
seat_counters[room_no][assigned_side] += 1
|
| 225 |
+
students_assigned += 1
|
| 226 |
+
student_idx += 1
|
| 227 |
+
|
| 228 |
+
# Second student in bench (if available and not the last odd student)
|
| 229 |
+
if student_idx < len(students) and (bench < benches_needed - 1 or students_to_assign % 2 == 0):
|
| 230 |
+
student = students[student_idx]
|
| 231 |
+
seat_number = f"{assigned_side}{seat_counters[room_no][assigned_side]}"
|
| 232 |
+
|
| 233 |
+
self.seating_plan.append({
|
| 234 |
+
'Exam Date': exam_date,
|
| 235 |
+
'Room Number': room_no,
|
| 236 |
+
'Bench Number': bench + 1,
|
| 237 |
+
'Seat Position': assigned_side,
|
| 238 |
+
'Seat Number': seat_number,
|
| 239 |
+
'Student ID': student['id'],
|
| 240 |
+
'Student Name': student['name'],
|
| 241 |
+
'Subject Code': subject_code,
|
| 242 |
+
'Subject Name': subject_name
|
| 243 |
+
})
|
| 244 |
+
|
| 245 |
+
seat_counters[room_no][assigned_side] += 1
|
| 246 |
+
students_assigned += 1
|
| 247 |
+
student_idx += 1
|
| 248 |
+
|
| 249 |
+
# Record unassigned students
|
| 250 |
+
remaining_students = len(students) - students_assigned
|
| 251 |
+
if remaining_students > 0:
|
| 252 |
+
for i in range(students_assigned, len(students)):
|
| 253 |
+
student = students[i]
|
| 254 |
+
self.unassigned_students.append({
|
| 255 |
+
'Exam Date': exam_date,
|
| 256 |
+
'Student ID': student['id'],
|
| 257 |
+
'Student Name': student['name'],
|
| 258 |
+
'Subject Code': subject_code,
|
| 259 |
+
'Subject Name': subject_name,
|
| 260 |
+
'Reason': 'Insufficient room capacity'
|
| 261 |
+
})
|
| 262 |
+
|
| 263 |
+
print(f"Assigned {len(self.seating_plan)} students to seats")
|
| 264 |
+
print(f"Unable to assign {len(self.unassigned_students)} students")
|
| 265 |
+
|
| 266 |
+
def export_to_excel(self, output=None):
|
| 267 |
+
"""Export seating plan and unassigned students to Excel
|
| 268 |
+
|
| 269 |
+
Args:
|
| 270 |
+
output: Can be a file path or an IO buffer. If None, returns a BytesIO object.
|
| 271 |
+
|
| 272 |
+
Returns:
|
| 273 |
+
BytesIO if output is None
|
| 274 |
+
"""
|
| 275 |
+
# If no output is provided, create an in-memory buffer
|
| 276 |
+
if output is None:
|
| 277 |
+
output = io.BytesIO()
|
| 278 |
+
|
| 279 |
+
with pd.ExcelWriter(output, engine='openpyxl') as writer:
|
| 280 |
+
# Export seating plan
|
| 281 |
+
if self.seating_plan:
|
| 282 |
+
seating_df = pd.DataFrame(self.seating_plan)
|
| 283 |
+
seating_df.sort_values(['Exam Date', 'Room Number', 'Bench Number', 'Seat Position'], inplace=True)
|
| 284 |
+
seating_df.to_excel(writer, sheet_name='SeatingPlan', index=False)
|
| 285 |
+
|
| 286 |
+
# Export unassigned students
|
| 287 |
+
if self.unassigned_students:
|
| 288 |
+
unassigned_df = pd.DataFrame(self.unassigned_students)
|
| 289 |
+
unassigned_df.to_excel(writer, sheet_name='Unassigned Students', index=False)
|
| 290 |
+
|
| 291 |
+
# Export room usage summary
|
| 292 |
+
if self.seating_plan:
|
| 293 |
+
room_usage = pd.DataFrame(self.seating_plan).groupby(['Exam Date', 'Room Number']).size().reset_index()
|
| 294 |
+
room_usage.columns = ['Exam Date', 'Room Number', 'Students Assigned']
|
| 295 |
+
room_capacity = self.room_data.copy()
|
| 296 |
+
room_capacity['Student Capacity'] = room_capacity['RoomCapacity'] * 2
|
| 297 |
+
room_capacity['RoomNo'] = room_capacity['RoomNo'].astype(str)
|
| 298 |
+
room_usage = pd.merge(room_capacity, room_usage, left_on='RoomNo', right_on='Room Number', how='right')
|
| 299 |
+
room_usage['Students Assigned'] = room_usage['Students Assigned'].fillna(0).astype(int)
|
| 300 |
+
room_usage['Utilization %'] = (room_usage['Students Assigned'] / room_usage['Student Capacity'] * 100).round(1)
|
| 301 |
+
room_usage.to_excel(writer, sheet_name='RoomUtilization', index=False)
|
| 302 |
+
|
| 303 |
+
# Export subject summary
|
| 304 |
+
if self.seating_plan:
|
| 305 |
+
subject_summary = pd.DataFrame(self.seating_plan).groupby(['Exam Date', 'Subject Code', 'Subject Name']).size().reset_index()
|
| 306 |
+
subject_summary.columns = ['Exam Date', 'Subject Code', 'Subject Name', 'Students Assigned']
|
| 307 |
+
|
| 308 |
+
if self.unassigned_students:
|
| 309 |
+
subject_unassigned = pd.DataFrame(self.unassigned_students).groupby(['Exam Date', 'Subject Code', 'Subject Name']).size().reset_index()
|
| 310 |
+
subject_unassigned.columns = ['Exam Date', 'Subject Code', 'Subject Name', 'Students Unassigned']
|
| 311 |
+
subject_summary = pd.merge(subject_summary, subject_unassigned, on=['Exam Date', 'Subject Code', 'Subject Name'], how='left')
|
| 312 |
+
subject_summary['Students Unassigned'] = subject_summary['Students Unassigned'].fillna(0).astype(int)
|
| 313 |
+
else:
|
| 314 |
+
subject_summary['Students Unassigned'] = 0
|
| 315 |
+
|
| 316 |
+
subject_summary['Total Students'] = subject_summary['Students Assigned'] + subject_summary['Students Unassigned']
|
| 317 |
+
subject_summary.to_excel(writer, sheet_name='SubjectSummary', index=False)
|
| 318 |
+
|
| 319 |
+
# If using BytesIO, seek to beginning for reading
|
| 320 |
+
if isinstance(output, io.BytesIO):
|
| 321 |
+
output.seek(0)
|
| 322 |
+
return output
|
| 323 |
+
|
| 324 |
+
print(f"Seating plan exported")
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
def index(request):
|
| 328 |
+
if request.session['is_login'] == False:
|
| 329 |
+
return redirect('login_view')
|
| 330 |
+
else:
|
| 331 |
+
return render(request, 'index.html')
|
| 332 |
+
# @login_required('login')
|
| 333 |
+
def generate_schedule(request):
|
| 334 |
+
if request.session['is_login'] == False:
|
| 335 |
+
return redirect('login_view')
|
| 336 |
+
else:
|
| 337 |
+
if request.method == 'POST':
|
| 338 |
+
room_file = request.FILES['room_data']
|
| 339 |
+
exam_schedule_file = request.FILES['exam_schedule']
|
| 340 |
+
student_file = request.FILES['student_data']
|
| 341 |
+
|
| 342 |
+
generator = ExamSeatingPlanGenerator()
|
| 343 |
+
generator.load_data_from_excel(room_file, exam_schedule_file, student_file)
|
| 344 |
+
generator.generate_seating_plan()
|
| 345 |
+
|
| 346 |
+
# Generate Excel file in memory instead of saving to disk
|
| 347 |
+
output = generator.export_to_excel()
|
| 348 |
+
|
| 349 |
+
# Create response with the in-memory file
|
| 350 |
+
response = HttpResponse(
|
| 351 |
+
output.getvalue(),
|
| 352 |
+
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
| 353 |
+
)
|
| 354 |
+
response['Content-Disposition'] = 'attachment; filename=Exam_Seating_Plan_Output.xlsx'
|
| 355 |
+
return response
|
| 356 |
+
|
| 357 |
+
return render(request, 'generate_schedule.html')
|
| 358 |
+
|
| 359 |
+
# @login_required('login')
|
| 360 |
+
def upload_schedule(request):
|
| 361 |
+
if request.session['is_login'] == False:
|
| 362 |
+
return redirect('login_view')
|
| 363 |
+
else:
|
| 364 |
+
if request.method == 'POST':
|
| 365 |
+
schedule_file = request.FILES['schedule_file']
|
| 366 |
+
# Load the workbook and select the Seating Plan sheet
|
| 367 |
+
workbook = openpyxl.load_workbook(schedule_file)
|
| 368 |
+
sheet = workbook['SeatingPlan']
|
| 369 |
+
|
| 370 |
+
# Iterate over the rows of the sheet and insert data into the database
|
| 371 |
+
for row in sheet.iter_rows(min_row=2, values_only=True): # Assuming first row is header
|
| 372 |
+
exam_date,room_number,benchNumber,seat_position,seat_number,student_id,student_name,subject_code,subject_name = row
|
| 373 |
+
ExamSchedule.objects.create(
|
| 374 |
+
student_name=student_name,
|
| 375 |
+
student_id=student_id,
|
| 376 |
+
exam_date=datetime.strptime(str(exam_date), '%Y-%m-%d').date(),
|
| 377 |
+
room_number=room_number,
|
| 378 |
+
subject_name=subject_name,
|
| 379 |
+
subject_code=subject_code,
|
| 380 |
+
seat_Number=seat_number
|
| 381 |
+
)
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
return render(request, 'upload_schedule.html',{'message': 'Schedule uploaded successfully!'})
|
| 385 |
+
return render(request, 'upload_schedule.html')
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
def view_schedule(request):
|
| 389 |
+
|
| 390 |
+
if request.method == 'POST':
|
| 391 |
+
student_id = request.POST.get('student_id')
|
| 392 |
+
exam_date = request.POST.get('exam_date',None)
|
| 393 |
+
today = datetime.today().date()
|
| 394 |
+
tomorrow = today + timedelta(days=1)
|
| 395 |
+
schedule = ExamSchedule.objects.filter(student_id=student_id, exam_date__in=[today,tomorrow])
|
| 396 |
+
print(schedule)
|
| 397 |
+
|
| 398 |
+
if len(schedule) > 0:
|
| 399 |
+
return render(request, 'view_schedule.html', {'schedules': schedule})
|
| 400 |
+
else:
|
| 401 |
+
return render(request, 'view_schedule.html', {'message': 'No schedule found for this student ID'})
|
| 402 |
+
else:
|
| 403 |
+
return render(request, 'view_schedule.html')
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
|
manage.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Django's command-line utility for administrative tasks."""
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def main():
|
| 8 |
+
"""Run administrative tasks."""
|
| 9 |
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ExamscheduleViewer.settings')
|
| 10 |
+
try:
|
| 11 |
+
from django.core.management import execute_from_command_line
|
| 12 |
+
except ImportError as exc:
|
| 13 |
+
raise ImportError(
|
| 14 |
+
"Couldn't import Django. Are you sure it's installed and "
|
| 15 |
+
"available on your PYTHONPATH environment variable? Did you "
|
| 16 |
+
"forget to activate a virtual environment?"
|
| 17 |
+
) from exc
|
| 18 |
+
execute_from_command_line(sys.argv)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
if __name__ == '__main__':
|
| 22 |
+
main()
|
requirements.txt
ADDED
|
Binary file (532 Bytes). View file
|
|
|
templates/index.html
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Generate Schedule</title>
|
| 7 |
+
<style>
|
| 8 |
+
form {
|
| 9 |
+
width: 400px;
|
| 10 |
+
margin: 0 auto;
|
| 11 |
+
padding: 20px;
|
| 12 |
+
background-color: #f2f2f2;
|
| 13 |
+
border: 1px solid #ccc;
|
| 14 |
+
border-radius: 5px;
|
| 15 |
+
box-shadow: 0 0 10px rgba(0,0,0,0.2);
|
| 16 |
+
margin-top: 10%;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
label {
|
| 20 |
+
display: block;
|
| 21 |
+
margin-bottom: 10px;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
input[type="file"] {
|
| 25 |
+
display: block;
|
| 26 |
+
margin-bottom: 10px;
|
| 27 |
+
padding: 10px;
|
| 28 |
+
border: 1px solid #ccc;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
input[type="submit"] {
|
| 32 |
+
width: 100%;
|
| 33 |
+
background-color: #4CAF50;
|
| 34 |
+
color: white;
|
| 35 |
+
padding: 14px 20px;
|
| 36 |
+
margin: 8px 0;
|
| 37 |
+
border: none;
|
| 38 |
+
border-radius: 4px;
|
| 39 |
+
cursor: pointer;
|
| 40 |
+
}
|
| 41 |
+
nav {
|
| 42 |
+
text-align: center;
|
| 43 |
+
margin-bottom: 20px;
|
| 44 |
+
}
|
| 45 |
+
nav a {
|
| 46 |
+
margin: 0 15px;
|
| 47 |
+
text-decoration: none;
|
| 48 |
+
color: #333;
|
| 49 |
+
}
|
| 50 |
+
nav a:hover {
|
| 51 |
+
text-decoration: underline;
|
| 52 |
+
}
|
| 53 |
+
body {
|
| 54 |
+
font-family: Arial, sans-serif;
|
| 55 |
+
background-color: #f2f2f2;
|
| 56 |
+
margin: 0;
|
| 57 |
+
padding: 20px;
|
| 58 |
+
}
|
| 59 |
+
</style>
|
| 60 |
+
</head>
|
| 61 |
+
<body>
|
| 62 |
+
<nav>
|
| 63 |
+
<a href="{% url 'index' %}">Home</a> |
|
| 64 |
+
<a href="{% url 'view_schedule' %}">View Schedule</a> |
|
| 65 |
+
<a href="{% url 'logout_view' %}">Logout</a>
|
| 66 |
+
</nav>
|
| 67 |
+
<form action="{% url 'generate_schedule' %}" method="post" enctype="multipart/form-data">
|
| 68 |
+
{% csrf_token %}
|
| 69 |
+
<label for="room_data">Room Data:</label>
|
| 70 |
+
<input type="file" name="room_data" id="room_data" accept=".xlsx"><br>
|
| 71 |
+
<label for="exam_schedule">Exam Schedule:</label>
|
| 72 |
+
<input type="file" name="exam_schedule" id="exam_schedule" accept=".xlsx"><br>
|
| 73 |
+
<label for="student_data">Student Data:</label>
|
| 74 |
+
<input type="file" name="student_data" id="student_data" accept=".xlsx"><br>
|
| 75 |
+
<input type="submit" value="Generate Schedule">
|
| 76 |
+
</form>
|
| 77 |
+
|
| 78 |
+
</body>
|
| 79 |
+
|
| 80 |
+
</html>
|
templates/login.html
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Login</title>
|
| 7 |
+
<style>
|
| 8 |
+
body {
|
| 9 |
+
font-family: Arial, sans-serif;
|
| 10 |
+
background-color: #f2f2f2;
|
| 11 |
+
margin: 0;
|
| 12 |
+
padding: 20px;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
h1 {
|
| 16 |
+
text-align: center;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
form {
|
| 20 |
+
text-align: center;
|
| 21 |
+
margin-bottom: 20px;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
label {
|
| 25 |
+
display: block;
|
| 26 |
+
margin-bottom: 5px;
|
| 27 |
+
font-weight: bold;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
input[type="text"],
|
| 31 |
+
input[type="date"] {
|
| 32 |
+
padding:6px;
|
| 33 |
+
border: 1px solid #ccc;
|
| 34 |
+
border-radius: 4px;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
input[type="submit"] {
|
| 38 |
+
padding: 8px 16px;
|
| 39 |
+
background-color: #4CAF50;
|
| 40 |
+
color: white;
|
| 41 |
+
border: none;
|
| 42 |
+
border-radius: 4px;
|
| 43 |
+
cursor: pointer;
|
| 44 |
+
}
|
| 45 |
+
</style>
|
| 46 |
+
</head>
|
| 47 |
+
<body>
|
| 48 |
+
|
| 49 |
+
<h1>Login</h1>
|
| 50 |
+
<form action="" method="post">
|
| 51 |
+
{% csrf_token %}
|
| 52 |
+
<label for="username">Username:</label><br>
|
| 53 |
+
<input type="text" id="username" name="username"><br><br>
|
| 54 |
+
<label for="password">Password:</label><br>
|
| 55 |
+
<input type="password" id="password" name="password" style="margin-bottom: 10px;"><br>
|
| 56 |
+
|
| 57 |
+
<input type="submit" value="Submit">
|
| 58 |
+
</form>
|
| 59 |
+
<p>{{ message }}</p>
|
| 60 |
+
</body>
|
| 61 |
+
</html>
|
templates/upload_schedule.html
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Upload Schedule</title>
|
| 7 |
+
<style>
|
| 8 |
+
form {
|
| 9 |
+
width: 400px;
|
| 10 |
+
margin: 0 auto;
|
| 11 |
+
padding: 20px;
|
| 12 |
+
background-color: #f2f2f2;
|
| 13 |
+
border: 1px solid #ccc;
|
| 14 |
+
border-radius: 5px;
|
| 15 |
+
box-shadow: 0 0 10px rgba(0,0,0,0.2);
|
| 16 |
+
margin-top: 10%;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
label {
|
| 20 |
+
display: block;
|
| 21 |
+
margin-bottom: 10px;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
input[type="file"] {
|
| 25 |
+
display: block;
|
| 26 |
+
margin-bottom: 10px;
|
| 27 |
+
padding: 10px;
|
| 28 |
+
border: 1px solid #ccc;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
input[type="submit"] {
|
| 32 |
+
width: 100%;
|
| 33 |
+
background-color: #4CAF50;
|
| 34 |
+
color: white;
|
| 35 |
+
padding: 14px 20px;
|
| 36 |
+
margin: 8px 0;
|
| 37 |
+
border: none;
|
| 38 |
+
border-radius: 4px;
|
| 39 |
+
cursor: pointer;
|
| 40 |
+
}
|
| 41 |
+
nav {
|
| 42 |
+
text-align: center;
|
| 43 |
+
margin-bottom: 20px;
|
| 44 |
+
}
|
| 45 |
+
nav a {
|
| 46 |
+
margin: 0 15px;
|
| 47 |
+
text-decoration: none;
|
| 48 |
+
color: #333;
|
| 49 |
+
}
|
| 50 |
+
nav a:hover {
|
| 51 |
+
text-decoration: underline;
|
| 52 |
+
}
|
| 53 |
+
body {
|
| 54 |
+
font-family: Arial, sans-serif;
|
| 55 |
+
background-color: #f2f2f2;
|
| 56 |
+
margin: 0;
|
| 57 |
+
padding: 20px;
|
| 58 |
+
}
|
| 59 |
+
</style>
|
| 60 |
+
</head>
|
| 61 |
+
<body>
|
| 62 |
+
<nav>
|
| 63 |
+
<a href="{% url 'index' %}">Home</a> |
|
| 64 |
+
<a href="{% url 'upload_schedule' %}">Upload Seating Plan</a> |
|
| 65 |
+
<a href="{% url 'view_schedule' %}">View Schedule</a> |
|
| 66 |
+
<a href="{% url 'logout_view' %}">Logout</a>
|
| 67 |
+
</nav>
|
| 68 |
+
{% if messages %}
|
| 69 |
+
{% for message in messages %}
|
| 70 |
+
<p>{{ message }}</p>
|
| 71 |
+
{% endfor %}
|
| 72 |
+
{% endif %}
|
| 73 |
+
<form action="{% url 'upload_schedule' %}" method="post" enctype="multipart/form-data">
|
| 74 |
+
{% csrf_token %}
|
| 75 |
+
<label for="schedule_file">Schedule File:</label>
|
| 76 |
+
<input type="file" name="schedule_file" id="schedule_file" accept=".xlsx"><br>
|
| 77 |
+
<input type="submit" value="Upload Seating Plan">
|
| 78 |
+
</form>
|
| 79 |
+
|
| 80 |
+
</body>
|
| 81 |
+
</html>
|
templates/view_schedule.html
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>View Schedule</title>
|
| 7 |
+
<style>
|
| 8 |
+
body {
|
| 9 |
+
font-family: Arial, sans-serif;
|
| 10 |
+
background-color: #f2f2f2;
|
| 11 |
+
margin: 0;
|
| 12 |
+
padding: 20px;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
h1 {
|
| 16 |
+
text-align: center;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
form {
|
| 20 |
+
text-align: center;
|
| 21 |
+
margin-bottom: 20px;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
label {
|
| 25 |
+
display: block;
|
| 26 |
+
margin-bottom: 5px;
|
| 27 |
+
font-weight: bold;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
input[type="text"],
|
| 31 |
+
input[type="date"] {
|
| 32 |
+
padding: 8px;
|
| 33 |
+
border: 1px solid #ccc;
|
| 34 |
+
border-radius: 4px;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
input[type="submit"] {
|
| 38 |
+
padding: 8px 16px;
|
| 39 |
+
background-color: #4CAF50;
|
| 40 |
+
color: white;
|
| 41 |
+
border: none;
|
| 42 |
+
border-radius: 4px;
|
| 43 |
+
cursor: pointer;
|
| 44 |
+
}
|
| 45 |
+
table {
|
| 46 |
+
border-collapse: collapse;
|
| 47 |
+
width: 100%;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
th, td {
|
| 51 |
+
border: 1px solid #ddd;
|
| 52 |
+
padding: 8px;
|
| 53 |
+
text-align: left;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
th {
|
| 57 |
+
background-color: #f2f2f2;
|
| 58 |
+
}
|
| 59 |
+
nav {
|
| 60 |
+
text-align: center;
|
| 61 |
+
margin-bottom: 20px;
|
| 62 |
+
}
|
| 63 |
+
nav a {
|
| 64 |
+
margin: 0 15px;
|
| 65 |
+
text-decoration: none;
|
| 66 |
+
color: #333;
|
| 67 |
+
}
|
| 68 |
+
nav a:hover {
|
| 69 |
+
text-decoration: underline;
|
| 70 |
+
}
|
| 71 |
+
body {
|
| 72 |
+
font-family: Arial, sans-serif;
|
| 73 |
+
background-color: #f2f2f2;
|
| 74 |
+
margin: 0;
|
| 75 |
+
padding: 20px;
|
| 76 |
+
}
|
| 77 |
+
</style>
|
| 78 |
+
</head>
|
| 79 |
+
<body>
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
<form action="{% url 'view_schedule' %}" method="post">
|
| 83 |
+
{% csrf_token %}
|
| 84 |
+
<label for="student_id">Student ID:</label>
|
| 85 |
+
<input type="text" name="student_id" id="student_id" required><br><br>
|
| 86 |
+
<label for="exam_date">Exam Date:</label>
|
| 87 |
+
<input type="date" name="exam_date" id="exam_date" required><br><br>
|
| 88 |
+
<input type="submit" value="Search">
|
| 89 |
+
</form>
|
| 90 |
+
{% if schedules %}
|
| 91 |
+
<table>
|
| 92 |
+
<tr>
|
| 93 |
+
<th>Student Name</th>
|
| 94 |
+
<th>Student ID</th>
|
| 95 |
+
<th>Exam Date</th>
|
| 96 |
+
<th>Room Number</th>
|
| 97 |
+
<th>Subject Name</th>
|
| 98 |
+
<th>Subject Code</th>
|
| 99 |
+
<th>Seat Number</th>
|
| 100 |
+
</tr>
|
| 101 |
+
{% for schedule in schedules %}
|
| 102 |
+
<tr>
|
| 103 |
+
<td>{{schedule.student_name}}</td>
|
| 104 |
+
<td>{{schedule.student_id}}</td>
|
| 105 |
+
<td>{{schedule.exam_date}}</td>
|
| 106 |
+
<td>{{schedule.room_number}}</td>
|
| 107 |
+
<td>{{schedule.subject_name}}</td>
|
| 108 |
+
<td>{{schedule.subject_code}}</td>
|
| 109 |
+
<td>{{schedule.seat_Number}}</td>
|
| 110 |
+
</tr>
|
| 111 |
+
{% endfor %}
|
| 112 |
+
</table>
|
| 113 |
+
|
| 114 |
+
{% endif %}
|
| 115 |
+
|
| 116 |
+
{% if message %}
|
| 117 |
+
<p>{{ message }}</p>
|
| 118 |
+
{% endif %}
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
</body>
|
| 124 |
+
</html>
|