Skip to content

Commit 99fadc2

Browse files
committed
fix: qr code display and mechanics
1 parent 465d0fe commit 99fadc2

File tree

14 files changed

+343
-166
lines changed

14 files changed

+343
-166
lines changed

intranet/apps/dashboard/views.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from ..eighth.models import EighthBlock, EighthScheduledActivity, EighthSignup, EighthSponsor
2323
from ..enrichment.models import EnrichmentActivity
2424
from ..events.models import Event, TJStarUUIDMap
25-
from ..schedule.models import Day
25+
from ..schedule.models import Day, DayType
2626
from ..schedule.views import decode_date, schedule_context
2727
from ..seniors.models import Senior
2828

@@ -684,6 +684,14 @@ def dashboard_view(request, show_widgets=True, show_expired=False, show_hidden_c
684684
self_awaiting_teacher = AnnouncementRequest.objects.filter(posted=None, rejected=False, teachers_requested=request.user).this_year()
685685
context.update({"self_awaiting_teacher": self_awaiting_teacher})
686686

687+
try:
688+
daytype = Day.objects.select_related("day_type").get(date=now).day_type # For checking whether to display eighth attendance code for students
689+
context.update({"attendance_open": daytype.eighth_auto_open <= now.time() <= daytype.eighth_auto_close})
690+
except Day.DoesNotExist:
691+
context.update({"attendance_open": False})
692+
except DayType.DoesNotExist:
693+
context.update({"attendance_open": False})
694+
687695
if show_welcome:
688696
return render(request, "welcome/student.html", context)
689697
else:

intranet/apps/eighth/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1843,7 +1843,7 @@ def accept_pass(self):
18431843
self.was_absent = False
18441844
self.pass_accepted = True
18451845
self.attendance_marked = True
1846-
self.save(update_fields=["was_absent", "pass_accepted"])
1846+
self.save(update_fields=["was_absent", "pass_accepted", "attendance_marked"])
18471847

18481848
def reject_pass(self):
18491849
"""Rejects an eighth period pass for the EighthSignup object."""

intranet/apps/eighth/urls.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
re_path(r"^/absences$", attendance.eighth_absences_view, name="eighth_absences"),
2121
re_path(r"^/absences/(?P<user_id>\d+)$", attendance.eighth_absences_view, name="eighth_absences"),
2222
re_path(r"^/glance$", signup.eighth_location, name="eighth_location"),
23-
re_path(r"^/student_attendance$", attendance.student_attendance_view, name="student_attendance"),
24-
re_path(r"^/qr/(?P<act_id>\w+)/(?P<code>\w+)$", attendance.qr_attendance_view, name="qr_attendance"),
23+
re_path(r"^/student_attendance(?:/(?P<act_id>\d+))?(?:/(?P<code>[A-Za-z0-9]+))?$", attendance.student_attendance_view, name="student_attendance"),
2524
# Teachers
2625
re_path(r"^/attendance$", attendance.teacher_choose_scheduled_activity_view, name="eighth_attendance_choose_scheduled_activity"),
2726
re_path(r"^/attendance/(?P<scheduled_activity_id>\d+)$", attendance.take_attendance_view, name="eighth_take_attendance"),

intranet/apps/eighth/views/attendance.py

Lines changed: 117 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from django.contrib import messages
1111
from django.contrib.auth import get_user_model
1212
from django.contrib.auth.decorators import login_required
13-
from django.db.models import Q
13+
from django.db.models import Q, QuerySet
1414
from django.http import Http404
1515
from django.shortcuts import get_object_or_404, redirect, render
1616
from django.urls import reverse
@@ -23,17 +23,17 @@
2323
from reportlab.lib.units import inch
2424
from reportlab.platypus import PageBreak, Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle
2525

26+
from ....settings.__init__ import ATTENDANCE_CODE_BUFFER
2627
from ....utils.date import get_date_range_this_year
2728
from ...auth.decorators import attendance_taker_required, deny_restricted, eighth_admin_required
2829
from ...dashboard.views import gen_sponsor_schedule
29-
from ...schedule.models import Day
30+
from ...schedule.models import Block, Day, shift_time
3031
from ...schedule.views import decode_date
3132
from ..forms.admin.activities import ActivitySelectionForm
3233
from ..forms.admin.blocks import BlockSelectionForm
3334
from ..models import EighthActivity, EighthBlock, EighthScheduledActivity, EighthSignup, EighthSponsor, EighthWaitlist
3435
from ..tasks import email_scheduled_activity_students_task
3536
from ..utils import get_start_date
36-
from .signup import shift_time
3737

3838
logger = logging.getLogger(__name__)
3939

@@ -420,6 +420,21 @@ def take_attendance_view(request, scheduled_activity_id):
420420

421421
members.sort(key=lambda m: m["name"])
422422

423+
auto_time_string = ""
424+
try:
425+
day_block = (
426+
Day.objects.select_related("day_type")
427+
.get(date=scheduled_activity.block.date)
428+
.day_type.blocks.get(name="8" + scheduled_activity.block.block_letter)
429+
)
430+
start_time = shift_time(tm(hour=day_block.start.hour, minute=day_block.start.minute), -ATTENDANCE_CODE_BUFFER)
431+
end_time = shift_time(tm(hour=day_block.end.hour, minute=day_block.end.minute), ATTENDANCE_CODE_BUFFER)
432+
auto_time_string = "(" + start_time.strftime("%H:%M") + " - " + end_time.strftime("%H:%M") + ")"
433+
except Day.DoesNotExist:
434+
auto_time_string = ""
435+
except Block.DoesNotExist:
436+
auto_time_string = ""
437+
print(auto_time_string)
423438
context = {
424439
"scheduled_activity": scheduled_activity,
425440
"passes": passes,
@@ -430,7 +445,8 @@ def take_attendance_view(request, scheduled_activity_id):
430445
"show_checkboxes": (scheduled_activity.block.locked or request.user.is_eighth_admin),
431446
"show_icons": (scheduled_activity.block.locked and scheduled_activity.block.attendance_locked() and not request.user.is_eighth_admin),
432447
"bbcu_script": settings.BBCU_SCRIPT,
433-
"qr_url": request.build_absolute_uri(reverse("qr_attendance", args=[scheduled_activity.id, scheduled_activity.attendance_code])),
448+
"qr_url": request.build_absolute_uri(reverse("student_attendance", args=[scheduled_activity.id, scheduled_activity.attendance_code])),
449+
"auto_time_string": auto_time_string,
434450
}
435451

436452
if request.user.is_eighth_admin:
@@ -776,67 +792,120 @@ def email_students_view(request, scheduled_activity_id):
776792

777793
@login_required
778794
@deny_restricted
779-
def student_attendance_view(request):
795+
def student_attendance_view(request, act_id=None, code=None):
796+
"""Marks attendance for code and QR code student-based attendance processes. Renders the student attendance page.
797+
Also handles GET requests from students accessing the student attendance page.
798+
Args:
799+
request: the user's request
800+
act_id: (For QR code attendance) the id of the act for which the student is attempting to take attendance
801+
code: (For QR code attendance) the code which the student is attempting to take attendance with
802+
Returns:
803+
An HttpResponse rendering the student attendance view (built from the student_frontend function), possibly with messages
804+
regarding the result of the attendance-marking attempt
805+
Can also possibly redirect to index or the generic student_attendance page if encountered an error
806+
"""
780807
blocks = EighthBlock.objects.get_blocks_today()
781808
mark_block = None
782809
mark_result = None
783-
if request.method == "POST":
784-
now = timezone.localtime()
785-
try:
786-
day_blocks = Day.objects.select_related("day_type").get(date=now).day_type.blocks
787-
except Day.DoesNotExist:
788-
messages.error(request, "Error. Attendance is only available on school days.")
789-
return redirect("index")
810+
qr_mark = False
811+
now = timezone.localtime()
812+
try:
813+
day_blocks = Day.objects.select_related("day_type").get(date=now).day_type.blocks
814+
except Day.DoesNotExist:
815+
messages.error(request, "Error. Attendance is only available on school days.")
816+
return redirect("index")
817+
if act_id is None and code is None:
790818
for block in blocks:
791819
block_letter = block.block_letter
792820
code = request.POST.get(block_letter)
793821
if code is None:
794822
continue
795823
act = request.user.eighthscheduledactivity_set.get(block=block)
796-
if act.get_code_mode_display() == "Auto":
797-
try:
798-
day_block = day_blocks.get(name="8" + block_letter)
799-
except Exception:
800-
mark_result = "invalid_time"
801-
mark_block = block
802-
break
803-
start_time = shift_time(tm(hour=day_block.start.hour, minute=day_block.start.minute), -20)
804-
end_time = shift_time(tm(hour=day_block.end.hour, minute=day_block.end.minute), 20)
805-
if not start_time <= now.time() <= end_time:
806-
mark_result = "invalid_time"
807-
mark_block = block
808-
break
809-
elif act.get_code_mode_display() == "Closed":
810-
mark_result = "code_closed"
824+
result = check_attendance_open(act, day_blocks)
825+
if result is not None:
811826
mark_block = block
812-
break
813-
code = code.upper()
814-
if code == act.attendance_code:
815-
try:
816-
present = EighthSignup.objects.get(scheduled_activity=act, user__in=[request.user.id])
817-
present.was_absent = False
818-
present.attendance_marked = True
819-
present.save()
820-
invalidate_obj(present)
821-
act.attendance_taken = True
822-
act.save()
823-
invalidate_obj(act)
824-
mark_result = "code_correct"
825-
mark_block = block
826-
except Exception:
827-
mark_result = "code_fail"
828-
mark_block = block
829-
break
830-
else:
831-
mark_result = "code_fail"
827+
mark_result = result
828+
elif act_id is not None and code is not None:
829+
try:
830+
act = request.user.eighthscheduledactivity_set.get(id=act_id)
831+
block = act.block
832+
result = check_attendance_open(act, day_blocks)
833+
qr_mark = True
834+
if result is not None:
832835
mark_block = block
833-
break
836+
mark_result = result
837+
except EighthScheduledActivity.DoesNotExist:
838+
messages.error(request, "Error marking attendance.")
839+
return redirect("student_attendance")
840+
if code is not None and mark_result is None:
841+
mark_result = mark_attendance(request, act, code)
842+
mark_block = block
843+
if qr_mark:
844+
if mark_result != "code_correct":
845+
messages.error(request, "Error marking attendance.")
846+
return redirect("student_attendance")
834847
return student_frontend(request, mark_block, mark_result)
835848

836849

850+
def mark_attendance(request, act, code):
851+
"""Handles the process of checking the code and marking attedance for the user.
852+
Args:
853+
request: the user's request
854+
act: the EighthScheduledActivity for which the user attempts to take attendance
855+
code: the code which the user is attempting to take attendance
856+
Returns:
857+
A string reporting the result of the attempt. Either "code_correct" or "code_fail"
858+
"""
859+
if code.upper() == act.attendance_code:
860+
try:
861+
present = EighthSignup.objects.get(scheduled_activity=act, user__in=[request.user.id])
862+
present.was_absent = False
863+
present.attendance_marked = True
864+
present.save()
865+
invalidate_obj(present)
866+
act.attendance_taken = True
867+
act.save()
868+
invalidate_obj(act)
869+
return "code_correct"
870+
except EighthSignup.DoesNotExist:
871+
return "code_fail"
872+
return "code_fail"
873+
874+
875+
def check_attendance_open(act: EighthScheduledActivity, day_blocks: QuerySet[Block]):
876+
"""Checks whether attendance is open for an EighthScheduledActivity.
877+
Args:
878+
act: the EighthScheduledActivity in question
879+
day_blocks: the set of Block objects in the current day's day_type (defined in student_attendance_view)
880+
Returns:
881+
mark_result: a string, either "invalid_time" or "code_closed", or None if attendance is open.
882+
"""
883+
block = act.block
884+
mark_result = None
885+
if act.get_code_mode_display() == "Auto":
886+
try:
887+
day_block = day_blocks.get(name="8" + block.block_letter)
888+
except Block.DoesNotExist:
889+
mark_result = "invalid_time"
890+
start_time = shift_time(tm(hour=day_block.start.hour, minute=day_block.start.minute), -ATTENDANCE_CODE_BUFFER)
891+
end_time = shift_time(tm(hour=day_block.end.hour, minute=day_block.end.minute), ATTENDANCE_CODE_BUFFER)
892+
if not start_time <= timezone.localtime().time() <= end_time:
893+
mark_result = "invalid_time"
894+
elif act.get_code_mode_display() == "Closed":
895+
mark_result = "code_closed"
896+
return mark_result
897+
898+
837899
@login_required
838900
@deny_restricted
839901
def student_frontend(request, mark_block: EighthBlock = None, mark_result: str = None):
902+
"""Generates the student frontend view for their eighth period blocks.
903+
Args:
904+
request: the user's HttpRequest, mark_block: the block for which the system attempted to mark the student's attendance,
905+
mark_result: the result of that attendance marking attempt ("invalid time", "code_closed", "code_fail", or "code_correct")
906+
Returns:
907+
An HttpResponse rendering the student frontend using the student_submit_attendance.html template, or redirecting to index.
908+
"""
840909
blocks = EighthBlock.objects.get_blocks_today()
841910
if blocks:
842911
sch_acts = []
@@ -868,51 +937,3 @@ def student_frontend(request, mark_block: EighthBlock = None, mark_result: str =
868937
messages.error(request, "There are no eighth period blocks scheduled today.")
869938
response = redirect("index")
870939
return response
871-
872-
873-
@login_required
874-
@deny_restricted
875-
def qr_attendance_view(request, act_id, code):
876-
act = get_object_or_404(EighthScheduledActivity, id=act_id)
877-
error = False
878-
block = act.block
879-
mark_result = None
880-
if act.get_code_mode_display() == "Auto":
881-
now = timezone.localtime()
882-
day_blocks = Day.objects.select_related("day_type").get(date=now).day_type.blocks
883-
try:
884-
day_block = day_blocks.get(name="8" + block.block_letter)
885-
start_time = shift_time(tm(hour=day_block.start.hour, minute=day_block.start.minute), -20)
886-
end_time = shift_time(tm(hour=day_block.end.hour, minute=day_block.end.minute), 20)
887-
if not start_time <= now.time() <= end_time:
888-
mark_result = "invalid_time"
889-
error = True
890-
except Exception:
891-
mark_result = "invalid_time"
892-
error = True
893-
elif act.get_code_mode_display() == "Closed":
894-
mark_result = "code_closed"
895-
error = True
896-
if not error:
897-
code = code.upper()
898-
if code == act.attendance_code:
899-
try:
900-
present = EighthSignup.objects.get(scheduled_activity=act, user__in=[request.user.id])
901-
present.was_absent = False
902-
present.attendance_marked = True
903-
present.save()
904-
invalidate_obj(present)
905-
act.attendance_taken = True
906-
act.save()
907-
invalidate_obj(act)
908-
mark_result = "code_correct"
909-
messages.success(request, "Attendance marked.")
910-
except Exception:
911-
mark_result = "code_fail"
912-
messages.error(request, "Failed to mark attendance.")
913-
else:
914-
mark_result = "code_fail"
915-
messages.error(request, "Failed to mark attendance.")
916-
else:
917-
messages.error(request, "Failed to mark attendance.")
918-
return student_frontend(request, block, mark_result)

0 commit comments

Comments
 (0)