diff --git a/Dockerfile b/Dockerfile index 5eb2890..0fbee5b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM python:3.8.16 # Install cron -RUN apt-get update && apt-get install -y cron +# RUN apt-get update && apt-get install -y cron # Keeps Python from generating .pyc files in the container ENV PYTHONDONTWRITEBYTECODE=1 @@ -17,14 +17,14 @@ RUN python -m pip install -r requirements.txt WORKDIR /app COPY . /app -# Copy cron job file -COPY cronjob /etc/cron.d/cronjob +# # Copy cron job file +# COPY cronjob /etc/cron.d/cronjob -# Give execution rights on the cron job -RUN chmod 0644 /etc/cron.d/cronjob +# # Give execution rights on the cron job +# RUN chmod 0644 /etc/cron.d/cronjob -# Create the log file to be able to run tail -RUN touch /var/log/cron.log +# # Create the log file to be able to run tail +# RUN touch /var/log/cron.log # Creates a non-root user with an explicit UID and adds permission to access the /app folder # For more info, please refer to https://aka.ms/vscode-docker-python-configure-containers diff --git a/apps/global_settings/__init__.py b/apps/global_settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/global_settings/admin.py b/apps/global_settings/admin.py new file mode 100644 index 0000000..d976426 --- /dev/null +++ b/apps/global_settings/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from .models import Settings + +# Register your models here. +admin.site.register(Settings) \ No newline at end of file diff --git a/apps/global_settings/apps.py b/apps/global_settings/apps.py new file mode 100644 index 0000000..033229a --- /dev/null +++ b/apps/global_settings/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class GlobalSettingsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.global_settings' diff --git a/apps/global_settings/migrations/0001_initial.py b/apps/global_settings/migrations/0001_initial.py new file mode 100644 index 0000000..c532022 --- /dev/null +++ b/apps/global_settings/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.7 on 2023-12-11 22:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Settings', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('enable_hostel_timers', models.BooleanField(default=False)), + ('frontend_checkin_timer', models.IntegerField(blank=True, default=0, null=True)), + ('backend_checkin_timer', models.IntegerField(blank=True, default=0, null=True)), + ('last_entry_without_hostel_checkout', models.TimeField(blank=True, null=True)), + ('valid_entry_without_hostel_checkout', models.TimeField(blank=True, null=True)), + ], + ), + ] diff --git a/apps/global_settings/migrations/0002_settings_enable_gender_ratio_settings_female_ratio_and_more.py b/apps/global_settings/migrations/0002_settings_enable_gender_ratio_settings_female_ratio_and_more.py new file mode 100644 index 0000000..0075e02 --- /dev/null +++ b/apps/global_settings/migrations/0002_settings_enable_gender_ratio_settings_female_ratio_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.7 on 2023-12-11 22:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('global_settings', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='settings', + name='enable_gender_ratio', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='settings', + name='female_ratio', + field=models.FloatField(blank=True, default=0.5, null=True), + ), + migrations.AddField( + model_name='settings', + name='male_ratio', + field=models.FloatField(blank=True, default=0.5, null=True), + ), + ] diff --git a/apps/global_settings/migrations/0003_alter_settings_options_settings_max_violation_count.py b/apps/global_settings/migrations/0003_alter_settings_options_settings_max_violation_count.py new file mode 100644 index 0000000..779c64a --- /dev/null +++ b/apps/global_settings/migrations/0003_alter_settings_options_settings_max_violation_count.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.7 on 2023-12-13 01:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('global_settings', '0002_settings_enable_gender_ratio_settings_female_ratio_and_more'), + ] + + operations = [ + migrations.AlterModelOptions( + name='settings', + options={'verbose_name_plural': 'Settings'}, + ), + migrations.AddField( + model_name='settings', + name='max_violation_count', + field=models.IntegerField(blank=True, default=3, null=True), + ), + ] diff --git a/apps/global_settings/migrations/__init__.py b/apps/global_settings/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/global_settings/models.py b/apps/global_settings/models.py new file mode 100644 index 0000000..83780ab --- /dev/null +++ b/apps/global_settings/models.py @@ -0,0 +1,19 @@ +from django.db import models + +class Settings(models.Model): + + enable_hostel_timers = models.BooleanField(default=False) + frontend_checkin_timer = models.IntegerField(blank=True, null=True, default=0) + backend_checkin_timer = models.IntegerField(blank=True, null=True, default=0) + + last_entry_without_hostel_checkout = models.TimeField(blank=True, null=True) + valid_entry_without_hostel_checkout = models.TimeField(blank=True, null=True) + + enable_gender_ratio = models.BooleanField(default=False) + male_ratio = models.FloatField(blank=True, null=True, default=0.5) + female_ratio = models.FloatField(blank=True, null=True, default=0.5) + + max_violation_count = models.IntegerField(blank=True, null=True, default=3) + + class Meta: + verbose_name_plural = 'Settings' \ No newline at end of file diff --git a/apps/global_settings/tests.py b/apps/global_settings/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/global_settings/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/global_settings/views.py b/apps/global_settings/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/apps/global_settings/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/apps/nightpass/migrations/0003_hostel_backend_checkin_timer_and_more.py b/apps/nightpass/migrations/0003_hostel_backend_checkin_timer_and_more.py new file mode 100644 index 0000000..98d6cba --- /dev/null +++ b/apps/nightpass/migrations/0003_hostel_backend_checkin_timer_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.7 on 2023-12-11 22:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nightpass', '0002_alter_campusresource_slots_booked'), + ] + + operations = [ + migrations.AddField( + model_name='hostel', + name='backend_checkin_timer', + field=models.IntegerField(blank=True, default=0, null=True), + ), + migrations.AddField( + model_name='hostel', + name='frontend_checkin_timer', + field=models.IntegerField(blank=True, default=0, null=True), + ), + ] diff --git a/apps/nightpass/models.py b/apps/nightpass/models.py index ed42976..09e091a 100644 --- a/apps/nightpass/models.py +++ b/apps/nightpass/models.py @@ -10,6 +10,9 @@ class Hostel(models.Model): contact_number = models.CharField(max_length=15) email = models.EmailField(max_length=100) + frontend_checkin_timer = models.IntegerField(blank=True, null=True, default=0) + backend_checkin_timer = models.IntegerField(blank=True, null=True, default=0) + def __str__(self): return self.name diff --git a/apps/nightpass/static/serviceworker.js b/apps/nightpass/static/serviceworker.js new file mode 100644 index 0000000..e1b407d --- /dev/null +++ b/apps/nightpass/static/serviceworker.js @@ -0,0 +1,26 @@ +var staticCacheName = 'thapar-nightpass'; + +self.addEventListener('install', function(event) { +event.waitUntil( + caches.open(staticCacheName).then(function(cache) { + return cache.addAll([ + '', + ]); + }) +); +}); + +self.addEventListener('fetch', function(event) { +var requestUrl = new URL(event.request.url); + if (requestUrl.origin === location.origin) { + if ((requestUrl.pathname === '/')) { + event.respondWith(caches.match('')); + return; + } + } + event.respondWith( + caches.match(event.request).then(function(response) { + return response || fetch(event.request); + }) + ); +}); diff --git a/apps/nightpass/static/styles.css b/apps/nightpass/static/styles.css index f2ea7b0..8de3a70 100644 --- a/apps/nightpass/static/styles.css +++ b/apps/nightpass/static/styles.css @@ -235,7 +235,61 @@ select { color: #fff; } +footer { + background-color: #696b6d; + color: #fff; + text-align: center; + padding: 10px; + position: fixed; + bottom: 0; + width: 100%; +} + +.custom-table { + width: 100%; + border-collapse: collapse; +} +.custom-table th, +.custom-table td { + border: 1px solid #ddd; + padding: 8px; + text-align: left; +} + +.custom-table th { + background-color: #eb1b23; + color: #fff; + font-weight: bold; +} + +.red { + background-color: #d92550; +} + +.green { + background-color: #3ac47d; +} + +.yellow { + background-color: #f7b924; +} + +.badge { + color: white; + padding: 5px 10px; + border-radius: 5px; +} + +.table-container { + max-width: 800px; /* Adjust the maximum width as needed */ + margin: 10px auto; /* Center the container horizontally */ + padding: 20px; /* Add some padding for spacing */ + overflow-x: auto; /* Add horizontal scrolling for small screens */ + background-color: #fff; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + +} @media (max-width: 768px) { .profile-card, .resource-card { @@ -246,7 +300,7 @@ select { flex-wrap: wrap; justify-content: center; align-items: center; -} + } .activity-table { font-size: 14px; diff --git a/apps/nightpass/static/thapar_logo.png b/apps/nightpass/static/thapar_logo.png new file mode 100644 index 0000000..49b1550 Binary files /dev/null and b/apps/nightpass/static/thapar_logo.png differ diff --git a/apps/nightpass/templates/base.html b/apps/nightpass/templates/base.html new file mode 100644 index 0000000..9eda960 --- /dev/null +++ b/apps/nightpass/templates/base.html @@ -0,0 +1,55 @@ + + + + + + Thapar NightPass + + + + + + + + +
+ +

