Skip to content

Commit 6fa1d3e

Browse files
committed
feat: modes and calendar times for attendance code
1 parent f18729e commit 6fa1d3e

File tree

9 files changed

+190
-88
lines changed

9 files changed

+190
-88
lines changed

intranet/apps/eighth/migrations/0072_auto_20250428_1239.py renamed to intranet/apps/eighth/migrations/0072_auto_20250429_1128.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 3.2.25 on 2025-04-28 16:39
1+
# Generated by Django 3.2.25 on 2025-04-29 15:28
22

33
from django.db import migrations, models
44
import intranet.apps.eighth.models
@@ -34,4 +34,14 @@ class Migration(migrations.Migration):
3434
field=models.CharField(default=intranet.apps.eighth.models.EighthScheduledActivity.random_code, max_length=6),
3535
),
3636
migrations.RunPython(generate_attendance_codes, reverse_code=migrations.RunPython.noop),
37+
migrations.AddField(
38+
model_name='eighthscheduledactivity',
39+
name='code_mode',
40+
field=models.IntegerField(choices=[(0, 'Auto'), (1, 'Open'), (2, 'Closed')], default=0),
41+
),
42+
migrations.AddField(
43+
model_name='historicaleighthscheduledactivity',
44+
name='code_mode',
45+
field=models.IntegerField(choices=[(0, 'Auto'), (1, 'Open'), (2, 'Closed')], default=0),
46+
),
3747
]

intranet/apps/eighth/models.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# pylint: disable=too-many-lines; Allow more than 1000 lines
22
import datetime
33
import logging
4-
import string
54
import random
5+
import string
66
from collections.abc import Sequence
77
from typing import Collection, Iterable, List, Optional, Union
88

@@ -826,6 +826,11 @@ class EighthScheduledActivity(AbstractBaseEighthModel):
826826
attendance_code
827827
Random 6-character code of digits and uppercase letters to check attendance
828828
Distinct for each 8th activity-block
829+
code_mode
830+
Whether the activity is automatically enabling/disabling the attendance code based on the daily schedule
831+
Is an integer:
832+
0 = Automatic; 1 = Open; 2 = Closed
833+
default = 0
829834
special
830835
Whether this scheduled instance of the activity is special. If
831836
not set, falls back on the EighthActivity's special setting.
@@ -847,16 +852,20 @@ class EighthScheduledActivity(AbstractBaseEighthModel):
847852
)
848853

849854
def random_code():
850-
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
855+
return "".join(random.choices(string.ascii_uppercase + string.digits, k=6))
851856

852-
attendance_code = models.CharField(max_length=6,default=random_code)
853-
#Warning: Must manually fix codes for pre-existing entires
857+
attendance_code = models.CharField(max_length=6, default=random_code)
858+
mode_choices = [
859+
(0, "Auto"),
860+
(1, "Open"),
861+
(2, "Closed"),
862+
]
863+
code_mode = models.IntegerField(choices=mode_choices, default=0)
854864

855865
admin_comments = models.CharField(max_length=1000, blank=True)
856866
title = models.CharField(max_length=1000, blank=True)
857867
comments = models.CharField(max_length=1000, blank=True)
858868

859-
860869
# Overridden attributes
861870
sponsors = models.ManyToManyField(EighthSponsor, blank=True)
862871
rooms = models.ManyToManyField(EighthRoom, blank=True)
@@ -1206,8 +1215,9 @@ def set_sticky_students(self, users: "Sequence[AbstractBaseUser]") -> None:
12061215
bcc=True,
12071216
)
12081217

1209-
def get_attendance_code(self):
1210-
return self.attendance_code
1218+
def set_code_mode(self, mode):
1219+
self.code_mode = mode
1220+
self.save(update_fields=["code_mode"])
12111221

12121222
@transaction.atomic # This MUST be run in a transaction. Do NOT remove this decorator.
12131223
def add_user(
@@ -1652,8 +1662,6 @@ def __str__(self):
16521662
return f"{self.activity}{suff} on {self.block}{cancelled_str}"
16531663

16541664

1655-
1656-
16571665
class EighthSignupManager(Manager):
16581666
"""Model manager for EighthSignup."""
16591667

intranet/apps/eighth/urls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +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$", signup.student_attendance, name="student_attendance"),
23+
re_path(r"^/student_attendance$", attendance.student_attendance_view, name="student_attendance"),
2424
# Teachers
2525
re_path(r"^/attendance$", attendance.teacher_choose_scheduled_activity_view, name="eighth_attendance_choose_scheduled_activity"),
2626
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: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import csv
22
import io
33
import logging
4+
from datetime import time as tm
45
from html import escape
56

67
from cacheops import invalidate_obj
@@ -25,12 +26,14 @@
2526
from ....utils.date import get_date_range_this_year
2627
from ...auth.decorators import attendance_taker_required, deny_restricted, eighth_admin_required
2728
from ...dashboard.views import gen_sponsor_schedule
29+
from ...schedule.models import Day
2830
from ...schedule.views import decode_date
2931
from ..forms.admin.activities import ActivitySelectionForm
3032
from ..forms.admin.blocks import BlockSelectionForm
3133
from ..models import EighthActivity, EighthBlock, EighthScheduledActivity, EighthSignup, EighthSponsor, EighthWaitlist
3234
from ..tasks import email_scheduled_activity_students_task
3335
from ..utils import get_start_date
36+
from .signup import shift_time
3437

3538
logger = logging.getLogger(__name__)
3639

@@ -304,13 +307,20 @@ def take_attendance_view(request, scheduled_activity_id):
304307
status=403,
305308
)
306309

