Skip to content

Commit bea5ce8

Browse files
committed
feat: qr code attendance with teacher pop-up
1 parent cf984ec commit bea5ce8

File tree

6 files changed

+155
-45
lines changed

6 files changed

+155
-45
lines changed

intranet/apps/eighth/models.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# pylint: disable=too-many-lines; Allow more than 1000 lines
22
import datetime
33
import logging
4-
import random
4+
import secrets
55
import string
66
from collections.abc import Sequence
77
from typing import Collection, Iterable, List, Optional, Union
@@ -795,6 +795,10 @@ def for_sponsor(self, sponsor: EighthSponsor, include_cancelled: bool = False) -
795795
return sched_acts
796796

797797

798+
def random_code():
799+
return "".join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(6))
800+
801+
798802
class EighthScheduledActivity(AbstractBaseEighthModel):
799803
r"""Represents the relationship between an activity and a block in which it has been scheduled.
800804
Attributes:
@@ -851,9 +855,6 @@ class EighthScheduledActivity(AbstractBaseEighthModel):
851855
blank=True,
852856
)
853857

854-
def random_code():
855-
return "".join(random.choices(string.ascii_uppercase + string.digits, k=6))
856-
857858
attendance_code = models.CharField(max_length=6, default=random_code)
858859
mode_choices = [
859860
(0, "Auto"),

intranet/apps/eighth/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
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"),
2323
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"),
2425
# Teachers
2526
re_path(r"^/attendance$", attendance.teacher_choose_scheduled_activity_view, name="eighth_attendance_choose_scheduled_activity"),
2627
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: 77 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ def take_attendance_view(request, scheduled_activity_id):
430430
"show_checkboxes": (scheduled_activity.block.locked or request.user.is_eighth_admin),
431431
"show_icons": (scheduled_activity.block.locked and scheduled_activity.block.attendance_locked() and not request.user.is_eighth_admin),
432432
"bbcu_script": settings.BBCU_SCRIPT,
433-
"is_sponsor": scheduled_activity.user_is_sponsor(request.user),
433+
"qrurl": request.build_absolute_uri(reverse("qr_attendance", args=[scheduled_activity.id, scheduled_activity.attendance_code])),
434434
}
435435

436436
if request.user.is_eighth_admin:
@@ -774,31 +774,21 @@ def email_students_view(request, scheduled_activity_id):
774774

775775
@login_required
776776
@deny_restricted
777-
def student_attendance_view(request):
777+
def student_attendance_view(request, attc=None, attf=None, attimef=None, atteachf=None):
778778
blocks = EighthBlock.objects.get_blocks_today()
779-
attc = None
780-
attf = None
781-
attimef = None
782-
atteachf = None
783779
if request.method == "POST":
784780
now = timezone.localtime()
785-
dayblks = Day.objects.select_related("day_type").get(date=now).day_type.blocks.all()
781+
dayblks = Day.objects.select_related("day_type").get(date=now).day_type.blocks
786782
for blk in blocks:
787783
blklet = blk.block_letter
788784
code = request.POST.get(blklet)
789785
if code is None:
790786
continue
791787
act = request.user.eighthscheduledactivity_set.get(block=blk)
792788
if act.get_code_mode_display() == "Auto":
793-
dayblk = None
794-
for bk in dayblks:
795-
name = bk.name
796-
if name is None:
797-
continue
798-
if blklet in name and "8" in name:
799-
dayblk = bk
800-
break
801-
if dayblk is None:
789+
try:
790+
dayblk = dayblks.get(name="8" + blklet)
791+
except Exception:
802792
attimef = blk
803793
break
804794
start_time = shift_time(tm(hour=dayblk.start.hour, minute=dayblk.start.minute), -20)
@@ -811,34 +801,95 @@ def student_attendance_view(request):
811801
break
812802
code = code.upper()
813803
if code == act.attendance_code:
814-
present = EighthSignup.objects.filter(scheduled_activity=act, user__in=[request.user.id])
815-
present.update(was_absent=False)
816-
attc = blk
817-
for s in present:
818-
invalidate_obj(s)
819-
act.attendance_taken = True
820-
act.save()
821-
invalidate_obj(act)
804+
try:
805+
present = EighthSignup.objects.get(scheduled_activity=act, user__in=[request.user.id])
806+
present.was_absent = False
807+
invalidate_obj(present)
808+
act.attendance_taken = True
809+
act.save()
810+
invalidate_obj(act)
811+
attc = blk
812+
except Exception:
813+
attf = blk
822814
break
823815
else:
824816
attf = blk
825817
break
818+
return student_frontend(request, attc, attf, attimef, atteachf)
819+
820+
821+
@login_required
822+
@deny_restricted
823+
def student_frontend(request, attc=None, attf=None, attimef=None, atteachf=None):
824+
blocks = EighthBlock.objects.get_blocks_today()
826825
if blocks:
827826
sch_acts = []
827+
att_marked = []
828828
for b in blocks:
829829
try:
830830
act = request.user.eighthscheduledactivity_set.get(block=b)
831831
if act.activity.name != "z - Hybrid Sticky":
832832
sch_acts.append([b, act, ", ".join([r.name for r in act.get_true_rooms()]), ", ".join([s.name for s in act.get_true_sponsors()])])
833-
833+
signup = EighthSignup.objects.get(user=request.user, scheduled_activity=act)
834+
if not signup.was_absent:
835+
att_marked.append(b)
834836
except EighthScheduledActivity.DoesNotExist:
835837
sch_acts.append([b, None])
836838
response = render(
837839
request,
838840
"eighth/student_submit_attendance.html",
839-
context={"sch_acts": sch_acts, "attc": attc, "attf": attf, "attimef": attimef, "atteachf": atteachf},
841+
context={"sch_acts": sch_acts, "att_marked": att_marked, "attc": attc, "attf": attf, "attimef": attimef, "atteachf": atteachf},
840842
)
841843
else:
842844
messages.error(request, "There are no eighth period blocks scheduled today.")
843845
response = redirect("index")
844846
return response
847+
848+
849+
@login_required
850+
@deny_restricted
851+
def qr_attendance_view(request, act_id, code):
852+
act = get_object_or_404(EighthScheduledActivity, id=act_id)
853+
error = False
854+
block = act.block
855+
attc = None
856+
attf = None
857+
attimef = None
858+
atteachf = None
859+
if act.get_code_mode_display() == "Auto":
860+
now = timezone.localtime()
861+
dayblks = Day.objects.select_related("day_type").get(date=now).day_type.blocks
862+
try:
863+
dayblk = dayblks.get(name="8" + block.block_letter)
864+
start_time = shift_time(tm(hour=dayblk.start.hour, minute=dayblk.start.minute), -20)
865+
end_time = shift_time(tm(hour=dayblk.end.hour, minute=dayblk.end.minute), 20)
866+
if not start_time <= now.time() <= end_time:
867+
attimef = block
868+
error = True
869+
except Exception:
870+
attimef = block
871+
error = True
872+
elif act.get_code_mode_display() == "Closed":
873+
atteachf = block
874+
error = True
875+
if not error:
876+
code = code.upper()
877+
if code == act.attendance_code:
878+
try:
879+
present = EighthSignup.objects.get(scheduled_activity=act, user__in=[request.user.id])
880+
present.was_absent = False
881+
invalidate_obj(present)
882+
act.attendance_taken = True
883+
act.save()
884+
invalidate_obj(act)
885+
attc = block
886+
messages.success(request, "Attendance marked.")
887+
except Exception:
888+
attf = block
889+
messages.error(request, "Failed to mark attendance.")
890+
else:
891+
attf = block
892+
messages.error(request, "Failed to mark attendance.")
893+
else:
894+
messages.error(request, "Failed to mark attendance.")
895+
return student_frontend(request, attc, attf, attimef, atteachf)

0 commit comments

Comments
 (0)