Thapar NightPass

+ +
+ {% block content %} + {% endblock %} + + + + + + +{% if messages %} +{% for message in messages %} + +{% endfor %} +{% endif %} + + diff --git a/apps/nightpass/templates/caretaker.html b/apps/nightpass/templates/caretaker.html new file mode 100644 index 0000000..858439f --- /dev/null +++ b/apps/nightpass/templates/caretaker.html @@ -0,0 +1,41 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+ + + + + + + + + + + {% for pass in hostel_passes %} + + + + {% if pass.user.student.is_checked_in %} + + {% else %} + {% if pass %} + {% if pass.check_in and not pass.check_out %} + + {% else %} + + {% endif %} + {% else %} + + {% endif %} + {% endif %} + + + {% endfor %} + +
NameRoom NoStatusAction
{{ pass.user.student.name }}{{ pass.user.student.room_number }}Checked In{{ pass.campus_resource }}OutsideOutside
+ +
+
+{% endblock %} diff --git a/apps/nightpass/templates/lmao.html b/apps/nightpass/templates/lmao.html index f189660..608f9e9 100644 --- a/apps/nightpass/templates/lmao.html +++ b/apps/nightpass/templates/lmao.html @@ -1,21 +1,5 @@ - - - - - - Thapar NightPass - - - - - - - -
- -