310+
if "att_code_mode" in request.POST:
311+
selected_mode = int(request.POST["att_code_mode"])
312+
if scheduled_activity.code_mode != selected_mode:
313+
scheduled_activity.set_code_mode(selected_mode)
314+
redirect_url = reverse(url_name, args=[scheduled_activity.id])
315+
return redirect(redirect_url)
316+
307317
if not scheduled_activity.block.locked and request.user.is_eighth_admin:
308318
messages.success(request, "Note: Taking attendance on an unlocked block.")
309319

310-
present_user_ids = list(request.POST.keys()) #list of member.ids checked off
320+
present_user_ids = list(request.POST.keys()) # list of member.ids checked off
311321

312322
if request.FILES.get("attendance"):
313-
#csv attendance
323+
# csv attendance
314324
try:
315325
csv_file = request.FILES["attendance"].read().decode("utf-8")
316326
data = csv.DictReader(io.StringIO(csv_file))
@@ -335,6 +345,9 @@ def take_attendance_view(request, scheduled_activity_id):
335345
except (csv.Error, ValueError, KeyError, IndexError):
336346
messages.error(request, "Could not interpret file. Did you upload a Google Meet attendance report without modification?")
337347

348+
if "att_code_mode" in present_user_ids:
349+
present_user_ids.remove("att_code_mode")
350+
338351
csrf = "csrfmiddlewaretoken"
339352
if csrf in present_user_ids:
340353
present_user_ids.remove(csrf)
@@ -417,7 +430,7 @@ def take_attendance_view(request, scheduled_activity_id):
417430
"show_checkboxes": (scheduled_activity.block.locked or request.user.is_eighth_admin),
418431
"show_icons": (scheduled_activity.block.locked and scheduled_activity.block.attendance_locked() and not request.user.is_eighth_admin),
419432
"bbcu_script": settings.BBCU_SCRIPT,
420-
"attendance_code": scheduled_activity.get_attendance_code(),
433+
"is_sponsor": scheduled_activity.user_is_sponsor(request.user),
421434
}
422435

423436
if request.user.is_eighth_admin:
@@ -757,3 +770,75 @@ def email_students_view(request, scheduled_activity_id):
757770
context = {"scheduled_activity": scheduled_activity}
758771

759772
return render(request, "eighth/email_students.html", context)
773+
774+
775+
@login_required
776+
@deny_restricted
777+
def student_attendance_view(request):
778+
blocks = EighthBlock.objects.get_blocks_today()
779+
attc = None
780+
attf = None
781+
attimef = None
782+
atteachf = None
783+
if request.method == "POST":
784+
now = timezone.localtime()
785+
dayblks = Day.objects.select_related("day_type").get(date=now).day_type.blocks.all()
786+
for blk in blocks:
787+
blklet = blk.block_letter
788+
code = request.POST.get(blklet)
789+
if code is None:
790+
continue
791+
act = request.user.eighthscheduledactivity_set.get(block=blk)
792+
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:
802+
attimef = blk
803+
break
804+
start_time = shift_time(tm(hour=dayblk.start.hour, minute=dayblk.start.minute), -20)
805+
end_time = shift_time(tm(hour=dayblk.end.hour, minute=dayblk.end.minute), 20)
806+
if not start_time <= now.time() <= end_time:
807+
attimef = blk
808+
break
809+
elif act.get_code_mode_display() == "Closed":
810+
atteachf = blk
811+
break
812+
code = code.upper()
813+
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)
822+
break
823+
else:
824+
attf = blk
825+
break
826+
if blocks:
827+
sch_acts = []
828+
for b in blocks:
829+
try:
830+
act = request.user.eighthscheduledactivity_set.get(block=b)
831+
if act.activity.name != "z - Hybrid Sticky":
832+
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+
834+
except EighthScheduledActivity.DoesNotExist:
835+
sch_acts.append([b, None])
836+
response = render(
837+
request,
838+
"eighth/student_submit_attendance.html",
839+
context={"sch_acts": sch_acts, "attc": attc, "attf": attf, "attimef": attimef, "atteachf": atteachf},
840+
)
841+
else:
842+
messages.error(request, "There are no eighth period blocks scheduled today.")
843+
response = redirect("index")
844+
return response

0 commit comments

Comments
 (0)