diff --git a/.gitignore b/.gitignore index 7c1e2fa..afe85b6 100644 --- a/.gitignore +++ b/.gitignore @@ -61,7 +61,6 @@ local_settings.py db.sqlite3 db.sqlite3-journal staticfiles -static *.xlsx *.csv *.xls @@ -165,8 +164,4 @@ cython_debug/ #.idea/ -#dockerfiles -*.yml -*.yaml -Dockerfile -*.sh + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5eb2890 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +# For more information, please refer to https://aka.ms/vscode-docker-python +FROM python:3.8.16 + +# Install cron +RUN apt-get update && apt-get install -y cron + +# Keeps Python from generating .pyc files in the container +ENV PYTHONDONTWRITEBYTECODE=1 + +# Turns off buffering for easier container logging +ENV PYTHONUNBUFFERED=1 + +# Install pip requirements +COPY requirements.txt . +RUN python -m pip install -r requirements.txt + +WORKDIR /app +COPY . /app + +# Copy cron job file +COPY cronjob /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 + +# 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 +# RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /app +USER root + +# During debugging, this entry point will be overridden. For more information, please refer to https://aka.ms/vscode-docker-python-debug +CMD ["sh", "run.sh"] \ No newline at end of file diff --git a/apps/nightpass/management/__init__.py b/apps/nightpass/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/nightpass/management/commands/__init__.py b/apps/nightpass/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/nightpass/management/commands/reset_campus_resources.py b/apps/nightpass/management/commands/reset_campus_resources.py new file mode 100644 index 0000000..af48b72 --- /dev/null +++ b/apps/nightpass/management/commands/reset_campus_resources.py @@ -0,0 +1,10 @@ +from django.core.management.base import BaseCommand +from ...models import CampusResource + +class Command(BaseCommand): + help = 'Reset all campus resources' + + def handle(self, *args, **options): + self.stdout.write(self.style.SUCCESS('Running your cron job...')) + CampusResource.objects.all().update(slots_booked=0, booking_complete=False, is_booking = False) + self.stdout.write(self.style.SUCCESS('Cron job completed successfully')) \ No newline at end of file diff --git a/apps/nightpass/management/commands/stop_booking.py b/apps/nightpass/management/commands/stop_booking.py new file mode 100644 index 0000000..e7927d3 --- /dev/null +++ b/apps/nightpass/management/commands/stop_booking.py @@ -0,0 +1,10 @@ +from django.core.management.base import BaseCommand +from ...models import CampusResource + +class Command(BaseCommand): + help = 'Stop booking for all campus resources' + + def handle(self, *args, **options): + self.stdout.write(self.style.SUCCESS('Running your cron job...')) + CampusResource.objects.all().update(is_booking=False, booking_complete=True) + self.stdout.write(self.style.SUCCESS('Cron job completed successfully')) \ No newline at end of file diff --git a/apps/nightpass/migrations/0002_alter_campusresource_slots_booked.py b/apps/nightpass/migrations/0002_alter_campusresource_slots_booked.py new file mode 100644 index 0000000..76168fc --- /dev/null +++ b/apps/nightpass/migrations/0002_alter_campusresource_slots_booked.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.7 on 2023-12-03 06:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nightpass', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='campusresource', + name='slots_booked', + field=models.IntegerField(default=0, editable=False), + ), + ] diff --git a/apps/nightpass/models.py b/apps/nightpass/models.py index 324d83f..ed42976 100644 --- a/apps/nightpass/models.py +++ b/apps/nightpass/models.py @@ -1,6 +1,8 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from django.db import models +from django.db.models.signals import post_save +from django.dispatch import receiver class Hostel(models.Model): @@ -21,12 +23,9 @@ class CampusResource(models.Model): is_booking = models.BooleanField(default=False) is_display = models.BooleanField(default=False) booking_complete = models.BooleanField(default=False) - slots_booked = models.IntegerField(default=0) + slots_booked = models.IntegerField(default=0, editable=False) type = models.CharField(max_length=100, choices=[('hostel', 'Hostel'), ('location', 'Location'), ('gate', 'Gate')], null=True, blank=True) def __str__(self): - return self.name - - - + return self.name \ No newline at end of file diff --git a/apps/nightpass/static/styles.css b/apps/nightpass/static/styles.css new file mode 100644 index 0000000..f2ea7b0 --- /dev/null +++ b/apps/nightpass/static/styles.css @@ -0,0 +1,258 @@ +body { + font-family: Arial, sans-serif; + background-color: #f5f5f5; + margin: 0; + padding: 0; + line-height: 1.6; +} + +.header { + display: flex; + align-items: center; + 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 { + display: flex; + flex-wrap: nowrap; + justify-content: center; + align-items: center; +} + + +.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; +} + +.profile-card:hover, .resource-card:hover { + transform: scale(1.05); +} + +.profile-img { + width: 100px; + border-radius: 50%; + object-fit: cover; + margin-bottom: 20px; +} + +.qr-code { + width: 100px; + height: 100px; + margin-bottom: 20px; +} + +.profile-details { + font-size: 18px; + margin-bottom: 20px; + text-align: left; +} + +.profile-details p { + margin-bottom: 10px; +} + +.safe { + color: green; +} + +.unsafe { + color: red; +} + +.book-button { + background-color: #eb1b23; + color: #fff; + border: none; + padding: 10px 20px; + border-radius: 10px; + cursor: pointer; + transition: background-color 0.2s; +} + +.booked { + background-color: grey; +} + +.book-button:hover { + background-color: #ff3336; +} + +.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; + position: relative; + display: flex; +} + +.modal-image { + flex: 1; +} + +.modal-image img { + width: 100%; + height: auto; + object-fit: cover; +} + +.modal-details { + flex: 2; + padding: 10px; + text-align: left; +} + +/* Close button for the modal */ +.close { + position: absolute; + top: 10px; + right: 10px; + cursor: pointer; +} + +.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 { + 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/nightpass/templates/change_list.html b/apps/nightpass/templates/change_list.html new file mode 100644 index 0000000..f5617c3 --- /dev/null +++ b/apps/nightpass/templates/change_list.html @@ -0,0 +1,8 @@ +{% extends "admin/change_list.html" %} + +{% block object-tools %} + {{ block.super }} +
+ Refresh Slot Count +
+{% endblock %} diff --git a/apps/nightpass/templates/defaulter_email.html b/apps/nightpass/templates/defaulter_email.html new file mode 100644 index 0000000..9aebd84 --- /dev/null +++ b/apps/nightpass/templates/defaulter_email.html @@ -0,0 +1,105 @@ + + + + + + Default Warning + + + +
+