Thapar NightPass

- -
+{% extends 'base.html' %} +{% block content %}
@@ -48,7 +32,7 @@

Thapar NightPass

- -{% if messages %} -{% for message in messages %} - -{% endfor %} -{% endif %} - - + + {% endblock %} \ No newline at end of file diff --git a/apps/nightpass/urls.py b/apps/nightpass/urls.py index b20f329..2ec9444 100644 --- a/apps/nightpass/urls.py +++ b/apps/nightpass/urls.py @@ -4,4 +4,6 @@ urlpatterns = [ path('', views.campus_resources_home), path('book/', views.generate_pass), - path('cancel/', views.cancel_pass),] + path('cancel/', views.cancel_pass), + path('hostel/', views.hostel_home), + ] diff --git a/apps/nightpass/views.py b/apps/nightpass/views.py index 45d7b21..cfef5b7 100644 --- a/apps/nightpass/views.py +++ b/apps/nightpass/views.py @@ -5,6 +5,7 @@ from django.utils import timezone from .models import * from ..users.models import * +from ..global_settings.models import Settings as settings import random import string import json @@ -16,14 +17,22 @@ + @login_required def campus_resources_home(request): + Settings = settings.objects.first() campus_resources = CampusResource.objects.filter(is_display=True) user = request.user if user.user_type == 'student': - user_pass = NightPass.objects.filter(user=user, check_out=False).first() + user_pass = NightPass.objects.filter(user=user, valid=True).first() user_incidents = NightPass.objects.filter(user=user, defaulter=True) - return render(request, 'lmao.html', {'user':user.student,'campus_resources':campus_resources, 'user_pass':user_pass, 'user_incidents':user_incidents}) + + if Settings.enable_hostel_timers: + checkin_timer = user.student.hostel.frontend_checkin_timer + else: + checkin_timer = Settings.frontend_checkin_timer + + return render(request, 'lmao.html', {'user':user.student,'campus_resources':campus_resources, 'user_pass':user_pass, 'user_incidents':user_incidents, 'frontend_checkin_timer':checkin_timer}) elif user.user_type == 'security': return redirect('/access') elif user.user_type == 'admin': @@ -38,17 +47,46 @@ def generate_pass(request, campus_resource): if (campus_resource.is_display == False) or not campus_resource.is_booking: data = { 'status':False, - 'message':'Booking is currently not available for this resource' + 'message':'Booking is currently not available for this resource.' } return HttpResponse(json.dumps(data)) if campus_resource.booking_complete: data = { 'status':False, - 'message':'No slots available!' + 'message':'All slots are booked for today!' } return HttpResponse(json.dumps(data)) + Settings = settings.objects.first() + if Settings.enable_gender_ratio: + if user.student.gender == 'male': + male_pass_count = NightPass.objects.filter(valid=True, campus_resource=campus_resource, + user__student__gender='male').count() + if (male_pass_count > Settings.male_ratio*(campus_resource.max_capacity)) or Settings.male_ratio==float(0): + data = { + 'status':False, + 'message':'All slots are booked for today!' + } + return HttpResponse(json.dumps(data)) + + elif user.student.gender == 'female': + female_pass_count = NightPass.objects.filter(valid=True, campus_resource=campus_resource, + user__student__gender='female').count() + if (female_pass_count > Settings.female_ratio*(campus_resource.max_capacity)) or Settings.female_ratio==float(0) : + data = { + 'status':False, + 'message':'All slots are booked for today!' + } + return HttpResponse(json.dumps(data)) + + if int(user.student.violation_flags) >= int(Settings.max_violation_count): + data = { + 'status':False, + 'message':'Nightpass facility has been temporarily suspended! Contact DOSA office for further details.' + } + return HttpResponse(json.dumps(data)) + user_pass = NightPass.objects.filter(user=user, date=date.today()).first() if not user_pass: campus_resource.refresh_from_db() @@ -119,18 +157,13 @@ def cancel_pass(request): } return HttpResponse(json.dumps(data)) else: - if user_nightpass.check_out and user_nightpass.check_in: + if user_nightpass.check_out or user_nightpass.check_in or user_nightpass.hostel_checkout_time: data={ 'status':False, 'message':f"Cannot cancel pass after utilization." } return HttpResponse(json.dumps(data)) - elif user_nightpass.check_in: - data={ - 'status':False, - 'message':f"Cannot cancel pass once you enter {user_nightpass.campus_resource}." - } - return HttpResponse(json.dumps(data)) + else: user_nightpass.delete() user_nightpass.campus_resource.slots_booked -= 1 @@ -142,3 +175,12 @@ def cancel_pass(request): 'message':f"Pass cancelled successfully!" } return HttpResponse(json.dumps(data)) + + +def hostel_home(request): + user = request.user + if request.user.is_staff and user.user_type == 'security': + hostel_passes = NightPass.objects.filter(valid=True, user__student__hostel=request.user.security.hostel) | NightPass.objects.filter(date=date.today(), user__student__hostel=request.user.security.hostel).order_by('check_out') + return render(request, 'caretaker.html', {'hostel_passes':hostel_passes}) + else: + return redirect('/access') \ No newline at end of file diff --git a/apps/users/admin.py b/apps/users/admin.py index a2da4bc..b0b378f 100644 --- a/apps/users/admin.py +++ b/apps/users/admin.py @@ -3,7 +3,7 @@ from django.http import HttpResponse from django.utils import timezone from django.utils.html import format_html -from rangefilter.filter import DateRangeFilter +from rangefilter.filters import DateRangeFilter from tablib import Dataset from .models import * from import_export.admin import ImportExportModelAdmin @@ -15,14 +15,14 @@ class NightPassAdmin(admin.ModelAdmin): - list_display = ( 'user','hostel','date', 'campus_resource','hostel_check_out', 'check_in', 'check_out', 'hostel_check_in', 'defaulter') + list_display = ( 'name','user','hostel','date', 'campus_resource','hostel_check_out', 'check_in', 'check_out', 'hostel_check_in', 'defaulter') search_fields = ('user__student__name','user__student__registration_number','user__student__email') actions = ['export_as_xlsx'] - list_filter = (('date', DateRangeFilter),'campus_resource','user__student__hostel', 'defaulter', 'check_in', 'check_out') + list_filter = (('date', DateRangeFilter),'campus_resource','user__student__gender','user__student__hostel', 'defaulter', 'check_in', 'check_out') autocomplete_fields = ('user', 'campus_resource') readonly_fields = ('pass_id', 'check_in_time', 'check_out_time', 'hostel_checkout_time', 'hostel_checkin_time') - def user(self, obj): + def name(self, obj): return obj.user.student.name def hostel(self, obj): diff --git a/apps/users/management/commands/check_defaulters.py b/apps/users/management/commands/check_defaulters.py index f2f4a97..557603c 100644 --- a/apps/users/management/commands/check_defaulters.py +++ b/apps/users/management/commands/check_defaulters.py @@ -1,10 +1,13 @@ from django.core.management.base import BaseCommand -from ...models import NightPass, Student +from ...models import NightPass +from ....global_settings.models import Settings as settings from datetime import date, timedelta, datetime, time from django.utils import timezone + def check_defaulters(): + Settings = settings.objects.first() previous_day_nightpasses = NightPass.objects.filter(date=date.today()-timedelta(days=1)) previous_day_nightpasses.update(defaulter=False, defaulter_remarks='') for nightpass in previous_day_nightpasses: @@ -15,15 +18,23 @@ def check_defaulters(): defaulter = True remarks+= f"Did not visit {nightpass.campus_resource.name}" else: - start_default_time = timezone.make_aware(datetime.combine(nightpass.check_in_time.date(), time(20,45)), timezone.get_current_timezone()) - end_default_time = timezone.make_aware(datetime.combine(nightpass.check_in_time.date(), time(21,00)), timezone.get_current_timezone()) + # start_default_time = timezone.make_aware(datetime.combine(nightpass.check_in_time.date(), time(20,45)), timezone.get_current_timezone()) + # end_default_time = timezone.make_aware(datetime.combine(nightpass.check_in_time.date(), time(21,00)), timezone.get_current_timezone()) + start_default_time = Settings.valid_entry_without_hostel_checkout + end_default_time = Settings.last_entry_without_hostel_checkout + + if Settings.enable_hostel_timers: + checkin_timer = nightpass.user.student.hostel.backend_checkin_timer + else: + checkin_timer = Settings.backend_checkin_timer + if nightpass.hostel_checkout_time: - if (nightpass.check_in_time - nightpass.hostel_checkout_time) > timedelta(minutes=30): - if (nightpass.check_in_time > start_default_time): + if (nightpass.check_in_time - nightpass.hostel_checkout_time) > timedelta(minutes=checkin_timer): + if (nightpass.check_in_time.time() > start_default_time): defaulter = True remarks+= f"Late check in at {nightpass.campus_resource.name}" else: - if (nightpass.check_in_time > end_default_time): + if (nightpass.check_in_time.time() > end_default_time): defaulter = True remarks+= f"Late check in at {nightpass.campus_resource.name}" if not nightpass.check_out_time: @@ -33,7 +44,7 @@ def check_defaulters(): if not nightpass.hostel_checkin_time: defaulter = True remarks+= "Late check in at hostel" - elif (nightpass.hostel_checkin_time - nightpass.check_out_time) > timedelta(minutes=30): + elif (nightpass.hostel_checkin_time - nightpass.check_out_time) > timedelta(minutes=checkin_timer): defaulter = True remarks+= "Late check in at hostel" if (nightpass.check_out_time-nightpass.check_in_time) < timedelta(minutes=10): diff --git a/apps/users/migrations/0009_alter_nightpass_options_alter_security_options.py b/apps/users/migrations/0009_alter_nightpass_options_alter_security_options.py new file mode 100644 index 0000000..cb145df --- /dev/null +++ b/apps/users/migrations/0009_alter_nightpass_options_alter_security_options.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.7 on 2023-12-13 01:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0008_student_gender_alter_nightpass_valid'), + ] + + operations = [ + migrations.AlterModelOptions( + name='nightpass', + options={'verbose_name_plural': 'Night Passes'}, + ), + migrations.AlterModelOptions( + name='security', + options={'verbose_name_plural': 'Security'}, + ), + ] diff --git a/apps/users/models.py b/apps/users/models.py index 9841238..2da1360 100644 --- a/apps/users/models.py +++ b/apps/users/models.py @@ -123,6 +123,9 @@ class Security(models.Model): def __str__(self): return self.name + class Meta: + verbose_name_plural = 'Security' + class NightPass(models.Model): user = models.ForeignKey(CustomUser, on_delete=models.CASCADE) @@ -142,4 +145,7 @@ class NightPass(models.Model): defaulter_remarks = models.TextField(blank=True, null=True) def __str__(self): - return self.pass_id \ No newline at end of file + return self.pass_id + + class Meta: + verbose_name_plural = 'Night Passes' \ No newline at end of file diff --git a/apps/users/static/login.css b/apps/users/static/login.css index d7a23f7..f2ea7b0 100644 --- a/apps/users/static/login.css +++ b/apps/users/static/login.css @@ -1,98 +1,258 @@ body { - background-color: #940d0d; font-family: Arial, sans-serif; + background-color: #f5f5f5; margin: 0; + padding: 0; + line-height: 1.6; +} + +.header { display: flex; - justify-content: center; align-items: center; - height: 100vh; + justify-content: space-between; + background-color: #eb1b23; + color: #fff; + text-align: center; + padding: 2px; /* Reduced header padding */ +} + +.header-logo { + width: 100px; /* Set the width of your college logo */ + height: auto; /* Maintain aspect ratio */ +} + +.logout-button { + background-color: #fff; + color: #eb1b23; + border: none; + padding: 5px 10px; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.2s; + margin-right: 20px; +} + +.logout-button:hover { + background-color: #ccc; } .container { - background-color: white; - max-width: 300px; - padding: 20px; - width: 45%; - border-radius: 8px; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); + display: flex; + flex-wrap: nowrap; + justify-content: center; + align-items: center; } -.logo { + +.profile-card, .resource-card { + background-color: #fff; + border-radius: 10px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + margin: 20px; + width: 300px; text-align: center; + padding: 20px; + transition: transform 0.2s; } -.logo img { - height: 100px; +.profile-card:hover, .resource-card:hover { + transform: scale(1.05); } -.login-box { - text-align: center; +.profile-img { + width: 100px; + border-radius: 50%; + object-fit: cover; + margin-bottom: 20px; } -.login-box h2 { +.qr-code { + width: 100px; + height: 100px; margin-bottom: 20px; } -.container form { - display: flex; - flex-direction: column; +.profile-details { + font-size: 18px; + margin-bottom: 20px; + text-align: left; } -.container input { - padding: 10px; +.profile-details p { margin-bottom: 10px; - border: 1px solid #ccc; - border-radius: 4px; } -.container button { - padding: 10px; - background-color: #940d0d; - color: white; +.safe { + color: green; +} + +.unsafe { + color: red; +} + +.book-button { + background-color: #eb1b23; + color: #fff; border: none; - border-radius: 4px; + padding: 10px 20px; + border-radius: 10px; cursor: pointer; + transition: background-color 0.2s; +} + +.booked { + background-color: grey; } -.container button:hover { - background-color: #ed0505; +.book-button:hover { + background-color: #ff3336; } -/* "activate-account" link */ -.activate-account { +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); +} + +.modal-content { + background-color: #fff; + width: 80%; + max-width: 400px; + margin: 20% auto; + padding: 20px; + border-radius: 5px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); text-align: center; - margin-top: 15px; + position: relative; + display: flex; } -.activate-account a { - color: #7240ce; - text-decoration: none; +.modal-image { + flex: 1; } -.activate-account a:hover { - text-decoration: underline; +.modal-image img { + width: 100%; + height: auto; + object-fit: cover; } -/* Animation */ -.container { - animation: fadeIn 1s ease; +.modal-details { + flex: 2; + padding: 10px; + text-align: left; } -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(-20px); - } - to { - opacity: 1; - transform: translateY(0); - } +/* Close button for the modal */ +.close { + position: absolute; + top: 10px; + right: 10px; + cursor: pointer; } -/* Responsive styles */ -@media screen and (max-width: 500px) { +.modal-button { + background-color: #eb1b23; + color: #fff; + border: none; + padding: 10px 20px; + border-radius: 10px; + cursor: pointer; + transition: background-color 0.2s; +} + +.modal-button:hover { + background-color: #ff3336; +} + +select { + width: 100%; + padding: 10px; + margin: 10px 0; +} +.resource-card { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} +.resource-card img { + width: 100px; + height: 100px; + object-fit: cover; + border-radius: 5px; + margin-right: 5px; +} +.resource-details { + flex: 1; + padding: 10px; + text-align: left; +} +.resource-details h2 { + margin: 0; +} +.resource-details p { + margin: 0; +} +.location-cards +{ + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; +} + +.table-section { + margin-top: 10px; + overflow-x: auto; + /* Add horizontal scroll for small screens */ +} + +.activity-table { + width: 100%; + max-width: 100%; /* Set a maximum width for the table */ + border-collapse: collapse; + margin-top: 20px; + white-space: nowrap; /* Prevent text wrapping */ + overflow: hidden; +} + +.activity-table th, .activity-table td { + border: 1px solid #ddd; + padding: 8px; + text-align: left; + overflow: hidden; + text-overflow: ellipsis; /* Display ellipsis for overflowed text */ + white-space: wrap; +} + +.activity-table th { + background-color: #eb1b23; + color: #fff; +} + + + +@media (max-width: 768px) { + .profile-card, .resource-card { + width: 90%; + } .container { - max-width: 90%; - width: 70%; + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; +} + + .activity-table { + font-size: 14px; + } + + .activity-table th, .activity-table td { + padding: 5px; } } diff --git a/apps/users/templates/index.html b/apps/users/templates/index.html new file mode 100644 index 0000000..50545fd --- /dev/null +++ b/apps/users/templates/index.html @@ -0,0 +1,114 @@ + + + + + + Thapar NightPass + + + + + + + + + + +
+ +

Thapar NightPass

+ +
+ +
+

Welcome to the Night Pass Management System

+

Book your night pass to access campus resources and facilities. Enjoy a seamless experience!

+
+ {% csrf_token %} + +
+
+ + + + +{% if messages %} +{% for message in messages %} + +{% endfor %} +{% endif %} + + diff --git a/apps/users/templates/login.html b/apps/users/templates/login.html deleted file mode 100644 index 2b63741..0000000 --- a/apps/users/templates/login.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - - Login | Thapar NightPass - - - - - - - - -
- -
-
-
-
-
-
- -
- - {% if messages %} - {% for message in messages %} - - {% endfor %} - {% endif %} - - - - \ No newline at end of file diff --git a/apps/users/views.py b/apps/users/views.py index 614ca3e..f97b7cc 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -108,7 +108,7 @@ def login_user(request): if request.method == 'POST': return gauth(request) else: - return render(request=request, template_name='login.html') + return render(request=request, template_name='index.html') else: return redirect('/') diff --git a/apps/validation/static/info.js b/apps/validation/static/info.js index ea05514..7e75d2c 100644 --- a/apps/validation/static/info.js +++ b/apps/validation/static/info.js @@ -70,45 +70,62 @@ function resetPass() { } +var timeouts = []; function updateProfile(data) { + // document.querySelector('.profile-card').innerHTML=` + // Student Picture + //
+ //

+ //

Registration Number:

+ //

Hostel:

+ //

Room Number:

+ //

In Hostel:

+ //

Hostel Check-in Time:

+ //

Hostel Check-out Time:

+ //

Last Check-out Time:

+ //

Has Booked:

+ //
`; + document.querySelector('.profile-card').innerHTML=` - Student Picture -
-

-

Registration Number:

-

Hostel:

-

Room Number:

-

In Hostel:

-

Hostel Check-in Time:

-

Hostel Check-out Time:

-

Last Check-out Time:

-

Has Booked:

-
`; - const profileDetails = document.querySelector('.profile-details'); +

+

Roll Number:

+ Student Picture
+`; + + + const profileDetails = document.querySelector('.profile-card'); const nameElement = profileDetails.querySelector('h2'); - const regNumberElement = profileDetails.querySelector('p:nth-child(2)'); - const hostelElement = profileDetails.querySelector('p:nth-child(3)'); - const roomNumberElement = profileDetails.querySelector('p:nth-child(4)'); - const inHostelElement = profileDetails.querySelector('p:nth-child(5)'); - const hostelCheckinElement = profileDetails.querySelector('p:nth-child(6)'); - const hostelCheckoutElement = profileDetails.querySelector('p:nth-child(7)'); - const lastCheckoutElement = profileDetails.querySelector('p:nth-child(8)'); - const hasBookedElement = profileDetails.querySelector('p:nth-child(9)'); - const options = { hour: '2-digit', minute: '2-digit', hour12: true, month: 'long', day: '2-digit' }; - const checkinTime = new Date(data.hostel_checkin_time).toLocaleString('en-US', options); - const checkoutTime = new Date(data.hostel_checkout_time).toLocaleString('en-US', options); - const lastCheckoutTime = new Date(data.last_checkout_time).toLocaleString('en-US', options); + const regNumberElement = profileDetails.querySelector('p'); + + + // const profileDetails = document.querySelector('.profile-details'); + // const nameElement = profileDetails.querySelector('h2'); + // const regNumberElement = profileDetails.querySelector('p:nth-child(2)'); + // const hostelElement = profileDetails.querySelector('p:nth-child(3)'); + // const roomNumberElement = profileDetails.querySelector('p:nth-child(4)'); + // const inHostelElement = profileDetails.querySelector('p:nth-child(5)'); + // const hostelCheckinElement = profileDetails.querySelector('p:nth-child(6)'); + // const hostelCheckoutElement = profileDetails.querySelector('p:nth-child(7)'); + // const lastCheckoutElement = profileDetails.querySelector('p:nth-child(8)'); + // const hasBookedElement = profileDetails.querySelector('p:nth-child(9)'); + // const options = { hour: '2-digit', minute: '2-digit', hour12: true, month: 'long', day: '2-digit' }; + // const checkinTime = new Date(data.hostel_checkin_time).toLocaleString('en-US', options); + // const checkoutTime = new Date(data.hostel_checkout_time).toLocaleString('en-US', options); + // const lastCheckoutTime = new Date(data.last_checkout_time).toLocaleString('en-US', options); document.getElementById('user_picture').src = data.picture; nameElement.textContent = data.name; regNumberElement.textContent = `Registration Number: ${data.registration_number}`; - hostelElement.textContent = `Hostel: ${data.hostel}`; - roomNumberElement.textContent = `Room Number: ${data.room_number}`; - inHostelElement.textContent = `In Hostel: ${data.is_checked_in ? 'Yes' : 'No'}`; - hostelCheckinElement.textContent = `Hostel Check-in Time: ${checkinTime}`; - hostelCheckoutElement.textContent = `Hostel Check-out Time: ${checkoutTime}`; - lastCheckoutElement.textContent = `Last Check-out Time: ${lastCheckoutTime}`; - hasBookedElement.textContent = `Has Booked: ${data.has_booked ? 'Yes' : 'No'}`; - setTimeout(function(){resetProfile();}, 10000); + // hostelElement.textContent = `Hostel: ${data.hostel}`; + // roomNumberElement.textContent = `Room Number: ${data.room_number}`; + // inHostelElement.textContent = `In Hostel: ${data.is_checked_in ? 'Yes' : 'No'}`; + // hostelCheckinElement.textContent = `Hostel Check-in Time: ${checkinTime}`; + // hostelCheckoutElement.textContent = `Hostel Check-out Time: ${checkoutTime}`; + // lastCheckoutElement.textContent = `Last Check-out Time: ${lastCheckoutTime}`; + // hasBookedElement.textContent = `Has Booked: ${data.has_booked ? 'Yes' : 'No'}`; + for (var i = 0; i < timeouts.length; i++) { + clearTimeout(timeouts[i]); + } + timeouts.push(setTimeout(function(){resetProfile();}, 7000)); } // Function to update the user pass section with data @@ -214,7 +231,10 @@ function checkIn(registration_number) { if (res) { if (response.student_stats) {updateStats(response.student_stats);} toastr.success(response.message); - setTimeout(function(){ resetProfile(); }, 5000); + for (var i = 0; i < timeouts.length; i++) { + clearTimeout(timeouts[i]); + } + timeouts.push(setTimeout(function(){ resetProfile(); }, 5000)); } else { @@ -241,7 +261,10 @@ function checkOut(registration_number) { if (res) { if (response.student_stats) {updateStats(response.student_stats);} toastr.success(response.message); - setTimeout(function(){ resetProfile(); }, 7000); + for (var i = 0; i < timeouts.length; i++) { + clearTimeout(timeouts[i]); + } + timeouts.push(setTimeout(function(){ resetProfile(); }, 7000)); } else { toastr.error(response.message); @@ -272,4 +295,4 @@ function checkInput() { setInterval(function () { checkInput() -}, 1000); \ No newline at end of file +}, 300); \ No newline at end of file diff --git a/apps/validation/templates/info.html b/apps/validation/templates/info.html index 838fecc..5c01593 100644 --- a/apps/validation/templates/info.html +++ b/apps/validation/templates/info.html @@ -97,7 +97,7 @@

Booking: {{ total_count }}


var audio = new Audio('../static/beep.mp3'); audio.play(); if (code.length == 9) {fetch_data({'registration_number':code})} - lastScannedBarcode.concat(code); + lastScannedBarcode.push(code); } }); @@ -109,7 +109,7 @@