Thapar NightPass: Incident Logged

+

+ Dear {{ user_pass.user.student.name }}, +

+

+ This is to inform you that an incident has been recorded for your recent actions. Please review and address this matter promptly. Further incidents may lead to revocation of the NightPass facility. +

+

+ Incident Remark: {{ defaulter_remarks}} +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Date{{ user_pass.date }}
Location {{ user_pass.campus_resource.name }}
Hostel Check Out{{ user_pass.hostel_checkout_time }}
{{ user_pass.campus_resource.name }} Check In{{ user_pass.check_in_time }}
{{ user_pass.campus_resource.name }} Check Out{{ user_pass.check_out_time }}
Hostel Check In{{ user_pass.hostel_checkin_time }}
+

+ If you have any questions or concerns, please contact the Office of the Dean of Student Affairs. +

+

+ DoSA Office
+ TIET, Patiala +

+
+ + diff --git a/apps/nightpass/templates/lmao.html b/apps/nightpass/templates/lmao.html index 49fcef3..f189660 100644 --- a/apps/nightpass/templates/lmao.html +++ b/apps/nightpass/templates/lmao.html @@ -8,223 +8,7 @@ - +
@@ -264,7 +48,7 @@

Thapar NightPass

+ + diff --git a/apps/nightpass/views.py b/apps/nightpass/views.py index e7cc438..45d7b21 100644 --- a/apps/nightpass/views.py +++ b/apps/nightpass/views.py @@ -1,9 +1,8 @@ from django.shortcuts import render, HttpResponse, redirect -from django.contrib import messages from django.conf import settings from django.contrib.auth.decorators import login_required from django.views.decorators.csrf import csrf_exempt -from django.template.loader import render_to_string +from django.utils import timezone from .models import * from ..users.models import * import random @@ -13,9 +12,8 @@ import random, string from .models import * from ..users.views import * -from datetime import datetime -import json -from django.db.utils import IntegrityError +from datetime import datetime, date, timedelta + @login_required @@ -24,7 +22,8 @@ def campus_resources_home(request): user = request.user if user.user_type == 'student': user_pass = NightPass.objects.filter(user=user, check_out=False).first() - return render(request, 'lmao.html', {'user':user.student,'campus_resources':campus_resources, 'user_pass':user_pass}) + 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}) elif user.user_type == 'security': return redirect('/access') elif user.user_type == 'admin': @@ -49,83 +48,97 @@ def generate_pass(request, campus_resource): 'message':'No slots available!' } 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() + if campus_resource.slots_booked < campus_resource.max_capacity: + while True: + pass_id = ''.join(random.choices(string.ascii_uppercase + + string.digits, k=16)) - if not user.student.has_booked: - campus_resource.refresh_from_db() - if campus_resource.slots_booked < campus_resource.max_capacity: - while True: - pass_id = ''.join(random.choices(string.ascii_uppercase + - string.digits, k=16)) - - if not NightPass.objects.filter(pass_id=pass_id).count(): - break - generated_pass = NightPass(campus_resource=campus_resource, pass_id=pass_id, user=user , date=date.today(), start_time=datetime.now(), end_time=datetime.now()) - generated_pass.save() + if not NightPass.objects.filter(pass_id=pass_id).count(): + break + generated_pass = NightPass(campus_resource=campus_resource, pass_id=pass_id, user=user , date=date.today(), start_time=datetime.now(), end_time=datetime.now()) + generated_pass.save() - user.student.has_booked = True - campus_resource.slots_booked += 1 - user.student.save() - campus_resource.save() - data = { - 'pass_qr':None, - 'status':True, - 'message':f"Pass generated successfully for {campus_resource.name}!" - } - return HttpResponse(json.dumps(data)) - else: - data={ - 'status':False, - 'message':f"No more slots available for {campus_resource.name}!" - } - return HttpResponse(json.dumps(data)) - elif user.student.has_booked: - user_nightpass = NightPass.objects.filter(user=user, check_out = False).first() - if user_nightpass.check_in: + user.student.has_booked = True + campus_resource.slots_booked += 1 + user.student.save() + campus_resource.save() + data = { + 'pass_qr':None, + 'status':True, + 'message':f"Pass generated successfully for {campus_resource.name}!" + } + return HttpResponse(json.dumps(data)) + else: data={ 'status':False, - 'message':f"New slot can be booked once you exit {user_nightpass.campus_resource}." + 'message':f"No more slots available for {campus_resource.name}!" + } + return HttpResponse(json.dumps(data)) + elif user_pass.valid: + if user_pass.check_in: + data={ + 'status':False, + 'message':f"New slot can be booked once you exit {user_pass.campus_resource}." } return HttpResponse(json.dumps(data)) else: data={ 'status':False, - 'message':f"Cancel the booking for {user_nightpass.campus_resource} to book a new slot!" + 'message':f"Cancel the booking for {user_pass.campus_resource} to book a new slot!" } return HttpResponse(json.dumps(data)) + elif user_pass: + data={ + 'status':False, + 'message':f"Pass already generated for today!" + } + return HttpResponse(json.dumps(data)) @csrf_exempt @login_required def cancel_pass(request): user = request.user - user_nightpass = NightPass.objects.filter(user=user, check_in=False).first() - user_nightpass = user_nightpass if user_nightpass else NightPass.objects.filter(user=user).first() + user_nightpass = NightPass.objects.filter(user=user, valid=True).first() if not user_nightpass: data={ 'status':False, 'message':f"No pass to cancel!" } return HttpResponse(json.dumps(data)) - if user_nightpass.check_out and user_nightpass.check_in: - 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 - user_nightpass.campus_resource.save() - user.student.has_booked = False - user.student.save() - data={ - 'status':True, - 'message':f"Pass cancelled successfully!" - } - return HttpResponse(json.dumps(data)) + last_time = timezone.make_aware(datetime.combine(date.today(), time(20,00)), timezone.get_current_timezone()) + if timezone.now() > last_time: + data = { + 'status':False, + 'message':f"Cannot cancel pass after 8pm." + } + return HttpResponse(json.dumps(data)) + else: + if user_nightpass.check_out and user_nightpass.check_in: + 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 + user_nightpass.campus_resource.save() + user.student.has_booked = False + user.student.save() + data={ + 'status':True, + 'message':f"Pass cancelled successfully!" + } + return HttpResponse(json.dumps(data)) diff --git a/apps/users/admin.py b/apps/users/admin.py index 42325dc..a2da4bc 100644 --- a/apps/users/admin.py +++ b/apps/users/admin.py @@ -1,27 +1,99 @@ from django.contrib import admin from django.apps import apps +from django.http import HttpResponse +from django.utils import timezone +from django.utils.html import format_html +from rangefilter.filter import DateRangeFilter +from tablib import Dataset from .models import * from import_export.admin import ImportExportModelAdmin -from .resources import StudentResource +from .resources import * +from xlsxwriter import Workbook +from datetime import date, datetime +import io + class NightPassAdmin(admin.ModelAdmin): - list_display = ('pass_id', 'user','date', 'campus_resource', 'check_in', 'check_out') - search_fields = ('pass_id', 'user__name','user__registration_number', 'campus_resource__name') + list_display = ( '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') + 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): + return obj.user.student.name + + def hostel(self, obj): + return obj.user.student.hostel.name + + def hostel_check_out(self, obj): + return format_html('True') if obj.hostel_checkout_time is not None else format_html('False') + + def hostel_check_in(self, obj): + return format_html('True') if obj.hostel_checkin_time is not None else format_html('False') + + hostel_check_out.allow_tags = True + hostel_check_in.allow_tags = True + hostel_check_out.short_description = 'Hostel Out' + hostel_check_in.short_description = 'Hostel In' + + def export_as_xlsx(modeladmin, request, queryset): + headers = ['User', 'Email', 'Hostel', 'Gender','Pass ID', 'Date', 'Campus Resource', 'Check In', 'Check Out', 'Hostel Check Out Time', 'Check In Time', 'Check Out Time' ,'Hostel Check In Time', 'Defaulter', 'Remarks'] + data = [] + for obj in queryset: + data.append([obj.user.student.name, + obj.user.email , + obj.user.student.hostel.name, + obj.user.student.gender, + obj.pass_id, + obj.date.strftime('%d/%m/%y'), + obj.campus_resource.name, + obj.check_in, + obj.check_out, + timezone.localtime(obj.hostel_checkout_time).strftime('%H:%M:%S') if obj.hostel_checkout_time is not None else None, + timezone.localtime(obj.check_in_time).strftime('%H:%M:%S') if obj.check_in_time is not None else None, + timezone.localtime(obj.check_out_time).strftime('%H:%M:%S') if obj.check_out_time is not None else None, + timezone.localtime(obj.hostel_checkin_time).strftime('%H:%M:%S') if obj.hostel_checkin_time is not None else None, + obj.defaulter, + obj.defaulter_remarks]) + output = io.BytesIO() + wb = Workbook(output, {'in_memory': True, 'remove_timezone':True}) + ws = wb.add_worksheet() + + for col_num, header in enumerate(headers): + ws.write(0, col_num, header) + + for row_num, obj in enumerate(data, start=1): + for col_num, cell_value in enumerate(obj): + ws.write(row_num, col_num, cell_value) + wb.close() + output.seek(0) + response = HttpResponse(output.read(), content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + response['Content-Disposition'] = f'attachment; filename="nightpass_{date.today()}.xlsx"' + output.close() + + return response + export_as_xlsx.short_description = "Export Selected as XLSX" class StudentAdmin(ImportExportModelAdmin): list_display = ('name','registration_number', 'hostel', 'has_booked', 'violation_flags') search_fields = ('name','registration_number',) autocomplete_fields = ('user',) resource_class = StudentResource + readonly_fields = ('hostel_checkin_time', 'hostel_checkout_time', 'last_checkout_time',) + list_filter = ('hostel', 'has_booked', 'violation_flags') class SecurityAdmin(admin.ModelAdmin): list_display = ('name', 'admin_incharge', 'user') search_fields = ('name', 'admin_incharge', 'user') + autocomplete_fields = ('user',) class AdminAdmin(admin.ModelAdmin): list_display = ('name', 'user', 'designation', 'department', "staff_id") search_fields = ('name', 'user', 'designation', 'department', "staff_id") + autocomplete_fields = ('user',) class CustomUserAdmin(admin.ModelAdmin): list_display = ('email', 'user_type') diff --git a/apps/users/management/commands/check_defaulters.py b/apps/users/management/commands/check_defaulters.py new file mode 100644 index 0000000..f2f4a97 --- /dev/null +++ b/apps/users/management/commands/check_defaulters.py @@ -0,0 +1,58 @@ +from django.core.management.base import BaseCommand +from ...models import NightPass, Student +from datetime import date, timedelta, datetime, time +from django.utils import timezone + + +def check_defaulters(): + 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: + print(nightpass.user.email) + defaulter = False + remarks = "" + if not nightpass.check_in: + 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()) + 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): + defaulter = True + remarks+= f"Late check in at {nightpass.campus_resource.name}" + else: + if (nightpass.check_in_time > end_default_time): + defaulter = True + remarks+= f"Late check in at {nightpass.campus_resource.name}" + if not nightpass.check_out_time: + defaulter= True + remarks+= f"Left {nightpass.campus_resource.name} without checking out." + else: + 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): + defaulter = True + remarks+= "Late check in at hostel" + if (nightpass.check_out_time-nightpass.check_in_time) < timedelta(minutes=10): + defaulter = True + remarks+= f"Stayed for very less time at {nightpass.campus_resource.name}" + + if defaulter: + nightpass.defaulter = True + nightpass.defaulter_remarks = remarks + nightpass.save() + nightpass.user.student.violation_flags+=1 + nightpass.user.student.save() + + +class Command(BaseCommand): + help = 'Check for defaulters' + + def handle(self, *args, **options): + self.stdout.write(self.style.SUCCESS('Running your cron job...')) + check_defaulters() + self.stdout.write(self.style.SUCCESS('Cron job completed successfully')) + diff --git a/apps/users/management/commands/reset_nightpass.py b/apps/users/management/commands/reset_nightpass.py new file mode 100644 index 0000000..34dd84d --- /dev/null +++ b/apps/users/management/commands/reset_nightpass.py @@ -0,0 +1,11 @@ +from django.core.management.base import BaseCommand +from ...models import NightPass +from datetime import date, timedelta + +class Command(BaseCommand): + help = 'Reset all nightpasses' + + def handle(self, *args, **options): + self.stdout.write(self.style.SUCCESS('Running your cron job...')) + NightPass.objects.filter(date=date.today()-timedelta(days=1)).update(valid=False) + self.stdout.write(self.style.SUCCESS('Cron job completed successfully')) \ No newline at end of file diff --git a/apps/users/management/commands/reset_users.py b/apps/users/management/commands/reset_users.py new file mode 100644 index 0000000..d521c35 --- /dev/null +++ b/apps/users/management/commands/reset_users.py @@ -0,0 +1,10 @@ +from django.core.management.base import BaseCommand +from ...models import Student + +class Command(BaseCommand): + help = 'Reset all users' + + def handle(self, *args, **options): + self.stdout.write(self.style.SUCCESS('Running your cron job...')) + Student.objects.all().update(is_checked_in=True, last_checkout_time=None, hostel_checkin_time=None, hostel_checkout_time=None, has_booked=False) + self.stdout.write(self.style.SUCCESS('Cron job completed successfully')) \ No newline at end of file diff --git a/apps/users/migrations/0004_nightpass_defaulter_nightpass_defaulter_remarks.py b/apps/users/migrations/0004_nightpass_defaulter_nightpass_defaulter_remarks.py new file mode 100644 index 0000000..264e1e3 --- /dev/null +++ b/apps/users/migrations/0004_nightpass_defaulter_nightpass_defaulter_remarks.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.7 on 2023-12-03 01:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0003_customuser_unique_id_alter_student_address_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='nightpass', + name='defaulter', + field=models.BooleanField(blank=True, default=False, null=True), + ), + migrations.AddField( + model_name='nightpass', + name='defaulter_remarks', + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/apps/users/migrations/0005_alter_nightpass_pass_id.py b/apps/users/migrations/0005_alter_nightpass_pass_id.py new file mode 100644 index 0000000..d1befd5 --- /dev/null +++ b/apps/users/migrations/0005_alter_nightpass_pass_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.7 on 2023-12-03 06:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0004_nightpass_defaulter_nightpass_defaulter_remarks'), + ] + + operations = [ + migrations.AlterField( + model_name='nightpass', + name='pass_id', + field=models.CharField(editable=False, max_length=20, primary_key=True, serialize=False, unique=True), + ), + ] diff --git a/apps/users/migrations/0006_alter_nightpass_check_in_time_and_more.py b/apps/users/migrations/0006_alter_nightpass_check_in_time_and_more.py new file mode 100644 index 0000000..9a651ab --- /dev/null +++ b/apps/users/migrations/0006_alter_nightpass_check_in_time_and_more.py @@ -0,0 +1,48 @@ +# Generated by Django 4.2.7 on 2023-12-03 06:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0005_alter_nightpass_pass_id'), + ] + + operations = [ + migrations.AlterField( + model_name='nightpass', + name='check_in_time', + field=models.DateTimeField(blank=True, editable=False, null=True), + ), + migrations.AlterField( + model_name='nightpass', + name='check_out_time', + field=models.DateTimeField(blank=True, editable=False, null=True), + ), + migrations.AlterField( + model_name='nightpass', + name='hostel_checkin_time', + field=models.DateTimeField(blank=True, editable=False, null=True), + ), + migrations.AlterField( + model_name='nightpass', + name='hostel_checkout_time', + field=models.DateTimeField(blank=True, editable=False, null=True), + ), + migrations.AlterField( + model_name='student', + name='hostel_checkin_time', + field=models.DateTimeField(blank=True, editable=False, null=True), + ), + migrations.AlterField( + model_name='student', + name='hostel_checkout_time', + field=models.DateTimeField(blank=True, editable=False, null=True), + ), + migrations.AlterField( + model_name='student', + name='last_checkout_time', + field=models.DateTimeField(blank=True, editable=False, null=True), + ), + ] diff --git a/apps/users/migrations/0007_alter_nightpass_check_in_alter_nightpass_check_out_and_more.py b/apps/users/migrations/0007_alter_nightpass_check_in_alter_nightpass_check_out_and_more.py new file mode 100644 index 0000000..c1dda27 --- /dev/null +++ b/apps/users/migrations/0007_alter_nightpass_check_in_alter_nightpass_check_out_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.7 on 2023-12-03 06:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0006_alter_nightpass_check_in_time_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='nightpass', + name='check_in', + field=models.BooleanField(default=False, editable=False), + ), + migrations.AlterField( + model_name='nightpass', + name='check_out', + field=models.BooleanField(default=False, editable=False), + ), + migrations.AlterField( + model_name='nightpass', + name='date', + field=models.DateField(editable=False), + ), + migrations.AlterField( + model_name='nightpass', + name='valid', + field=models.BooleanField(default=True, editable=False), + ), + ] diff --git a/apps/users/migrations/0008_student_gender_alter_nightpass_valid.py b/apps/users/migrations/0008_student_gender_alter_nightpass_valid.py new file mode 100644 index 0000000..10fc460 --- /dev/null +++ b/apps/users/migrations/0008_student_gender_alter_nightpass_valid.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.7 on 2023-12-06 13:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0007_alter_nightpass_check_in_alter_nightpass_check_out_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='student', + name='gender', + field=models.CharField(blank=True, choices=[('male', 'Male'), ('female', 'Female')], max_length=10, null=True), + ), + migrations.AlterField( + model_name='nightpass', + name='valid', + field=models.BooleanField(default=True), + ), + ] diff --git a/apps/users/models.py b/apps/users/models.py index 533a3cf..9841238 100644 --- a/apps/users/models.py +++ b/apps/users/models.py @@ -90,6 +90,7 @@ class Student(models.Model): registration_number = models.CharField(max_length=20, unique=True) branch = models.CharField(max_length=50, null=True, blank=True) date_of_birth = models.DateField(null=True, blank=True) + gender = models.CharField(max_length=10, null=True, blank=True, choices=(('male','Male'), ('female','Female'))) father_name = models.CharField(max_length=100, null=True, blank=True) mother_name = models.CharField(max_length=100, null=True, blank=True) course = models.CharField(max_length=50, null=True, blank=True) @@ -101,9 +102,9 @@ class Student(models.Model): room_number = models.CharField(max_length=10, null=True, blank=True) has_booked = models.BooleanField(default=False) is_checked_in = models.BooleanField(default=True) - hostel_checkout_time = models.DateTimeField(blank=True, null=True) - hostel_checkin_time = models.DateTimeField(blank=True, null=True) - last_checkout_time = models.DateTimeField(blank=True, null=True) + hostel_checkout_time = models.DateTimeField(blank=True, null=True, editable=False) + hostel_checkin_time = models.DateTimeField(blank=True, null=True, editable=False) + last_checkout_time = models.DateTimeField(blank=True, null=True, editable=False) violation_flags = models.IntegerField(default=0) user = models.OneToOneField(CustomUser, on_delete=models.CASCADE, primary_key=True) email = models.EmailField(max_length=100, blank=True, null=True) @@ -125,18 +126,20 @@ def __str__(self): class NightPass(models.Model): user = models.ForeignKey(CustomUser, on_delete=models.CASCADE) - pass_id = models.CharField(max_length=20, unique=True, primary_key=True) + pass_id = models.CharField(max_length=20, unique=True, primary_key=True, editable=False) start_time = models.TimeField() end_time = models.TimeField() - date = models.DateField() + date = models.DateField(editable=False) campus_resource = models.ForeignKey(CampusResource, on_delete=models.CASCADE) - check_in = models.BooleanField(default=False) - check_out = models.BooleanField(default=False) - check_in_time = models.DateTimeField(blank=True, null=True) - check_out_time = models.DateTimeField(blank=True, null=True) - hostel_checkin_time = models.DateTimeField(blank=True, null=True) - hostel_checkout_time = models.DateTimeField(blank=True, null=True) + check_in = models.BooleanField(default=False, editable=False) + check_out = models.BooleanField(default=False, editable=False) + check_in_time = models.DateTimeField(blank=True, null=True, editable=False) + check_out_time = models.DateTimeField(blank=True, null=True, editable=False) + hostel_checkin_time = models.DateTimeField(blank=True, null=True, editable=False) + hostel_checkout_time = models.DateTimeField(blank=True, null=True, editable=False) valid = models.BooleanField(default=True) + defaulter = models.BooleanField(default=False, blank=True, null=True) + defaulter_remarks = models.TextField(blank=True, null=True) def __str__(self): return self.pass_id \ No newline at end of file diff --git a/apps/users/resources.py b/apps/users/resources.py index e324672..9d017ca 100644 --- a/apps/users/resources.py +++ b/apps/users/resources.py @@ -4,7 +4,7 @@ class StudentResource(resources.ModelResource): class Meta: model = Student - fields = ('name', 'contact_number', 'registration_number', 'branch', 'date_of_birth', 'father_name', 'mother_name', 'course', 'semester', 'parent_contact', 'address', 'picture', 'hostel', 'room_number', 'email') + fields = ('name', 'contact_number', 'registration_number','gender', 'branch', 'date_of_birth', 'father_name', 'mother_name', 'course', 'semester', 'parent_contact', 'address', 'picture', 'hostel', 'room_number', 'email') def save_instance(self, instance, is_create, using_transactions=True, dry_run=False): user = CustomUser.objects.filter(email=instance.email).first() @@ -13,6 +13,8 @@ def save_instance(self, instance, is_create, using_transactions=True, dry_run=Fa return super().save_instance(instance, is_create, using_transactions, dry_run) else: if not user.student: + instance.registration_number = int(instance.registration_number) + instance.semester = int(instance.semester) instance.user = user return super().save_instance(instance, is_create, using_transactions, dry_run) diff --git a/apps/users/static/login.css b/apps/users/static/login.css new file mode 100644 index 0000000..d7a23f7 --- /dev/null +++ b/apps/users/static/login.css @@ -0,0 +1,98 @@ +body { + background-color: #940d0d; + font-family: Arial, sans-serif; + margin: 0; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +} + +.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); +} + +.logo { + text-align: center; +} + +.logo img { + height: 100px; +} + +.login-box { + text-align: center; +} + +.login-box h2 { + margin-bottom: 20px; +} + +.container form { + display: flex; + flex-direction: column; +} + +.container input { + padding: 10px; + margin-bottom: 10px; + border: 1px solid #ccc; + border-radius: 4px; +} + +.container button { + padding: 10px; + background-color: #940d0d; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.container button:hover { + background-color: #ed0505; +} + +/* "activate-account" link */ +.activate-account { + text-align: center; + margin-top: 15px; +} + +.activate-account a { + color: #7240ce; + text-decoration: none; +} + +.activate-account a:hover { + text-decoration: underline; +} + +/* Animation */ +.container { + animation: fadeIn 1s ease; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Responsive styles */ +@media screen and (max-width: 500px) { + .container { + max-width: 90%; + width: 70%; + } +} diff --git a/apps/users/templates/login.html b/apps/users/templates/login.html index 8a3fcf3..2b63741 100644 --- a/apps/users/templates/login.html +++ b/apps/users/templates/login.html @@ -164,21 +164,21 @@ // spinbox.style.display = "block"; // or "inline-block" depending on your layout }); toastr.options = { - "closeButton": true, + "closeButton": false, "newestOnTop": true, - "progressBar": true, - "positionClass": "toast-top-right", - "preventDuplicates": false, + "progressBar": false, + "positionClass": "toast-bottom-full-width", + "preventDuplicates": true, "onclick": null, "showDuration": "300", "hideDuration": "1000", - "timeOut": "7000", + "timeOut": "3000", "extendedTimeOut": "1000", "showEasing": "swing", "hideEasing": "linear", "showMethod": "fadeIn", "hideMethod": "fadeOut" - } + } {% if messages %} {% for message in messages %} + + + + + +
+ +

Thapar NightPass

+ +
+ +
+
+
+ + {% if data.status %} + + Student Picture +
+

{{ data.user.name }}

+

Registration Number: {{ data.user.registration_number }}

+

Hostel: {{ data.user.hostel }}

+

Hostel Check-out Time: {{ data.user.hostel_checkout_time }}

+

Access: {{ data.user_pass.campus_resource }}

+

Check-in: {{ data.user_pass.check_in }}

+

Entry Time: {{ data.user_pass.check_in_time }}

+

Check-out: {{ data.user_pass.check_out }}

+

Exit Time: {{ data.user_pass.check_out_time }}

+
+ {% else %} + Profile Picture + {% endif %} +
+
+
+ + diff --git a/apps/validation/templates/info.html b/apps/validation/templates/info.html index 8cf40c3..838fecc 100644 --- a/apps/validation/templates/info.html +++ b/apps/validation/templates/info.html @@ -38,8 +38,8 @@

User Pass Information

-

QR Code Scanner

- + +
+ {% if request.user.security.campus_resource %} +
+

+ {{ request.user.security.campus_resource }} +

+

Students: {{ check_in_count }}


+

Booking: {{ total_count }}


+
+ {% endif %} + diff --git a/apps/validation/urls.py b/apps/validation/urls.py index 3cf5525..81a3b0d 100644 --- a/apps/validation/urls.py +++ b/apps/validation/urls.py @@ -6,4 +6,5 @@ path('', scanner), path('checkin/', check_in), path('checkout/', check_out), + path('extension/fetchuser/performtask/', kiosk_extension) ] diff --git a/apps/validation/views.py b/apps/validation/views.py index adad950..0271d76 100644 --- a/apps/validation/views.py +++ b/apps/validation/views.py @@ -4,8 +4,9 @@ from django.views.decorators.csrf import csrf_exempt from django.contrib.auth.decorators import login_required import json -from datetime import datetime - +from datetime import datetime, date, timedelta +import requests +from django.utils import timezone # Create your views here. def fetch_user_status(request): @@ -70,19 +71,20 @@ def fetch_user_status(request): 'check_in':True if not user.is_checked_in else False, 'check_out':True if user.is_checked_in else False } + data['request_user_location'] = 'hostel' return HttpResponse(json.dumps(data)) elif type(admin_campus_resource) == CampusResource and admin_campus_resource == (user_pass.campus_resource if user_pass else None): data['task'] = { 'check_in':True if not user_pass.check_in else False, 'check_out':True if (not user_pass.check_out and user_pass.check_in) else False } + data['request_user_location'] = 'campus_resource' return HttpResponse(json.dumps(data)) else: data = { 'status':False, 'message':f'Pass for {admin_campus_resource.name} does not exist!' } - return HttpResponse(json.dumps(data)) except Student.DoesNotExist: @@ -91,108 +93,147 @@ def fetch_user_status(request): 'message':'Invalid!' } return HttpResponse(json.dumps(data)) + + +def req_library_logs(registration_number): + req = requests.session() + url = "https://library.thapar.edu/inout/login_verify.php" + data = { + "name": "user", + "pass": "$#**123", + "loc": "TESTLIB", + "submit": "Login" + } + response = req.post(url, data=data, verify=False) + req.get(f"https://library.thapar.edu/inout/user.php?id={registration_number}") + req.close() + @csrf_exempt @login_required def check_out(request): - if request.method == 'POST': - data = request.POST - try: - user = Student.objects.get(registration_number=data['registration_number']) - user_pass = NightPass.objects.filter(user=user.user, valid=True).first() - admin_campus_resource = request.user.security.campus_resource if request.user.security.campus_resource else request.user.security.hostel - if not user_pass: + if request.user.is_staff: + if request.method == 'POST': + data = request.POST + try: + user = Student.objects.get(registration_number=data['registration_number']) + user_pass = NightPass.objects.filter(user=user.user, valid=True).first() + admin_campus_resource = request.user.security.campus_resource if request.user.security.campus_resource else request.user.security.hostel + if not user_pass: + data = { + 'status':False, + 'message':'Pass does not exist!' + } + return HttpResponse(json.dumps(data)) + if type(admin_campus_resource) == Hostel: + return checkout_from_hostel(user_pass) + elif type(admin_campus_resource) == CampusResource: + if admin_campus_resource.name == 'Library': + req_library_logs(user.registration_number) + return checkout_from_location(user_pass, admin_campus_resource) + except Student.DoesNotExist: data = { - 'status':False, - 'message':'Pass does not exist!' - } + 'status':False, + 'message':'Invalid!' + } return HttpResponse(json.dumps(data)) - if type(admin_campus_resource) == Hostel: - return checkout_from_hostel(user_pass) - elif type(admin_campus_resource) == CampusResource: - return checkout_from_location(user_pass) - except Student.DoesNotExist: - data = { - 'status':False, - 'message':'Invalid!' - } - return HttpResponse(json.dumps(data)) else: return HttpResponse('Invalid Operation') -def checkout_from_hostel(user_pass:NightPass): +def checkout_from_hostel(user_pass:NightPass, direct:bool=True): user = user_pass.user user.student.is_checked_in = False - user.student.hostel_checkout_time = user_pass.hostel_checkout_time = datetime.now() - user.student.last_checkout_time = datetime.now() + user.student.hostel_checkout_time = user_pass.hostel_checkout_time = datetime.now() if direct else None + user.student.last_checkout_time = datetime.now() if direct else None user.student.save() user_pass.save() data = { 'status':True, 'message':'Successfully checked out!' } + data['student_stats'] = None return HttpResponse(json.dumps(data)) -def checkout_from_location(user_pass): +def checkout_from_location(user_pass, admin_campus_resource:CampusResource=None ,direct:bool=True, ): user = user_pass.user - user.student.last_checkout_time = datetime.now() - user.student.has_booked = False - user.student.save() - user_pass.check_out = True - user_pass.check_out_time = datetime.now() - user_pass.save() - data = { - 'status':True, - 'message':'Successfully checked out!' - } + if not is_repeated_scan(user_pass): + user.student.last_checkout_time = datetime.now() if direct else None + user.student.has_booked = False + user.student.save() + user_pass.check_out = True if direct else False + user_pass.check_out_time = datetime.now() if direct else None + user_pass.save() + data = { + 'status':True, + 'message':'Successfully checked out!' + } + data['student_stats'] = { + 'check_in_count':NightPass.objects.filter(check_in=True, check_out=False,valid=True, date=date.today(), campus_resource=admin_campus_resource).count(), + 'total_count':NightPass.objects.filter(valid=True, date=date.today(), campus_resource=admin_campus_resource).count() + } + else: + data = { + 'status':False, + 'message':'You just checked in! Please wait for 10mins before checking out' + } return HttpResponse(json.dumps(data)) @csrf_exempt @login_required def check_in(request): - if request.method == 'POST': - data = request.POST - try: - user = Student.objects.get(registration_number=data['registration_number']) - user_pass = NightPass.objects.filter(user=user.user, valid=True).first() - admin_campus_resource = request.user.security.campus_resource if request.user.security.campus_resource else request.user.security.hostel - - if type(admin_campus_resource) == Hostel: - return checkin_to_hostel(user) - elif type(admin_campus_resource) == CampusResource: - if not user_pass: - data = { + if request.user.is_staff: + if request.method == 'POST': + data = request.POST + try: + user = Student.objects.get(registration_number=data['registration_number']) + user_pass = NightPass.objects.filter(user=user.user, valid=True).first() + admin_campus_resource = request.user.security.campus_resource if request.user.security.campus_resource else request.user.security.hostel + if type(admin_campus_resource) == Hostel: + return checkin_to_hostel(user) + elif type(admin_campus_resource) == CampusResource: + if not user_pass: + data = { + 'status':False, + 'message':'Pass does not exist!' + } + return HttpResponse(json.dumps(data)) + if admin_campus_resource.name == 'Library': + req_library_logs(user.registration_number) + return checkin_to_location(user_pass, admin_campus_resource) + except Student.DoesNotExist: + data = { 'status':False, - 'message':'Pass does not exist!' - } - return HttpResponse(json.dumps(data)) - return checkin_to_location(user_pass) - except Student.DoesNotExist: - data = { - 'status':False, - 'message':'Invalid!' - } - return HttpResponse(json.dumps(data)) + 'message':'Invalid!' + } + return HttpResponse(json.dumps(data)) else: return HttpResponse('Invalid Operation') def checkin_to_hostel(user:Student): if not user.is_checked_in: user_pass = NightPass.objects.filter(user=user.user, valid=True).first() - user.is_checked_in = True - user.hostel_checkin_time = user_pass.hostel_checkin_time= datetime.now() - user.save() - user_pass.valid = False - user_pass.save() - if (user_pass.user.student.is_checked_in if user_pass else False): - checkout_from_location(user_pass) - data = { - 'status':True, - 'message':'Successfully checked in!' - } + if not is_repeated_scan(user_pass): + user.is_checked_in = True + user.hostel_checkin_time = user_pass.hostel_checkin_time= datetime.now() + user.save() + user_pass.valid = False + user_pass.save() + if (not user_pass.check_out if user_pass else False): + checkout_from_location(user_pass,admin_campus_resource=user_pass.campus_resource ,direct=False) + data = { + 'status':True, + 'message':'Successfully checked in!' + } + data['student_stats'] = None - return HttpResponse(json.dumps(data)) + return HttpResponse(json.dumps(data)) + else: + data = { + 'status':False, + 'message':'You just checked out! Please wait for 10mins before checking in again' + } + return HttpResponse(json.dumps(data)) else: data = { 'status':False, @@ -200,10 +241,10 @@ def checkin_to_hostel(user:Student): } return HttpResponse(json.dumps(data)) -def checkin_to_location(user_pass): +def checkin_to_location(user_pass, admin_campus_resource:CampusResource): user = user_pass.user if user.student.is_checked_in: - checkout_from_hostel(user_pass) + checkout_from_hostel(user_pass, direct=False) user_pass.check_in = True user_pass.check_in_time = datetime.now() user_pass.save() @@ -211,6 +252,10 @@ def checkin_to_location(user_pass): 'status':True, 'message':'Successfully checked in!' } + data['student_stats'] = { + 'check_in_count':NightPass.objects.filter(check_in=True, check_out=False,valid=True, date=date.today(), campus_resource=admin_campus_resource).count(), + 'total_count':NightPass.objects.filter(valid=True, date=date.today(), campus_resource=admin_campus_resource).count() + } return HttpResponse(json.dumps(data)) @csrf_exempt @@ -219,12 +264,86 @@ def scanner(request): if request.method == "POST": return fetch_user_status(request) if request.user.is_staff: - if request.iOS: - return render(request, 'info.html') - else: - return render(request, 'info.html') + if request.user.security.campus_resource: + check_in_count = NightPass.objects.filter(check_in=True, check_out=False,valid=True, date=date.today(), campus_resource=request.user.security.campus_resource).count() + total_count = NightPass.objects.filter(valid=True, date=date.today(), campus_resource=request.user.security.campus_resource).count() + return render(request, 'info.html', {'check_in_count':check_in_count, 'total_count':total_count}) + elif request.user.security.hostel: + return render(request, 'info.html', {'check_in_count':None, 'total_count':None}) else: return HttpResponse('Invalid Operation') +def is_repeated_scan(user_pass:NightPass): + if (user_pass.check_in and not user_pass.check_out) and (timezone.make_aware(datetime.now(), timezone.get_current_timezone())-user_pass.check_in_time> /var/log/cron.log 2>&1 +21 7 * * * /usr/local/bin/python /app/manage.py reset_nightpass >> /var/log/cron.log 2>&1 +21 7 * * * /usr/local/bin/python /app/manage.py reset_campus_resources >> /var/log/cron.log 2>&1 +21 7 * * * /usr/local/bin/python /app/manage.py stop_booking >> /var/log/cron.log 2>&1 +21 7 * * * /usr/local/bin/python /app/manage.py check_defaulters >> /var/log/cron.log 2>&1 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..14d06d0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +version: '3.4' + +services: + campus_nightpass: + image: tiet/campus_nightpass + build: + context: . + dockerfile: ./Dockerfile + ports: + - "4376:4376" + volumes: + - ./data:/app/data + - ./static:/app/static + restart: always + # command: "gunicorn -b 0.0.0.0:4376 core.wsgi:application" diff --git a/requirements.txt b/requirements.txt index 0c2ac83..220bfdb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,8 @@ django-detect dj-database-url django-import-export django-crontab -uuid \ No newline at end of file +uuid +tablib +XlsxWriter +django-admin-rangefilter +pytz \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..1ed22fd --- /dev/null +++ b/run.sh @@ -0,0 +1,12 @@ +python manage.py makemigrations +python manage.py migrate +python manage.py collectstatic --noinput +# service cron start +# touch /var/log/cron.log +# mv /workspace/conf/root /var/spool/cron/crontabs/root +# chmod +x /var/spool/cron/crontabs/root +# crontab /var/spool/cron/crontabs/root +# echo ">>> Done!" + +# tail -f /var/log/cron.log +gunicorn -b 0.0.0.0:4376 core.wsgi:application \ No newline at end of file