Booking: {{ total_count }}


setInterval(function () { lastScannedBarcode.pop(); - }, 10000); + }, 5000); diff --git a/core/settings.py b/core/settings.py index d092754..170ebdb 100644 --- a/core/settings.py +++ b/core/settings.py @@ -30,7 +30,7 @@ DEVELOPMENT_MODE = os.getenv("DEVELOPMENT_MODE", "False") == "True" # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = os.getenv("DEBUG", "False") == "True" ALLOWED_HOSTS = ['https://permissions.onlinehostel.in','*', 'localhost', '127.0.0.1', '0.0.0.0'] @@ -43,6 +43,7 @@ 'apps.users', 'apps.nightpass', 'apps.validation', + 'apps.global_settings', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -50,11 +51,13 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'corsheaders', + 'explorer', 'import_export', 'django_crontab', 'hijack', 'hijack.contrib.admin', - 'rangefilter' + "rangefilter", + # 'pwa' ] @@ -194,3 +197,49 @@ # ] # CRONTAB_TIME_ZONE = 'Asia/Kolkata' + +EXPLORER_CONNECTIONS = { 'Default': 'default' } +EXPLORER_DEFAULT_CONNECTION = 'default' + +EXPLORER_SCHEMA_EXCLUDE_TABLE_PREFIXES = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.admin' +) + +EXPLORER_PERMISSION_CHANGE = lambda r: r.user.is_superuser +EXPLORER_PERMISSION_VIEW = lambda r: r.user.is_superuser + + +# PWA_SERVICE_WORKER_PATH = os.path.join(BASE_DIR, 'static', 'serviceworker.js') + +# PWA_APP_NAME = 'Thapar NightPass' +# PWA_APP_DESCRIPTION = "Thapar NightPass" +# PWA_APP_THEME_COLOR = '#eb1b23' +# PWA_APP_BACKGROUND_COLOR = '#ffffff' +# PWA_APP_DISPLAY = 'standalone' +# PWA_APP_SCOPE = '/' +# PWA_APP_ORIENTATION = 'any' +# PWA_APP_START_URL = '/' +# PWA_APP_STATUS_BAR_COLOR = '#eb1b23' +# PWA_APP_ICONS = [ +# { +# 'src': 'static/thapar_logo.png', +# 'sizes': '160x160' +# } +# ] +# PWA_APP_ICONS_APPLE = [ +# { +# 'src': 'static/thapar_logo.png', +# 'sizes': '160x160' +# } +# ] +# PWA_APP_SPLASH_SCREEN = [ +# { +# 'src': 'static/thapar_logo.png', +# 'media': '(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)' +# } +# ] +# PWA_APP_DIR = 'ltr' +# PWA_APP_LANG = 'en-US' diff --git a/core/urls.py b/core/urls.py index c66abfe..a1e4420 100644 --- a/core/urls.py +++ b/core/urls.py @@ -29,4 +29,8 @@ path('users/', include("django.contrib.auth.urls")), path('accounts/google/login/callback/', user_views.oauth_callback), path('access/', include("apps.validation.urls")), - path('hijack/', include('hijack.urls')),] + path('hijack/', include('hijack.urls')), + path('explorer/', include('explorer.urls')), +# path("", include('pwa.urls')) + ] + diff --git a/requirements.txt b/requirements.txt index 220bfdb..3ab7b03 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,8 @@ django-import-export django-crontab uuid tablib -XlsxWriter django-admin-rangefilter -pytz \ No newline at end of file +XlsxWriter +pytz +django-sql-explorer[xls] +django-pwa \ No newline at end of file