From cf984eccc0a62e7e864b80d8c646d39887c975a1 Mon Sep 17 00:00:00 2001 From: Tiger Deng Date: Fri, 25 Apr 2025 20:58:12 -0400 Subject: [PATCH 1/4] feat: add code-based attendance option --- .gitignore | 1 + .../migrations/0072_auto_20250429_1128.py | 47 ++++++++++ intranet/apps/eighth/models.py | 24 +++++ intranet/apps/eighth/urls.py | 1 + intranet/apps/eighth/views/attendance.py | 89 ++++++++++++++++++- intranet/apps/eighth/views/signup.py | 60 ++++++++++++- intranet/templates/eighth/location.html | 4 + intranet/templates/eighth/signup.html | 9 +- .../eighth/student_submit_attendance.html | 69 ++++++++++++++ .../templates/eighth/take_attendance.html | 31 +++++-- 10 files changed, 323 insertions(+), 12 deletions(-) create mode 100644 intranet/apps/eighth/migrations/0072_auto_20250429_1128.py create mode 100644 intranet/templates/eighth/student_submit_attendance.html diff --git a/.gitignore b/.gitignore index 18e47145c08..ebb85391f66 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,4 @@ package-lock.json # Virtual environments venv/ .venv/ + diff --git a/intranet/apps/eighth/migrations/0072_auto_20250429_1128.py b/intranet/apps/eighth/migrations/0072_auto_20250429_1128.py new file mode 100644 index 00000000000..64f065f58b0 --- /dev/null +++ b/intranet/apps/eighth/migrations/0072_auto_20250429_1128.py @@ -0,0 +1,47 @@ +# Generated by Django 3.2.25 on 2025-04-29 15:28 + +from django.db import migrations, models +import intranet.apps.eighth.models + +def generate_attendance_codes(apps, schema_editor): + EighthScheduledActivity = apps.get_model('eighth', 'EighthScheduledActivity') + HistoricalEighthScheduledActivity = apps.get_model('eighth', 'HistoricalEighthScheduledActivity') + + for activity in EighthScheduledActivity.objects.all(): + activity.attendance_code = intranet.apps.eighth.models.EighthScheduledActivity.random_code() + activity.save(update_fields=['attendance_code']) + + for historical_activity in HistoricalEighthScheduledActivity.objects.all(): + historical_activity.attendance_code = intranet.apps.eighth.models.EighthScheduledActivity.random_code() + historical_activity.save(update_fields=['attendance_code']) + + +class Migration(migrations.Migration): + + dependencies = [ + ('eighth', '0071_eighthscheduledactivity_sticky_students'), + ] + + operations = [ + migrations.AddField( + model_name='eighthscheduledactivity', + name='attendance_code', + field=models.CharField(default=intranet.apps.eighth.models.EighthScheduledActivity.random_code, max_length=6), + ), + migrations.AddField( + model_name='historicaleighthscheduledactivity', + name='attendance_code', + field=models.CharField(default=intranet.apps.eighth.models.EighthScheduledActivity.random_code, max_length=6), + ), + migrations.RunPython(generate_attendance_codes, reverse_code=migrations.RunPython.noop), + migrations.AddField( + model_name='eighthscheduledactivity', + name='code_mode', + field=models.IntegerField(choices=[(0, 'Auto'), (1, 'Open'), (2, 'Closed')], default=0), + ), + migrations.AddField( + model_name='historicaleighthscheduledactivity', + name='code_mode', + field=models.IntegerField(choices=[(0, 'Auto'), (1, 'Open'), (2, 'Closed')], default=0), + ), + ] diff --git a/intranet/apps/eighth/models.py b/intranet/apps/eighth/models.py index cc4946ab6ea..643dab6350f 100644 --- a/intranet/apps/eighth/models.py +++ b/intranet/apps/eighth/models.py @@ -1,6 +1,7 @@ # pylint: disable=too-many-lines; Allow more than 1000 lines import datetime import logging +import random import string from collections.abc import Sequence from typing import Collection, Iterable, List, Optional, Union @@ -822,6 +823,14 @@ class EighthScheduledActivity(AbstractBaseEighthModel): attendance_taken Whether the :class:`EighthSponsor` for the scheduled :class:`EighthActivity` has taken attendance yet + attendance_code + Random 6-character code of digits and uppercase letters to check attendance + Distinct for each 8th activity-block + code_mode + Whether the activity is automatically enabling/disabling the attendance code based on the daily schedule + Is an integer: + 0 = Automatic; 1 = Open; 2 = Closed + default = 0 special Whether this scheduled instance of the activity is special. If not set, falls back on the EighthActivity's special setting. @@ -842,6 +851,17 @@ class EighthScheduledActivity(AbstractBaseEighthModel): blank=True, ) + def random_code(): + return "".join(random.choices(string.ascii_uppercase + string.digits, k=6)) + + attendance_code = models.CharField(max_length=6, default=random_code) + mode_choices = [ + (0, "Auto"), + (1, "Open"), + (2, "Closed"), + ] + code_mode = models.IntegerField(choices=mode_choices, default=0) + admin_comments = models.CharField(max_length=1000, blank=True) title = models.CharField(max_length=1000, blank=True) comments = models.CharField(max_length=1000, blank=True) @@ -1195,6 +1215,10 @@ def set_sticky_students(self, users: "Sequence[AbstractBaseUser]") -> None: bcc=True, ) + def set_code_mode(self, mode): + self.code_mode = mode + self.save(update_fields=["code_mode"]) + @transaction.atomic # This MUST be run in a transaction. Do NOT remove this decorator. def add_user( self, diff --git a/intranet/apps/eighth/urls.py b/intranet/apps/eighth/urls.py index 1663c781681..3f142d3dca2 100644 --- a/intranet/apps/eighth/urls.py +++ b/intranet/apps/eighth/urls.py @@ -20,6 +20,7 @@ re_path(r"^/absences$", attendance.eighth_absences_view, name="eighth_absences"), re_path(r"^/absences/(?P\d+)$", attendance.eighth_absences_view, name="eighth_absences"), re_path(r"^/glance$", signup.eighth_location, name="eighth_location"), + re_path(r"^/student_attendance$", attendance.student_attendance_view, name="student_attendance"), # Teachers re_path(r"^/attendance$", attendance.teacher_choose_scheduled_activity_view, name="eighth_attendance_choose_scheduled_activity"), re_path(r"^/attendance/(?P\d+)$", attendance.take_attendance_view, name="eighth_take_attendance"), diff --git a/intranet/apps/eighth/views/attendance.py b/intranet/apps/eighth/views/attendance.py index 2385dd09e5c..300dd6973b7 100644 --- a/intranet/apps/eighth/views/attendance.py +++ b/intranet/apps/eighth/views/attendance.py @@ -1,6 +1,7 @@ import csv import io import logging +from datetime import time as tm from html import escape from cacheops import invalidate_obj @@ -25,12 +26,14 @@ from ....utils.date import get_date_range_this_year from ...auth.decorators import attendance_taker_required, deny_restricted, eighth_admin_required from ...dashboard.views import gen_sponsor_schedule +from ...schedule.models import Day from ...schedule.views import decode_date from ..forms.admin.activities import ActivitySelectionForm from ..forms.admin.blocks import BlockSelectionForm from ..models import EighthActivity, EighthBlock, EighthScheduledActivity, EighthSignup, EighthSponsor, EighthWaitlist from ..tasks import email_scheduled_activity_students_task from ..utils import get_start_date +from .signup import shift_time logger = logging.getLogger(__name__) @@ -304,12 +307,20 @@ def take_attendance_view(request, scheduled_activity_id): status=403, ) + if "att_code_mode" in request.POST: + selected_mode = int(request.POST["att_code_mode"]) + if scheduled_activity.code_mode != selected_mode: + scheduled_activity.set_code_mode(selected_mode) + redirect_url = reverse(url_name, args=[scheduled_activity.id]) + return redirect(redirect_url) + if not scheduled_activity.block.locked and request.user.is_eighth_admin: messages.success(request, "Note: Taking attendance on an unlocked block.") - present_user_ids = list(request.POST.keys()) + present_user_ids = list(request.POST.keys()) # list of member.ids checked off if request.FILES.get("attendance"): + # csv attendance try: csv_file = request.FILES["attendance"].read().decode("utf-8") data = csv.DictReader(io.StringIO(csv_file)) @@ -334,6 +345,9 @@ def take_attendance_view(request, scheduled_activity_id): except (csv.Error, ValueError, KeyError, IndexError): messages.error(request, "Could not interpret file. Did you upload a Google Meet attendance report without modification?") + if "att_code_mode" in present_user_ids: + present_user_ids.remove("att_code_mode") + csrf = "csrfmiddlewaretoken" if csrf in present_user_ids: present_user_ids.remove(csrf) @@ -416,6 +430,7 @@ def take_attendance_view(request, scheduled_activity_id): "show_checkboxes": (scheduled_activity.block.locked or request.user.is_eighth_admin), "show_icons": (scheduled_activity.block.locked and scheduled_activity.block.attendance_locked() and not request.user.is_eighth_admin), "bbcu_script": settings.BBCU_SCRIPT, + "is_sponsor": scheduled_activity.user_is_sponsor(request.user), } if request.user.is_eighth_admin: @@ -755,3 +770,75 @@ def email_students_view(request, scheduled_activity_id): context = {"scheduled_activity": scheduled_activity} return render(request, "eighth/email_students.html", context) + + +@login_required +@deny_restricted +def student_attendance_view(request): + blocks = EighthBlock.objects.get_blocks_today() + attc = None + attf = None + attimef = None + atteachf = None + if request.method == "POST": + now = timezone.localtime() + dayblks = Day.objects.select_related("day_type").get(date=now).day_type.blocks.all() + for blk in blocks: + blklet = blk.block_letter + code = request.POST.get(blklet) + if code is None: + continue + act = request.user.eighthscheduledactivity_set.get(block=blk) + if act.get_code_mode_display() == "Auto": + dayblk = None + for bk in dayblks: + name = bk.name + if name is None: + continue + if blklet in name and "8" in name: + dayblk = bk + break + if dayblk is None: + attimef = blk + break + start_time = shift_time(tm(hour=dayblk.start.hour, minute=dayblk.start.minute), -20) + end_time = shift_time(tm(hour=dayblk.end.hour, minute=dayblk.end.minute), 20) + if not start_time <= now.time() <= end_time: + attimef = blk + break + elif act.get_code_mode_display() == "Closed": + atteachf = blk + break + code = code.upper() + if code == act.attendance_code: + present = EighthSignup.objects.filter(scheduled_activity=act, user__in=[request.user.id]) + present.update(was_absent=False) + attc = blk + for s in present: + invalidate_obj(s) + act.attendance_taken = True + act.save() + invalidate_obj(act) + break + else: + attf = blk + break + if blocks: + sch_acts = [] + for b in blocks: + try: + act = request.user.eighthscheduledactivity_set.get(block=b) + if act.activity.name != "z - Hybrid Sticky": + 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()])]) + + except EighthScheduledActivity.DoesNotExist: + sch_acts.append([b, None]) + response = render( + request, + "eighth/student_submit_attendance.html", + context={"sch_acts": sch_acts, "attc": attc, "attf": attf, "attimef": attimef, "atteachf": atteachf}, + ) + else: + messages.error(request, "There are no eighth period blocks scheduled today.") + response = redirect("index") + return response diff --git a/intranet/apps/eighth/views/signup.py b/intranet/apps/eighth/views/signup.py index 1162eab3824..9fe10ce7696 100644 --- a/intranet/apps/eighth/views/signup.py +++ b/intranet/apps/eighth/views/signup.py @@ -1,6 +1,8 @@ import datetime import logging import time +from datetime import time as tm +from datetime import timedelta from django import http from django.conf import settings @@ -18,6 +20,7 @@ from ....utils.locking import lock_on from ....utils.serialization import safe_json from ...auth.decorators import deny_restricted, eighth_admin_required +from ...schedule.models import Day from ..exceptions import SignupException from ..models import EighthActivity, EighthBlock, EighthScheduledActivity, EighthSignup, EighthWaitlist from ..serializers import EighthBlockDetailSerializer @@ -28,6 +31,11 @@ eighth_signup_submits = Summary("intranet_eighth_signup_submits", "Number of eighth period signups performed from the eighth signup view") +def shift_time(time, minutes): + dt = datetime.datetime.combine(datetime.datetime.today(), time) + return (dt + timedelta(minutes=minutes)).time() + + @login_required @deny_restricted def eighth_signup_view(request, block_id=None): @@ -215,6 +223,30 @@ def eighth_signup_view(request, block_id=None): except KeyError: active_block_current_signup = None + attendance_open = False + try: + now = timezone.localtime() + dayblks = Day.objects.select_related("day_type").get(date=now).day_type.blocks.all() + earliest = None + latest = None + for blk in dayblks: + name = blk.name + if name is None: + continue + if "8" in name: + start = tm(hour=blk.start.hour, minute=blk.start.minute) + end = tm(hour=blk.end.hour, minute=blk.end.minute) + if earliest is None or start <= earliest: + earliest = start + if latest is None or end >= latest: + latest = end + if earliest is not None: + earliest = shift_time(earliest, -20) + latest = shift_time(latest, 20) + if earliest <= now.time() <= latest: + attendance_open = True + except Exception: + attendance_open = False context = { "user": user, "real_user": request.user, @@ -222,6 +254,7 @@ def eighth_signup_view(request, block_id=None): "activities_list": safe_json(block_info["activities"]), "active_block": block, "active_block_current_signup": active_block_current_signup, + "attopen": attendance_open, } ####### @@ -451,8 +484,31 @@ def eighth_location(request): 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()])]) except EighthScheduledActivity.DoesNotExist: sch_acts.append([b, None]) - - response = render(request, "eighth/location.html", context={"sch_acts": sch_acts}) + attendance_open = False + try: + now = timezone.localtime() + dayblks = Day.objects.select_related("day_type").get(date=now).day_type.blocks.all() + earliest = None + latest = None + for blk in dayblks: + name = blk.name + if name is None: + continue + if "8" in name: + start = tm(hour=blk.start.hour, minute=blk.start.minute) + end = tm(hour=blk.end.hour, minute=blk.end.minute) + if earliest is None or start <= earliest: + earliest = start + if latest is None or end >= latest: + latest = end + if earliest is not None: + earliest = shift_time(earliest, -20) + latest = shift_time(latest, 20) + if earliest <= now.time() <= latest: + attendance_open = True + except Exception: + attendance_open = False + response = render(request, "eighth/location.html", context={"sch_acts": sch_acts, "real_user": request.user, "attopen": attendance_open}) else: messages.error(request, "There are no eighth period blocks scheduled today.") response = redirect("index") diff --git a/intranet/templates/eighth/location.html b/intranet/templates/eighth/location.html index dc2e85a77bc..fe6d9e15e1e 100644 --- a/intranet/templates/eighth/location.html +++ b/intranet/templates/eighth/location.html @@ -43,6 +43,10 @@
Sponsor(s): {{ s.3 }}


{% endfor %} + {% if real_user.is_student and attopen%} + Enter Attendance Code for Today's Blocks + {% endif %} +
Return to Dashboard {% endblock %} diff --git a/intranet/templates/eighth/signup.html b/intranet/templates/eighth/signup.html index d07dbdd6eed..717dedfbf9e 100644 --- a/intranet/templates/eighth/signup.html +++ b/intranet/templates/eighth/signup.html @@ -350,10 +350,15 @@

<% } %> <%}%> <%}%> - +
+ {% endverbatim %} + {% if real_user.is_student and attopen and not real_user.is_eighth_admin%} + Enter Attendance Code for Today's Blocks + {% endif %}
+ {% verbatim %} <% if (!showingRosterButton) { %> <% if(isEighthAdmin) { %> @@ -548,4 +553,4 @@

Passes

{% if request.user.is_eighth_admin %} - {% if not scheduled_activity.block.locked %} - - {% elif scheduled_activity.block.attendance_locked %} - - {% else %} - - {% endif %} + {% endif %} @@ -445,6 +439,29 @@

Passes

{% endfor %} + + +
+ Attendance code: {{ scheduled_activity.attendance_code }} +

Share this code with students present at this activity.

+ + + + + Select Mode: +
+ {% csrf_token %} + {% for value, label in scheduled_activity.mode_choices %} + + {% endfor %} +
+    + +
From 83788ade67bf036c7fce3cd3502ee70b8f33d0b9 Mon Sep 17 00:00:00 2001 From: Tiger Deng Date: Thu, 1 May 2025 13:58:38 -0400 Subject: [PATCH 2/4] feat: qr code attendance with teacher pop-up --- .../migrations/0072_auto_20250429_1128.py | 4 +- intranet/apps/eighth/models.py | 9 +- intranet/apps/eighth/urls.py | 1 + intranet/apps/eighth/views/attendance.py | 103 +++++++++++++----- intranet/static/vendor/qrcode.min.js | 1 + .../eighth/student_submit_attendance.html | 3 + .../templates/eighth/take_attendance.html | 83 +++++++++++--- 7 files changed, 157 insertions(+), 47 deletions(-) create mode 100644 intranet/static/vendor/qrcode.min.js diff --git a/intranet/apps/eighth/migrations/0072_auto_20250429_1128.py b/intranet/apps/eighth/migrations/0072_auto_20250429_1128.py index 64f065f58b0..7f971b768ce 100644 --- a/intranet/apps/eighth/migrations/0072_auto_20250429_1128.py +++ b/intranet/apps/eighth/migrations/0072_auto_20250429_1128.py @@ -26,12 +26,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='eighthscheduledactivity', name='attendance_code', - field=models.CharField(default=intranet.apps.eighth.models.EighthScheduledActivity.random_code, max_length=6), + field=models.CharField(default=intranet.apps.eighth.models.random_code, max_length=6), ), migrations.AddField( model_name='historicaleighthscheduledactivity', name='attendance_code', - field=models.CharField(default=intranet.apps.eighth.models.EighthScheduledActivity.random_code, max_length=6), + field=models.CharField(default=intranet.apps.eighth.models.random_code, max_length=6), ), migrations.RunPython(generate_attendance_codes, reverse_code=migrations.RunPython.noop), migrations.AddField( diff --git a/intranet/apps/eighth/models.py b/intranet/apps/eighth/models.py index 643dab6350f..d2f4210a883 100644 --- a/intranet/apps/eighth/models.py +++ b/intranet/apps/eighth/models.py @@ -1,7 +1,7 @@ # pylint: disable=too-many-lines; Allow more than 1000 lines import datetime import logging -import random +import secrets import string from collections.abc import Sequence from typing import Collection, Iterable, List, Optional, Union @@ -795,6 +795,10 @@ def for_sponsor(self, sponsor: EighthSponsor, include_cancelled: bool = False) - return sched_acts +def random_code(): + return "".join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(6)) + + class EighthScheduledActivity(AbstractBaseEighthModel): r"""Represents the relationship between an activity and a block in which it has been scheduled. Attributes: @@ -851,9 +855,6 @@ class EighthScheduledActivity(AbstractBaseEighthModel): blank=True, ) - def random_code(): - return "".join(random.choices(string.ascii_uppercase + string.digits, k=6)) - attendance_code = models.CharField(max_length=6, default=random_code) mode_choices = [ (0, "Auto"), diff --git a/intranet/apps/eighth/urls.py b/intranet/apps/eighth/urls.py index 3f142d3dca2..9040e336a18 100644 --- a/intranet/apps/eighth/urls.py +++ b/intranet/apps/eighth/urls.py @@ -21,6 +21,7 @@ re_path(r"^/absences/(?P\d+)$", attendance.eighth_absences_view, name="eighth_absences"), re_path(r"^/glance$", signup.eighth_location, name="eighth_location"), re_path(r"^/student_attendance$", attendance.student_attendance_view, name="student_attendance"), + re_path(r"^/qr/(?P\w+)/(?P\w+)$", attendance.qr_attendance_view, name="qr_attendance"), # Teachers re_path(r"^/attendance$", attendance.teacher_choose_scheduled_activity_view, name="eighth_attendance_choose_scheduled_activity"), re_path(r"^/attendance/(?P\d+)$", attendance.take_attendance_view, name="eighth_take_attendance"), diff --git a/intranet/apps/eighth/views/attendance.py b/intranet/apps/eighth/views/attendance.py index 300dd6973b7..8093cbbd5a7 100644 --- a/intranet/apps/eighth/views/attendance.py +++ b/intranet/apps/eighth/views/attendance.py @@ -430,7 +430,7 @@ def take_attendance_view(request, scheduled_activity_id): "show_checkboxes": (scheduled_activity.block.locked or request.user.is_eighth_admin), "show_icons": (scheduled_activity.block.locked and scheduled_activity.block.attendance_locked() and not request.user.is_eighth_admin), "bbcu_script": settings.BBCU_SCRIPT, - "is_sponsor": scheduled_activity.user_is_sponsor(request.user), + "qrurl": request.build_absolute_uri(reverse("qr_attendance", args=[scheduled_activity.id, scheduled_activity.attendance_code])), } if request.user.is_eighth_admin: @@ -774,15 +774,11 @@ def email_students_view(request, scheduled_activity_id): @login_required @deny_restricted -def student_attendance_view(request): +def student_attendance_view(request, attc=None, attf=None, attimef=None, atteachf=None): blocks = EighthBlock.objects.get_blocks_today() - attc = None - attf = None - attimef = None - atteachf = None if request.method == "POST": now = timezone.localtime() - dayblks = Day.objects.select_related("day_type").get(date=now).day_type.blocks.all() + dayblks = Day.objects.select_related("day_type").get(date=now).day_type.blocks for blk in blocks: blklet = blk.block_letter code = request.POST.get(blklet) @@ -790,15 +786,9 @@ def student_attendance_view(request): continue act = request.user.eighthscheduledactivity_set.get(block=blk) if act.get_code_mode_display() == "Auto": - dayblk = None - for bk in dayblks: - name = bk.name - if name is None: - continue - if blklet in name and "8" in name: - dayblk = bk - break - if dayblk is None: + try: + dayblk = dayblks.get(name="8" + blklet) + except Exception: attimef = blk break start_time = shift_time(tm(hour=dayblk.start.hour, minute=dayblk.start.minute), -20) @@ -811,34 +801,95 @@ def student_attendance_view(request): break code = code.upper() if code == act.attendance_code: - present = EighthSignup.objects.filter(scheduled_activity=act, user__in=[request.user.id]) - present.update(was_absent=False) - attc = blk - for s in present: - invalidate_obj(s) - act.attendance_taken = True - act.save() - invalidate_obj(act) + try: + present = EighthSignup.objects.get(scheduled_activity=act, user__in=[request.user.id]) + present.was_absent = False + invalidate_obj(present) + act.attendance_taken = True + act.save() + invalidate_obj(act) + attc = blk + except Exception: + attf = blk break else: attf = blk break + return student_frontend(request, attc, attf, attimef, atteachf) + + +@login_required +@deny_restricted +def student_frontend(request, attc=None, attf=None, attimef=None, atteachf=None): + blocks = EighthBlock.objects.get_blocks_today() if blocks: sch_acts = [] + att_marked = [] for b in blocks: try: act = request.user.eighthscheduledactivity_set.get(block=b) if act.activity.name != "z - Hybrid Sticky": 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()])]) - + signup = EighthSignup.objects.get(user=request.user, scheduled_activity=act) + if not signup.was_absent: + att_marked.append(b) except EighthScheduledActivity.DoesNotExist: sch_acts.append([b, None]) response = render( request, "eighth/student_submit_attendance.html", - context={"sch_acts": sch_acts, "attc": attc, "attf": attf, "attimef": attimef, "atteachf": atteachf}, + context={"sch_acts": sch_acts, "att_marked": att_marked, "attc": attc, "attf": attf, "attimef": attimef, "atteachf": atteachf}, ) else: messages.error(request, "There are no eighth period blocks scheduled today.") response = redirect("index") return response + + +@login_required +@deny_restricted +def qr_attendance_view(request, act_id, code): + act = get_object_or_404(EighthScheduledActivity, id=act_id) + error = False + block = act.block + attc = None + attf = None + attimef = None + atteachf = None + if act.get_code_mode_display() == "Auto": + now = timezone.localtime() + dayblks = Day.objects.select_related("day_type").get(date=now).day_type.blocks + try: + dayblk = dayblks.get(name="8" + block.block_letter) + start_time = shift_time(tm(hour=dayblk.start.hour, minute=dayblk.start.minute), -20) + end_time = shift_time(tm(hour=dayblk.end.hour, minute=dayblk.end.minute), 20) + if not start_time <= now.time() <= end_time: + attimef = block + error = True + except Exception: + attimef = block + error = True + elif act.get_code_mode_display() == "Closed": + atteachf = block + error = True + if not error: + code = code.upper() + if code == act.attendance_code: + try: + present = EighthSignup.objects.get(scheduled_activity=act, user__in=[request.user.id]) + present.was_absent = False + invalidate_obj(present) + act.attendance_taken = True + act.save() + invalidate_obj(act) + attc = block + messages.success(request, "Attendance marked.") + except Exception: + attf = block + messages.error(request, "Failed to mark attendance.") + else: + attf = block + messages.error(request, "Failed to mark attendance.") + else: + messages.error(request, "Failed to mark attendance.") + return student_frontend(request, attc, attf, attimef, atteachf) diff --git a/intranet/static/vendor/qrcode.min.js b/intranet/static/vendor/qrcode.min.js new file mode 100644 index 00000000000..993e88f3966 --- /dev/null +++ b/intranet/static/vendor/qrcode.min.js @@ -0,0 +1 @@ +var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j=0?p.get(q):0}}for(var r=0,m=0;mm;m++)for(var j=0;jm;m++)for(var j=0;j=0;)b^=f.G15<=0;)b^=f.G18<>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;cf;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=[''],h=0;d>h;h++){g.push("");for(var i=0;d>i;i++)g.push('');g.push("")}g.push("
"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}(); \ No newline at end of file diff --git a/intranet/templates/eighth/student_submit_attendance.html b/intranet/templates/eighth/student_submit_attendance.html index c48d72d209f..6bd7374154b 100644 --- a/intranet/templates/eighth/student_submit_attendance.html +++ b/intranet/templates/eighth/student_submit_attendance.html @@ -37,6 +37,9 @@
Sponsor(s): {{ s.3 }}

Comments: {% if s.1.comments %}{{ s.1.comments }}{% else %}None{% endif %}


{{ s.1.activity.description }}

+ {% if s.0 in att_marked%} +

Attendance marked.

+ {% endif %}
{% csrf_token %}

Attendance Code:

diff --git a/intranet/templates/eighth/take_attendance.html b/intranet/templates/eighth/take_attendance.html index d360a52eefb..d23527067c1 100644 --- a/intranet/templates/eighth/take_attendance.html +++ b/intranet/templates/eighth/take_attendance.html @@ -23,6 +23,7 @@ + {% if wizard %} + + {% endif %}
From 465d0feba0ba14937e69934489a0ee1b53c0f363 Mon Sep 17 00:00:00 2001 From: Tiger Deng Date: Sat, 10 May 2025 15:09:50 -0400 Subject: [PATCH 3/4] fix: collection of fixes --- .../migrations/0072_auto_20250429_1128.py | 14 ++- intranet/apps/eighth/models.py | 4 + intranet/apps/eighth/views/attendance.py | 117 +++++++++++------- intranet/apps/eighth/views/signup.py | 6 +- intranet/templates/eighth/location.html | 2 +- intranet/templates/eighth/signup.html | 2 +- .../eighth/student_submit_attendance.html | 13 +- .../templates/eighth/take_attendance.html | 27 ++-- 8 files changed, 116 insertions(+), 69 deletions(-) diff --git a/intranet/apps/eighth/migrations/0072_auto_20250429_1128.py b/intranet/apps/eighth/migrations/0072_auto_20250429_1128.py index 7f971b768ce..a503eed9d5d 100644 --- a/intranet/apps/eighth/migrations/0072_auto_20250429_1128.py +++ b/intranet/apps/eighth/migrations/0072_auto_20250429_1128.py @@ -8,11 +8,11 @@ def generate_attendance_codes(apps, schema_editor): HistoricalEighthScheduledActivity = apps.get_model('eighth', 'HistoricalEighthScheduledActivity') for activity in EighthScheduledActivity.objects.all(): - activity.attendance_code = intranet.apps.eighth.models.EighthScheduledActivity.random_code() + activity.attendance_code = intranet.apps.eighth.models.random_code() activity.save(update_fields=['attendance_code']) for historical_activity in HistoricalEighthScheduledActivity.objects.all(): - historical_activity.attendance_code = intranet.apps.eighth.models.EighthScheduledActivity.random_code() + historical_activity.attendance_code = intranet.apps.eighth.models.random_code() historical_activity.save(update_fields=['attendance_code']) @@ -44,4 +44,14 @@ class Migration(migrations.Migration): name='code_mode', field=models.IntegerField(choices=[(0, 'Auto'), (1, 'Open'), (2, 'Closed')], default=0), ), + migrations.AddField( + model_name='eighthsignup', + name='attendance_marked', + field=models.BooleanField(blank=True, default=False), + ), + migrations.AddField( + model_name='historicaleighthsignup', + name='attendance_marked', + field=models.BooleanField(blank=True, default=False), + ), ] diff --git a/intranet/apps/eighth/models.py b/intranet/apps/eighth/models.py index d2f4210a883..2209261b730 100644 --- a/intranet/apps/eighth/models.py +++ b/intranet/apps/eighth/models.py @@ -1728,6 +1728,8 @@ class EighthSignup(AbstractBaseEighthModel): Whether the pass was accepted was_absent Whether the student was absent. + attendance_marked + Whether the student has been marked / filled out attendance. absence_acknowledged Whether the student has dismissed the absence notification. absence_emailed @@ -1750,6 +1752,7 @@ class EighthSignup(AbstractBaseEighthModel): pass_accepted = models.BooleanField(default=False, blank=True) was_absent = models.BooleanField(default=False, blank=True) + attendance_marked = models.BooleanField(default=False, blank=True) absence_acknowledged = models.BooleanField(default=False, blank=True) absence_emailed = models.BooleanField(default=False, blank=True) @@ -1839,6 +1842,7 @@ def accept_pass(self): """Accepts an eighth period pass for the EighthSignup object.""" self.was_absent = False self.pass_accepted = True + self.attendance_marked = True self.save(update_fields=["was_absent", "pass_accepted"]) def reject_pass(self): diff --git a/intranet/apps/eighth/views/attendance.py b/intranet/apps/eighth/views/attendance.py index 8093cbbd5a7..65bfa0b58e5 100644 --- a/intranet/apps/eighth/views/attendance.py +++ b/intranet/apps/eighth/views/attendance.py @@ -307,12 +307,11 @@ def take_attendance_view(request, scheduled_activity_id): status=403, ) - if "att_code_mode" in request.POST: - selected_mode = int(request.POST["att_code_mode"]) - if scheduled_activity.code_mode != selected_mode: - scheduled_activity.set_code_mode(selected_mode) - redirect_url = reverse(url_name, args=[scheduled_activity.id]) - return redirect(redirect_url) + att_code_mode = request.POST.get("att_code_mode") + if att_code_mode is not None and int(att_code_mode) != scheduled_activity.code_mode: + scheduled_activity.set_code_mode(int(att_code_mode)) + redirect_url = reverse(url_name, args=[scheduled_activity.id]) + return redirect(redirect_url) if not scheduled_activity.block.locked and request.user.is_eighth_admin: messages.success(request, "Note: Taking attendance on an unlocked block.") @@ -363,6 +362,7 @@ def take_attendance_view(request, scheduled_activity_id): present_signups = EighthSignup.objects.filter(scheduled_activity=scheduled_activity, user__in=present_user_ids) present_signups.update(was_absent=False) + present_signups.update(attendance_marked=True) for s in present_signups: invalidate_obj(s) @@ -430,7 +430,7 @@ def take_attendance_view(request, scheduled_activity_id): "show_checkboxes": (scheduled_activity.block.locked or request.user.is_eighth_admin), "show_icons": (scheduled_activity.block.locked and scheduled_activity.block.attendance_locked() and not request.user.is_eighth_admin), "bbcu_script": settings.BBCU_SCRIPT, - "qrurl": request.build_absolute_uri(reverse("qr_attendance", args=[scheduled_activity.id, scheduled_activity.attendance_code])), + "qr_url": request.build_absolute_uri(reverse("qr_attendance", args=[scheduled_activity.id, scheduled_activity.attendance_code])), } if request.user.is_eighth_admin: @@ -540,7 +540,9 @@ def accept_all_passes_view(request, scheduled_activity_id): if not can_accept: return render(request, "error/403.html", {"reason": "You do not have permission to take accept these passes."}, status=403) - EighthSignup.objects.filter(after_deadline=True, scheduled_activity=scheduled_activity).update(pass_accepted=True, was_absent=False) + EighthSignup.objects.filter(after_deadline=True, scheduled_activity=scheduled_activity).update( + pass_accepted=True, was_absent=False, attendance_marked=True + ) invalidate_obj(scheduled_activity) if "admin" in request.path: @@ -774,71 +776,93 @@ def email_students_view(request, scheduled_activity_id): @login_required @deny_restricted -def student_attendance_view(request, attc=None, attf=None, attimef=None, atteachf=None): +def student_attendance_view(request): blocks = EighthBlock.objects.get_blocks_today() + mark_block = None + mark_result = None if request.method == "POST": now = timezone.localtime() - dayblks = Day.objects.select_related("day_type").get(date=now).day_type.blocks - for blk in blocks: - blklet = blk.block_letter - code = request.POST.get(blklet) + try: + day_blocks = Day.objects.select_related("day_type").get(date=now).day_type.blocks + except Day.DoesNotExist: + messages.error(request, "Error. Attendance is only available on school days.") + return redirect("index") + for block in blocks: + block_letter = block.block_letter + code = request.POST.get(block_letter) if code is None: continue - act = request.user.eighthscheduledactivity_set.get(block=blk) + act = request.user.eighthscheduledactivity_set.get(block=block) if act.get_code_mode_display() == "Auto": try: - dayblk = dayblks.get(name="8" + blklet) + day_block = day_blocks.get(name="8" + block_letter) except Exception: - attimef = blk + mark_result = "invalid_time" + mark_block = block break - start_time = shift_time(tm(hour=dayblk.start.hour, minute=dayblk.start.minute), -20) - end_time = shift_time(tm(hour=dayblk.end.hour, minute=dayblk.end.minute), 20) + start_time = shift_time(tm(hour=day_block.start.hour, minute=day_block.start.minute), -20) + end_time = shift_time(tm(hour=day_block.end.hour, minute=day_block.end.minute), 20) if not start_time <= now.time() <= end_time: - attimef = blk + mark_result = "invalid_time" + mark_block = block break elif act.get_code_mode_display() == "Closed": - atteachf = blk + mark_result = "code_closed" + mark_block = block break code = code.upper() if code == act.attendance_code: try: present = EighthSignup.objects.get(scheduled_activity=act, user__in=[request.user.id]) present.was_absent = False + present.attendance_marked = True + present.save() invalidate_obj(present) act.attendance_taken = True act.save() invalidate_obj(act) - attc = blk + mark_result = "code_correct" + mark_block = block except Exception: - attf = blk + mark_result = "code_fail" + mark_block = block break else: - attf = blk + mark_result = "code_fail" + mark_block = block break - return student_frontend(request, attc, attf, attimef, atteachf) + return student_frontend(request, mark_block, mark_result) @login_required @deny_restricted -def student_frontend(request, attc=None, attf=None, attimef=None, atteachf=None): +def student_frontend(request, mark_block: EighthBlock = None, mark_result: str = None): blocks = EighthBlock.objects.get_blocks_today() if blocks: sch_acts = [] att_marked = [] - for b in blocks: + for block in blocks: try: - act = request.user.eighthscheduledactivity_set.get(block=b) + act = request.user.eighthscheduledactivity_set.get(block=block) if act.activity.name != "z - Hybrid Sticky": - 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()])]) + sch_acts.append( + [block, act, ", ".join([r.name for r in act.get_true_rooms()]), ", ".join([s.name for s in act.get_true_sponsors()])] + ) signup = EighthSignup.objects.get(user=request.user, scheduled_activity=act) - if not signup.was_absent: - att_marked.append(b) + if (not signup.was_absent) and signup.attendance_marked and act.attendance_taken: + att_marked.append(block) except EighthScheduledActivity.DoesNotExist: - sch_acts.append([b, None]) + sch_acts.append([block, None]) + results = { + "code_correct": "", + "code_closed": '

Error. Ask your teacher to open the attendance code.

', + "code_fail": '

Invalid Code.

', + "invalid_time": f'

Invalid time. Please fill this out during {block.block_letter} block.

', + } response = render( request, "eighth/student_submit_attendance.html", - context={"sch_acts": sch_acts, "att_marked": att_marked, "attc": attc, "attf": attf, "attimef": attimef, "atteachf": atteachf}, + context={"sch_acts": sch_acts, "att_marked": att_marked, "mark_block": mark_block, "attendance_result": results.get(mark_result)}, ) else: messages.error(request, "There are no eighth period blocks scheduled today.") @@ -852,25 +876,22 @@ def qr_attendance_view(request, act_id, code): act = get_object_or_404(EighthScheduledActivity, id=act_id) error = False block = act.block - attc = None - attf = None - attimef = None - atteachf = None + mark_result = None if act.get_code_mode_display() == "Auto": now = timezone.localtime() - dayblks = Day.objects.select_related("day_type").get(date=now).day_type.blocks + day_blocks = Day.objects.select_related("day_type").get(date=now).day_type.blocks try: - dayblk = dayblks.get(name="8" + block.block_letter) - start_time = shift_time(tm(hour=dayblk.start.hour, minute=dayblk.start.minute), -20) - end_time = shift_time(tm(hour=dayblk.end.hour, minute=dayblk.end.minute), 20) + day_block = day_blocks.get(name="8" + block.block_letter) + start_time = shift_time(tm(hour=day_block.start.hour, minute=day_block.start.minute), -20) + end_time = shift_time(tm(hour=day_block.end.hour, minute=day_block.end.minute), 20) if not start_time <= now.time() <= end_time: - attimef = block + mark_result = "invalid_time" error = True except Exception: - attimef = block + mark_result = "invalid_time" error = True elif act.get_code_mode_display() == "Closed": - atteachf = block + mark_result = "code_closed" error = True if not error: code = code.upper() @@ -878,18 +899,20 @@ def qr_attendance_view(request, act_id, code): try: present = EighthSignup.objects.get(scheduled_activity=act, user__in=[request.user.id]) present.was_absent = False + present.attendance_marked = True + present.save() invalidate_obj(present) act.attendance_taken = True act.save() invalidate_obj(act) - attc = block + mark_result = "code_correct" messages.success(request, "Attendance marked.") except Exception: - attf = block + mark_result = "code_fail" messages.error(request, "Failed to mark attendance.") else: - attf = block + mark_result = "code_fail" messages.error(request, "Failed to mark attendance.") else: messages.error(request, "Failed to mark attendance.") - return student_frontend(request, attc, attf, attimef, atteachf) + return student_frontend(request, block, mark_result) diff --git a/intranet/apps/eighth/views/signup.py b/intranet/apps/eighth/views/signup.py index 9fe10ce7696..8ddab3d1836 100644 --- a/intranet/apps/eighth/views/signup.py +++ b/intranet/apps/eighth/views/signup.py @@ -254,7 +254,7 @@ def eighth_signup_view(request, block_id=None): "activities_list": safe_json(block_info["activities"]), "active_block": block, "active_block_current_signup": active_block_current_signup, - "attopen": attendance_open, + "attendance_open": attendance_open, } ####### @@ -508,7 +508,9 @@ def eighth_location(request): attendance_open = True except Exception: attendance_open = False - response = render(request, "eighth/location.html", context={"sch_acts": sch_acts, "real_user": request.user, "attopen": attendance_open}) + response = render( + request, "eighth/location.html", context={"sch_acts": sch_acts, "real_user": request.user, "attendance_open": attendance_open} + ) else: messages.error(request, "There are no eighth period blocks scheduled today.") response = redirect("index") diff --git a/intranet/templates/eighth/location.html b/intranet/templates/eighth/location.html index fe6d9e15e1e..3b2f78a9f1d 100644 --- a/intranet/templates/eighth/location.html +++ b/intranet/templates/eighth/location.html @@ -43,7 +43,7 @@
Sponsor(s): {{ s.3 }}


{% endfor %} - {% if real_user.is_student and attopen%} + {% if real_user.is_student and attendance_open%} Enter Attendance Code for Today's Blocks {% endif %}
diff --git a/intranet/templates/eighth/signup.html b/intranet/templates/eighth/signup.html index 717dedfbf9e..aaa8bad0bd5 100644 --- a/intranet/templates/eighth/signup.html +++ b/intranet/templates/eighth/signup.html @@ -352,7 +352,7 @@

<%}%>
{% endverbatim %} - {% if real_user.is_student and attopen and not real_user.is_eighth_admin%} + {% if real_user.is_student and attendance_open and not real_user.is_eighth_admin%} Enter Attendance Code for Today's Blocks {% endif %}
diff --git a/intranet/templates/eighth/student_submit_attendance.html b/intranet/templates/eighth/student_submit_attendance.html index 6bd7374154b..8bc95946fa2 100644 --- a/intranet/templates/eighth/student_submit_attendance.html +++ b/intranet/templates/eighth/student_submit_attendance.html @@ -39,7 +39,7 @@
Sponsor(s): {{ s.3 }}

{{ s.1.activity.description }}

{% if s.0 in att_marked%}

Attendance marked.

- {% endif %} + {% else %} {% csrf_token %}

Attendance Code:

@@ -52,14 +52,9 @@
Sponsor(s): {{ s.3 }}
/> - {% if s.0 == attc %} -

Attendance marked successfully.

- {% elif s.0 == attimef %} -

Invalid time. Please fill this out during {{ s.0.block_letter }} block.

- {% elif s.0 == attf %} -

Invalid Code.

- {% elif s.0 == atteachf %} -

Error. Ask your teacher to open the attendance code.

+ {% endif %} + {% if s.0 == mark_block %} + {{ attendance_result|safe }} {% endif %} {% else %}

You are not signed up for any activity.

diff --git a/intranet/templates/eighth/take_attendance.html b/intranet/templates/eighth/take_attendance.html index d23527067c1..0933525b544 100644 --- a/intranet/templates/eighth/take_attendance.html +++ b/intranet/templates/eighth/take_attendance.html @@ -466,12 +466,12 @@

Passes

  
-
-
- +
From 9de757082c60b2f6b586c364571f31f0a975d850 Mon Sep 17 00:00:00 2001 From: Tiger Deng Date: Sun, 18 May 2025 21:43:48 -0400 Subject: [PATCH 4/4] fix: qr code display and mechanics --- .github/workflows/ci.yml | 24 +- .gitignore | 1 + .pre-commit-config.yaml | 5 +- README.rst | 8 +- ci/spec.yml | 22 +- config/docker/Dockerfile | 4 +- config/docker/secret.py | 6 +- config/scripts/create_users.py | 17 +- docs/Makefile | 24 ++ docs/_ext/djangodocs.py | 8 - docs/_templates/relations.html | 0 docs/developing/styleguide.rst | 107 ------- docs/make.bat | 35 +++ docs/requirements.txt | 4 + docs/rtd-requirements.txt | 68 ----- docs/{ => source}/_static/custom.css | 0 docs/{ => source/_static}/favicon.ico | Bin docs/source/_static/logo-dark.svg | 1 + docs/source/_static/logo-light.svg | 1 + docs/source/_templates/autosummary/class.rst | 34 +++ docs/source/_templates/autosummary/module.rst | 52 ++++ docs/{ => source}/architecture/index.rst | 0 docs/{ => source}/conf.py | 196 +++++-------- .../source/developing/contributing.md | 6 +- .../{ => source}/developing/eighth-models.rst | 0 docs/{ => source}/developing/howto.rst | 35 ++- docs/{ => source}/developing/index.rst | 1 + docs/{ => source}/developing/oauth.rst | 0 docs/{ => source}/developing/requirements.rst | 0 docs/source/developing/styleguide.rst | 96 +++++++ docs/{ => source}/developing/testing.rst | 10 +- docs/{ => source}/developing/updates.rst | 15 +- .../source/developing/usernames.md | 26 +- docs/{ => source}/index.rst | 2 +- docs/source/reference_index.rst | 21 ++ .../reference_index/apps/announcements.rst | 18 ++ docs/source/reference_index/apps/api.rst | 14 + docs/source/reference_index/apps/auth.rst | 20 ++ docs/source/reference_index/apps/bus.rst | 20 ++ docs/source/reference_index/apps/cslapps.rst | 15 + .../reference_index/apps/customthemes.rst | 11 + .../source/reference_index/apps/dashboard.rst | 11 + .../reference_index/apps/dataimport.rst | 19 ++ docs/source/reference_index/apps/django.rst | 10 + docs/source/reference_index/apps/eighth.rst | 76 +++++ docs/source/reference_index/apps/emailfwd.rst | 16 ++ docs/source/reference_index/apps/emerg.rst | 13 + .../reference_index/apps/enrichment.rst | 14 + docs/source/reference_index/apps/error.rst | 11 + docs/source/reference_index/apps/events.rst | 18 ++ docs/source/reference_index/apps/features.rst | 16 ++ docs/source/reference_index/apps/feedback.rst | 15 + docs/source/reference_index/apps/files.rst | 15 + docs/source/reference_index/apps/groups.rst | 14 + docs/source/reference_index/apps/itemreg.rst | 17 ++ docs/source/reference_index/apps/logs.rst | 15 + .../source/reference_index/apps/lostfound.rst | 17 ++ .../reference_index/apps/nomination.rst | 14 + .../reference_index/apps/notifications.rst | 14 + docs/source/reference_index/apps/oauth.rst | 14 + docs/source/reference_index/apps/parking.rst | 16 ++ docs/source/reference_index/apps/polls.rst | 15 + .../reference_index/apps/preferences.rst | 14 + docs/source/reference_index/apps/printing.rst | 15 + docs/source/reference_index/apps/schedule.rst | 19 ++ docs/source/reference_index/apps/search.rst | 13 + docs/source/reference_index/apps/seniors.rst | 17 ++ .../reference_index/apps/sessionmgmt.rst | 15 + docs/source/reference_index/apps/signage.rst | 18 ++ .../reference_index/apps/templatetags.rst | 20 ++ docs/source/reference_index/apps/users.rst | 24 ++ docs/source/reference_index/apps/welcome.rst | 12 + docs/source/reference_index/middleware.rst | 20 ++ SECURITY.md => docs/source/security.md | 1 + docs/{ => source}/setup/index.rst | 1 + docs/{ => source}/setup/server.rst | 0 SETUP.md => docs/source/setup/setup.md | 2 +- .../sourcedoc/intranet.apps.announcements.rst | 85 ------ docs/sourcedoc/intranet.apps.api.rst | 53 ---- ...intranet.apps.auth.management.commands.rst | 21 -- .../intranet.apps.auth.management.rst | 18 -- docs/sourcedoc/intranet.apps.auth.rst | 101 ------- .../intranet.apps.bus.management.commands.rst | 29 -- .../intranet.apps.bus.management.rst | 18 -- docs/sourcedoc/intranet.apps.bus.rst | 93 ------- ...ranet.apps.cslapps.management.commands.rst | 21 -- .../intranet.apps.cslapps.management.rst | 18 -- docs/sourcedoc/intranet.apps.cslapps.rst | 61 ---- docs/sourcedoc/intranet.apps.customthemes.rst | 29 -- docs/sourcedoc/intranet.apps.dashboard.rst | 29 -- ...et.apps.dataimport.management.commands.rst | 77 ------ .../intranet.apps.dataimport.management.rst | 18 -- docs/sourcedoc/intranet.apps.dataimport.rst | 37 --- .../intranet.apps.eighth.forms.admin.rst | 69 ----- docs/sourcedoc/intranet.apps.eighth.forms.rst | 37 --- ...tranet.apps.eighth.management.commands.rst | 101 ------- .../intranet.apps.eighth.management.rst | 18 -- docs/sourcedoc/intranet.apps.eighth.rst | 96 ------- .../intranet.apps.eighth.tests.admin.rst | 93 ------- docs/sourcedoc/intranet.apps.eighth.tests.rst | 101 ------- .../intranet.apps.eighth.views.admin.rst | 101 ------- docs/sourcedoc/intranet.apps.eighth.views.rst | 77 ------ ...anet.apps.emailfwd.management.commands.rst | 21 -- .../intranet.apps.emailfwd.management.rst | 18 -- docs/sourcedoc/intranet.apps.emailfwd.rst | 69 ----- docs/sourcedoc/intranet.apps.emerg.rst | 45 --- docs/sourcedoc/intranet.apps.enrichment.rst | 53 ---- docs/sourcedoc/intranet.apps.error.rst | 29 -- ...tranet.apps.events.management.commands.rst | 21 -- .../intranet.apps.events.management.rst | 18 -- docs/sourcedoc/intranet.apps.events.rst | 85 ------ docs/sourcedoc/intranet.apps.features.rst | 69 ----- docs/sourcedoc/intranet.apps.feedback.rst | 61 ---- docs/sourcedoc/intranet.apps.files.rst | 61 ---- docs/sourcedoc/intranet.apps.groups.rst | 53 ---- docs/sourcedoc/intranet.apps.itemreg.rst | 77 ------ .../intranet.apps.itemreg.templatetags.rst | 21 -- docs/sourcedoc/intranet.apps.logs.rst | 61 ---- docs/sourcedoc/intranet.apps.lostfound.rst | 69 ----- docs/sourcedoc/intranet.apps.nomination.rst | 53 ---- .../sourcedoc/intranet.apps.notifications.rst | 53 ---- docs/sourcedoc/intranet.apps.oauth.rst | 53 ---- docs/sourcedoc/intranet.apps.parking.rst | 69 ----- docs/sourcedoc/intranet.apps.polls.rst | 61 ---- docs/sourcedoc/intranet.apps.preferences.rst | 53 ---- docs/sourcedoc/intranet.apps.printing.rst | 61 ---- docs/sourcedoc/intranet.apps.rst | 64 ----- ...anet.apps.schedule.management.commands.rst | 21 -- .../intranet.apps.schedule.management.rst | 18 -- docs/sourcedoc/intranet.apps.schedule.rst | 93 ------- docs/sourcedoc/intranet.apps.search.rst | 45 --- ...ranet.apps.seniors.management.commands.rst | 29 -- .../intranet.apps.seniors.management.rst | 18 -- docs/sourcedoc/intranet.apps.seniors.rst | 69 ----- docs/sourcedoc/intranet.apps.sessionmgmt.rst | 61 ---- docs/sourcedoc/intranet.apps.signage.rst | 85 ------ .../intranet.apps.signage.templatetags.rst | 21 -- docs/sourcedoc/intranet.apps.templatetags.rst | 93 ------- ...ntranet.apps.users.management.commands.rst | 29 -- .../intranet.apps.users.management.rst | 18 -- docs/sourcedoc/intranet.apps.users.rst | 102 ------- .../intranet.apps.users.templatetags.rst | 37 --- docs/sourcedoc/intranet.apps.welcome.rst | 37 --- docs/sourcedoc/intranet.middleware.rst | 101 ------- docs/sourcedoc/intranet.rst | 57 ---- docs/sourcedoc/intranet.settings.rst | 21 -- docs/sourcedoc/intranet.test.rst | 29 -- docs/sourcedoc/intranet.utils.rst | 85 ------ docs/sourcedoc/modules.rst | 7 - intranet/apps/announcements/admin.py | 6 +- intranet/apps/announcements/api.py | 4 +- ...ouncement_historicalwarningannouncement.py | 4 +- intranet/apps/announcements/models.py | 10 +- intranet/apps/announcements/urls.py | 40 +-- intranet/apps/announcements/views.py | 2 +- intranet/apps/api/urls.py | 42 +-- intranet/apps/auth/helpers.py | 3 +- intranet/apps/auth/urls.py | 14 +- intranet/apps/auth/views.py | 10 +- intranet/apps/bus/urls.py | 10 +- intranet/apps/context_processors.py | 4 +- intranet/apps/cslapps/admin.py | 4 +- intranet/apps/cslapps/models.py | 17 +- intranet/apps/cslapps/urls.py | 4 +- intranet/apps/customthemes/urls.py | 6 +- intranet/apps/dashboard/views.py | 31 ++- .../management/commands/import_tj_star.py | 1 + .../management/commands/year_cleanup.py | 4 +- intranet/apps/eighth/admin.py | 14 +- intranet/apps/eighth/exceptions.py | 2 +- .../management/commands/find_duplicates.py | 2 +- ..._alter_eighthscheduledactivity_waitlist.py | 20 ++ ...1128.py => 0073_eighth_attendance_code.py} | 2 +- intranet/apps/eighth/models.py | 62 ++--- intranet/apps/eighth/serializers.py | 3 + intranet/apps/eighth/tasks.py | 8 +- .../eighth/tests/admin/test_admin_general.py | 12 +- .../eighth/tests/admin/test_admin_sponsors.py | 4 +- intranet/apps/eighth/tests/test_activities.py | 16 +- intranet/apps/eighth/tests/test_general.py | 12 +- intranet/apps/eighth/urls.py | 221 ++++++++------- intranet/apps/eighth/views/admin/blocks.py | 2 +- intranet/apps/eighth/views/admin/groups.py | 9 +- intranet/apps/eighth/views/admin/hybrid.py | 3 +- intranet/apps/eighth/views/admin/rooms.py | 6 +- .../apps/eighth/views/admin/scheduling.py | 2 +- intranet/apps/eighth/views/attendance.py | 261 +++++++++--------- intranet/apps/eighth/views/monitoring.py | 2 +- intranet/apps/eighth/views/signup.py | 65 +---- intranet/apps/emailfwd/urls.py | 4 +- intranet/apps/emerg/views.py | 31 ++- intranet/apps/enrichment/admin.py | 4 +- intranet/apps/enrichment/urls.py | 16 +- intranet/apps/enrichment/views.py | 2 +- intranet/apps/events/admin.py | 2 +- intranet/apps/events/tests.py | 4 +- intranet/apps/events/urls.py | 22 +- intranet/apps/features/helpers.py | 5 +- intranet/apps/features/tests.py | 10 +- intranet/apps/features/urls.py | 6 +- intranet/apps/feedback/admin.py | 4 +- intranet/apps/feedback/urls.py | 4 +- intranet/apps/feedback/views.py | 4 +- intranet/apps/files/urls.py | 6 +- intranet/apps/files/views.py | 7 +- intranet/apps/groups/tests.py | 6 +- intranet/apps/groups/urls.py | 4 +- intranet/apps/itemreg/admin.py | 8 +- intranet/apps/itemreg/urls.py | 6 +- intranet/apps/logs/admin.py | 12 +- intranet/apps/logs/tests.py | 38 +-- intranet/apps/logs/urls.py | 6 +- intranet/apps/lostfound/admin.py | 6 +- .../migrations/0003_auto_20250517_1958.py | 23 ++ intranet/apps/lostfound/models.py | 2 + intranet/apps/lostfound/tasks.py | 18 ++ intranet/apps/lostfound/urls.py | 8 +- intranet/apps/lostfound/views.py | 4 +- intranet/apps/notifications/emails.py | 2 +- intranet/apps/notifications/urls.py | 12 +- ...cslapplication_allowed_origins_and_more.py | 36 +++ intranet/apps/oauth/views.py | 2 +- intranet/apps/parking/admin.py | 6 +- .../migrations/0003_auto_20160606_1448.py | 11 +- intranet/apps/parking/urls.py | 10 +- intranet/apps/polls/urls.py | 20 +- intranet/apps/polls/views.py | 2 +- intranet/apps/preferences/fields.py | 4 +- intranet/apps/preferences/tests.py | 4 +- intranet/apps/preferences/urls.py | 4 +- intranet/apps/printing/admin.py | 4 +- intranet/apps/printing/forms.py | 88 +++++- intranet/apps/printing/urls.py | 4 +- intranet/apps/printing/views.py | 43 ++- .../migrations/0012_attendance_auto_times.py | 37 +++ intranet/apps/schedule/models.py | 44 +++ intranet/apps/schedule/urls.py | 14 +- intranet/apps/schedule/views.py | 1 + intranet/apps/search/tests.py | 2 +- intranet/apps/search/urls.py | 4 +- intranet/apps/search/utils.py | 2 +- intranet/apps/seniors/urls.py | 4 +- intranet/apps/sessionmgmt/admin.py | 4 +- intranet/apps/sessionmgmt/urls.py | 10 +- intranet/apps/signage/admin.py | 6 +- intranet/apps/signage/urls.py | 6 +- intranet/apps/signage/views.py | 7 +- intranet/apps/templatetags/paginate.py | 4 +- intranet/apps/templatetags/svg.py | 55 ++++ intranet/apps/users/admin.py | 2 +- intranet/apps/users/courses_urls.py | 6 +- intranet/apps/users/models.py | 45 +-- intranet/apps/users/tests.py | 2 +- intranet/apps/welcome/urls.py | 10 +- intranet/middleware/access_log.py | 6 +- intranet/middleware/monitoring.py | 2 +- intranet/middleware/same_origin.py | 4 +- intranet/routing.py | 3 +- intranet/settings/__init__.py | 29 +- intranet/static/css/bus.scss | 4 +- intranet/static/css/dashboard.widgets.scss | 1 - intranet/static/css/page_base.scss | 76 ++++- intranet/static/css/responsive.core.scss | 9 + intranet/static/css/tjstar_ribbon.scss | 6 + intranet/static/vendor/qr-code-styling.js | 2 + intranet/static/vendor/qrcode.min.js | 1 - intranet/templates/credits.html | 3 + intranet/templates/dashboard/links.html | 4 +- intranet/templates/eighth/location.html | 3 - intranet/templates/eighth/signup.html | 11 +- intranet/templates/eighth/signup_widget.html | 18 +- .../eighth/student_submit_attendance.html | 101 ++++--- .../templates/eighth/take_attendance.html | 52 +++- intranet/templates/nav.html | 45 +++ intranet/templates/page_with_header.html | 24 +- intranet/templates/signage/pages/bus.html | 2 +- intranet/urls.py | 93 ++++--- pyproject.toml | 8 +- requirements-dev.txt | 4 +- requirements.txt | 64 ++--- scripts/build_docs.py | 100 +++++++ scripts/build_docs.sh | 6 - scripts/build_ensure_no_changes.sh | 1 + scripts/build_sources.sh | 5 - scripts/check.sh | 21 -- scripts/format.sh | 4 - scripts/lint.sh | 4 - scripts/push_docs.sh | 2 +- scripts/static_templates_format.sh | 7 - 289 files changed, 2591 insertions(+), 5130 deletions(-) create mode 100644 docs/Makefile delete mode 100644 docs/_ext/djangodocs.py delete mode 100644 docs/_templates/relations.html delete mode 100644 docs/developing/styleguide.rst create mode 100644 docs/make.bat create mode 100644 docs/requirements.txt delete mode 100644 docs/rtd-requirements.txt rename docs/{ => source}/_static/custom.css (100%) rename docs/{ => source/_static}/favicon.ico (100%) create mode 100644 docs/source/_static/logo-dark.svg create mode 100644 docs/source/_static/logo-light.svg create mode 100644 docs/source/_templates/autosummary/class.rst create mode 100644 docs/source/_templates/autosummary/module.rst rename docs/{ => source}/architecture/index.rst (100%) rename docs/{ => source}/conf.py (58%) rename CONTRIBUTING.md => docs/source/developing/contributing.md (78%) rename docs/{ => source}/developing/eighth-models.rst (100%) rename docs/{ => source}/developing/howto.rst (75%) rename docs/{ => source}/developing/index.rst (85%) rename docs/{ => source}/developing/oauth.rst (100%) rename docs/{ => source}/developing/requirements.rst (100%) create mode 100644 docs/source/developing/styleguide.rst rename docs/{ => source}/developing/testing.rst (81%) rename docs/{ => source}/developing/updates.rst (63%) rename USERNAMES.md => docs/source/developing/usernames.md (74%) rename docs/{ => source}/index.rst (98%) create mode 100644 docs/source/reference_index.rst create mode 100644 docs/source/reference_index/apps/announcements.rst create mode 100644 docs/source/reference_index/apps/api.rst create mode 100644 docs/source/reference_index/apps/auth.rst create mode 100644 docs/source/reference_index/apps/bus.rst create mode 100644 docs/source/reference_index/apps/cslapps.rst create mode 100644 docs/source/reference_index/apps/customthemes.rst create mode 100644 docs/source/reference_index/apps/dashboard.rst create mode 100644 docs/source/reference_index/apps/dataimport.rst create mode 100644 docs/source/reference_index/apps/django.rst create mode 100644 docs/source/reference_index/apps/eighth.rst create mode 100644 docs/source/reference_index/apps/emailfwd.rst create mode 100644 docs/source/reference_index/apps/emerg.rst create mode 100644 docs/source/reference_index/apps/enrichment.rst create mode 100644 docs/source/reference_index/apps/error.rst create mode 100644 docs/source/reference_index/apps/events.rst create mode 100644 docs/source/reference_index/apps/features.rst create mode 100644 docs/source/reference_index/apps/feedback.rst create mode 100644 docs/source/reference_index/apps/files.rst create mode 100644 docs/source/reference_index/apps/groups.rst create mode 100644 docs/source/reference_index/apps/itemreg.rst create mode 100644 docs/source/reference_index/apps/logs.rst create mode 100644 docs/source/reference_index/apps/lostfound.rst create mode 100644 docs/source/reference_index/apps/nomination.rst create mode 100644 docs/source/reference_index/apps/notifications.rst create mode 100644 docs/source/reference_index/apps/oauth.rst create mode 100644 docs/source/reference_index/apps/parking.rst create mode 100644 docs/source/reference_index/apps/polls.rst create mode 100644 docs/source/reference_index/apps/preferences.rst create mode 100644 docs/source/reference_index/apps/printing.rst create mode 100644 docs/source/reference_index/apps/schedule.rst create mode 100644 docs/source/reference_index/apps/search.rst create mode 100644 docs/source/reference_index/apps/seniors.rst create mode 100644 docs/source/reference_index/apps/sessionmgmt.rst create mode 100644 docs/source/reference_index/apps/signage.rst create mode 100644 docs/source/reference_index/apps/templatetags.rst create mode 100644 docs/source/reference_index/apps/users.rst create mode 100644 docs/source/reference_index/apps/welcome.rst create mode 100644 docs/source/reference_index/middleware.rst rename SECURITY.md => docs/source/security.md (97%) rename docs/{ => source}/setup/index.rst (74%) rename docs/{ => source}/setup/server.rst (100%) rename SETUP.md => docs/source/setup/setup.md (99%) delete mode 100644 docs/sourcedoc/intranet.apps.announcements.rst delete mode 100644 docs/sourcedoc/intranet.apps.api.rst delete mode 100644 docs/sourcedoc/intranet.apps.auth.management.commands.rst delete mode 100644 docs/sourcedoc/intranet.apps.auth.management.rst delete mode 100644 docs/sourcedoc/intranet.apps.auth.rst delete mode 100644 docs/sourcedoc/intranet.apps.bus.management.commands.rst delete mode 100644 docs/sourcedoc/intranet.apps.bus.management.rst delete mode 100644 docs/sourcedoc/intranet.apps.bus.rst delete mode 100644 docs/sourcedoc/intranet.apps.cslapps.management.commands.rst delete mode 100644 docs/sourcedoc/intranet.apps.cslapps.management.rst delete mode 100644 docs/sourcedoc/intranet.apps.cslapps.rst delete mode 100644 docs/sourcedoc/intranet.apps.customthemes.rst delete mode 100644 docs/sourcedoc/intranet.apps.dashboard.rst delete mode 100644 docs/sourcedoc/intranet.apps.dataimport.management.commands.rst delete mode 100644 docs/sourcedoc/intranet.apps.dataimport.management.rst delete mode 100644 docs/sourcedoc/intranet.apps.dataimport.rst delete mode 100644 docs/sourcedoc/intranet.apps.eighth.forms.admin.rst delete mode 100644 docs/sourcedoc/intranet.apps.eighth.forms.rst delete mode 100644 docs/sourcedoc/intranet.apps.eighth.management.commands.rst delete mode 100644 docs/sourcedoc/intranet.apps.eighth.management.rst delete mode 100644 docs/sourcedoc/intranet.apps.eighth.rst delete mode 100644 docs/sourcedoc/intranet.apps.eighth.tests.admin.rst delete mode 100644 docs/sourcedoc/intranet.apps.eighth.tests.rst delete mode 100644 docs/sourcedoc/intranet.apps.eighth.views.admin.rst delete mode 100644 docs/sourcedoc/intranet.apps.eighth.views.rst delete mode 100644 docs/sourcedoc/intranet.apps.emailfwd.management.commands.rst delete mode 100644 docs/sourcedoc/intranet.apps.emailfwd.management.rst delete mode 100644 docs/sourcedoc/intranet.apps.emailfwd.rst delete mode 100644 docs/sourcedoc/intranet.apps.emerg.rst delete mode 100644 docs/sourcedoc/intranet.apps.enrichment.rst delete mode 100644 docs/sourcedoc/intranet.apps.error.rst delete mode 100644 docs/sourcedoc/intranet.apps.events.management.commands.rst delete mode 100644 docs/sourcedoc/intranet.apps.events.management.rst delete mode 100644 docs/sourcedoc/intranet.apps.events.rst delete mode 100644 docs/sourcedoc/intranet.apps.features.rst delete mode 100644 docs/sourcedoc/intranet.apps.feedback.rst delete mode 100644 docs/sourcedoc/intranet.apps.files.rst delete mode 100644 docs/sourcedoc/intranet.apps.groups.rst delete mode 100644 docs/sourcedoc/intranet.apps.itemreg.rst delete mode 100644 docs/sourcedoc/intranet.apps.itemreg.templatetags.rst delete mode 100644 docs/sourcedoc/intranet.apps.logs.rst delete mode 100644 docs/sourcedoc/intranet.apps.lostfound.rst delete mode 100644 docs/sourcedoc/intranet.apps.nomination.rst delete mode 100644 docs/sourcedoc/intranet.apps.notifications.rst delete mode 100644 docs/sourcedoc/intranet.apps.oauth.rst delete mode 100644 docs/sourcedoc/intranet.apps.parking.rst delete mode 100644 docs/sourcedoc/intranet.apps.polls.rst delete mode 100644 docs/sourcedoc/intranet.apps.preferences.rst delete mode 100644 docs/sourcedoc/intranet.apps.printing.rst delete mode 100644 docs/sourcedoc/intranet.apps.rst delete mode 100644 docs/sourcedoc/intranet.apps.schedule.management.commands.rst delete mode 100644 docs/sourcedoc/intranet.apps.schedule.management.rst delete mode 100644 docs/sourcedoc/intranet.apps.schedule.rst delete mode 100644 docs/sourcedoc/intranet.apps.search.rst delete mode 100644 docs/sourcedoc/intranet.apps.seniors.management.commands.rst delete mode 100644 docs/sourcedoc/intranet.apps.seniors.management.rst delete mode 100644 docs/sourcedoc/intranet.apps.seniors.rst delete mode 100644 docs/sourcedoc/intranet.apps.sessionmgmt.rst delete mode 100644 docs/sourcedoc/intranet.apps.signage.rst delete mode 100644 docs/sourcedoc/intranet.apps.signage.templatetags.rst delete mode 100644 docs/sourcedoc/intranet.apps.templatetags.rst delete mode 100644 docs/sourcedoc/intranet.apps.users.management.commands.rst delete mode 100644 docs/sourcedoc/intranet.apps.users.management.rst delete mode 100644 docs/sourcedoc/intranet.apps.users.rst delete mode 100644 docs/sourcedoc/intranet.apps.users.templatetags.rst delete mode 100644 docs/sourcedoc/intranet.apps.welcome.rst delete mode 100644 docs/sourcedoc/intranet.middleware.rst delete mode 100644 docs/sourcedoc/intranet.rst delete mode 100644 docs/sourcedoc/intranet.settings.rst delete mode 100644 docs/sourcedoc/intranet.test.rst delete mode 100644 docs/sourcedoc/intranet.utils.rst delete mode 100644 docs/sourcedoc/modules.rst create mode 100644 intranet/apps/eighth/migrations/0072_alter_eighthscheduledactivity_waitlist.py rename intranet/apps/eighth/migrations/{0072_auto_20250429_1128.py => 0073_eighth_attendance_code.py} (97%) create mode 100644 intranet/apps/lostfound/migrations/0003_auto_20250517_1958.py create mode 100644 intranet/apps/lostfound/tasks.py create mode 100644 intranet/apps/oauth/migrations/0010_cslapplication_allowed_origins_and_more.py create mode 100644 intranet/apps/schedule/migrations/0012_attendance_auto_times.py create mode 100644 intranet/apps/templatetags/svg.py create mode 100644 intranet/static/vendor/qr-code-styling.js delete mode 100644 intranet/static/vendor/qrcode.min.js create mode 100755 scripts/build_docs.py delete mode 100755 scripts/build_docs.sh delete mode 100755 scripts/build_sources.sh delete mode 100755 scripts/check.sh delete mode 100755 scripts/format.sh delete mode 100755 scripts/lint.sh delete mode 100755 scripts/static_templates_format.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bfa3110fe71..b91b8e37200 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ "strategy": { "matrix": { "python-version": [ - 3.8 + 3.13 ] }, "fail-fast": false @@ -47,7 +47,7 @@ }, { "name": "Set up packages", - "run": "set -e\n\npip install -U pip setuptools wheel\npip install -U \\\n pre-commit coveralls pyyaml pytest-django\npip install -U -r requirements.txt\n\necho \"PATH=$PATH\" >> $GITHUB_ENV\n" + "run": "set -e\n\nsudo apt-get update\nsudo apt-get install -y libfreetype6-dev\n\npip install -U pip setuptools wheel\npip install -U \\\n pre-commit coveralls pyyaml pytest-django\npip install -U -r requirements.txt\n\necho \"PATH=$PATH\" >> $GITHUB_ENV\n" }, { "name": "Copy secret.py", @@ -64,7 +64,7 @@ "strategy": { "matrix": { "python-version": [ - 3.8 + 3.13 ] }, "fail-fast": false @@ -94,7 +94,7 @@ }, { "name": "Set up packages", - "run": "set -e\n\npip install -U pip setuptools wheel\npip install -U \\\n pre-commit coveralls pyyaml pytest-django\npip install -U -r requirements.txt\n\necho \"PATH=$PATH\" >> $GITHUB_ENV\n" + "run": "set -e\n\nsudo apt-get update\nsudo apt-get install -y libfreetype6-dev\n\npip install -U pip setuptools wheel\npip install -U \\\n pre-commit coveralls pyyaml pytest-django\npip install -U -r requirements.txt\n\necho \"PATH=$PATH\" >> $GITHUB_ENV\n" }, { "name": "Copy secret.py", @@ -118,13 +118,21 @@ "if": "github.event_name == 'push' && (github.repository_owner != 'tjcsl' || github.ref != 'refs/heads/master' || github.ref != 'refs/heads/dev')", "run": "git fetch origin ${{ github.event.before }} && ./scripts/validate-commit-messages.py ${{ github.event.before }}..${{ github.event.after }}" }, + { + "name": "Install docs dependencies", + "run": "# sphinxcontrib_django imports our django project, so we need the deps\npip install -U -r requirements.txt\ncd docs\npip install -U -r requirements.txt\n" + }, + { + "name": "Check docs build up to date", + "run": "./scripts/build_ensure_no_changes.sh python3 scripts/build_docs.py" + }, { "name": "Build docs", - "run": "./scripts/build_ensure_no_changes.sh ./scripts/build_docs.sh" + "run": "cd docs\nmake html\n" }, { "name": "Push docs", - "if": "github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository_owner == 'tjcsl' && matrix.python-version == 3.8", + "if": "github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository_owner == 'tjcsl' && matrix.python-version == 3.13", "run": "./scripts/push_docs.sh", "env": { "GH_TOKEN": "${{ secrets.DOCS_GH_TOKEN }}" @@ -137,7 +145,7 @@ "strategy": { "matrix": { "python-version": [ - 3.8 + 3.13 ], "node-version": [ "14.x" @@ -195,7 +203,7 @@ }, { "name": "Set up packages", - "run": "set -e\n\npip install -U pip setuptools wheel\npip install -U \\\n pre-commit coveralls pyyaml pytest-django\npip install -U -r requirements.txt\n\necho \"PATH=$PATH\" >> $GITHUB_ENV\n" + "run": "set -e\n\nsudo apt-get update\nsudo apt-get install -y libfreetype6-dev\n\npip install -U pip setuptools wheel\npip install -U \\\n pre-commit coveralls pyyaml pytest-django\npip install -U -r requirements.txt\n\necho \"PATH=$PATH\" >> $GITHUB_ENV\n" }, { "name": "Copy secret.py", diff --git a/.gitignore b/.gitignore index ebb85391f66..16769fb6162 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ Pipfile build .coverage dist/*.tar.gz +docs/source/reference # General ignores *.crt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 10ca460affa..bbf3787c036 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,7 @@ repos: - id: check-yaml - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.5 + rev: v0.12.3 hooks: - id: ruff args: [ "--fix", "--exit-non-zero-on-fix" ] @@ -38,6 +38,3 @@ repos: hooks: - id: codespell files: ^.*\.(py|md|rst)$ - # TODO: Remove after python version >= 3.11 - additional_dependencies: - - tomli diff --git a/README.rst b/README.rst index f868b6130e1..3913cbc66ac 100644 --- a/README.rst +++ b/README.rst @@ -18,12 +18,12 @@ Documentation (in RestructuredText format) is available inside the "docs" folder **What does the TJ Intranet do?** Ion allows students, teachers, and staff at TJHSST to access student information, manage activity signups for TJ's Eighth Period program, and view information on news and events. `Read more about how Ion is used at Thomas Jefferson `_. -**Ion now requires Python 3.8+** Python 3.8.5 is currently used in both production and testing environments. +**Ion now requires Python 3.13+** Python 3.13.5 is currently used in both production and testing environments. -**How can I create a testing environment?** Read the section on `Setting up a Dev Environment `_ in the documentation. +**How can I create a testing environment?** Read the section on `Setting up a Dev Environment `_ in the documentation. **How can I contribute to Ion? How can I report a bug? How can I report a security issue/vulnerability?** -Please read our `contributing guide `_ for more information. +Please read our `contributing guide `_ for more information. -Current Intranet maintainers: `alanzhu0 `_ and `NotFish232 `_ +Current Intranet maintainers: `aarushtools `_ and `shrysjain `_ diff --git a/ci/spec.yml b/ci/spec.yml index d5eaf34eb78..9d154b1d934 100644 --- a/ci/spec.yml +++ b/ci/spec.yml @@ -23,7 +23,7 @@ env: # and change that version in the "if" to the latest version # you put here. python_versions: &python_versions - - 3.8 + - 3.13 node_versions: &node_versions - 14.x @@ -55,6 +55,9 @@ env: run: | set -e + sudo apt-get update + sudo apt-get install -y libfreetype6-dev + pip install -U pip setuptools wheel pip install -U \ pre-commit coveralls pyyaml pytest-django @@ -119,11 +122,24 @@ jobs: run: 'git fetch origin ${{ github.event.before }} && ./scripts/validate-commit-messages.py ${{ github.event.before }}..${{ github.event.after }}' # Build/push docs + - name: Install docs dependencies + run: | + # sphinxcontrib_django imports our django project, so we need the deps + pip install -U -r requirements.txt + cd docs + pip install -U -r requirements.txt + + - name: Check docs build up to date + run: ./scripts/build_ensure_no_changes.sh python3 scripts/build_docs.py + - name: Build docs - run: ./scripts/build_ensure_no_changes.sh ./scripts/build_docs.sh + run: | + cd docs + make html + - name: Push docs # Only run for pushes to the main Ion repo - if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository_owner == 'tjcsl' && matrix.python-version == 3.8 + if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository_owner == 'tjcsl' && matrix.python-version == 3.13 run: ./scripts/push_docs.sh env: GH_TOKEN: ${{ secrets.DOCS_GH_TOKEN }} diff --git a/config/docker/Dockerfile b/config/docker/Dockerfile index 2929ecf8aee..faa07affb74 100644 --- a/config/docker/Dockerfile +++ b/config/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8.19-alpine3.18 +FROM python:3.13-alpine3.22 ENV TZ America/New_York ENV C_FORCE_ROOT true @@ -8,7 +8,7 @@ COPY requirements.txt . COPY requirements-dev.txt . RUN apk update && \ - apk add bash git curl build-base libpq-dev freetype-dev libffi-dev ruby-full libmagic krb5 kinit rsync nodejs npm tzdata libxml2-dev libxslt-dev && \ + apk add bash git curl build-base libpq-dev freetype-dev libffi-dev ruby-full libmagic krb5 rsync nodejs npm tzdata libxml2-dev libxslt-dev && \ npm install -g sass && \ ln -s /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone && \ pip3 install -Ir requirements.txt && \ diff --git a/config/docker/secret.py b/config/docker/secret.py index 284aef54428..fc6133f5228 100644 --- a/config/docker/secret.py +++ b/config/docker/secret.py @@ -7,9 +7,9 @@ CACHEOPS_REDIS = {"host": "redis", "port": 6379, "db": 1, "socket_timeout": 1} CACHES = { "default": { - "BACKEND": "redis_cache.RedisCache", - "LOCATION": "redis:6379", - "OPTIONS": {"PARSER_CLASS": "redis.connection.HiredisParser", "PICKLE_VERSION": 4}, + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://redis:6379", + "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient", "PICKLE_VERSION": 4 }, "KEY_PREFIX": "ion", } } diff --git a/config/scripts/create_users.py b/config/scripts/create_users.py index 78c85baa93f..449230c0af4 100644 --- a/config/scripts/create_users.py +++ b/config/scripts/create_users.py @@ -5,10 +5,9 @@ import random from datetime import datetime -import names -from dateutil.relativedelta import relativedelta - import django +from dateutil.relativedelta import relativedelta +from faker import Faker os.environ.setdefault("DJANGO_SETTINGS_MODULE", "intranet.settings") django.setup() @@ -18,6 +17,8 @@ GRADES = ["freshman", "sophomore", "junior", "senior"] +fake = Faker() + def grade_to_year(year: str) -> str: delta = 0 @@ -103,7 +104,15 @@ def generate_names(args: argparse.Namespace) -> "list[tuple[str]]": last_name = name username += name else: - first_name, last_name = names.get_full_name(gender=args.gender).split(" ") + if args.gender == "male": + first_name = fake.first_name_male() + last_name = fake.last_name_male() + elif args.gender == "female": + first_name = fake.first_name_female() + last_name = fake.last_name_female() + else: + first_name = fake.first_name() + last_name = fake.last_name() username += first_name[0].lower() + last_name[:7].lower() args.names[i] = (first_name, last_name, username) return args.names diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000000..23091170e63 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,24 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= -j auto +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +cleanall: clean + @echo "Cleaning all references" + @rm source/reference/* + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py deleted file mode 100644 index 07897109504..00000000000 --- a/docs/_ext/djangodocs.py +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 -*- -def setup(app): - """Setup for djangodocs.""" - app.add_crossref_type( - directivename="setting", - rolename="setting", - indextemplate="pair: %s; setting", - ) diff --git a/docs/_templates/relations.html b/docs/_templates/relations.html deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/docs/developing/styleguide.rst b/docs/developing/styleguide.rst deleted file mode 100644 index 15abe4fc1f2..00000000000 --- a/docs/developing/styleguide.rst +++ /dev/null @@ -1,107 +0,0 @@ -****************** -Coding Style Guide -****************** - -Follow `PEP8 `_ (the official style guide for Python). Most PEP8 formatting conventions are enforced in the build by ``pylint``, ``flake8``, and a combination of ``black``, ``autopep8``, and ``isort``. Therefore, if you do not follow them, the build may not pass. - -However, for Ion, we limit the lengths of lines to 150 characters, not 80 characters. - -Note: CSS/JS/template formatting is enforced by ``scripts/static_templates_format.sh``. Currently, this just strips trailing whitespace. - -Main points -=========== - -- Indent using 4 spaces. -- Use underscores in favor of camel case for all names except the names of classes. -- Limit the line length of docstrings or comments to 72 characters. -- Separate top-level functions and class definitions with two blank lines. -- Separate method definitions inside a class with a single blank line. -- Use two spaces before inline comments and one space between the pound sign and comment. -- Use a plugin for your text editor to check for/remind you of PEP8 conventions. -- When in doubt, running ``./scripts/format.sh`` will fix a lot of things. -- Capitalize and punctuate comments and Git commit messages properly. - -What is enforced in the build -============================= - -At the time of this writing, the GitHub Actions build runs the following commands: - -.. code-block:: sh - - flake8 --max-line-length 150 --exclude=*/migrations/* . - pylint --jobs=0 --disable=fixme,broad-except,global-statement,attribute-defined-outside-init intranet/ - isort --check --recursive intranet - ./scripts.format.sh - ./scripts/static_templates_format.sh # Static/template files - -Note: When the ``./scripts/format.sh`` and ``./scripts/static_templates_format.sh`` checks are run, the build will fail if they have to make any changes. - -``flake8`` is a PEP8 style checker, ``pylint`` is a linter (but it also enforces some PEP8 conventions), and ``isort``, when called with these options, checks that all imports are sorted alphabetically. - -``./scripts/format.sh`` runs ``black intranet && autopep8 --in-place --recursive intranet && isort --recursive intranet``. The reason for the multiple commands is that ``black`` introduces certain formatting changes which ``flake8``/``pylint`` do not agree with (and offers no options to change them), so we have ``autopep8`` fix it. - -It is recommended that you run all of these locally before opening a pull request (though the Ion developers sometimes skip running the ``pylint`` check locally because it takes a long time to run). All of them are intended to be run from the root directory of the Git repository. - -If ``flake8`` or ``pylint`` throw errors, the error messages are usually human-readable. if ``isort`` gives any errors, you can have it automatically correct the order of all imports by running ``isort --recursive intranet``. If the build fails because running ``scripts/format.sh`` resulted in changes, you can simply run ``./scripts/format.sh`` to fix your formatting. - -Imports -======= - -- Group imports in the following order: - #. Standard library imports - #. Third-party imports - #. Imports from Django - #. Local imports - -- Within these groups, place ``from ... import ...`` imports after ``import ...`` imports, and order imports alphabetically within *those* groups. - -- Avoid using ``from ... import *``. - -- Explicitly import each module used. - -- Use relative imports to avoid hardcoding a module's package name. This greatly improves portability and is useful when importing from another module in the current app. - -Examples --------- - -Standard library imports: - -.. code-block:: python - - from math import sqrt - from os.path import abspath - -Core Django imports: - -.. code-block:: python - - from django.db import models - -Third-party app imports: - -.. code-block:: python - - from django_extensions.db.models import TimeStampedModel - -Good: - -.. code-block:: python - - from .models import SomeModel # explicit relative import - from otherdjangoapp.models import OtherModel # absolute import - -Bad: - -.. code-block:: python - - # intranet/apps/users/views.py - from intranet.apps.users.models import MyModel # absolute import within same package - - -References -========== - -- `Google Python Style Guide `_. -- `Google HTML/CSS Style Guide `_. -- `Google Javascript Style Guide `_. -- `PEP8: Official Python Style Guide `_. diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000000..dc1312ab09c --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000000..564c2860c28 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,4 @@ +furo +myst-parser +sphinx-copybutton +sphinxcontrib-django diff --git a/docs/rtd-requirements.txt b/docs/rtd-requirements.txt deleted file mode 100644 index 71bbe37d0a4..00000000000 --- a/docs/rtd-requirements.txt +++ /dev/null @@ -1,68 +0,0 @@ -argon2-cffi==21.3.0 -autobahn==22.7.1 -Babel==2.10.3 -bcrypt==4.0.0 -beautifulsoup4==4.11.1 -bleach==5.0.1 -celery==5.2.7 -certifi==2024.07.04 -channels==3.0.5 -channels-redis==3.4.1 -contextlib2==21.6.0 -cryptography==42.0.4 -decorator==5.1.1 -Django==3.2.25 -django-cacheops==7.0.1 -django-cors-headers==3.13.0 -django-debug-toolbar==3.6.0 -django-extensions==3.2.0 -django-formtools==2.3 -django-inline-svg==0.1.1 -django-maintenance-mode==0.16.3 -django-oauth-toolkit==2.3.0 -django-pipeline==2.0.9 -django-prometheus==2.2.0 -django-redis-cache==3.0.1 -django-redis-sessions==0.6.2 -django-referrer-policy==1.0 -django-requestlogging-redux==1.2.1 -django-simple-history==3.1.1 -django-user-agents==0.4.0 -django-widget-tweaks==1.4.12 -djangorestframework==3.14.0 -docutils==0.19 -Fabric3==1.14.post1 -flower==1.2.0 -gunicorn==22.0.0 -hiredis==2.0.0 -ipython==8.10.0 -isort==5.12.0 -lxml==4.9.1 -objgraph==3.5.0 -pexpect==4.8.0 -prometheus-client==0.17.0 -psycopg2==2.9.9 -pycryptodome==3.19.1 -pyrankvote==2.0.5 -pysftp==0.2.9 -python-dateutil==2.8.2 -python-magic==0.4.27 -python-pam==2.0.2 -reportlab==3.6.13 -requests==2.32.0 -requests-oauthlib==1.3.1 -sentry-sdk==1.15.0 -setuptools-git==1.2 -six==1.16.0 -Sphinx==5.2.3 -sphinx-bootstrap-theme==0.8.1 -tblib==1.7.0 -vine==5.0.0 -xhtml2pdf==0.2.11 - -# Not direct dependencies, but need to be bumped for some reason -# (for example, bug or security fixes) -asgiref>=3.3.4 -pillow>=9.0.0 -tinycss2 -twisted>=21.7.0 diff --git a/docs/_static/custom.css b/docs/source/_static/custom.css similarity index 100% rename from docs/_static/custom.css rename to docs/source/_static/custom.css diff --git a/docs/favicon.ico b/docs/source/_static/favicon.ico similarity index 100% rename from docs/favicon.ico rename to docs/source/_static/favicon.ico diff --git a/docs/source/_static/logo-dark.svg b/docs/source/_static/logo-dark.svg new file mode 100644 index 00000000000..42b1477cb1c --- /dev/null +++ b/docs/source/_static/logo-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/source/_static/logo-light.svg b/docs/source/_static/logo-light.svg new file mode 100644 index 00000000000..cfa12c9cc10 --- /dev/null +++ b/docs/source/_static/logo-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/source/_templates/autosummary/class.rst b/docs/source/_templates/autosummary/class.rst new file mode 100644 index 00000000000..7ef38d10824 --- /dev/null +++ b/docs/source/_templates/autosummary/class.rst @@ -0,0 +1,34 @@ +{{ name | escape | underline}} + +Qualified name: ``{{ fullname | escape }}`` + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + :show-inheritance: + :members: + :private-members: + + + {% block methods %} + {%- if methods %} + .. rubric:: {{ _('Methods') }} + + .. autosummary:: + :nosignatures: + {% for item in methods if item != '__init__' and item[0] != '_' and item not in inherited_members %} + ~{{ name }}.{{ item }} + {%- endfor %} + {%- endif %} + {%- endblock %} + + {% block attributes %} + {%- if attributes %} + .. rubric:: {{ _('Attributes') }} + + .. autosummary:: + {% for item in attributes if item[0] != '_' and item not in inherited_members %} + ~{{ name }}.{{ item }} + {%- endfor %} + {%- endif %} + {% endblock %} diff --git a/docs/source/_templates/autosummary/module.rst b/docs/source/_templates/autosummary/module.rst new file mode 100644 index 00000000000..8aba97b3ba4 --- /dev/null +++ b/docs/source/_templates/autosummary/module.rst @@ -0,0 +1,52 @@ +{{ name | escape | underline }} + +.. currentmodule:: {{ fullname }} + +.. automodule:: {{ fullname }} + + {% block classes %} + {% if classes %} + .. rubric:: Classes + + .. autosummary:: + :toctree: . + :nosignatures: + {% for class in classes %} + {{ class }} + {% endfor %} + {% endif %} + {% endblock %} + + {% block functions %} + {% if functions %} + .. rubric:: {{ _('Functions') }} + + {% for item in functions %} + .. autofunction:: {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block exceptions %} + {% if exceptions %} + .. rubric:: {{ _('Exceptions') }} + + .. autosummary:: + {% for item in exceptions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + +{% block modules %} +{% if modules %} +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: +{% for item in modules %} + {{ item }} +{%- endfor %} +{% endif %} +{% endblock %} diff --git a/docs/architecture/index.rst b/docs/source/architecture/index.rst similarity index 100% rename from docs/architecture/index.rst rename to docs/source/architecture/index.rst diff --git a/docs/conf.py b/docs/source/conf.py similarity index 58% rename from docs/conf.py rename to docs/source/conf.py index ca745334770..f13d9ab5c9b 100644 --- a/docs/conf.py +++ b/docs/source/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # TJ Intranet documentation build configuration file, created by # sphinx-quickstart on Wed Jan 7 14:37:16 2015. # @@ -15,42 +13,33 @@ import os import sys from datetime import datetime -from unittest import mock - -import sphinx_bootstrap_theme +from pathlib import Path -import django +# -- Add to sys.path ------------------------------------------------------ +ION_ROOT = Path(__file__).parent.parent.parent.resolve() +sys.path.insert(0, os.fspath(ION_ROOT)) -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = ["sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinx.ext.viewcode", "sphinx.ext.napoleon", "djangodocs"] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# The suffix of source filenames. -source_suffix = ".rst" - -# The encoding of source files. -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = "index" - # General information about the project. project = "TJ Intranet" -copyright = "{}, TJ Intranet Development Team".format(datetime.now().year) +copyright = f"{datetime.now().year}, TJ Intranet Development Team" + +django_settings = "intranet.settings" + +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.intersphinx", + "sphinx.ext.extlinks", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", + "sphinx_copybutton", + "myst_parser", + "sphinxcontrib_django", +] + +autosummary_generate = True # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -62,100 +51,76 @@ release = "v3" if "GITHUB_RUN_NUMBER" in os.environ: - release += " (Build #{})".format(os.environ["GITHUB_RUN_NUMBER"]) + release += f" (Build #{os.environ['GITHUB_RUN_NUMBER']})" -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# language = None -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] -# The reST default role (used for this markup: `text`) to use for all -# documents. -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True +# We depend on sphinxcontrib_django for intersphinx mappings -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" +extlinks = { + "issue": ("https://github.com/tjcsl/ion/issues/%s", "issue %s"), + "pr": ("https://github.com/tjcsl/ion/pull/%s", "pr #%s"), +} +# warn hardcoded links +extlinks_detect_hardcoded_links = True -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] +source_suffix = [".rst", ".md"] -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = "bootstrap" +html_theme = "furo" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { - # Navigation bar title. (Default: ``project`` value) - # 'navbar_title': "", - # Tab name for entire site. (Default: "Site") - "navbar_site_name": "Topics", - "navbar_pagenav_name": "Section", - # Global TOC depth for "site" navbar tab. (Default: 1) - # Switching to -1 shows all levels. - "globaltoc_depth": 2, - # Include hidden TOCs in Site navbar? - # - # Note: If this is "false", you cannot have mixed ``:hidden:`` and - # non-hidden ``toctree`` directives in the same page, or else the build - # will break. - # - # Values: "true" (default) or "false" - "globaltoc_includehidden": "true", - # HTML navbar class (Default: "navbar") to attach to
element. - # For black navbar, do "navbar navbar-inverse" - "navbar_class": "navbar", - # Fix navigation bar to top of page? - # Values: "true" (default) or "false" - "navbar_fixed_top": "true", - # Location of link to source. - # Options are "nav" (default), "footer" or anything else to exclude. - "source_link_position": "footer", - # Bootswatch (http://bootswatch.com/) theme. - # - # Options are nothing with "" (default) or the name of a valid theme - # such as "amelia" or "cosmo". - # - # Note that this is served off CDN, so won't be available offline. - "bootswatch_theme": "pulse", - "bootstrap_version": "3", + "source_repository": "https://github.com/tjcsl/ion/", + "source_branch": "dev", + "source_directory": "docs/source/", + "light_logo": "logo-light.svg", + "dark_logo": "logo-dark.svg", + "sidebar_hide_name": True, + "light_css_variables": { + "color-content-foreground": "#000000", + "color-background-primary": "#ffffff", + "color-background-border": "#ffffff", + "color-sidebar-background": "#f8f9fb", + "color-brand-content": "#1c00e3", + "color-brand-primary": "#192bd0", + "color-link": "#c93434", + "color-link--hover": "#5b0000", + "color-inline-code-background": "#f6f6f6;", + "color-foreground-secondary": "#000", + }, + "dark_css_variables": { + "color-content-foreground": "#ffffffd9", + "color-background-primary": "#131416", + "color-background-border": "#303335", + "color-sidebar-background": "#1a1c1e", + "color-brand-content": "#2196f3", + "color-brand-primary": "#007fff", + "color-link": "#51ba86", + "color-link--hover": "#9cefc6", + "color-inline-code-background": "#262626", + "color-foreground-secondary": "#ffffffd9", + }, } -# Add any paths that contain custom themes here, relative to this directory. -html_theme_path = sphinx_bootstrap_theme.get_html_theme_path() - # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -# html_title = None +html_title = "TJHSST Intranet" # A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None +html_short_title = "Ion" # The name of an image file (relative to this directory) to place at the top # of the sidebar. @@ -294,11 +259,6 @@ # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = { - "python": ("https://docs.python.org/3", None), - "django": ("https://docs.djangoproject.com/en/dev", "https://docs.djangoproject.com/en/dev/_objects/"), -} autodoc_inherit_docstrings = False @@ -314,28 +274,6 @@ napoleon_use_param = True napoleon_use_rtype = True -# -- Django Setup ------------------------------------------------------------- - -# add project root -sys.path.append(os.path.dirname(os.path.dirname(__file__))) - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "intranet.settings") - -# Don't spin up a ldap server when building docs. -sys.modules["ldap_test"] = mock.MagicMock() - -# Django docs -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "_ext"))) - -django.setup() - - -def skip(app, what, name, obj, skip, options): - """Methods to skip when generating documentation from docstrings.""" - if name in ("__weakref__", "__dict__", "base_fields", "media", "history_object"): - return True - return skip - - -def setup(app): - app.connect("autodoc-skip-member", skip) +napoleon_custom_sections = [ + ("WIDGETS", "params_style"), +] diff --git a/CONTRIBUTING.md b/docs/source/developing/contributing.md similarity index 78% rename from CONTRIBUTING.md rename to docs/source/developing/contributing.md index c0217802820..cd4d039bf37 100644 --- a/CONTRIBUTING.md +++ b/docs/source/developing/contributing.md @@ -8,13 +8,13 @@ If you are a TJHSST student, you can join the `#ion` channel on the [TJ CSL Slac - Please use the templates. If none of them is a perfect match, choose the closest one. ## Security Vulnerabilities & Responsible Disclosure -Please see [our security policy](SECURITY.md). Do not report security vulnerabilities in the public issue tracker. +Please see [our security policy](#security-policy). Do not report security vulnerabilities in the public issue tracker. ## Pull Requests - All PRs should target `dev`, not `master`. - If the change is anything more than a simple typo or a fairly obvious fix, please [set up a development environment](docs/setup/vagrant.rst) and test the change there before submitting a PR. -- It is strongly recommended that you [run `flake8`, `pylint`, `isort`, `scripts/format.sh`](docs/developing/styleguide.rst#what-is-enforced-in-the-build), and [the test suite](docs/developing/testing.rst#running-tests) to ensure that the build will pass. Please also read the entire [style guide](docs/developing/styleguide.rst). -- Please read [Formatting commit messages](docs/developing/howto.rst#formatting-commit-messages). +- It is strongly recommended that you [run `pre-commit`](#pre_commit), and [the test suite](#running-tests) to ensure that the build will pass. Please also read the entire [style guide](styleguide.rst). +- Please read [Formatting commit messages](#formatting_commits). - If your PR closes an issue, include "Closes #XXX" or similar in the messages of the commits that close each issue so the issues will be [automatically closed](https://help.github.com/en/articles/closing-issues-using-keywords) when the commits are merged into `master`. Note that including this text in your PR's description will have no effect because the PR will be merged into `dev`, not `master`, so GitHub does not close the issue. You must add the auto-closing keywords to the *commit* messages. - Keep each commit/PR minimal. diff --git a/docs/developing/eighth-models.rst b/docs/source/developing/eighth-models.rst similarity index 100% rename from docs/developing/eighth-models.rst rename to docs/source/developing/eighth-models.rst diff --git a/docs/developing/howto.rst b/docs/source/developing/howto.rst similarity index 75% rename from docs/developing/howto.rst rename to docs/source/developing/howto.rst index 9220a86ef04..e13a5c54669 100644 --- a/docs/developing/howto.rst +++ b/docs/source/developing/howto.rst @@ -5,31 +5,40 @@ How to do Ion development Formatting your code ==================== -You can run ``./scripts/format.sh`` to automatically format your Python code to adhere to PEP8 standards. +You can run ``pre-commit run --all-files`` to automatically format your Python code to adhere to PEP8 standards. Updating the dev branch ======================= -- git fetch --all -- git checkout dev -- git pull origin dev (note that if dev has been rebased, this may cause a conflict) +- ``git fetch --all`` +- ``git checkout dev`` +- ``git pull origin dev`` (note that if dev has been rebased, this may cause a conflict) - if dev has been rebased, make *absolutely* sure that you don't have any local changes - and run git reset --hard origin/dev this *will* destroy any local changes you have made to dev. -- git push + and run ``git reset --hard origin/dev`` + + .. danger:: + + This *will* destroy any local changes you have made to ``dev``. + +- ``git push`` Fixing build ============ -You can run ``./deploy`` to fix or find most of your build problems. If you add a new file, you may need to run ``./setup.py egg_info``. +You can run ``./deploy`` to fix or find most of your build problems. Viewing documentation locally ============================= -You can view the documentation locally by running the commands below in the virtual machine. +You can view the documentation locally by running:: + + make html + +and then opening the file ``build/html/index.html`` in your browser. + -- ./scripts/build_docs.sh -- cd build/sphinx/html -- python -m http.server 8080 + +.. _formatting_commits: Formatting commit messages ========================== @@ -37,7 +46,9 @@ Formatting commit messages Effective June 2019, the Ion team uses the `Conventional Commit specification `_ to format commit messages. This means that all commit messages should be structured as follows + .. code-block:: text + [optional scope]: [optional body] @@ -58,7 +69,9 @@ The types that we use are: - ``test``: Adding missing tests or correcting existing tests For example, if a commit adds a new feature to the ``emailfwd`` app, a good commit message would be: + .. code-block:: text + feat(emailfwd): add email confirmation functionality Fixes #1 diff --git a/docs/developing/index.rst b/docs/source/developing/index.rst similarity index 85% rename from docs/developing/index.rst rename to docs/source/developing/index.rst index deedcd3e2a6..9935e3ddc09 100644 --- a/docs/developing/index.rst +++ b/docs/source/developing/index.rst @@ -3,6 +3,7 @@ Developing for Intranet *********************** .. toctree:: + :maxdepth: 2 :glob: * diff --git a/docs/developing/oauth.rst b/docs/source/developing/oauth.rst similarity index 100% rename from docs/developing/oauth.rst rename to docs/source/developing/oauth.rst diff --git a/docs/developing/requirements.rst b/docs/source/developing/requirements.rst similarity index 100% rename from docs/developing/requirements.rst rename to docs/source/developing/requirements.rst diff --git a/docs/source/developing/styleguide.rst b/docs/source/developing/styleguide.rst new file mode 100644 index 00000000000..3221813fa02 --- /dev/null +++ b/docs/source/developing/styleguide.rst @@ -0,0 +1,96 @@ +****************** +Coding Style Guide +****************** + +Follow `PEP8 `_ (the official style guide for Python). +However, if you want, you can use your own personal style guide, and then use ``pre-commit`` to format before committing. + +.. caution:: + + The CI will fail if the code is not formatted according to ``pre-commit``, so it's recommendded to use ``pre-commit``. + +Main points +=========== + +- Indent using 4 spaces. +- Use underscores in favor of camel case for all names. For classes use PascalCase. +- Limit the line length of docstrings or comments to 72 characters. +- Separate top-level functions and class definitions with two blank lines. +- Separate method definitions inside a class with a single blank line. +- Use two spaces before inline comments and one space between the pound sign and comment. +- Use a plugin for your text editor to check for/remind you of PEP8 conventions. +- When in doubt, running ``pre-commit run --all-files`` will fix a lot of things. +- Capitalize and punctuate comments and Git commit messages properly. + + +.. _pre_commit: + +What is enforced in the build +============================= + +At the time of this writing, the GitHub Actions build runs the following commands: + +.. code-block:: bash + + pre-commit run --all-files + + +It will fail if it has to make changes to the code. + +The ``pre-commit`` config should run ``ruff``, ``codespell``, and some other linters. Most of the time it should autofix problems, +but there may be some linting errors that require manual intervention. + +Imports +======= + +- Avoid using ``from ... import *``. + +- Explicitly import each module used. + +- Use relative imports to avoid hardcoding a module's package name. This greatly improves portability and is useful when importing + from another module in the current app. + +Examples +-------- + +Standard library imports: + +.. code-block:: python + + from math import sqrt + from os.path import abspath + +Core Django imports: + +.. code-block:: python + + from django.db import models + +Third-party app imports: + +.. code-block:: python + + from django_extensions.db.models import TimeStampedModel + +Good: + +.. code-block:: python + + from .models import SomeModel # explicit relative import + from otherdjangoapp.models import OtherModel # absolute import + +Bad: + +.. code-block:: python + + # intranet/apps/users/views.py + from intranet.apps.users.models import MyModel # absolute import within same package + + +References +========== + +- `Google Python Style Guide `_. +- `Google HTML/CSS Style Guide `_. +- `Google Javascript Style Guide `_. +- `PEP8: Official Python Style Guide `_. diff --git a/docs/developing/testing.rst b/docs/source/developing/testing.rst similarity index 81% rename from docs/developing/testing.rst rename to docs/source/developing/testing.rst index 5896d7181d4..a4335c1a5ea 100644 --- a/docs/developing/testing.rst +++ b/docs/source/developing/testing.rst @@ -9,6 +9,8 @@ Unit Tests For most modules, the unit tests go in ``intranet/apps//tests.py``. Currently, the sole exception is ``eighth``, where tests are broken out into several different files under ``intranet/apps/eighth/tests/``. Testing functionality that is useful for multiple tests can be found in ``intranet/test``. +.. _running-tests: + Running Tests ============= @@ -35,7 +37,13 @@ Looking at pre-existing tests can give you a good idea how to structure your tes Every test should include comments that explain, almost in narrative form, what the test is doing and what the expected results are. -Generally, there are two ways that you test Ion's code. These are not comprehensive, but should work for most cases. The first is calling methods directly; the second is making a GET or POST request to the view that you are interested in testing. The best tests utilize both. After you do either/both of these to call the code, you should use assertions to see if the code behaved as expected. A list of assertion options can be found `here `. A useful tool for making requests to the view is ``self.client``. More documentation on that can be found `here `_. Alternatively, just search through Ion's testing files for examples of using `self.client`. +Generally, there are two ways that you test Ion's code. These are not comprehensive, but should work for most cases. +The first is calling methods directly; the second is making a GET or POST request to the view that you are interested in testing. +The best tests utilize both. After you do either/both of these to call the code, you should use assertions to see if the code behaved as expected. +A list of assertion options can be found `here `_. +A useful tool for making requests to the view is ``self.client``. More documentation on that can be found +`here `_. Alternatively, just search through Ion's +testing files for examples of using `self.client`. Good Testing Examples ===================== diff --git a/docs/developing/updates.rst b/docs/source/developing/updates.rst similarity index 63% rename from docs/developing/updates.rst rename to docs/source/developing/updates.rst index ca2a314b2a7..43e2a6fda9a 100644 --- a/docs/developing/updates.rst +++ b/docs/source/developing/updates.rst @@ -5,18 +5,18 @@ Keeping things up-to-date Vendored Libraries ================== -- `CKEditor _` -- `datetimepicker _` +- `CKEditor `_ +- `datetimepicker `_ - jQuery-UI: *Note: This has very specific CSL customizations)* -- `Messenger _` -- `selectize _` -- `sortable _` +- `Messenger `_ +- `selectize `_ +- `sortable `_ Updating Top-level Requirements ================================ -If any commit changes the direct dependencies of Ion, you must update `the requirements documentation`_ to reflect the changes to Ion's dependencies. That page is organized into sections for each dependency, with a line for the package's source URL, a general description of the package, the usage of the package in Ion, and the package's license. +If any commit changes the direct dependencies of Ion, you must update `the requirements documentation `_ to reflect the changes to Ion's dependencies. That page is organized into sections for each dependency, with a line for the package's source URL, a general description of the package, the usage of the package in Ion, and the package's license. For example, here is a valid section: @@ -36,6 +36,7 @@ Additional lines may be added to identify needed actions regarding the package. Requirements for Dependencies ============================== -**All** dependencies to Ion must be licensed under an `OSI-approved open source license `_. The use of the package must be compatible with the terms of the GNU General Public License v2 (or later version). +**All** dependencies to Ion must be licensed under an `OSI-approved open source license `_. +The use of the package must be compatible with the terms of the GNU General Public License v2 (or later version). **All** direct dependencies to Ion must be reported in the requirements documentation. diff --git a/USERNAMES.md b/docs/source/developing/usernames.md similarity index 74% rename from USERNAMES.md rename to docs/source/developing/usernames.md index 9c170e46cb7..239539566c7 100644 --- a/USERNAMES.md +++ b/docs/source/developing/usernames.md @@ -5,18 +5,6 @@ There are more usernames generated by default than the ones in this table, but t | username | user_type | graduation_year | is_superuser | |--------------|-----------|-----------------|--------------| | admin | student | | TRUE | -| 2024admin5 | student | 2024 | TRUE | -| 2024admin4 | student | 2024 | TRUE | -| 2024admin3 | student | 2024 | TRUE | -| 2024admin2 | student | 2024 | TRUE | -| 2024admin1 | student | 2024 | TRUE | -| 2024admin | student | 2024 | TRUE | -| 2024student5 | student | 2024 | FALSE | -| 2024student4 | student | 2024 | FALSE | -| 2024student3 | student | 2024 | FALSE | -| 2024student2 | student | 2024 | FALSE | -| 2024student1 | student | 2024 | FALSE | -| 2024student | student | 2024 | FALSE | | 2025admin5 | student | 2025 | TRUE | | 2025admin4 | student | 2025 | TRUE | | 2025admin3 | student | 2025 | TRUE | @@ -53,3 +41,17 @@ There are more usernames generated by default than the ones in this table, but t | 2027student2 | student | 2027 | FALSE | | 2027student1 | student | 2027 | FALSE | | 2027student | student | 2027 | FALSE | +| 2028admin5 | student | 2028 | TRUE | +| 2028admin4 | student | 2028 | TRUE | +| 2028admin3 | student | 2028 | TRUE | +| 2028admin2 | student | 2028 | TRUE | +| 2028admin1 | student | 2028 | TRUE | +| 2028admin | student | 2028 | TRUE | +| 2028student5 | student | 2028 | FALSE | +| 2028student4 | student | 2028 | FALSE | +| 2028student3 | student | 2028 | FALSE | +| 2028student2 | student | 2028 | FALSE | +| 2028student1 | student | 2028 | FALSE | +| 2028student | student | 2028 | FALSE | + +The usernames in the table above depend on when they are generated - if the current school year has started (in August), then 1 year will be appended to the current year for graduation year calculation. diff --git a/docs/index.rst b/docs/source/index.rst similarity index 98% rename from docs/index.rst rename to docs/source/index.rst index e23cae37882..d06bbdf66b0 100644 --- a/docs/index.rst +++ b/docs/source/index.rst @@ -17,7 +17,7 @@ Contents setup/index architecture/index developing/index - sourcedoc/modules + reference_index Setup ----- diff --git a/docs/source/reference_index.rst b/docs/source/reference_index.rst new file mode 100644 index 00000000000..e60b414fce6 --- /dev/null +++ b/docs/source/reference_index.rst @@ -0,0 +1,21 @@ +############### +Reference Index +############### + +This is a collection of autogenerated documentation about Ion. +It's meant to be a reference - feel free to check it out! + +Middleware +~~~~~~~~~~ + +.. toctree:: + + reference_index/middleware + +Apps +~~~~ + +.. toctree:: + :glob: + + reference_index/apps/* diff --git a/docs/source/reference_index/apps/announcements.rst b/docs/source/reference_index/apps/announcements.rst new file mode 100644 index 00000000000..997230e90b1 --- /dev/null +++ b/docs/source/reference_index/apps/announcements.rst @@ -0,0 +1,18 @@ +############# +announcements +############# + +.. currentmodule:: intranet.apps.announcements + +.. autosummary:: + :toctree: ../../reference + + admin + api + forms + models + notifications + serializers + tests + urls + views diff --git a/docs/source/reference_index/apps/api.rst b/docs/source/reference_index/apps/api.rst new file mode 100644 index 00000000000..5870968f015 --- /dev/null +++ b/docs/source/reference_index/apps/api.rst @@ -0,0 +1,14 @@ +### +api +### + +.. currentmodule:: intranet.apps.api + +.. autosummary:: + :toctree: ../../reference + + authentication + tests + urls + utils + views diff --git a/docs/source/reference_index/apps/auth.rst b/docs/source/reference_index/apps/auth.rst new file mode 100644 index 00000000000..d21580aef07 --- /dev/null +++ b/docs/source/reference_index/apps/auth.rst @@ -0,0 +1,20 @@ +#### +auth +#### + +.. currentmodule:: intranet.apps.auth + +.. autosummary:: + :toctree: ../../reference + + apps + backends + decorators + forms + helpers + management.commands.grant_admin + rest_permissions + signals + tests + urls + views diff --git a/docs/source/reference_index/apps/bus.rst b/docs/source/reference_index/apps/bus.rst new file mode 100644 index 00000000000..4e952d4e8a4 --- /dev/null +++ b/docs/source/reference_index/apps/bus.rst @@ -0,0 +1,20 @@ +### +bus +### + +.. currentmodule:: intranet.apps.bus + +.. autosummary:: + :toctree: ../../reference + + admin + api + consumers + management.commands.import_routes + management.commands.reset_routes + models + serializers + tasks + tests + urls + views diff --git a/docs/source/reference_index/apps/cslapps.rst b/docs/source/reference_index/apps/cslapps.rst new file mode 100644 index 00000000000..d7fde24deaa --- /dev/null +++ b/docs/source/reference_index/apps/cslapps.rst @@ -0,0 +1,15 @@ +####### +cslapps +####### + +.. currentmodule:: intranet.apps.cslapps + +.. autosummary:: + :toctree: ../../reference + + admin + management.commands.dev_create_cslapps + models + tests + urls + views diff --git a/docs/source/reference_index/apps/customthemes.rst b/docs/source/reference_index/apps/customthemes.rst new file mode 100644 index 00000000000..a6cde80e3c0 --- /dev/null +++ b/docs/source/reference_index/apps/customthemes.rst @@ -0,0 +1,11 @@ +############ +customthemes +############ + +.. currentmodule:: intranet.apps.customthemes + +.. autosummary:: + :toctree: ../../reference + + urls + views diff --git a/docs/source/reference_index/apps/dashboard.rst b/docs/source/reference_index/apps/dashboard.rst new file mode 100644 index 00000000000..a88ec83c21c --- /dev/null +++ b/docs/source/reference_index/apps/dashboard.rst @@ -0,0 +1,11 @@ +######### +dashboard +######### + +.. currentmodule:: intranet.apps.dashboard + +.. autosummary:: + :toctree: ../../reference + + tests + views diff --git a/docs/source/reference_index/apps/dataimport.rst b/docs/source/reference_index/apps/dataimport.rst new file mode 100644 index 00000000000..bd6ca1eea93 --- /dev/null +++ b/docs/source/reference_index/apps/dataimport.rst @@ -0,0 +1,19 @@ +########## +dataimport +########## + +.. currentmodule:: intranet.apps.dataimport + +.. autosummary:: + :toctree: ../../reference + + apps + management.commands.delete_users + management.commands.import_eighth + management.commands.import_photos + management.commands.import_staff + management.commands.import_students + management.commands.import_tj_star + management.commands.import_users + management.commands.year_cleanup + tests diff --git a/docs/source/reference_index/apps/django.rst b/docs/source/reference_index/apps/django.rst new file mode 100644 index 00000000000..ccd8fc67294 --- /dev/null +++ b/docs/source/reference_index/apps/django.rst @@ -0,0 +1,10 @@ +###### +django +###### + +.. currentmodule:: intranet.apps.django + +.. autosummary:: + :toctree: ../../reference + + management.commands.run diff --git a/docs/source/reference_index/apps/eighth.rst b/docs/source/reference_index/apps/eighth.rst new file mode 100644 index 00000000000..5ec6b966afe --- /dev/null +++ b/docs/source/reference_index/apps/eighth.rst @@ -0,0 +1,76 @@ +###### +eighth +###### + +.. currentmodule:: intranet.apps.eighth + +.. autosummary:: + :toctree: ../../reference + + admin + context_processors + exceptions + forms.activities + forms.admin.activities + forms.admin.blocks + forms.admin.general + forms.admin.groups + forms.admin.rooms + forms.admin.scheduling + forms.admin.sponsors + forms.fields + management.commands.absence_email + management.commands.delete_duplicate_signups + management.commands.dev_create_blocks + management.commands.dev_generate_signups + management.commands.find_duplicates + management.commands.generate_similarities + management.commands.generate_statistics + management.commands.remove_withdrawn_students + management.commands.signup_statistics + management.commands.signup_status_email + management.commands.update_counselors + models + notifications + serializers + tasks + tests.admin.test_admin_activities + tests.admin.test_admin_attendance + tests.admin.test_admin_blocks + tests.admin.test_admin_general + tests.admin.test_admin_groups + tests.admin.test_admin_maintenance + tests.admin.test_admin_rooms + tests.admin.test_admin_scheduling + tests.admin.test_admin_sponsors + tests.admin.test_admin_users + tests.eighth_test + tests.test_activities + tests.test_attendance + tests.test_commands + tests.test_exceptions + tests.test_general + tests.test_monitoring + tests.test_profile + tests.test_routers + tests.test_signup + urls + utils + views.activities + views.admin.activities + views.admin.attendance + views.admin.blocks + views.admin.general + views.admin.groups + views.admin.hybrid + views.admin.maintenance + views.admin.rooms + views.admin.scheduling + views.admin.sponsors + views.admin.users + views.api + views.attendance + views.monitoring + views.profile + views.routers + views.signup diff --git a/docs/source/reference_index/apps/emailfwd.rst b/docs/source/reference_index/apps/emailfwd.rst new file mode 100644 index 00000000000..a7e32d64806 --- /dev/null +++ b/docs/source/reference_index/apps/emailfwd.rst @@ -0,0 +1,16 @@ +######## +emailfwd +######## + +.. currentmodule:: intranet.apps.emailfwd + +.. autosummary:: + :toctree: ../../reference + + apps + forms + management.commands.get_senior_forwards + models + tests + urls + views diff --git a/docs/source/reference_index/apps/emerg.rst b/docs/source/reference_index/apps/emerg.rst new file mode 100644 index 00000000000..4aef0f892f1 --- /dev/null +++ b/docs/source/reference_index/apps/emerg.rst @@ -0,0 +1,13 @@ +##### +emerg +##### + +.. currentmodule:: intranet.apps.emerg + +.. autosummary:: + :toctree: ../../reference + + api + tasks + tests + views diff --git a/docs/source/reference_index/apps/enrichment.rst b/docs/source/reference_index/apps/enrichment.rst new file mode 100644 index 00000000000..8445cb57469 --- /dev/null +++ b/docs/source/reference_index/apps/enrichment.rst @@ -0,0 +1,14 @@ +########## +enrichment +########## + +.. currentmodule:: intranet.apps.enrichment + +.. autosummary:: + :toctree: ../../reference + + admin + forms + models + urls + views diff --git a/docs/source/reference_index/apps/error.rst b/docs/source/reference_index/apps/error.rst new file mode 100644 index 00000000000..13afb97b957 --- /dev/null +++ b/docs/source/reference_index/apps/error.rst @@ -0,0 +1,11 @@ +##### +error +##### + +.. currentmodule:: intranet.apps.error + +.. autosummary:: + :toctree: ../../reference + + tests + views diff --git a/docs/source/reference_index/apps/events.rst b/docs/source/reference_index/apps/events.rst new file mode 100644 index 00000000000..2fc8877faed --- /dev/null +++ b/docs/source/reference_index/apps/events.rst @@ -0,0 +1,18 @@ +###### +events +###### + +.. currentmodule:: intranet.apps.events + +.. autosummary:: + :toctree: ../../reference + + admin + forms + management.commands.import_sports + models + notifications + tasks + tests + urls + views diff --git a/docs/source/reference_index/apps/features.rst b/docs/source/reference_index/apps/features.rst new file mode 100644 index 00000000000..89386e690fc --- /dev/null +++ b/docs/source/reference_index/apps/features.rst @@ -0,0 +1,16 @@ +######## +features +######## + +.. currentmodule:: intranet.apps.features + +.. autosummary:: + :toctree: ../../reference + + admin + context_processors + helpers + models + tests + urls + views diff --git a/docs/source/reference_index/apps/feedback.rst b/docs/source/reference_index/apps/feedback.rst new file mode 100644 index 00000000000..c5b96aa7f52 --- /dev/null +++ b/docs/source/reference_index/apps/feedback.rst @@ -0,0 +1,15 @@ +######## +feedback +######## + +.. currentmodule:: intranet.apps.feedback + +.. autosummary:: + :toctree: ../../reference + + admin + forms + models + tests + urls + views diff --git a/docs/source/reference_index/apps/files.rst b/docs/source/reference_index/apps/files.rst new file mode 100644 index 00000000000..7fc06fecee3 --- /dev/null +++ b/docs/source/reference_index/apps/files.rst @@ -0,0 +1,15 @@ +##### +files +##### + +.. currentmodule:: intranet.apps.files + +.. autosummary:: + :toctree: ../../reference + + admin + forms + models + tests + urls + views diff --git a/docs/source/reference_index/apps/groups.rst b/docs/source/reference_index/apps/groups.rst new file mode 100644 index 00000000000..010deb86536 --- /dev/null +++ b/docs/source/reference_index/apps/groups.rst @@ -0,0 +1,14 @@ +###### +groups +###### + +.. currentmodule:: intranet.apps.groups + +.. autosummary:: + :toctree: ../../reference + + forms + models + tests + urls + views diff --git a/docs/source/reference_index/apps/itemreg.rst b/docs/source/reference_index/apps/itemreg.rst new file mode 100644 index 00000000000..6cec2560b7b --- /dev/null +++ b/docs/source/reference_index/apps/itemreg.rst @@ -0,0 +1,17 @@ +####### +itemreg +####### + +.. currentmodule:: intranet.apps.itemreg + +.. autosummary:: + :toctree: ../../reference + + admin + apps + forms + models + templatetags.texthighlight + tests + urls + views diff --git a/docs/source/reference_index/apps/logs.rst b/docs/source/reference_index/apps/logs.rst new file mode 100644 index 00000000000..22d4a28b596 --- /dev/null +++ b/docs/source/reference_index/apps/logs.rst @@ -0,0 +1,15 @@ +#### +logs +#### + +.. currentmodule:: intranet.apps.logs + +.. autosummary:: + :toctree: ../../reference + + admin + forms + models + tests + urls + views diff --git a/docs/source/reference_index/apps/lostfound.rst b/docs/source/reference_index/apps/lostfound.rst new file mode 100644 index 00000000000..102df8fc35f --- /dev/null +++ b/docs/source/reference_index/apps/lostfound.rst @@ -0,0 +1,17 @@ +######### +lostfound +######### + +.. currentmodule:: intranet.apps.lostfound + +.. autosummary:: + :toctree: ../../reference + + admin + apps + forms + models + tasks + tests + urls + views diff --git a/docs/source/reference_index/apps/nomination.rst b/docs/source/reference_index/apps/nomination.rst new file mode 100644 index 00000000000..6da955542a3 --- /dev/null +++ b/docs/source/reference_index/apps/nomination.rst @@ -0,0 +1,14 @@ +########## +nomination +########## + +.. currentmodule:: intranet.apps.nomination + +.. autosummary:: + :toctree: ../../reference + + apps + models + tests + urls + views diff --git a/docs/source/reference_index/apps/notifications.rst b/docs/source/reference_index/apps/notifications.rst new file mode 100644 index 00000000000..74d4c22681e --- /dev/null +++ b/docs/source/reference_index/apps/notifications.rst @@ -0,0 +1,14 @@ +############# +notifications +############# + +.. currentmodule:: intranet.apps.notifications + +.. autosummary:: + :toctree: ../../reference + + emails + models + tasks + urls + views diff --git a/docs/source/reference_index/apps/oauth.rst b/docs/source/reference_index/apps/oauth.rst new file mode 100644 index 00000000000..82c87be264f --- /dev/null +++ b/docs/source/reference_index/apps/oauth.rst @@ -0,0 +1,14 @@ +##### +oauth +##### + +.. currentmodule:: intranet.apps.oauth + +.. autosummary:: + :toctree: ../../reference + + admin + apps + models + tests + views diff --git a/docs/source/reference_index/apps/parking.rst b/docs/source/reference_index/apps/parking.rst new file mode 100644 index 00000000000..d6aafff94a0 --- /dev/null +++ b/docs/source/reference_index/apps/parking.rst @@ -0,0 +1,16 @@ +####### +parking +####### + +.. currentmodule:: intranet.apps.parking + +.. autosummary:: + :toctree: ../../reference + + admin + apps + forms + models + tests + urls + views diff --git a/docs/source/reference_index/apps/polls.rst b/docs/source/reference_index/apps/polls.rst new file mode 100644 index 00000000000..abf17dcb947 --- /dev/null +++ b/docs/source/reference_index/apps/polls.rst @@ -0,0 +1,15 @@ +##### +polls +##### + +.. currentmodule:: intranet.apps.polls + +.. autosummary:: + :toctree: ../../reference + + admin + forms + models + tests + urls + views diff --git a/docs/source/reference_index/apps/preferences.rst b/docs/source/reference_index/apps/preferences.rst new file mode 100644 index 00000000000..b4c4cbee198 --- /dev/null +++ b/docs/source/reference_index/apps/preferences.rst @@ -0,0 +1,14 @@ +########### +preferences +########### + +.. currentmodule:: intranet.apps.preferences + +.. autosummary:: + :toctree: ../../reference + + fields + forms + tests + urls + views diff --git a/docs/source/reference_index/apps/printing.rst b/docs/source/reference_index/apps/printing.rst new file mode 100644 index 00000000000..ad9c29f34a7 --- /dev/null +++ b/docs/source/reference_index/apps/printing.rst @@ -0,0 +1,15 @@ +######## +printing +######## + +.. currentmodule:: intranet.apps.printing + +.. autosummary:: + :toctree: ../../reference + + admin + forms + models + tests + urls + views diff --git a/docs/source/reference_index/apps/schedule.rst b/docs/source/reference_index/apps/schedule.rst new file mode 100644 index 00000000000..5d2f6605e2c --- /dev/null +++ b/docs/source/reference_index/apps/schedule.rst @@ -0,0 +1,19 @@ +######## +schedule +######## + +.. currentmodule:: intranet.apps.schedule + +.. autosummary:: + :toctree: ../../reference + + admin + api + forms + management.commands.schedule_notify + models + notifications + serializers + tests + urls + views diff --git a/docs/source/reference_index/apps/search.rst b/docs/source/reference_index/apps/search.rst new file mode 100644 index 00000000000..bcfe0a4c782 --- /dev/null +++ b/docs/source/reference_index/apps/search.rst @@ -0,0 +1,13 @@ +###### +search +###### + +.. currentmodule:: intranet.apps.search + +.. autosummary:: + :toctree: ../../reference + + tests + urls + utils + views diff --git a/docs/source/reference_index/apps/seniors.rst b/docs/source/reference_index/apps/seniors.rst new file mode 100644 index 00000000000..ad1c4193a4e --- /dev/null +++ b/docs/source/reference_index/apps/seniors.rst @@ -0,0 +1,17 @@ +####### +seniors +####### + +.. currentmodule:: intranet.apps.seniors + +.. autosummary:: + :toctree: ../../reference + + admin + forms + management.commands.cleanup_destinations + management.commands.import_colleges + models + tests + urls + views diff --git a/docs/source/reference_index/apps/sessionmgmt.rst b/docs/source/reference_index/apps/sessionmgmt.rst new file mode 100644 index 00000000000..2dc6c1d8faa --- /dev/null +++ b/docs/source/reference_index/apps/sessionmgmt.rst @@ -0,0 +1,15 @@ +########### +sessionmgmt +########### + +.. currentmodule:: intranet.apps.sessionmgmt + +.. autosummary:: + :toctree: ../../reference + + admin + helpers + models + tests + urls + views diff --git a/docs/source/reference_index/apps/signage.rst b/docs/source/reference_index/apps/signage.rst new file mode 100644 index 00000000000..27c7e1e1ba9 --- /dev/null +++ b/docs/source/reference_index/apps/signage.rst @@ -0,0 +1,18 @@ +####### +signage +####### + +.. currentmodule:: intranet.apps.signage + +.. autosummary:: + :toctree: ../../reference + + admin + consumers + forms + models + pages + templatetags.signage + tests + urls + views diff --git a/docs/source/reference_index/apps/templatetags.rst b/docs/source/reference_index/apps/templatetags.rst new file mode 100644 index 00000000000..b9942f887e8 --- /dev/null +++ b/docs/source/reference_index/apps/templatetags.rst @@ -0,0 +1,20 @@ +############ +templatetags +############ + +.. currentmodule:: intranet.apps.templatetags + +.. autosummary:: + :toctree: ../../reference + + dates + dictionaries + form_field + forms + math + newtab_links + paginate + status_helper + strings + svg + tests diff --git a/docs/source/reference_index/apps/users.rst b/docs/source/reference_index/apps/users.rst new file mode 100644 index 00000000000..9b3a3dc14b9 --- /dev/null +++ b/docs/source/reference_index/apps/users.rst @@ -0,0 +1,24 @@ +##### +users +##### + +.. currentmodule:: intranet.apps.users + +.. autosummary:: + :toctree: ../../reference + + admin + api + courses_urls + forms + management.commands.import_groups + management.commands.lock + models + renderers + serializers + templatetags.grades + templatetags.phone_numbers + templatetags.users + tests + urls + views diff --git a/docs/source/reference_index/apps/welcome.rst b/docs/source/reference_index/apps/welcome.rst new file mode 100644 index 00000000000..352bd396672 --- /dev/null +++ b/docs/source/reference_index/apps/welcome.rst @@ -0,0 +1,12 @@ +####### +welcome +####### + +.. currentmodule:: intranet.apps.welcome + +.. autosummary:: + :toctree: ../../reference + + tests + urls + views diff --git a/docs/source/reference_index/middleware.rst b/docs/source/reference_index/middleware.rst new file mode 100644 index 00000000000..8be58e206cf --- /dev/null +++ b/docs/source/reference_index/middleware.rst @@ -0,0 +1,20 @@ +########## +Middleware +########## + +.. currentmodule:: intranet.middleware + +.. autosummary:: + :toctree: ../reference + + ~access_log + ~ajax + ~dark_mode + ~monitoring + ~profiler + ~same_origin + ~session_management + ~templates + ~threadlocals + ~traceback + ~url_slashes diff --git a/SECURITY.md b/docs/source/security.md similarity index 97% rename from SECURITY.md rename to docs/source/security.md index f29859d8781..88df92648d8 100644 --- a/SECURITY.md +++ b/docs/source/security.md @@ -1,3 +1,4 @@ +(security-policy)= # Security Policy ## Reporting a Vulnerability diff --git a/docs/setup/index.rst b/docs/source/setup/index.rst similarity index 74% rename from docs/setup/index.rst rename to docs/source/setup/index.rst index 14a150201ad..45af3f05834 100644 --- a/docs/setup/index.rst +++ b/docs/source/setup/index.rst @@ -2,6 +2,7 @@ Setup ***** .. toctree:: + :maxdepth: 2 :glob: * diff --git a/docs/setup/server.rst b/docs/source/setup/server.rst similarity index 100% rename from docs/setup/server.rst rename to docs/source/setup/server.rst diff --git a/SETUP.md b/docs/source/setup/setup.md similarity index 99% rename from SETUP.md rename to docs/source/setup/setup.md index 57a646507a4..8a013b60779 100644 --- a/SETUP.md +++ b/docs/source/setup/setup.md @@ -29,7 +29,7 @@ or another Alpine mirror to `/etc/apk/repositories` because `alpinelinux.org` ma ## Docker Post Set-Up -Navigate to http://localhost:8080 in the web browser of your choice. You might have to wait up to 60 seconds the first time that you create the container. When presented with the login page, log in with the username "admin" or a [generated username](https://github.com/tjcsl/ion/blob/dev/USERNAMES.md) and the password "notfish" (without the quotes). +Navigate to http://localhost:8080 in the web browser of your choice. You might have to wait up to 60 seconds the first time that you create the container. When presented with the login page, log in with the username "admin" or a [generated username](https://github.com/tjcsl/ion/blob/dev/docs/source/developing/usernames.md) and the password "notfish" (without the quotes). ## Useful Commands diff --git a/docs/sourcedoc/intranet.apps.announcements.rst b/docs/sourcedoc/intranet.apps.announcements.rst deleted file mode 100644 index 1d90c545af2..00000000000 --- a/docs/sourcedoc/intranet.apps.announcements.rst +++ /dev/null @@ -1,85 +0,0 @@ -intranet.apps.announcements package -=================================== - -Submodules ----------- - -intranet.apps.announcements.admin module ----------------------------------------- - -.. automodule:: intranet.apps.announcements.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.announcements.api module --------------------------------------- - -.. automodule:: intranet.apps.announcements.api - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.announcements.forms module ----------------------------------------- - -.. automodule:: intranet.apps.announcements.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.announcements.models module ------------------------------------------ - -.. automodule:: intranet.apps.announcements.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.announcements.notifications module ------------------------------------------------- - -.. automodule:: intranet.apps.announcements.notifications - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.announcements.serializers module ----------------------------------------------- - -.. automodule:: intranet.apps.announcements.serializers - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.announcements.tests module ----------------------------------------- - -.. automodule:: intranet.apps.announcements.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.announcements.urls module ---------------------------------------- - -.. automodule:: intranet.apps.announcements.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.announcements.views module ----------------------------------------- - -.. automodule:: intranet.apps.announcements.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.announcements - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.api.rst b/docs/sourcedoc/intranet.apps.api.rst deleted file mode 100644 index c8b80c74565..00000000000 --- a/docs/sourcedoc/intranet.apps.api.rst +++ /dev/null @@ -1,53 +0,0 @@ -intranet.apps.api package -========================= - -Submodules ----------- - -intranet.apps.api.authentication module ---------------------------------------- - -.. automodule:: intranet.apps.api.authentication - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.api.tests module ------------------------------- - -.. automodule:: intranet.apps.api.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.api.urls module ------------------------------ - -.. automodule:: intranet.apps.api.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.api.utils module ------------------------------- - -.. automodule:: intranet.apps.api.utils - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.api.views module ------------------------------- - -.. automodule:: intranet.apps.api.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.api - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.auth.management.commands.rst b/docs/sourcedoc/intranet.apps.auth.management.commands.rst deleted file mode 100644 index c5e8c74fb51..00000000000 --- a/docs/sourcedoc/intranet.apps.auth.management.commands.rst +++ /dev/null @@ -1,21 +0,0 @@ -intranet.apps.auth.management.commands package -============================================== - -Submodules ----------- - -intranet.apps.auth.management.commands.grant\_admin module ----------------------------------------------------------- - -.. automodule:: intranet.apps.auth.management.commands.grant_admin - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.auth.management.commands - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.auth.management.rst b/docs/sourcedoc/intranet.apps.auth.management.rst deleted file mode 100644 index 520aeeb9156..00000000000 --- a/docs/sourcedoc/intranet.apps.auth.management.rst +++ /dev/null @@ -1,18 +0,0 @@ -intranet.apps.auth.management package -===================================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.auth.management.commands - -Module contents ---------------- - -.. automodule:: intranet.apps.auth.management - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.auth.rst b/docs/sourcedoc/intranet.apps.auth.rst deleted file mode 100644 index cb474a06d54..00000000000 --- a/docs/sourcedoc/intranet.apps.auth.rst +++ /dev/null @@ -1,101 +0,0 @@ -intranet.apps.auth package -========================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.auth.management - -Submodules ----------- - -intranet.apps.auth.apps module ------------------------------- - -.. automodule:: intranet.apps.auth.apps - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.auth.backends module ----------------------------------- - -.. automodule:: intranet.apps.auth.backends - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.auth.decorators module ------------------------------------- - -.. automodule:: intranet.apps.auth.decorators - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.auth.forms module -------------------------------- - -.. automodule:: intranet.apps.auth.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.auth.helpers module ---------------------------------- - -.. automodule:: intranet.apps.auth.helpers - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.auth.rest\_permissions module -------------------------------------------- - -.. automodule:: intranet.apps.auth.rest_permissions - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.auth.signals module ---------------------------------- - -.. automodule:: intranet.apps.auth.signals - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.auth.tests module -------------------------------- - -.. automodule:: intranet.apps.auth.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.auth.urls module ------------------------------- - -.. automodule:: intranet.apps.auth.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.auth.views module -------------------------------- - -.. automodule:: intranet.apps.auth.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.auth - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.bus.management.commands.rst b/docs/sourcedoc/intranet.apps.bus.management.commands.rst deleted file mode 100644 index 14027f0f60d..00000000000 --- a/docs/sourcedoc/intranet.apps.bus.management.commands.rst +++ /dev/null @@ -1,29 +0,0 @@ -intranet.apps.bus.management.commands package -============================================= - -Submodules ----------- - -intranet.apps.bus.management.commands.import\_routes module ------------------------------------------------------------ - -.. automodule:: intranet.apps.bus.management.commands.import_routes - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.bus.management.commands.reset\_routes module ----------------------------------------------------------- - -.. automodule:: intranet.apps.bus.management.commands.reset_routes - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.bus.management.commands - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.bus.management.rst b/docs/sourcedoc/intranet.apps.bus.management.rst deleted file mode 100644 index 76a4a8c0cee..00000000000 --- a/docs/sourcedoc/intranet.apps.bus.management.rst +++ /dev/null @@ -1,18 +0,0 @@ -intranet.apps.bus.management package -==================================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.bus.management.commands - -Module contents ---------------- - -.. automodule:: intranet.apps.bus.management - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.bus.rst b/docs/sourcedoc/intranet.apps.bus.rst deleted file mode 100644 index 20a4b215591..00000000000 --- a/docs/sourcedoc/intranet.apps.bus.rst +++ /dev/null @@ -1,93 +0,0 @@ -intranet.apps.bus package -========================= - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.bus.management - -Submodules ----------- - -intranet.apps.bus.admin module ------------------------------- - -.. automodule:: intranet.apps.bus.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.bus.api module ----------------------------- - -.. automodule:: intranet.apps.bus.api - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.bus.consumers module ----------------------------------- - -.. automodule:: intranet.apps.bus.consumers - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.bus.models module -------------------------------- - -.. automodule:: intranet.apps.bus.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.bus.serializers module ------------------------------------- - -.. automodule:: intranet.apps.bus.serializers - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.bus.tasks module ------------------------------- - -.. automodule:: intranet.apps.bus.tasks - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.bus.tests module ------------------------------- - -.. automodule:: intranet.apps.bus.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.bus.urls module ------------------------------ - -.. automodule:: intranet.apps.bus.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.bus.views module ------------------------------- - -.. automodule:: intranet.apps.bus.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.bus - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.cslapps.management.commands.rst b/docs/sourcedoc/intranet.apps.cslapps.management.commands.rst deleted file mode 100644 index adf909e19da..00000000000 --- a/docs/sourcedoc/intranet.apps.cslapps.management.commands.rst +++ /dev/null @@ -1,21 +0,0 @@ -intranet.apps.cslapps.management.commands package -================================================= - -Submodules ----------- - -intranet.apps.cslapps.management.commands.dev\_create\_cslapps module ---------------------------------------------------------------------- - -.. automodule:: intranet.apps.cslapps.management.commands.dev_create_cslapps - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.cslapps.management.commands - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.cslapps.management.rst b/docs/sourcedoc/intranet.apps.cslapps.management.rst deleted file mode 100644 index 129b1204ba9..00000000000 --- a/docs/sourcedoc/intranet.apps.cslapps.management.rst +++ /dev/null @@ -1,18 +0,0 @@ -intranet.apps.cslapps.management package -======================================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.cslapps.management.commands - -Module contents ---------------- - -.. automodule:: intranet.apps.cslapps.management - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.cslapps.rst b/docs/sourcedoc/intranet.apps.cslapps.rst deleted file mode 100644 index f7811ab9c68..00000000000 --- a/docs/sourcedoc/intranet.apps.cslapps.rst +++ /dev/null @@ -1,61 +0,0 @@ -intranet.apps.cslapps package -============================= - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.cslapps.management - -Submodules ----------- - -intranet.apps.cslapps.admin module ----------------------------------- - -.. automodule:: intranet.apps.cslapps.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.cslapps.models module ------------------------------------ - -.. automodule:: intranet.apps.cslapps.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.cslapps.tests module ----------------------------------- - -.. automodule:: intranet.apps.cslapps.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.cslapps.urls module ---------------------------------- - -.. automodule:: intranet.apps.cslapps.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.cslapps.views module ----------------------------------- - -.. automodule:: intranet.apps.cslapps.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.cslapps - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.customthemes.rst b/docs/sourcedoc/intranet.apps.customthemes.rst deleted file mode 100644 index 52a18a72999..00000000000 --- a/docs/sourcedoc/intranet.apps.customthemes.rst +++ /dev/null @@ -1,29 +0,0 @@ -intranet.apps.customthemes package -================================== - -Submodules ----------- - -intranet.apps.customthemes.urls module --------------------------------------- - -.. automodule:: intranet.apps.customthemes.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.customthemes.views module ---------------------------------------- - -.. automodule:: intranet.apps.customthemes.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.customthemes - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.dashboard.rst b/docs/sourcedoc/intranet.apps.dashboard.rst deleted file mode 100644 index 6ae6ae1b474..00000000000 --- a/docs/sourcedoc/intranet.apps.dashboard.rst +++ /dev/null @@ -1,29 +0,0 @@ -intranet.apps.dashboard package -=============================== - -Submodules ----------- - -intranet.apps.dashboard.tests module ------------------------------------- - -.. automodule:: intranet.apps.dashboard.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.dashboard.views module ------------------------------------- - -.. automodule:: intranet.apps.dashboard.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.dashboard - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.dataimport.management.commands.rst b/docs/sourcedoc/intranet.apps.dataimport.management.commands.rst deleted file mode 100644 index de375eb6cc9..00000000000 --- a/docs/sourcedoc/intranet.apps.dataimport.management.commands.rst +++ /dev/null @@ -1,77 +0,0 @@ -intranet.apps.dataimport.management.commands package -==================================================== - -Submodules ----------- - -intranet.apps.dataimport.management.commands.delete\_users module ------------------------------------------------------------------ - -.. automodule:: intranet.apps.dataimport.management.commands.delete_users - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.dataimport.management.commands.import\_eighth module ------------------------------------------------------------------- - -.. automodule:: intranet.apps.dataimport.management.commands.import_eighth - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.dataimport.management.commands.import\_photos module ------------------------------------------------------------------- - -.. automodule:: intranet.apps.dataimport.management.commands.import_photos - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.dataimport.management.commands.import\_staff module ------------------------------------------------------------------ - -.. automodule:: intranet.apps.dataimport.management.commands.import_staff - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.dataimport.management.commands.import\_students module --------------------------------------------------------------------- - -.. automodule:: intranet.apps.dataimport.management.commands.import_students - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.dataimport.management.commands.import\_tj\_star module --------------------------------------------------------------------- - -.. automodule:: intranet.apps.dataimport.management.commands.import_tj_star - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.dataimport.management.commands.import\_users module ------------------------------------------------------------------ - -.. automodule:: intranet.apps.dataimport.management.commands.import_users - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.dataimport.management.commands.year\_cleanup module ------------------------------------------------------------------ - -.. automodule:: intranet.apps.dataimport.management.commands.year_cleanup - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.dataimport.management.commands - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.dataimport.management.rst b/docs/sourcedoc/intranet.apps.dataimport.management.rst deleted file mode 100644 index 1d465ff6deb..00000000000 --- a/docs/sourcedoc/intranet.apps.dataimport.management.rst +++ /dev/null @@ -1,18 +0,0 @@ -intranet.apps.dataimport.management package -=========================================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.dataimport.management.commands - -Module contents ---------------- - -.. automodule:: intranet.apps.dataimport.management - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.dataimport.rst b/docs/sourcedoc/intranet.apps.dataimport.rst deleted file mode 100644 index 2d7a2a5c60c..00000000000 --- a/docs/sourcedoc/intranet.apps.dataimport.rst +++ /dev/null @@ -1,37 +0,0 @@ -intranet.apps.dataimport package -================================ - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.dataimport.management - -Submodules ----------- - -intranet.apps.dataimport.apps module ------------------------------------- - -.. automodule:: intranet.apps.dataimport.apps - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.dataimport.tests module -------------------------------------- - -.. automodule:: intranet.apps.dataimport.tests - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.dataimport - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.eighth.forms.admin.rst b/docs/sourcedoc/intranet.apps.eighth.forms.admin.rst deleted file mode 100644 index 9e1841c17e6..00000000000 --- a/docs/sourcedoc/intranet.apps.eighth.forms.admin.rst +++ /dev/null @@ -1,69 +0,0 @@ -intranet.apps.eighth.forms.admin package -======================================== - -Submodules ----------- - -intranet.apps.eighth.forms.admin.activities module --------------------------------------------------- - -.. automodule:: intranet.apps.eighth.forms.admin.activities - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.forms.admin.blocks module ----------------------------------------------- - -.. automodule:: intranet.apps.eighth.forms.admin.blocks - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.forms.admin.general module ------------------------------------------------ - -.. automodule:: intranet.apps.eighth.forms.admin.general - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.forms.admin.groups module ----------------------------------------------- - -.. automodule:: intranet.apps.eighth.forms.admin.groups - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.forms.admin.rooms module ---------------------------------------------- - -.. automodule:: intranet.apps.eighth.forms.admin.rooms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.forms.admin.scheduling module --------------------------------------------------- - -.. automodule:: intranet.apps.eighth.forms.admin.scheduling - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.forms.admin.sponsors module ------------------------------------------------- - -.. automodule:: intranet.apps.eighth.forms.admin.sponsors - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.eighth.forms.admin - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.eighth.forms.rst b/docs/sourcedoc/intranet.apps.eighth.forms.rst deleted file mode 100644 index 05d9ad08c3d..00000000000 --- a/docs/sourcedoc/intranet.apps.eighth.forms.rst +++ /dev/null @@ -1,37 +0,0 @@ -intranet.apps.eighth.forms package -================================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.eighth.forms.admin - -Submodules ----------- - -intranet.apps.eighth.forms.activities module --------------------------------------------- - -.. automodule:: intranet.apps.eighth.forms.activities - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.forms.fields module ----------------------------------------- - -.. automodule:: intranet.apps.eighth.forms.fields - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.eighth.forms - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.eighth.management.commands.rst b/docs/sourcedoc/intranet.apps.eighth.management.commands.rst deleted file mode 100644 index 5e20fd893fe..00000000000 --- a/docs/sourcedoc/intranet.apps.eighth.management.commands.rst +++ /dev/null @@ -1,101 +0,0 @@ -intranet.apps.eighth.management.commands package -================================================ - -Submodules ----------- - -intranet.apps.eighth.management.commands.absence\_email module --------------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.management.commands.absence_email - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.management.commands.delete\_duplicate\_signups module --------------------------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.management.commands.delete_duplicate_signups - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.management.commands.dev\_create\_blocks module -------------------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.management.commands.dev_create_blocks - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.management.commands.dev\_generate\_signups module ----------------------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.management.commands.dev_generate_signups - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.management.commands.find\_duplicates module ----------------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.management.commands.find_duplicates - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.management.commands.generate\_similarities module ----------------------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.management.commands.generate_similarities - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.management.commands.generate\_statistics module --------------------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.management.commands.generate_statistics - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.management.commands.remove\_withdrawn\_students module ---------------------------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.management.commands.remove_withdrawn_students - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.management.commands.signup\_statistics module ------------------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.management.commands.signup_statistics - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.management.commands.signup\_status\_email module ---------------------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.management.commands.signup_status_email - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.management.commands.update\_counselors module ------------------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.management.commands.update_counselors - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.eighth.management.commands - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.eighth.management.rst b/docs/sourcedoc/intranet.apps.eighth.management.rst deleted file mode 100644 index 813666cb82e..00000000000 --- a/docs/sourcedoc/intranet.apps.eighth.management.rst +++ /dev/null @@ -1,18 +0,0 @@ -intranet.apps.eighth.management package -======================================= - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.eighth.management.commands - -Module contents ---------------- - -.. automodule:: intranet.apps.eighth.management - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.eighth.rst b/docs/sourcedoc/intranet.apps.eighth.rst deleted file mode 100644 index fb5d13378cd..00000000000 --- a/docs/sourcedoc/intranet.apps.eighth.rst +++ /dev/null @@ -1,96 +0,0 @@ -intranet.apps.eighth package -============================ - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.eighth.forms - intranet.apps.eighth.management - intranet.apps.eighth.tests - intranet.apps.eighth.views - -Submodules ----------- - -intranet.apps.eighth.admin module ---------------------------------- - -.. automodule:: intranet.apps.eighth.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.context\_processors module ------------------------------------------------ - -.. automodule:: intranet.apps.eighth.context_processors - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.exceptions module --------------------------------------- - -.. automodule:: intranet.apps.eighth.exceptions - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.models module ----------------------------------- - -.. automodule:: intranet.apps.eighth.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.notifications module ------------------------------------------ - -.. automodule:: intranet.apps.eighth.notifications - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.serializers module ---------------------------------------- - -.. automodule:: intranet.apps.eighth.serializers - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tasks module ---------------------------------- - -.. automodule:: intranet.apps.eighth.tasks - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.urls module --------------------------------- - -.. automodule:: intranet.apps.eighth.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.utils module ---------------------------------- - -.. automodule:: intranet.apps.eighth.utils - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.eighth - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.eighth.tests.admin.rst b/docs/sourcedoc/intranet.apps.eighth.tests.admin.rst deleted file mode 100644 index 2e634c2f537..00000000000 --- a/docs/sourcedoc/intranet.apps.eighth.tests.admin.rst +++ /dev/null @@ -1,93 +0,0 @@ -intranet.apps.eighth.tests.admin package -======================================== - -Submodules ----------- - -intranet.apps.eighth.tests.admin.test\_admin\_activities module ---------------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.tests.admin.test_admin_activities - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.admin.test\_admin\_attendance module ---------------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.tests.admin.test_admin_attendance - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.admin.test\_admin\_blocks module ------------------------------------------------------------ - -.. automodule:: intranet.apps.eighth.tests.admin.test_admin_blocks - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.admin.test\_admin\_general module ------------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.tests.admin.test_admin_general - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.admin.test\_admin\_groups module ------------------------------------------------------------ - -.. automodule:: intranet.apps.eighth.tests.admin.test_admin_groups - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.admin.test\_admin\_maintenance module ----------------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.tests.admin.test_admin_maintenance - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.admin.test\_admin\_rooms module ----------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.tests.admin.test_admin_rooms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.admin.test\_admin\_scheduling module ---------------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.tests.admin.test_admin_scheduling - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.admin.test\_admin\_sponsors module -------------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.tests.admin.test_admin_sponsors - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.admin.test\_admin\_users module ----------------------------------------------------------- - -.. automodule:: intranet.apps.eighth.tests.admin.test_admin_users - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.eighth.tests.admin - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.eighth.tests.rst b/docs/sourcedoc/intranet.apps.eighth.tests.rst deleted file mode 100644 index a29af75b3f2..00000000000 --- a/docs/sourcedoc/intranet.apps.eighth.tests.rst +++ /dev/null @@ -1,101 +0,0 @@ -intranet.apps.eighth.tests package -================================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.eighth.tests.admin - -Submodules ----------- - -intranet.apps.eighth.tests.eighth\_test module ----------------------------------------------- - -.. automodule:: intranet.apps.eighth.tests.eighth_test - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.test\_activities module --------------------------------------------------- - -.. automodule:: intranet.apps.eighth.tests.test_activities - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.test\_attendance module --------------------------------------------------- - -.. automodule:: intranet.apps.eighth.tests.test_attendance - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.test\_commands module ------------------------------------------------- - -.. automodule:: intranet.apps.eighth.tests.test_commands - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.test\_exceptions module --------------------------------------------------- - -.. automodule:: intranet.apps.eighth.tests.test_exceptions - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.test\_general module ------------------------------------------------ - -.. automodule:: intranet.apps.eighth.tests.test_general - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.test\_monitoring module --------------------------------------------------- - -.. automodule:: intranet.apps.eighth.tests.test_monitoring - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.test\_profile module ------------------------------------------------ - -.. automodule:: intranet.apps.eighth.tests.test_profile - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.test\_routers module ------------------------------------------------ - -.. automodule:: intranet.apps.eighth.tests.test_routers - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.tests.test\_signup module ----------------------------------------------- - -.. automodule:: intranet.apps.eighth.tests.test_signup - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.eighth.tests - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.eighth.views.admin.rst b/docs/sourcedoc/intranet.apps.eighth.views.admin.rst deleted file mode 100644 index ec5ce6c0b4e..00000000000 --- a/docs/sourcedoc/intranet.apps.eighth.views.admin.rst +++ /dev/null @@ -1,101 +0,0 @@ -intranet.apps.eighth.views.admin package -======================================== - -Submodules ----------- - -intranet.apps.eighth.views.admin.activities module --------------------------------------------------- - -.. automodule:: intranet.apps.eighth.views.admin.activities - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.views.admin.attendance module --------------------------------------------------- - -.. automodule:: intranet.apps.eighth.views.admin.attendance - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.views.admin.blocks module ----------------------------------------------- - -.. automodule:: intranet.apps.eighth.views.admin.blocks - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.views.admin.general module ------------------------------------------------ - -.. automodule:: intranet.apps.eighth.views.admin.general - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.views.admin.groups module ----------------------------------------------- - -.. automodule:: intranet.apps.eighth.views.admin.groups - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.views.admin.hybrid module ----------------------------------------------- - -.. automodule:: intranet.apps.eighth.views.admin.hybrid - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.views.admin.maintenance module ---------------------------------------------------- - -.. automodule:: intranet.apps.eighth.views.admin.maintenance - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.views.admin.rooms module ---------------------------------------------- - -.. automodule:: intranet.apps.eighth.views.admin.rooms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.views.admin.scheduling module --------------------------------------------------- - -.. automodule:: intranet.apps.eighth.views.admin.scheduling - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.views.admin.sponsors module ------------------------------------------------- - -.. automodule:: intranet.apps.eighth.views.admin.sponsors - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.views.admin.users module ---------------------------------------------- - -.. automodule:: intranet.apps.eighth.views.admin.users - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.eighth.views.admin - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.eighth.views.rst b/docs/sourcedoc/intranet.apps.eighth.views.rst deleted file mode 100644 index 70c3090dfcd..00000000000 --- a/docs/sourcedoc/intranet.apps.eighth.views.rst +++ /dev/null @@ -1,77 +0,0 @@ -intranet.apps.eighth.views package -================================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.eighth.views.admin - -Submodules ----------- - -intranet.apps.eighth.views.activities module --------------------------------------------- - -.. automodule:: intranet.apps.eighth.views.activities - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.views.api module -------------------------------------- - -.. automodule:: intranet.apps.eighth.views.api - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.views.attendance module --------------------------------------------- - -.. automodule:: intranet.apps.eighth.views.attendance - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.views.monitoring module --------------------------------------------- - -.. automodule:: intranet.apps.eighth.views.monitoring - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.views.profile module ------------------------------------------ - -.. automodule:: intranet.apps.eighth.views.profile - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.views.routers module ------------------------------------------ - -.. automodule:: intranet.apps.eighth.views.routers - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.eighth.views.signup module ----------------------------------------- - -.. automodule:: intranet.apps.eighth.views.signup - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.eighth.views - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.emailfwd.management.commands.rst b/docs/sourcedoc/intranet.apps.emailfwd.management.commands.rst deleted file mode 100644 index de6b12b3e74..00000000000 --- a/docs/sourcedoc/intranet.apps.emailfwd.management.commands.rst +++ /dev/null @@ -1,21 +0,0 @@ -intranet.apps.emailfwd.management.commands package -================================================== - -Submodules ----------- - -intranet.apps.emailfwd.management.commands.get\_senior\_forwards module ------------------------------------------------------------------------ - -.. automodule:: intranet.apps.emailfwd.management.commands.get_senior_forwards - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.emailfwd.management.commands - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.emailfwd.management.rst b/docs/sourcedoc/intranet.apps.emailfwd.management.rst deleted file mode 100644 index 4e1834f37f4..00000000000 --- a/docs/sourcedoc/intranet.apps.emailfwd.management.rst +++ /dev/null @@ -1,18 +0,0 @@ -intranet.apps.emailfwd.management package -========================================= - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.emailfwd.management.commands - -Module contents ---------------- - -.. automodule:: intranet.apps.emailfwd.management - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.emailfwd.rst b/docs/sourcedoc/intranet.apps.emailfwd.rst deleted file mode 100644 index a58bcae5e4c..00000000000 --- a/docs/sourcedoc/intranet.apps.emailfwd.rst +++ /dev/null @@ -1,69 +0,0 @@ -intranet.apps.emailfwd package -============================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.emailfwd.management - -Submodules ----------- - -intranet.apps.emailfwd.apps module ----------------------------------- - -.. automodule:: intranet.apps.emailfwd.apps - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.emailfwd.forms module ------------------------------------ - -.. automodule:: intranet.apps.emailfwd.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.emailfwd.models module ------------------------------------- - -.. automodule:: intranet.apps.emailfwd.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.emailfwd.tests module ------------------------------------ - -.. automodule:: intranet.apps.emailfwd.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.emailfwd.urls module ----------------------------------- - -.. automodule:: intranet.apps.emailfwd.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.emailfwd.views module ------------------------------------ - -.. automodule:: intranet.apps.emailfwd.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.emailfwd - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.emerg.rst b/docs/sourcedoc/intranet.apps.emerg.rst deleted file mode 100644 index 4232ed6d4a0..00000000000 --- a/docs/sourcedoc/intranet.apps.emerg.rst +++ /dev/null @@ -1,45 +0,0 @@ -intranet.apps.emerg package -=========================== - -Submodules ----------- - -intranet.apps.emerg.api module ------------------------------- - -.. automodule:: intranet.apps.emerg.api - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.emerg.tasks module --------------------------------- - -.. automodule:: intranet.apps.emerg.tasks - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.emerg.tests module --------------------------------- - -.. automodule:: intranet.apps.emerg.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.emerg.views module --------------------------------- - -.. automodule:: intranet.apps.emerg.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.emerg - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.enrichment.rst b/docs/sourcedoc/intranet.apps.enrichment.rst deleted file mode 100644 index 789f348bc00..00000000000 --- a/docs/sourcedoc/intranet.apps.enrichment.rst +++ /dev/null @@ -1,53 +0,0 @@ -intranet.apps.enrichment package -================================ - -Submodules ----------- - -intranet.apps.enrichment.admin module -------------------------------------- - -.. automodule:: intranet.apps.enrichment.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.enrichment.forms module -------------------------------------- - -.. automodule:: intranet.apps.enrichment.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.enrichment.models module --------------------------------------- - -.. automodule:: intranet.apps.enrichment.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.enrichment.urls module ------------------------------------- - -.. automodule:: intranet.apps.enrichment.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.enrichment.views module -------------------------------------- - -.. automodule:: intranet.apps.enrichment.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.enrichment - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.error.rst b/docs/sourcedoc/intranet.apps.error.rst deleted file mode 100644 index 87ecaed4177..00000000000 --- a/docs/sourcedoc/intranet.apps.error.rst +++ /dev/null @@ -1,29 +0,0 @@ -intranet.apps.error package -=========================== - -Submodules ----------- - -intranet.apps.error.tests module --------------------------------- - -.. automodule:: intranet.apps.error.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.error.views module --------------------------------- - -.. automodule:: intranet.apps.error.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.error - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.events.management.commands.rst b/docs/sourcedoc/intranet.apps.events.management.commands.rst deleted file mode 100644 index 5a948248af8..00000000000 --- a/docs/sourcedoc/intranet.apps.events.management.commands.rst +++ /dev/null @@ -1,21 +0,0 @@ -intranet.apps.events.management.commands package -================================================ - -Submodules ----------- - -intranet.apps.events.management.commands.import\_sports module --------------------------------------------------------------- - -.. automodule:: intranet.apps.events.management.commands.import_sports - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.events.management.commands - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.events.management.rst b/docs/sourcedoc/intranet.apps.events.management.rst deleted file mode 100644 index 2a68c6f6e9e..00000000000 --- a/docs/sourcedoc/intranet.apps.events.management.rst +++ /dev/null @@ -1,18 +0,0 @@ -intranet.apps.events.management package -======================================= - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.events.management.commands - -Module contents ---------------- - -.. automodule:: intranet.apps.events.management - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.events.rst b/docs/sourcedoc/intranet.apps.events.rst deleted file mode 100644 index 98b5f7c6fb7..00000000000 --- a/docs/sourcedoc/intranet.apps.events.rst +++ /dev/null @@ -1,85 +0,0 @@ -intranet.apps.events package -============================ - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.events.management - -Submodules ----------- - -intranet.apps.events.admin module ---------------------------------- - -.. automodule:: intranet.apps.events.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.events.forms module ---------------------------------- - -.. automodule:: intranet.apps.events.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.events.models module ----------------------------------- - -.. automodule:: intranet.apps.events.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.events.notifications module ------------------------------------------ - -.. automodule:: intranet.apps.events.notifications - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.events.tasks module ---------------------------------- - -.. automodule:: intranet.apps.events.tasks - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.events.tests module ---------------------------------- - -.. automodule:: intranet.apps.events.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.events.urls module --------------------------------- - -.. automodule:: intranet.apps.events.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.events.views module ---------------------------------- - -.. automodule:: intranet.apps.events.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.events - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.features.rst b/docs/sourcedoc/intranet.apps.features.rst deleted file mode 100644 index 44a9d6a1e5a..00000000000 --- a/docs/sourcedoc/intranet.apps.features.rst +++ /dev/null @@ -1,69 +0,0 @@ -intranet.apps.features package -============================== - -Submodules ----------- - -intranet.apps.features.admin module ------------------------------------ - -.. automodule:: intranet.apps.features.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.features.context\_processors module -------------------------------------------------- - -.. automodule:: intranet.apps.features.context_processors - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.features.helpers module -------------------------------------- - -.. automodule:: intranet.apps.features.helpers - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.features.models module ------------------------------------- - -.. automodule:: intranet.apps.features.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.features.tests module ------------------------------------ - -.. automodule:: intranet.apps.features.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.features.urls module ----------------------------------- - -.. automodule:: intranet.apps.features.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.features.views module ------------------------------------ - -.. automodule:: intranet.apps.features.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.features - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.feedback.rst b/docs/sourcedoc/intranet.apps.feedback.rst deleted file mode 100644 index eb3f4c2013e..00000000000 --- a/docs/sourcedoc/intranet.apps.feedback.rst +++ /dev/null @@ -1,61 +0,0 @@ -intranet.apps.feedback package -============================== - -Submodules ----------- - -intranet.apps.feedback.admin module ------------------------------------ - -.. automodule:: intranet.apps.feedback.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.feedback.forms module ------------------------------------ - -.. automodule:: intranet.apps.feedback.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.feedback.models module ------------------------------------- - -.. automodule:: intranet.apps.feedback.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.feedback.tests module ------------------------------------ - -.. automodule:: intranet.apps.feedback.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.feedback.urls module ----------------------------------- - -.. automodule:: intranet.apps.feedback.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.feedback.views module ------------------------------------ - -.. automodule:: intranet.apps.feedback.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.feedback - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.files.rst b/docs/sourcedoc/intranet.apps.files.rst deleted file mode 100644 index 6ca8c9c21e9..00000000000 --- a/docs/sourcedoc/intranet.apps.files.rst +++ /dev/null @@ -1,61 +0,0 @@ -intranet.apps.files package -=========================== - -Submodules ----------- - -intranet.apps.files.admin module --------------------------------- - -.. automodule:: intranet.apps.files.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.files.forms module --------------------------------- - -.. automodule:: intranet.apps.files.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.files.models module ---------------------------------- - -.. automodule:: intranet.apps.files.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.files.tests module --------------------------------- - -.. automodule:: intranet.apps.files.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.files.urls module -------------------------------- - -.. automodule:: intranet.apps.files.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.files.views module --------------------------------- - -.. automodule:: intranet.apps.files.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.files - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.groups.rst b/docs/sourcedoc/intranet.apps.groups.rst deleted file mode 100644 index d1f8036ca82..00000000000 --- a/docs/sourcedoc/intranet.apps.groups.rst +++ /dev/null @@ -1,53 +0,0 @@ -intranet.apps.groups package -============================ - -Submodules ----------- - -intranet.apps.groups.forms module ---------------------------------- - -.. automodule:: intranet.apps.groups.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.groups.models module ----------------------------------- - -.. automodule:: intranet.apps.groups.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.groups.tests module ---------------------------------- - -.. automodule:: intranet.apps.groups.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.groups.urls module --------------------------------- - -.. automodule:: intranet.apps.groups.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.groups.views module ---------------------------------- - -.. automodule:: intranet.apps.groups.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.groups - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.itemreg.rst b/docs/sourcedoc/intranet.apps.itemreg.rst deleted file mode 100644 index 97b116b495f..00000000000 --- a/docs/sourcedoc/intranet.apps.itemreg.rst +++ /dev/null @@ -1,77 +0,0 @@ -intranet.apps.itemreg package -============================= - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.itemreg.templatetags - -Submodules ----------- - -intranet.apps.itemreg.admin module ----------------------------------- - -.. automodule:: intranet.apps.itemreg.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.itemreg.apps module ---------------------------------- - -.. automodule:: intranet.apps.itemreg.apps - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.itemreg.forms module ----------------------------------- - -.. automodule:: intranet.apps.itemreg.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.itemreg.models module ------------------------------------ - -.. automodule:: intranet.apps.itemreg.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.itemreg.tests module ----------------------------------- - -.. automodule:: intranet.apps.itemreg.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.itemreg.urls module ---------------------------------- - -.. automodule:: intranet.apps.itemreg.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.itemreg.views module ----------------------------------- - -.. automodule:: intranet.apps.itemreg.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.itemreg - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.itemreg.templatetags.rst b/docs/sourcedoc/intranet.apps.itemreg.templatetags.rst deleted file mode 100644 index c389be430a2..00000000000 --- a/docs/sourcedoc/intranet.apps.itemreg.templatetags.rst +++ /dev/null @@ -1,21 +0,0 @@ -intranet.apps.itemreg.templatetags package -========================================== - -Submodules ----------- - -intranet.apps.itemreg.templatetags.texthighlight module -------------------------------------------------------- - -.. automodule:: intranet.apps.itemreg.templatetags.texthighlight - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.itemreg.templatetags - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.logs.rst b/docs/sourcedoc/intranet.apps.logs.rst deleted file mode 100644 index d0368bedf4b..00000000000 --- a/docs/sourcedoc/intranet.apps.logs.rst +++ /dev/null @@ -1,61 +0,0 @@ -intranet.apps.logs package -========================== - -Submodules ----------- - -intranet.apps.logs.admin module -------------------------------- - -.. automodule:: intranet.apps.logs.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.logs.forms module -------------------------------- - -.. automodule:: intranet.apps.logs.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.logs.models module --------------------------------- - -.. automodule:: intranet.apps.logs.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.logs.tests module -------------------------------- - -.. automodule:: intranet.apps.logs.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.logs.urls module ------------------------------- - -.. automodule:: intranet.apps.logs.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.logs.views module -------------------------------- - -.. automodule:: intranet.apps.logs.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.logs - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.lostfound.rst b/docs/sourcedoc/intranet.apps.lostfound.rst deleted file mode 100644 index ec4a293a0af..00000000000 --- a/docs/sourcedoc/intranet.apps.lostfound.rst +++ /dev/null @@ -1,69 +0,0 @@ -intranet.apps.lostfound package -=============================== - -Submodules ----------- - -intranet.apps.lostfound.admin module ------------------------------------- - -.. automodule:: intranet.apps.lostfound.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.lostfound.apps module ------------------------------------ - -.. automodule:: intranet.apps.lostfound.apps - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.lostfound.forms module ------------------------------------- - -.. automodule:: intranet.apps.lostfound.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.lostfound.models module -------------------------------------- - -.. automodule:: intranet.apps.lostfound.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.lostfound.tests module ------------------------------------- - -.. automodule:: intranet.apps.lostfound.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.lostfound.urls module ------------------------------------ - -.. automodule:: intranet.apps.lostfound.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.lostfound.views module ------------------------------------- - -.. automodule:: intranet.apps.lostfound.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.lostfound - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.nomination.rst b/docs/sourcedoc/intranet.apps.nomination.rst deleted file mode 100644 index 2c11f0fd21f..00000000000 --- a/docs/sourcedoc/intranet.apps.nomination.rst +++ /dev/null @@ -1,53 +0,0 @@ -intranet.apps.nomination package -================================ - -Submodules ----------- - -intranet.apps.nomination.apps module ------------------------------------- - -.. automodule:: intranet.apps.nomination.apps - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.nomination.models module --------------------------------------- - -.. automodule:: intranet.apps.nomination.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.nomination.tests module -------------------------------------- - -.. automodule:: intranet.apps.nomination.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.nomination.urls module ------------------------------------- - -.. automodule:: intranet.apps.nomination.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.nomination.views module -------------------------------------- - -.. automodule:: intranet.apps.nomination.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.nomination - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.notifications.rst b/docs/sourcedoc/intranet.apps.notifications.rst deleted file mode 100644 index 3f5e527d432..00000000000 --- a/docs/sourcedoc/intranet.apps.notifications.rst +++ /dev/null @@ -1,53 +0,0 @@ -intranet.apps.notifications package -=================================== - -Submodules ----------- - -intranet.apps.notifications.emails module ------------------------------------------ - -.. automodule:: intranet.apps.notifications.emails - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.notifications.models module ------------------------------------------ - -.. automodule:: intranet.apps.notifications.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.notifications.tasks module ----------------------------------------- - -.. automodule:: intranet.apps.notifications.tasks - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.notifications.urls module ---------------------------------------- - -.. automodule:: intranet.apps.notifications.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.notifications.views module ----------------------------------------- - -.. automodule:: intranet.apps.notifications.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.notifications - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.oauth.rst b/docs/sourcedoc/intranet.apps.oauth.rst deleted file mode 100644 index 4b3b094232d..00000000000 --- a/docs/sourcedoc/intranet.apps.oauth.rst +++ /dev/null @@ -1,53 +0,0 @@ -intranet.apps.oauth package -=========================== - -Submodules ----------- - -intranet.apps.oauth.admin module --------------------------------- - -.. automodule:: intranet.apps.oauth.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.oauth.apps module -------------------------------- - -.. automodule:: intranet.apps.oauth.apps - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.oauth.models module ---------------------------------- - -.. automodule:: intranet.apps.oauth.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.oauth.tests module --------------------------------- - -.. automodule:: intranet.apps.oauth.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.oauth.views module --------------------------------- - -.. automodule:: intranet.apps.oauth.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.oauth - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.parking.rst b/docs/sourcedoc/intranet.apps.parking.rst deleted file mode 100644 index 71b36f10fd3..00000000000 --- a/docs/sourcedoc/intranet.apps.parking.rst +++ /dev/null @@ -1,69 +0,0 @@ -intranet.apps.parking package -============================= - -Submodules ----------- - -intranet.apps.parking.admin module ----------------------------------- - -.. automodule:: intranet.apps.parking.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.parking.apps module ---------------------------------- - -.. automodule:: intranet.apps.parking.apps - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.parking.forms module ----------------------------------- - -.. automodule:: intranet.apps.parking.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.parking.models module ------------------------------------ - -.. automodule:: intranet.apps.parking.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.parking.tests module ----------------------------------- - -.. automodule:: intranet.apps.parking.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.parking.urls module ---------------------------------- - -.. automodule:: intranet.apps.parking.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.parking.views module ----------------------------------- - -.. automodule:: intranet.apps.parking.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.parking - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.polls.rst b/docs/sourcedoc/intranet.apps.polls.rst deleted file mode 100644 index d5e430efb60..00000000000 --- a/docs/sourcedoc/intranet.apps.polls.rst +++ /dev/null @@ -1,61 +0,0 @@ -intranet.apps.polls package -=========================== - -Submodules ----------- - -intranet.apps.polls.admin module --------------------------------- - -.. automodule:: intranet.apps.polls.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.polls.forms module --------------------------------- - -.. automodule:: intranet.apps.polls.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.polls.models module ---------------------------------- - -.. automodule:: intranet.apps.polls.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.polls.tests module --------------------------------- - -.. automodule:: intranet.apps.polls.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.polls.urls module -------------------------------- - -.. automodule:: intranet.apps.polls.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.polls.views module --------------------------------- - -.. automodule:: intranet.apps.polls.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.polls - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.preferences.rst b/docs/sourcedoc/intranet.apps.preferences.rst deleted file mode 100644 index 245e0cbe9eb..00000000000 --- a/docs/sourcedoc/intranet.apps.preferences.rst +++ /dev/null @@ -1,53 +0,0 @@ -intranet.apps.preferences package -================================= - -Submodules ----------- - -intranet.apps.preferences.fields module ---------------------------------------- - -.. automodule:: intranet.apps.preferences.fields - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.preferences.forms module --------------------------------------- - -.. automodule:: intranet.apps.preferences.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.preferences.tests module --------------------------------------- - -.. automodule:: intranet.apps.preferences.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.preferences.urls module -------------------------------------- - -.. automodule:: intranet.apps.preferences.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.preferences.views module --------------------------------------- - -.. automodule:: intranet.apps.preferences.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.preferences - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.printing.rst b/docs/sourcedoc/intranet.apps.printing.rst deleted file mode 100644 index 203267d42e5..00000000000 --- a/docs/sourcedoc/intranet.apps.printing.rst +++ /dev/null @@ -1,61 +0,0 @@ -intranet.apps.printing package -============================== - -Submodules ----------- - -intranet.apps.printing.admin module ------------------------------------ - -.. automodule:: intranet.apps.printing.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.printing.forms module ------------------------------------ - -.. automodule:: intranet.apps.printing.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.printing.models module ------------------------------------- - -.. automodule:: intranet.apps.printing.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.printing.tests module ------------------------------------ - -.. automodule:: intranet.apps.printing.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.printing.urls module ----------------------------------- - -.. automodule:: intranet.apps.printing.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.printing.views module ------------------------------------ - -.. automodule:: intranet.apps.printing.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.printing - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.rst b/docs/sourcedoc/intranet.apps.rst deleted file mode 100644 index a9060e44104..00000000000 --- a/docs/sourcedoc/intranet.apps.rst +++ /dev/null @@ -1,64 +0,0 @@ -intranet.apps package -===================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.announcements - intranet.apps.api - intranet.apps.auth - intranet.apps.bus - intranet.apps.cslapps - intranet.apps.customthemes - intranet.apps.dashboard - intranet.apps.dataimport - intranet.apps.eighth - intranet.apps.emailfwd - intranet.apps.emerg - intranet.apps.enrichment - intranet.apps.error - intranet.apps.events - intranet.apps.features - intranet.apps.feedback - intranet.apps.files - intranet.apps.groups - intranet.apps.itemreg - intranet.apps.logs - intranet.apps.lostfound - intranet.apps.nomination - intranet.apps.notifications - intranet.apps.oauth - intranet.apps.parking - intranet.apps.polls - intranet.apps.preferences - intranet.apps.printing - intranet.apps.schedule - intranet.apps.search - intranet.apps.seniors - intranet.apps.sessionmgmt - intranet.apps.signage - intranet.apps.templatetags - intranet.apps.users - intranet.apps.welcome - -Submodules ----------- - -intranet.apps.context\_processors module ----------------------------------------- - -.. automodule:: intranet.apps.context_processors - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.schedule.management.commands.rst b/docs/sourcedoc/intranet.apps.schedule.management.commands.rst deleted file mode 100644 index 89ed52a0dd1..00000000000 --- a/docs/sourcedoc/intranet.apps.schedule.management.commands.rst +++ /dev/null @@ -1,21 +0,0 @@ -intranet.apps.schedule.management.commands package -================================================== - -Submodules ----------- - -intranet.apps.schedule.management.commands.schedule\_notify module ------------------------------------------------------------------- - -.. automodule:: intranet.apps.schedule.management.commands.schedule_notify - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.schedule.management.commands - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.schedule.management.rst b/docs/sourcedoc/intranet.apps.schedule.management.rst deleted file mode 100644 index 6c86dc01daf..00000000000 --- a/docs/sourcedoc/intranet.apps.schedule.management.rst +++ /dev/null @@ -1,18 +0,0 @@ -intranet.apps.schedule.management package -========================================= - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.schedule.management.commands - -Module contents ---------------- - -.. automodule:: intranet.apps.schedule.management - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.schedule.rst b/docs/sourcedoc/intranet.apps.schedule.rst deleted file mode 100644 index 13257fcc2e8..00000000000 --- a/docs/sourcedoc/intranet.apps.schedule.rst +++ /dev/null @@ -1,93 +0,0 @@ -intranet.apps.schedule package -============================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.schedule.management - -Submodules ----------- - -intranet.apps.schedule.admin module ------------------------------------ - -.. automodule:: intranet.apps.schedule.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.schedule.api module ---------------------------------- - -.. automodule:: intranet.apps.schedule.api - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.schedule.forms module ------------------------------------ - -.. automodule:: intranet.apps.schedule.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.schedule.models module ------------------------------------- - -.. automodule:: intranet.apps.schedule.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.schedule.notifications module -------------------------------------------- - -.. automodule:: intranet.apps.schedule.notifications - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.schedule.serializers module ------------------------------------------ - -.. automodule:: intranet.apps.schedule.serializers - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.schedule.tests module ------------------------------------ - -.. automodule:: intranet.apps.schedule.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.schedule.urls module ----------------------------------- - -.. automodule:: intranet.apps.schedule.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.schedule.views module ------------------------------------ - -.. automodule:: intranet.apps.schedule.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.schedule - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.search.rst b/docs/sourcedoc/intranet.apps.search.rst deleted file mode 100644 index bc55d23e2fa..00000000000 --- a/docs/sourcedoc/intranet.apps.search.rst +++ /dev/null @@ -1,45 +0,0 @@ -intranet.apps.search package -============================ - -Submodules ----------- - -intranet.apps.search.tests module ---------------------------------- - -.. automodule:: intranet.apps.search.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.search.urls module --------------------------------- - -.. automodule:: intranet.apps.search.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.search.utils module ---------------------------------- - -.. automodule:: intranet.apps.search.utils - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.search.views module ---------------------------------- - -.. automodule:: intranet.apps.search.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.search - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.seniors.management.commands.rst b/docs/sourcedoc/intranet.apps.seniors.management.commands.rst deleted file mode 100644 index 787cde9fd38..00000000000 --- a/docs/sourcedoc/intranet.apps.seniors.management.commands.rst +++ /dev/null @@ -1,29 +0,0 @@ -intranet.apps.seniors.management.commands package -================================================= - -Submodules ----------- - -intranet.apps.seniors.management.commands.cleanup\_destinations module ----------------------------------------------------------------------- - -.. automodule:: intranet.apps.seniors.management.commands.cleanup_destinations - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.seniors.management.commands.import\_colleges module ------------------------------------------------------------------ - -.. automodule:: intranet.apps.seniors.management.commands.import_colleges - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.seniors.management.commands - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.seniors.management.rst b/docs/sourcedoc/intranet.apps.seniors.management.rst deleted file mode 100644 index b403f1fb432..00000000000 --- a/docs/sourcedoc/intranet.apps.seniors.management.rst +++ /dev/null @@ -1,18 +0,0 @@ -intranet.apps.seniors.management package -======================================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.seniors.management.commands - -Module contents ---------------- - -.. automodule:: intranet.apps.seniors.management - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.seniors.rst b/docs/sourcedoc/intranet.apps.seniors.rst deleted file mode 100644 index 108fb2db02e..00000000000 --- a/docs/sourcedoc/intranet.apps.seniors.rst +++ /dev/null @@ -1,69 +0,0 @@ -intranet.apps.seniors package -============================= - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.seniors.management - -Submodules ----------- - -intranet.apps.seniors.admin module ----------------------------------- - -.. automodule:: intranet.apps.seniors.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.seniors.forms module ----------------------------------- - -.. automodule:: intranet.apps.seniors.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.seniors.models module ------------------------------------ - -.. automodule:: intranet.apps.seniors.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.seniors.tests module ----------------------------------- - -.. automodule:: intranet.apps.seniors.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.seniors.urls module ---------------------------------- - -.. automodule:: intranet.apps.seniors.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.seniors.views module ----------------------------------- - -.. automodule:: intranet.apps.seniors.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.seniors - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.sessionmgmt.rst b/docs/sourcedoc/intranet.apps.sessionmgmt.rst deleted file mode 100644 index bcfff526d75..00000000000 --- a/docs/sourcedoc/intranet.apps.sessionmgmt.rst +++ /dev/null @@ -1,61 +0,0 @@ -intranet.apps.sessionmgmt package -================================= - -Submodules ----------- - -intranet.apps.sessionmgmt.admin module --------------------------------------- - -.. automodule:: intranet.apps.sessionmgmt.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.sessionmgmt.helpers module ----------------------------------------- - -.. automodule:: intranet.apps.sessionmgmt.helpers - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.sessionmgmt.models module ---------------------------------------- - -.. automodule:: intranet.apps.sessionmgmt.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.sessionmgmt.tests module --------------------------------------- - -.. automodule:: intranet.apps.sessionmgmt.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.sessionmgmt.urls module -------------------------------------- - -.. automodule:: intranet.apps.sessionmgmt.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.sessionmgmt.views module --------------------------------------- - -.. automodule:: intranet.apps.sessionmgmt.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.sessionmgmt - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.signage.rst b/docs/sourcedoc/intranet.apps.signage.rst deleted file mode 100644 index 0c195c42cc4..00000000000 --- a/docs/sourcedoc/intranet.apps.signage.rst +++ /dev/null @@ -1,85 +0,0 @@ -intranet.apps.signage package -============================= - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.signage.templatetags - -Submodules ----------- - -intranet.apps.signage.admin module ----------------------------------- - -.. automodule:: intranet.apps.signage.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.signage.consumers module --------------------------------------- - -.. automodule:: intranet.apps.signage.consumers - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.signage.forms module ----------------------------------- - -.. automodule:: intranet.apps.signage.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.signage.models module ------------------------------------ - -.. automodule:: intranet.apps.signage.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.signage.pages module ----------------------------------- - -.. automodule:: intranet.apps.signage.pages - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.signage.tests module ----------------------------------- - -.. automodule:: intranet.apps.signage.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.signage.urls module ---------------------------------- - -.. automodule:: intranet.apps.signage.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.signage.views module ----------------------------------- - -.. automodule:: intranet.apps.signage.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.signage - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.signage.templatetags.rst b/docs/sourcedoc/intranet.apps.signage.templatetags.rst deleted file mode 100644 index c0f2920c237..00000000000 --- a/docs/sourcedoc/intranet.apps.signage.templatetags.rst +++ /dev/null @@ -1,21 +0,0 @@ -intranet.apps.signage.templatetags package -========================================== - -Submodules ----------- - -intranet.apps.signage.templatetags.signage module -------------------------------------------------- - -.. automodule:: intranet.apps.signage.templatetags.signage - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.signage.templatetags - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.templatetags.rst b/docs/sourcedoc/intranet.apps.templatetags.rst deleted file mode 100644 index 331be35421b..00000000000 --- a/docs/sourcedoc/intranet.apps.templatetags.rst +++ /dev/null @@ -1,93 +0,0 @@ -intranet.apps.templatetags package -================================== - -Submodules ----------- - -intranet.apps.templatetags.dates module ---------------------------------------- - -.. automodule:: intranet.apps.templatetags.dates - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.templatetags.dictionaries module ----------------------------------------------- - -.. automodule:: intranet.apps.templatetags.dictionaries - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.templatetags.form\_field module ---------------------------------------------- - -.. automodule:: intranet.apps.templatetags.form_field - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.templatetags.forms module ---------------------------------------- - -.. automodule:: intranet.apps.templatetags.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.templatetags.math module --------------------------------------- - -.. automodule:: intranet.apps.templatetags.math - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.templatetags.newtab\_links module ------------------------------------------------ - -.. automodule:: intranet.apps.templatetags.newtab_links - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.templatetags.paginate module ------------------------------------------- - -.. automodule:: intranet.apps.templatetags.paginate - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.templatetags.status\_helper module ------------------------------------------------- - -.. automodule:: intranet.apps.templatetags.status_helper - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.templatetags.strings module ------------------------------------------ - -.. automodule:: intranet.apps.templatetags.strings - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.templatetags.tests module ---------------------------------------- - -.. automodule:: intranet.apps.templatetags.tests - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.templatetags - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.users.management.commands.rst b/docs/sourcedoc/intranet.apps.users.management.commands.rst deleted file mode 100644 index 62155120217..00000000000 --- a/docs/sourcedoc/intranet.apps.users.management.commands.rst +++ /dev/null @@ -1,29 +0,0 @@ -intranet.apps.users.management.commands package -=============================================== - -Submodules ----------- - -intranet.apps.users.management.commands.import\_groups module -------------------------------------------------------------- - -.. automodule:: intranet.apps.users.management.commands.import_groups - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.users.management.commands.lock module ---------------------------------------------------- - -.. automodule:: intranet.apps.users.management.commands.lock - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.users.management.commands - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.users.management.rst b/docs/sourcedoc/intranet.apps.users.management.rst deleted file mode 100644 index d5422432620..00000000000 --- a/docs/sourcedoc/intranet.apps.users.management.rst +++ /dev/null @@ -1,18 +0,0 @@ -intranet.apps.users.management package -====================================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.users.management.commands - -Module contents ---------------- - -.. automodule:: intranet.apps.users.management - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.users.rst b/docs/sourcedoc/intranet.apps.users.rst deleted file mode 100644 index f95f5f97436..00000000000 --- a/docs/sourcedoc/intranet.apps.users.rst +++ /dev/null @@ -1,102 +0,0 @@ -intranet.apps.users package -=========================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps.users.management - intranet.apps.users.templatetags - -Submodules ----------- - -intranet.apps.users.admin module --------------------------------- - -.. automodule:: intranet.apps.users.admin - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.users.api module ------------------------------- - -.. automodule:: intranet.apps.users.api - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.users.courses\_urls module ----------------------------------------- - -.. automodule:: intranet.apps.users.courses_urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.users.forms module --------------------------------- - -.. automodule:: intranet.apps.users.forms - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.users.models module ---------------------------------- - -.. automodule:: intranet.apps.users.models - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.users.renderers module ------------------------------------- - -.. automodule:: intranet.apps.users.renderers - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.users.serializers module --------------------------------------- - -.. automodule:: intranet.apps.users.serializers - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.users.tests module --------------------------------- - -.. automodule:: intranet.apps.users.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.users.urls module -------------------------------- - -.. automodule:: intranet.apps.users.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.users.views module --------------------------------- - -.. automodule:: intranet.apps.users.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.users - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.users.templatetags.rst b/docs/sourcedoc/intranet.apps.users.templatetags.rst deleted file mode 100644 index 670d206b67e..00000000000 --- a/docs/sourcedoc/intranet.apps.users.templatetags.rst +++ /dev/null @@ -1,37 +0,0 @@ -intranet.apps.users.templatetags package -======================================== - -Submodules ----------- - -intranet.apps.users.templatetags.grades module ----------------------------------------------- - -.. automodule:: intranet.apps.users.templatetags.grades - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.users.templatetags.phone\_numbers module ------------------------------------------------------- - -.. automodule:: intranet.apps.users.templatetags.phone_numbers - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.users.templatetags.users module ---------------------------------------------- - -.. automodule:: intranet.apps.users.templatetags.users - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.users.templatetags - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.apps.welcome.rst b/docs/sourcedoc/intranet.apps.welcome.rst deleted file mode 100644 index 4fd49dadd8f..00000000000 --- a/docs/sourcedoc/intranet.apps.welcome.rst +++ /dev/null @@ -1,37 +0,0 @@ -intranet.apps.welcome package -============================= - -Submodules ----------- - -intranet.apps.welcome.tests module ----------------------------------- - -.. automodule:: intranet.apps.welcome.tests - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.welcome.urls module ---------------------------------- - -.. automodule:: intranet.apps.welcome.urls - :members: - :undoc-members: - :show-inheritance: - -intranet.apps.welcome.views module ----------------------------------- - -.. automodule:: intranet.apps.welcome.views - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.apps.welcome - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.middleware.rst b/docs/sourcedoc/intranet.middleware.rst deleted file mode 100644 index fdacc55e1b5..00000000000 --- a/docs/sourcedoc/intranet.middleware.rst +++ /dev/null @@ -1,101 +0,0 @@ -intranet.middleware package -=========================== - -Submodules ----------- - -intranet.middleware.access\_log module --------------------------------------- - -.. automodule:: intranet.middleware.access_log - :members: - :undoc-members: - :show-inheritance: - -intranet.middleware.ajax module -------------------------------- - -.. automodule:: intranet.middleware.ajax - :members: - :undoc-members: - :show-inheritance: - -intranet.middleware.dark\_mode module -------------------------------------- - -.. automodule:: intranet.middleware.dark_mode - :members: - :undoc-members: - :show-inheritance: - -intranet.middleware.monitoring module -------------------------------------- - -.. automodule:: intranet.middleware.monitoring - :members: - :undoc-members: - :show-inheritance: - -intranet.middleware.profiler module ------------------------------------ - -.. automodule:: intranet.middleware.profiler - :members: - :undoc-members: - :show-inheritance: - -intranet.middleware.same\_origin module ---------------------------------------- - -.. automodule:: intranet.middleware.same_origin - :members: - :undoc-members: - :show-inheritance: - -intranet.middleware.session\_management module ----------------------------------------------- - -.. automodule:: intranet.middleware.session_management - :members: - :undoc-members: - :show-inheritance: - -intranet.middleware.templates module ------------------------------------- - -.. automodule:: intranet.middleware.templates - :members: - :undoc-members: - :show-inheritance: - -intranet.middleware.threadlocals module ---------------------------------------- - -.. automodule:: intranet.middleware.threadlocals - :members: - :undoc-members: - :show-inheritance: - -intranet.middleware.traceback module ------------------------------------- - -.. automodule:: intranet.middleware.traceback - :members: - :undoc-members: - :show-inheritance: - -intranet.middleware.url\_slashes module ---------------------------------------- - -.. automodule:: intranet.middleware.url_slashes - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.middleware - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.rst b/docs/sourcedoc/intranet.rst deleted file mode 100644 index 671f4081921..00000000000 --- a/docs/sourcedoc/intranet.rst +++ /dev/null @@ -1,57 +0,0 @@ -intranet package -================ - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - intranet.apps - intranet.middleware - intranet.settings - intranet.test - intranet.utils - -Submodules ----------- - -intranet.asgi module --------------------- - -.. automodule:: intranet.asgi - :members: - :undoc-members: - :show-inheritance: - -intranet.celery module ----------------------- - -.. automodule:: intranet.celery - :members: - :undoc-members: - :show-inheritance: - -intranet.routing module ------------------------ - -.. automodule:: intranet.routing - :members: - :undoc-members: - :show-inheritance: - -intranet.wsgi module --------------------- - -.. automodule:: intranet.wsgi - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.settings.rst b/docs/sourcedoc/intranet.settings.rst deleted file mode 100644 index a5eaa0add53..00000000000 --- a/docs/sourcedoc/intranet.settings.rst +++ /dev/null @@ -1,21 +0,0 @@ -intranet.settings package -========================= - -Submodules ----------- - -intranet.settings.ci\_secret module ------------------------------------ - -.. automodule:: intranet.settings.ci_secret - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.settings - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.test.rst b/docs/sourcedoc/intranet.test.rst deleted file mode 100644 index 084e3ba43aa..00000000000 --- a/docs/sourcedoc/intranet.test.rst +++ /dev/null @@ -1,29 +0,0 @@ -intranet.test package -===================== - -Submodules ----------- - -intranet.test.ion\_test module ------------------------------- - -.. automodule:: intranet.test.ion_test - :members: - :undoc-members: - :show-inheritance: - -intranet.test.test\_suite module --------------------------------- - -.. automodule:: intranet.test.test_suite - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.test - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/intranet.utils.rst b/docs/sourcedoc/intranet.utils.rst deleted file mode 100644 index d8788cb4f3c..00000000000 --- a/docs/sourcedoc/intranet.utils.rst +++ /dev/null @@ -1,85 +0,0 @@ -intranet.utils package -====================== - -Submodules ----------- - -intranet.utils.admin\_helpers module ------------------------------------- - -.. automodule:: intranet.utils.admin_helpers - :members: - :undoc-members: - :show-inheritance: - -intranet.utils.cache module ---------------------------- - -.. automodule:: intranet.utils.cache - :members: - :undoc-members: - :show-inheritance: - -intranet.utils.date module --------------------------- - -.. automodule:: intranet.utils.date - :members: - :undoc-members: - :show-inheritance: - -intranet.utils.deletion module ------------------------------- - -.. automodule:: intranet.utils.deletion - :members: - :undoc-members: - :show-inheritance: - -intranet.utils.helpers module ------------------------------ - -.. automodule:: intranet.utils.helpers - :members: - :undoc-members: - :show-inheritance: - -intranet.utils.html module --------------------------- - -.. automodule:: intranet.utils.html - :members: - :undoc-members: - :show-inheritance: - -intranet.utils.locking module ------------------------------ - -.. automodule:: intranet.utils.locking - :members: - :undoc-members: - :show-inheritance: - -intranet.utils.serialization module ------------------------------------ - -.. automodule:: intranet.utils.serialization - :members: - :undoc-members: - :show-inheritance: - -intranet.utils.session module ------------------------------ - -.. automodule:: intranet.utils.session - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: intranet.utils - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sourcedoc/modules.rst b/docs/sourcedoc/modules.rst deleted file mode 100644 index 9df35f534b0..00000000000 --- a/docs/sourcedoc/modules.rst +++ /dev/null @@ -1,7 +0,0 @@ -intranet -======== - -.. toctree:: - :maxdepth: 4 - - intranet diff --git a/intranet/apps/announcements/admin.py b/intranet/apps/announcements/admin.py index 1232daaeaf6..d3073ead74a 100644 --- a/intranet/apps/announcements/admin.py +++ b/intranet/apps/announcements/admin.py @@ -4,6 +4,7 @@ from .models import Announcement, WarningAnnouncement +@admin.register(Announcement) class AnnouncementAdmin(SimpleHistoryAdmin): list_display = ("title", "user", "author", "activity", "added") list_filter = ("added", "updated", "activity") @@ -12,11 +13,8 @@ class AnnouncementAdmin(SimpleHistoryAdmin): search_fields = ("title", "content", "user__first_name", "user__last_name", "user__username") +@admin.register(WarningAnnouncement) class WarningAnnouncementAdmin(SimpleHistoryAdmin): list_display = ("title", "content", "active") list_filter = ("active",) search_fields = ("title", "content") - - -admin.site.register(Announcement, AnnouncementAdmin) -admin.site.register(WarningAnnouncement, WarningAnnouncementAdmin) diff --git a/intranet/apps/announcements/api.py b/intranet/apps/announcements/api.py index 8f6b4bc4499..fcec44386e9 100644 --- a/intranet/apps/announcements/api.py +++ b/intranet/apps/announcements/api.py @@ -11,8 +11,8 @@ class IsAnnouncementAdminOrReadOnly(permissions.BasePermission): def has_permission(self, request, view): return ( - request.user and request.user.is_authenticated and not request.user.is_restricted and request.user.oauth_and_api_access or request.auth - ) and (request.method in permissions.SAFE_METHODS or request.user and request.user.is_announcements_admin) + (request.user and request.user.is_authenticated and not request.user.is_restricted and request.user.oauth_and_api_access) or request.auth + ) and (request.method in permissions.SAFE_METHODS or (request.user and request.user.is_announcements_admin)) class ListCreateAnnouncement(generics.ListCreateAPIView): diff --git a/intranet/apps/announcements/migrations/0034_historicalannouncement_historicalwarningannouncement.py b/intranet/apps/announcements/migrations/0034_historicalannouncement_historicalwarningannouncement.py index 30a9005aae9..d3007d6249c 100644 --- a/intranet/apps/announcements/migrations/0034_historicalannouncement_historicalwarningannouncement.py +++ b/intranet/apps/announcements/migrations/0034_historicalannouncement_historicalwarningannouncement.py @@ -4,7 +4,7 @@ from django.conf import settings from django.db import migrations, models import django.db.models.deletion -from django.utils.timezone import utc +from datetime import timezone import simple_history.models @@ -49,7 +49,7 @@ class Migration(migrations.Migration): ('author', models.CharField(blank=True, max_length=63)), ('added', models.DateTimeField(blank=True, editable=False)), ('updated', models.DateTimeField(blank=True, editable=False)), - ('expiration_date', models.DateTimeField(default=datetime.datetime(3000, 1, 1, 5, 0, tzinfo=utc))), + ('expiration_date', models.DateTimeField(default=datetime.datetime(3000, 1, 1, 5, 0, tzinfo=timezone.utc))), ('notify_post', models.BooleanField(default=True)), ('notify_email_all', models.BooleanField(default=False)), ('pinned', models.BooleanField(default=False)), diff --git a/intranet/apps/announcements/models.py b/intranet/apps/announcements/models.py index 68ed6688f4e..13706013f37 100644 --- a/intranet/apps/announcements/models.py +++ b/intranet/apps/announcements/models.py @@ -175,14 +175,12 @@ def is_visible(self, user): return self in Announcement.objects.visible_to_user(user) def can_modify(self, user): - return ( - user.is_announcements_admin - or self.is_club_announcement + return user.is_announcements_admin or ( + self.is_club_announcement and ( user in self.activity.officers.all() or user in self.activity.club_sponsors.all() - or EighthSponsor.objects.filter(user=user).exists() - and user.sponsor_obj in self.activity.sponsors.all() + or (EighthSponsor.objects.filter(user=user).exists() and user.sponsor_obj in self.activity.sponsors.all()) ) ) @@ -205,7 +203,7 @@ def is_visible_requester(self, user): def is_visible_submitter(self, user): try: - return self.user == user or self.announcementrequest and user == self.announcementrequest.user + return self.user == user or (self.announcementrequest and user == self.announcementrequest.user) except get_user_model().DoesNotExist: return False diff --git a/intranet/apps/announcements/urls.py b/intranet/apps/announcements/urls.py index 91c482d50ec..c929ac0d7b6 100644 --- a/intranet/apps/announcements/urls.py +++ b/intranet/apps/announcements/urls.py @@ -1,25 +1,25 @@ -from django.urls import re_path +from django.urls import path from . import views urlpatterns = [ - re_path(r"^$", views.view_announcements, name="view_announcements"), - re_path(r"^/archive$", views.view_announcements_archive, name="announcements_archive"), - re_path(r"^/club$", views.view_club_announcements, name="club_announcements"), - re_path(r"^/add$", views.add_announcement_view, name="add_announcement"), - re_path(r"^/request$", views.request_announcement_view, name="request_announcement"), - re_path(r"^/club/add$", views.add_club_announcement_view, name="add_club_announcement"), - re_path(r"^/club/modify/(?P\d+)$", views.modify_club_announcement_view, name="modify_club_announcement"), - re_path(r"^/request/success$", views.request_announcement_success_view, name="request_announcement_success"), - re_path(r"^/request/success_self$", views.request_announcement_success_self_view, name="request_announcement_success_self"), - re_path(r"^/approve/(?P\d+)$", views.approve_announcement_view, name="approve_announcement"), - re_path(r"^/approve/success$", views.approve_announcement_success_view, name="approve_announcement_success"), - re_path(r"^/approve/reject$", views.approve_announcement_reject_view, name="approve_announcement_reject"), - re_path(r"^/admin_approve/(?P\d+)$", views.admin_approve_announcement_view, name="admin_approve_announcement"), - re_path(r"^/admin_status$", views.admin_request_status_view, name="admin_request_status"), - re_path(r"^/modify/(?P\d+)$", views.modify_announcement_view, name="modify_announcement"), - re_path(r"^/delete/(?P\d+)$", views.delete_announcement_view, name="delete_announcement"), - re_path(r"^/show$", views.show_announcement_view, name="show_announcement"), - re_path(r"^/hide$", views.hide_announcement_view, name="hide_announcement"), - re_path(r"^/(?P\d+)$", views.view_announcement_view, name="view_announcement"), + path("", views.view_announcements, name="view_announcements"), + path("/archive", views.view_announcements_archive, name="announcements_archive"), + path("/club", views.view_club_announcements, name="club_announcements"), + path("/add", views.add_announcement_view, name="add_announcement"), + path("/request", views.request_announcement_view, name="request_announcement"), + path("/club/add", views.add_club_announcement_view, name="add_club_announcement"), + path("/club/modify/", views.modify_club_announcement_view, name="modify_club_announcement"), + path("/request/success", views.request_announcement_success_view, name="request_announcement_success"), + path("/request/success_self", views.request_announcement_success_self_view, name="request_announcement_success_self"), + path("/approve/", views.approve_announcement_view, name="approve_announcement"), + path("/approve/success", views.approve_announcement_success_view, name="approve_announcement_success"), + path("/approve/reject", views.approve_announcement_reject_view, name="approve_announcement_reject"), + path("/admin_approve/", views.admin_approve_announcement_view, name="admin_approve_announcement"), + path("/admin_status", views.admin_request_status_view, name="admin_request_status"), + path("/modify/", views.modify_announcement_view, name="modify_announcement"), + path("/delete/", views.delete_announcement_view, name="delete_announcement"), + path("/show", views.show_announcement_view, name="show_announcement"), + path("/hide", views.hide_announcement_view, name="hide_announcement"), + path("/", views.view_announcement_view, name="view_announcement"), ] diff --git a/intranet/apps/announcements/views.py b/intranet/apps/announcements/views.py index 4412ac0299b..c86b417ee74 100644 --- a/intranet/apps/announcements/views.py +++ b/intranet/apps/announcements/views.py @@ -436,7 +436,7 @@ def delete_announcement_view(request, announcement_id): """ announcement = get_object_or_404(Announcement, id=announcement_id) - if not (request.user.is_announcements_admin or announcement.is_club_announcement and announcement.can_modify(request.user)): + if not (request.user.is_announcements_admin or (announcement.is_club_announcement and announcement.can_modify(request.user))): messages.error(request, "You do not have permission to delete this announcement.") return redirect("index") diff --git a/intranet/apps/api/urls.py b/intranet/apps/api/urls.py index 2c14c24eb07..2d5eed8aa7f 100644 --- a/intranet/apps/api/urls.py +++ b/intranet/apps/api/urls.py @@ -1,4 +1,4 @@ -from django.urls import re_path +from django.urls import path, re_path from ..announcements import api as announcements_api from ..bus import api as bus_api @@ -9,18 +9,18 @@ from .views import api_root urlpatterns = [ - re_path(r"^$", api_root, name="api_root"), - re_path(r"^/announcements$", announcements_api.ListCreateAnnouncement.as_view(), name="api_announcements_list_create"), - re_path(r"^/announcements/(?P\d+)$", announcements_api.RetrieveUpdateDestroyAnnouncement.as_view(), name="api_announcements_detail"), - re_path(r"^/blocks$", eighth_api.EighthBlockList.as_view(), name="api_eighth_block_list"), - re_path(r"^/blocks/(?P\d+)$", eighth_api.EighthBlockDetail.as_view(), name="api_eighth_block_detail"), - re_path(r"^/search/(?P.+)$", users_api.Search.as_view(), name="api_user_search"), - re_path(r"^/activities$", eighth_api.EighthActivityList.as_view(), name="api_eighth_activity_list"), - re_path(r"^/activities/(?P\d+)$", eighth_api.EighthActivityDetail.as_view(), name="api_eighth_activity_detail"), - re_path(r"^/profile$", users_api.ProfileDetail.as_view(), name="api_user_myprofile_detail"), - re_path(r"^/profile/(?P\d+)$", users_api.ProfileDetail.as_view(), name="api_user_profile_detail"), + path("", api_root, name="api_root"), + path("/announcements", announcements_api.ListCreateAnnouncement.as_view(), name="api_announcements_list_create"), + path("/announcements/", announcements_api.RetrieveUpdateDestroyAnnouncement.as_view(), name="api_announcements_detail"), + path("/blocks", eighth_api.EighthBlockList.as_view(), name="api_eighth_block_list"), + path("/blocks/", eighth_api.EighthBlockDetail.as_view(), name="api_eighth_block_detail"), + path("/search/", users_api.Search.as_view(), name="api_user_search"), + path("/activities", eighth_api.EighthActivityList.as_view(), name="api_eighth_activity_list"), + path("/activities/", eighth_api.EighthActivityDetail.as_view(), name="api_eighth_activity_detail"), + path("/profile", users_api.ProfileDetail.as_view(), name="api_user_myprofile_detail"), + path("/profile/", users_api.ProfileDetail.as_view(), name="api_user_profile_detail"), re_path(r"^/profile/(?P[A-Za-z\d]+)$", users_api.ProfileDetail.as_view(), name="api_user_profile_detail_by_username"), - re_path(r"^/profile/(?P\d+)/picture$", users_api.ProfilePictureDetail.as_view(), name="api_user_profile_picture_default"), + path("/profile//picture", users_api.ProfilePictureDetail.as_view(), name="api_user_profile_picture_default"), re_path( r"^/profile/(?P[A-Za-z\d]+)/picture$", users_api.ProfilePictureDetail.as_view(), name="api_user_profile_picture_default_by_username" ), @@ -30,17 +30,17 @@ users_api.ProfilePictureDetail.as_view(), name="api_user_profile_picture_by_username", ), - re_path(r"^/signups/user$", eighth_api.EighthUserSignupListAdd.as_view(), name="api_eighth_user_signup_list_myid"), - re_path(r"^/signups/user/(?P\d+)$", eighth_api.EighthUserSignupListAdd.as_view(), name="api_eighth_user_signup_list"), - re_path(r"^/signups/user/favorites$", eighth_api.EighthUserFavoritesListToggle.as_view(), name="api_eighth_user_favorites_list_myid"), - re_path( - r"^/signups/scheduled_activity/(?P\d+)$", + path("/signups/user", eighth_api.EighthUserSignupListAdd.as_view(), name="api_eighth_user_signup_list_myid"), + path("/signups/user/", eighth_api.EighthUserSignupListAdd.as_view(), name="api_eighth_user_signup_list"), + path("/signups/user/favorites", eighth_api.EighthUserFavoritesListToggle.as_view(), name="api_eighth_user_favorites_list_myid"), + path( + "/signups/scheduled_activity/", eighth_api.EighthScheduledActivitySignupList.as_view(), name="api_eighth_scheduled_activity_signup_list", ), - re_path(r"^/schedule$", schedule_api.DayList.as_view(), name="api_schedule_day_list"), + path("/schedule", schedule_api.DayList.as_view(), name="api_schedule_day_list"), re_path(r"^/schedule/(?P.*)$", schedule_api.DayDetail.as_view(), name="api_schedule_day_detail"), - re_path(r"^/emerg$", emerg_api.emerg_status, name="api_emerg_status"), - re_path(r"^/bus$", bus_api.RouteList.as_view(), name="api_bus_list"), - re_path(r"^/bus/(?P\d+)$", bus_api.RouteDetail.as_view(), name="api_bus_detail"), + path("/emerg", emerg_api.emerg_status, name="api_emerg_status"), + path("/bus", bus_api.RouteList.as_view(), name="api_bus_list"), + path("/bus/", bus_api.RouteDetail.as_view(), name="api_bus_detail"), ] diff --git a/intranet/apps/auth/helpers.py b/intranet/apps/auth/helpers.py index f2f52bb5607..6e585ee38b1 100644 --- a/intranet/apps/auth/helpers.py +++ b/intranet/apps/auth/helpers.py @@ -11,8 +11,7 @@ def change_password(form_data): if form_data: form_data["username"] = re.sub(r"\W", "", form_data["username"]) if ( - form_data - and form_data["username"] == "unknown" + (form_data and form_data["username"] == "unknown") or form_data["old_password"] is None or form_data["new_password"] is None or form_data["new_password_confirm"] is None diff --git a/intranet/apps/auth/urls.py b/intranet/apps/auth/urls.py index e3680ee5dd6..b42152d7bbf 100644 --- a/intranet/apps/auth/urls.py +++ b/intranet/apps/auth/urls.py @@ -1,12 +1,12 @@ -from django.urls import re_path +from django.urls import path from . import views urlpatterns = [ - re_path(r"^$", views.index_view, name="index"), - re_path(r"^login$", views.LoginView.as_view(), name="login"), - re_path(r"^logout$", views.logout_view, name="logout"), - re_path(r"^about$", views.about_view, name="about"), - re_path(r"^reauthenticate$", views.reauthentication_view, name="reauth"), - re_path(r"^reset_password$", views.reset_password_view, name="reset_password"), + path("", views.index_view, name="index"), + path("login", views.LoginView.as_view(), name="login"), + path("logout", views.logout_view, name="logout"), + path("about", views.about_view, name="about"), + path("reauthenticate", views.reauthentication_view, name="reauth"), + path("reset_password", views.reset_password_view, name="reset_password"), ] diff --git a/intranet/apps/auth/views.py b/intranet/apps/auth/views.py index 7f684ca51a6..8dd1eecf89a 100644 --- a/intranet/apps/auth/views.py +++ b/intranet/apps/auth/views.py @@ -2,8 +2,8 @@ import random import re import time +from collections.abc import Container from datetime import timedelta -from typing import Container, Tuple from dateutil.relativedelta import relativedelta from django.conf import settings @@ -39,8 +39,8 @@ def log_auth(request, success): - if "HTTP_X_REAL_IP" in request.META: - ip = request.META["HTTP_X_REAL_IP"] + if "x-real-ip" in request.headers: + ip = request.headers["x-real-ip"] else: ip = request.META.get("REMOTE_ADDR", "") @@ -50,7 +50,7 @@ def log_auth(request, success): username = request.POST.get("username", "unknown") log_line = '{} - {} - auth {} - [{}] "{}" "{}"'.format( - ip, username, success, timezone.localtime(), request.get_full_path(), request.META.get("HTTP_USER_AGENT", "") + ip, username, success, timezone.localtime(), request.get_full_path(), request.headers.get("user-agent", "") ) auth_logger.info(log_line) @@ -84,7 +84,7 @@ def get_bg_pattern(request): return static(file_path + random.choice(files)) -def get_week_sports_school_events() -> Tuple[Container[Event], Container[Event]]: +def get_week_sports_school_events() -> tuple[Container[Event], Container[Event]]: """Lists the sports/school events for the next week. This information is cached. Returns: diff --git a/intranet/apps/bus/urls.py b/intranet/apps/bus/urls.py index a8bfa3ba3fb..37f26d62dcd 100644 --- a/intranet/apps/bus/urls.py +++ b/intranet/apps/bus/urls.py @@ -1,10 +1,10 @@ -from django.urls import re_path +from django.urls import path from . import views urlpatterns = [ - re_path(r"^$", views.home, name="bus"), - re_path(r"^/morning$", views.morning, name="morning_bus"), - re_path(r"^/afternoon$", views.afternoon, name="afternoon_bus"), - re_path(r"^/game$", views.game, name="bus_game"), + path("", views.home, name="bus"), + path("/morning", views.morning, name="morning_bus"), + path("/afternoon", views.afternoon, name="afternoon_bus"), + path("/game", views.game, name="bus_game"), ] diff --git a/intranet/apps/context_processors.py b/intranet/apps/context_processors.py index 19e52d9e98c..04c514a77c6 100644 --- a/intranet/apps/context_processors.py +++ b/intranet/apps/context_processors.py @@ -73,7 +73,7 @@ def mobile_app(request): ctx = {} try: - ua = request.META.get("HTTP_USER_AGENT", "") + ua = request.headers.get("user-agent", "") if "IonAndroid: gcmFrame" in ua: ctx["is_android_client"] = True @@ -133,7 +133,7 @@ def show_homecoming(request): def _get_current_ip(request): - x_real_ip = request.META.get("HTTP_X_REAL_IP") + x_real_ip = request.headers.get("x-real-ip") if x_real_ip: ip = x_real_ip.split(",", 1)[0] else: diff --git a/intranet/apps/cslapps/admin.py b/intranet/apps/cslapps/admin.py index 8b5f1beee54..efa6625e67b 100644 --- a/intranet/apps/cslapps/admin.py +++ b/intranet/apps/cslapps/admin.py @@ -3,6 +3,7 @@ from .models import App +@admin.register(App) class AppAdmin(admin.ModelAdmin): list_display = ( "name", @@ -16,6 +17,3 @@ class AppAdmin(admin.ModelAdmin): "description", "url", ) - - -admin.site.register(App, AppAdmin) diff --git a/intranet/apps/cslapps/models.py b/intranet/apps/cslapps/models.py index 4f3ea8d922c..1d4241b7f33 100644 --- a/intranet/apps/cslapps/models.py +++ b/intranet/apps/cslapps/models.py @@ -14,22 +14,7 @@ def visible_to_user(self, user): class App(models.Model): - """ - Represents an app maintained by the CSL. - Attributes: - name (str): The name of the app. - description (str): A description of the app. - order (int): The order in which the app should be displayed. - oauth_application (:obj:`CSLApplication`): The OAuth application associated with the app, if any. - auth_url (str): The URL to the app's authentication page (preferably, if available, using Ion OAuth). - url (str): The URL to the app. - image_url (str): The URL to the image icon for the app. - html_icon (str): HTML for the icon of the app, can be used for things like font awesome icons. - WARNING: this is rendered as safe. Do not allow untrusted content here. - invert_image_color_for_dark_mode (bool): Whether or not to invert the image color for dark mode. - Set to true if the image is a mostly dark color, which will require it to be inverted to appear on dark mode. - groups_visible (:obj:`list` of :obj:`Group`): Groups that can access this app. - """ + """Represents an app maintained by TJ CSL.""" objects = AppManager() diff --git a/intranet/apps/cslapps/urls.py b/intranet/apps/cslapps/urls.py index 151071e7706..3441263d38f 100644 --- a/intranet/apps/cslapps/urls.py +++ b/intranet/apps/cslapps/urls.py @@ -1,7 +1,7 @@ -from django.urls import re_path +from django.urls import path from . import views urlpatterns = [ - re_path(r"^$", views.redirect_to_app, name="apps"), + path("", views.redirect_to_app, name="apps"), ] diff --git a/intranet/apps/customthemes/urls.py b/intranet/apps/customthemes/urls.py index 9cc81977ea9..51a6c10b708 100644 --- a/intranet/apps/customthemes/urls.py +++ b/intranet/apps/customthemes/urls.py @@ -1,9 +1,9 @@ -from django.urls import re_path +from django.urls import path, re_path from . import views urlpatterns = [ - re_path(r"^/intranet4$", views.intranet4, name="april_fools_intranet4"), - re_path(r"^/intranet3$", views.intranet3, name="april_fools_intranet3"), + path("/intranet4", views.intranet4, name="april_fools_intranet4"), + path("/intranet3", views.intranet3, name="april_fools_intranet3"), re_path(r"^", views.chat_view, name="chat"), ] diff --git a/intranet/apps/dashboard/views.py b/intranet/apps/dashboard/views.py index 346e7e5a797..f9b4d72f3eb 100644 --- a/intranet/apps/dashboard/views.py +++ b/intranet/apps/dashboard/views.py @@ -1,9 +1,10 @@ from __future__ import annotations import logging +from collections.abc import Iterable, Sequence from datetime import datetime, time, timedelta from itertools import chain -from typing import Any, Generic, Iterable, Sequence, TypeVar +from typing import Any, TypeGuard, TypeVar from django.conf import settings from django.contrib.auth.decorators import login_required @@ -14,7 +15,7 @@ from django.urls import reverse from django.utils import timezone from django.utils.timezone import make_aware -from typing_extensions import TypedDict, TypeGuard +from typing_extensions import TypedDict from ...utils.date import get_senior_graduation_date, get_senior_graduation_year from ...utils.helpers import get_ap_week_warning, get_fcps_emerg, get_warning_html @@ -93,6 +94,16 @@ def gen_schedule(user, num_blocks: int = 6, surrounding_blocks: Iterable[EighthB # don't duplicate this info; already caught current_signup = current_signup.replace(" (Cancelled)", "") + # check if attendance open, if so, will display attendance button + attendance_open = False + if current_sched_act: + from ..eighth.views.attendance import check_attendance_open # noqa: PLC0415 + + attendance_open = check_attendance_open(current_sched_act) is None + sch_act_id = None + if attendance_open: + sch_act_id = current_sched_act.id + info = { "id": b.id, "block": b, @@ -107,6 +118,8 @@ def gen_schedule(user, num_blocks: int = 6, surrounding_blocks: Iterable[EighthB "signup_time": b.signup_time, "signup_time_future": b.signup_time_future(), "rooms": rooms, + "attendance_open": attendance_open, + "sch_act_id": sch_act_id, } schedule.append(info) @@ -203,6 +216,7 @@ def get_prerender_url(request): def get_announcements_list(request, context) -> list[Announcement | Event]: """ An announcement will be shown if: + * It is not expired * unless ?show_expired=1 @@ -218,6 +232,7 @@ def get_announcements_list(request, context) -> list[Announcement | Event]: * ...unless ?show_all=1 An event will be shown if: + * It is not expired * unless ?show_expired=1 @@ -254,7 +269,7 @@ def get_announcements_list(request, context) -> list[Announcement | Event]: if context["events_admin"] and context["show_all"]: events = Event.objects.all() elif context["show_expired"]: - events = Event.objects.visible_to_user(user) + events = Event.objects.visible_to_user(user).filter(show_on_dashboard=True) else: # Unlike announcements, show events for the rest of the day after they occur. midnight = timezone.localtime().replace(hour=0, minute=0, second=0, microsecond=0) @@ -322,7 +337,7 @@ def filter_club_announcements( return visible, hidden, unsubscribed -class RawPaginationData(TypedDict, Generic[T]): +class RawPaginationData[T: Announcement](TypedDict): club_items: Sequence[Announcement] items: Page[T] page_num: int @@ -332,7 +347,7 @@ class RawPaginationData(TypedDict, Generic[T]): page_obj: Paginator[T] -def paginate_announcements_list_raw( +def paginate_announcements_list_raw[T: Announcement]( request: HttpRequest, items: Sequence[T], visible_club_items: Sequence[Announcement] = (), @@ -350,7 +365,6 @@ def paginate_announcements_list_raw( Returns: A dictionary intended to be merged into the context. """ - DEFAULT_PAGE_NUM = 1 num = request.GET.get(query_param, "") @@ -377,7 +391,7 @@ def paginate_announcements_list_raw( for c in club_items: c.can_subscribe = c.activity.is_subscribable_for_user(request.user) for a in items: - if a.activity is not None: + if isinstance(a, Announcement) and a.activity is not None: a.can_subscribe = a.activity.is_subscribable_for_user(request.user) return RawPaginationData( @@ -391,7 +405,7 @@ def paginate_announcements_list_raw( ) -def paginate_announcements_list( +def paginate_announcements_list[T: Announcement]( request, context: dict[str, Any], items: Sequence[T], visible_club_items: Sequence[Announcement] = () ) -> tuple[dict[str, Any], Page[T]]: """Paginate ``items`` in groups of 15 @@ -417,6 +431,7 @@ def get_tjstar_mapping(user): def add_widgets_context(request, context): """ WIDGETS: + * Eighth signup (STUDENT) * Eighth attendance (TEACHER or ADMIN) * Enrichment activities (ALL if enrichment activity today) diff --git a/intranet/apps/dataimport/management/commands/import_tj_star.py b/intranet/apps/dataimport/management/commands/import_tj_star.py index 7ed5d2ad37c..3f10d19027d 100644 --- a/intranet/apps/dataimport/management/commands/import_tj_star.py +++ b/intranet/apps/dataimport/management/commands/import_tj_star.py @@ -68,6 +68,7 @@ def handle(self, *args, **options): eighth_block = EighthBlock.objects.get_or_create( date=settings.TJSTAR_DATE, block_letter=f"TJ STAR-{block_letter}", + locked=True, )[0] activity_name = f"{lab_1} & {lab_2}" if lab_2 else lab_1 diff --git a/intranet/apps/dataimport/management/commands/year_cleanup.py b/intranet/apps/dataimport/management/commands/year_cleanup.py index 86892f4a1ba..31150acc519 100644 --- a/intranet/apps/dataimport/management/commands/year_cleanup.py +++ b/intranet/apps/dataimport/management/commands/year_cleanup.py @@ -30,10 +30,10 @@ def ask(self, q): def chk(self, q, test): if test: - self.stdout.write("OK: %s" % q) + self.stdout.write(f"OK: {q}") return True else: - self.stdout.write("ERROR: %s" % q) + self.stdout.write(f"ERROR: {q}") self.stdout.write("Abort.") return False diff --git a/intranet/apps/eighth/admin.py b/intranet/apps/eighth/admin.py index 21be4a8603e..1143767833c 100644 --- a/intranet/apps/eighth/admin.py +++ b/intranet/apps/eighth/admin.py @@ -4,6 +4,7 @@ from .models import EighthActivity, EighthBlock, EighthRoom, EighthScheduledActivity, EighthSignup, EighthSponsor +@admin.register(EighthSponsor) class EighthSponsorAdmin(SimpleHistoryAdmin): list_display = ("first_name", "last_name", "user", "department", "full_time", "contracted_eighth", "online_attendance", "show_full_name") list_filter = ("department", "full_time", "contracted_eighth", "online_attendance", "show_full_name") @@ -11,12 +12,14 @@ class EighthSponsorAdmin(SimpleHistoryAdmin): search_fields = ("first_name", "last_name", "user__username") +@admin.register(EighthRoom) class EighthRoomAdmin(SimpleHistoryAdmin): list_display = ("name", "capacity", "available_for_eighth") ordering = ("name",) search_fields = ("name",) +@admin.register(EighthActivity) class EighthActivityAdmin(SimpleHistoryAdmin): list_display = ("name", "special", "administrative", "deleted", "sticky", "wed_a", "wed_b", "fri_a", "fri_b") list_filter = ("special", "administrative", "deleted", "sticky", "wed_a", "wed_b", "fri_a", "fri_b") @@ -24,12 +27,14 @@ class EighthActivityAdmin(SimpleHistoryAdmin): search_fields = ("name",) +@admin.register(EighthBlock) class EighthBlockAdmin(SimpleHistoryAdmin): list_display = ("date", "block_letter", "comments", "signup_time", "locked") list_filter = ("locked",) ordering = ("-date", "block_letter") +@admin.register(EighthScheduledActivity) class EighthScheduledActivityAdmin(SimpleHistoryAdmin): list_display = ("activity", "block", "comments", "admin_comments", "cancelled", "attendance_taken") list_filter = ("attendance_taken", "cancelled", "block") @@ -37,6 +42,7 @@ class EighthScheduledActivityAdmin(SimpleHistoryAdmin): search_fields = ("activity__name",) +@admin.register(EighthSignup) class EighthSignupAdmin(SimpleHistoryAdmin): def get_activity(self, obj): return obj.scheduled_activity.activity @@ -55,11 +61,3 @@ def get_block(self, obj): ordering = ("-scheduled_activity__block", "user__username") raw_id_fields = ("user", "scheduled_activity") search_fields = ("user__username", "user__first_name", "user__last_name", "scheduled_activity__activity__name") - - -admin.site.register(EighthSponsor, EighthSponsorAdmin) -admin.site.register(EighthRoom, EighthRoomAdmin) -admin.site.register(EighthActivity, EighthActivityAdmin) -admin.site.register(EighthBlock, EighthBlockAdmin) -admin.site.register(EighthScheduledActivity, EighthScheduledActivityAdmin) -admin.site.register(EighthSignup, EighthSignupAdmin) diff --git a/intranet/apps/eighth/exceptions.py b/intranet/apps/eighth/exceptions.py index f7bcea8038e..7b44b5bc447 100644 --- a/intranet/apps/eighth/exceptions.py +++ b/intranet/apps/eighth/exceptions.py @@ -51,7 +51,7 @@ def __init__(self): self.desc_errors = {} def __repr__(self): - return "SignupException(%s)" % ", ".join(sorted(self.errors)) + return "SignupException({})".format(", ".join(sorted(self.errors))) def __str__(self): return ", ".join(sorted(self.errors)) diff --git a/intranet/apps/eighth/management/commands/find_duplicates.py b/intranet/apps/eighth/management/commands/find_duplicates.py index 6add9425d26..f3e9a719fc2 100644 --- a/intranet/apps/eighth/management/commands/find_duplicates.py +++ b/intranet/apps/eighth/management/commands/find_duplicates.py @@ -22,7 +22,7 @@ def handle(self, *args, **options): for uid, bid in duplicates: su = EighthSignup.objects.filter(user_id=uid, scheduled_activity__block_id=bid) self.stdout.write(f"Duplicate: {uid} {bid}") - self.stdout.write("Scheduled activities: %s" % su) + self.stdout.write(f"Scheduled activities: {su}") if options["fix"]: if su[0].scheduled_activity.activity.both_blocks: sibling = su[0].scheduled_activity.get_both_blocks_sibling() diff --git a/intranet/apps/eighth/migrations/0072_alter_eighthscheduledactivity_waitlist.py b/intranet/apps/eighth/migrations/0072_alter_eighthscheduledactivity_waitlist.py new file mode 100644 index 00000000000..f87fc52d31e --- /dev/null +++ b/intranet/apps/eighth/migrations/0072_alter_eighthscheduledactivity_waitlist.py @@ -0,0 +1,20 @@ +# Generated by Django 5.2.4 on 2025-07-13 18:25 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eighth', '0071_eighthscheduledactivity_sticky_students'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name='eighthscheduledactivity', + name='waitlist', + field=models.ManyToManyField(related_name='%(class)s_scheduledactivity_set', through='eighth.EighthWaitlist', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/intranet/apps/eighth/migrations/0072_auto_20250429_1128.py b/intranet/apps/eighth/migrations/0073_eighth_attendance_code.py similarity index 97% rename from intranet/apps/eighth/migrations/0072_auto_20250429_1128.py rename to intranet/apps/eighth/migrations/0073_eighth_attendance_code.py index a503eed9d5d..1f0b766bbd6 100644 --- a/intranet/apps/eighth/migrations/0072_auto_20250429_1128.py +++ b/intranet/apps/eighth/migrations/0073_eighth_attendance_code.py @@ -19,7 +19,7 @@ def generate_attendance_codes(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('eighth', '0071_eighthscheduledactivity_sticky_students'), + ('eighth', '0072_alter_eighthscheduledactivity_waitlist'), ] operations = [ diff --git a/intranet/apps/eighth/models.py b/intranet/apps/eighth/models.py index 2209261b730..427ff494b4d 100644 --- a/intranet/apps/eighth/models.py +++ b/intranet/apps/eighth/models.py @@ -3,8 +3,8 @@ import logging import secrets import string -from collections.abc import Sequence -from typing import Collection, Iterable, List, Optional, Union +from collections.abc import Collection, Iterable, Sequence +from typing import Optional from cacheops import invalidate_obj from django.conf import settings @@ -326,7 +326,7 @@ def name_with_flags_no_restricted(self) -> str: """ return self._name_with_flags(False) - def _name_with_flags(self, include_restricted: bool, title: Optional[str] = None) -> str: + def _name_with_flags(self, include_restricted: bool, title: str | None = None) -> str: """Generates the activity's name with flags. Args: include_restricted: Whether to include the "restricted" flag. @@ -347,7 +347,7 @@ def _name_with_flags(self, include_restricted: bool, title: Optional[str] = None return name @classmethod - def restricted_activities_available_to_user(cls, user: "get_user_model()") -> List[int]: + def restricted_activities_available_to_user(cls, user: "get_user_model()") -> list[int]: """Finds the restricted activities available to the given user. Args: user: The User to find the restricted activities for. @@ -365,7 +365,7 @@ def restricted_activities_available_to_user(cls, user: "get_user_model()") -> Li return EighthActivity.objects.filter(q).values_list("id", flat=True) @classmethod - def available_ids(cls) -> List[int]: + def available_ids(cls) -> list[int]: """Returns all available IDs not used by an EighthActivity. Returns: A list of the available activity IDs. @@ -377,7 +377,7 @@ def available_ids(cls) -> List[int]: avail = nums - used return list(avail) - def get_active_schedulings(self) -> Union[QuerySet, Collection["EighthScheduledActivity"]]: # pylint: disable=unsubscriptable-object + def get_active_schedulings(self) -> QuerySet | Collection["EighthScheduledActivity"]: # pylint: disable=unsubscriptable-object """Returns all EighthScheduledActivitys scheduled this year for this activity. Returns: EighthScheduledActivitys of this activity occurring this year. @@ -396,7 +396,7 @@ def is_active(self) -> bool: return self.get_active_schedulings().exists() @property - def frequent_users(self) -> Union[QuerySet, Collection["get_user_model()"]]: # pylint: disable=unsubscriptable-object + def frequent_users(self) -> QuerySet | Collection["get_user_model()"]: # pylint: disable=unsubscriptable-object """Return a QuerySet of user id's and counts that have signed up for this activity more than `settings.SIMILAR_THRESHOLD` times. This is used for suggesting activities to users. @@ -459,7 +459,7 @@ def __str__(self): class EighthBlockQuerySet(models.query.QuerySet): - def this_year(self) -> Union[QuerySet, Collection["EighthBlock"]]: # pylint: disable=unsubscriptable-object + def this_year(self) -> QuerySet | Collection["EighthBlock"]: # pylint: disable=unsubscriptable-object """Get EighthBlocks from this school year only. Returns: A QuerySet containing all of the blocks selected by this QuerySet that occur during this school year. @@ -467,7 +467,7 @@ def this_year(self) -> Union[QuerySet, Collection["EighthBlock"]]: # pylint: di start_date, end_date = get_date_range_this_year() return self.filter(date__gte=start_date, date__lte=end_date) - def filter_today(self) -> Union[QuerySet, Collection["EighthBlock"]]: # pylint: disable=unsubscriptable-object + def filter_today(self) -> QuerySet | Collection["EighthBlock"]: # pylint: disable=unsubscriptable-object """Gets EighthBlocks that occur today. Returns: A QuerySet containing all of the blocks selected by this QuerySet that occur today. @@ -479,7 +479,7 @@ class EighthBlockManager(models.Manager): def get_queryset(self): return EighthBlockQuerySet(self.model, using=self._db) - def get_upcoming_blocks(self, max_number: int = -1) -> Union[QuerySet, Collection["EighthBlock"]]: # pylint: disable=unsubscriptable-object + def get_upcoming_blocks(self, max_number: int = -1) -> QuerySet | Collection["EighthBlock"]: # pylint: disable=unsubscriptable-object """Gets the given number of upcoming blocks that will take place in the future. If there is no block in the future, the most recent block will be returned. Returns: @@ -507,7 +507,7 @@ def get_first_upcoming_block(self) -> "EighthBlock": return self.get_upcoming_blocks().first() - def get_next_upcoming_blocks(self) -> Union[QuerySet, Collection["EighthBlock"]]: # pylint: disable=unsubscriptable-object + def get_next_upcoming_blocks(self) -> QuerySet | Collection["EighthBlock"]: # pylint: disable=unsubscriptable-object """Gets the next upccoming blocks. It finds the other blocks that are occurring on the day of the first upcoming block. @@ -523,7 +523,7 @@ def get_next_upcoming_blocks(self) -> Union[QuerySet, Collection["EighthBlock"]] next_blocks = EighthBlock.objects.filter(date=next_block.date) return next_blocks - def get_blocks_this_year(self) -> Union[QuerySet, Collection["EighthBlock"]]: # pylint: disable=unsubscriptable-object + def get_blocks_this_year(self) -> QuerySet | Collection["EighthBlock"]: # pylint: disable=unsubscriptable-object """Gets a QuerySet of blocks that occur this school year. Returns: A QuerySet of all the blocks that occur during this school year. @@ -533,7 +533,7 @@ def get_blocks_this_year(self) -> Union[QuerySet, Collection["EighthBlock"]]: # return EighthBlock.objects.filter(date__gte=date_start, date__lte=date_end) - def get_blocks_today(self) -> Union[QuerySet, Collection["EighthBlock"]]: # pylint: disable=unsubscriptable-object + def get_blocks_today(self) -> QuerySet | Collection["EighthBlock"]: # pylint: disable=unsubscriptable-object """Gets a QuerySet of blocks that occur today. Returns: A QuerySet of all the blocks that occur today. @@ -590,7 +590,7 @@ def save(self, *args, **kwargs): # pylint: disable=signature-differs super().save(*args, **kwargs) - def next_blocks(self, quantity: int = -1) -> Union[QuerySet, Collection["EighthBlock"]]: # pylint: disable=unsubscriptable-object + def next_blocks(self, quantity: int = -1) -> QuerySet | Collection["EighthBlock"]: # pylint: disable=unsubscriptable-object """Gets future blocks this school year in order. Args: quantity: The number of blocks to list after this block, or -1 for all following blocks. @@ -607,7 +607,7 @@ def next_blocks(self, quantity: int = -1) -> Union[QuerySet, Collection["EighthB return blocks return blocks[:quantity] - def previous_blocks(self, quantity: int = -1) -> Union[QuerySet, Collection["EighthBlock"]]: # pylint: disable=unsubscriptable-object + def previous_blocks(self, quantity: int = -1) -> QuerySet | Collection["EighthBlock"]: # pylint: disable=unsubscriptable-object """Gets the previous blocks this school year in order. Args: quantity: The number of blocks to list before this block, or -1 for all previous blocks. @@ -686,7 +686,7 @@ def num_no_signups(self) -> int: signup_users_count = get_user_model().objects.get_students().count() return signup_users_count - self.num_signups() - def get_unsigned_students(self) -> Union[QuerySet, Collection["get_user_model()"]]: # pylint: disable=unsubscriptable-object + def get_unsigned_students(self) -> QuerySet | Collection["get_user_model()"]: # pylint: disable=unsubscriptable-object """Return a QuerySet of people who haven't signed up for an activity during this block. Returns: @@ -694,7 +694,7 @@ def get_unsigned_students(self) -> Union[QuerySet, Collection["get_user_model()" """ return get_user_model().objects.get_students().exclude(eighthsignup__scheduled_activity__block=self) - def get_hidden_signups(self) -> Union[QuerySet, Collection["EighthSignup"]]: # pylint: disable=unsubscriptable-object + def get_hidden_signups(self) -> QuerySet | Collection["EighthSignup"]: # pylint: disable=unsubscriptable-object """Returns a QuerySet of EighthSignups whose users are not students but have signed up for an activity. This is usually a list of signups for the z-Withdrawn from TJ activity. @@ -771,7 +771,7 @@ class Meta: class EighthScheduledActivityManager(Manager): """Model Manager for EighthScheduledActivity.""" - def for_sponsor(self, sponsor: EighthSponsor, include_cancelled: bool = False) -> Union[QuerySet, Collection["EighthScheduledActivity"]]: # pylint: disable=unsubscriptable-object + def for_sponsor(self, sponsor: EighthSponsor, include_cancelled: bool = False) -> QuerySet | Collection["EighthScheduledActivity"]: # pylint: disable=unsubscriptable-object """Returns a QuerySet of EighthScheduledActivities where the given EighthSponsor is sponsoring. If a sponsorship is defined in an EighthActivity, it may be overridden @@ -884,7 +884,7 @@ class EighthScheduledActivity(AbstractBaseEighthModel): history = HistoricalRecords() - def get_all_associated_rooms(self) -> Union[QuerySet, Collection["EighthRoom"]]: # pylint: disable=unsubscriptable-object + def get_all_associated_rooms(self) -> QuerySet | Collection["EighthRoom"]: # pylint: disable=unsubscriptable-object """Returns a QuerySet of all the rooms associated with either this EighthScheduledActivity or its EighthActivity. Returns: A QuerySet of all the rooms associated with either this EighthScheduledActivity or its EighthActivity. @@ -935,7 +935,7 @@ def is_activity_sticky(self) -> bool: """ return self.sticky or self.activity.sticky - def get_true_sponsors(self) -> Union[QuerySet, Collection[EighthSponsor]]: # pylint: disable=unsubscriptable-object + def get_true_sponsors(self) -> QuerySet | Collection[EighthSponsor]: # pylint: disable=unsubscriptable-object """Retrieves the sponsors for the scheduled activity, taking into account activity defaults and overrides. Returns: @@ -952,7 +952,7 @@ def user_is_sponsor(self, user: "get_user_model()") -> bool: """ return self.get_true_sponsors().filter(user=user).exists() - def get_true_rooms(self) -> Union[QuerySet, Collection[EighthRoom]]: # pylint: disable=unsubscriptable-object + def get_true_rooms(self) -> QuerySet | Collection[EighthRoom]: # pylint: disable=unsubscriptable-object """Retrieves the rooms for the scheduled activity, taking into account activity defaults and overrides. Returns: @@ -1047,7 +1047,7 @@ def is_overbooked(self) -> bool: capacity = self.get_true_capacity() return capacity != -1 and self.eighthsignup_set.count() > capacity - def is_too_early_to_signup(self, now: Optional[datetime.datetime] = None) -> (bool, datetime): + def is_too_early_to_signup(self, now: datetime.datetime | None = None) -> (bool, datetime): """Returns whether it is too early to sign up for the activity if it is a presign. This contains the 2 day pre-signup logic. @@ -1075,7 +1075,7 @@ def has_open_passes(self) -> bool: """ return self.eighthsignup_set.filter(after_deadline=True, pass_accepted=False).exists() - def _get_viewable_members(self, user: "get_user_model()") -> Union[QuerySet, Collection["get_user_model()"]]: # pylint: disable=unsubscriptable-object + def _get_viewable_members(self, user: "get_user_model()") -> QuerySet | Collection["get_user_model()"]: # pylint: disable=unsubscriptable-object """Get an unsorted QuerySet of the members that you have permission to view. Args: user: The user who is attempting to view the member list. @@ -1090,7 +1090,7 @@ def _get_viewable_members(self, user: "get_user_model()") -> Union[QuerySet, Col q |= Q(id=user.id) return self.members.filter(q) - def get_viewable_members(self, user: "get_user_model()" = None) -> Union[QuerySet, Collection["get_user_model()"]]: # pylint: disable=unsubscriptable-object + def get_viewable_members(self, user: "get_user_model()" = None) -> QuerySet | Collection["get_user_model()"]: # pylint: disable=unsubscriptable-object """Returns a QuerySet of the members that you have permission to view, sorted alphabetically. Args: user: The user who is attempting to view the member list. @@ -1099,7 +1099,7 @@ def get_viewable_members(self, user: "get_user_model()" = None) -> Union[QuerySe """ return self._get_viewable_members(user).order_by("last_name", "first_name") - def get_viewable_members_serializer(self, request) -> Union[QuerySet, Collection["get_user_model()"]]: # pylint: disable=unsubscriptable-object + def get_viewable_members_serializer(self, request) -> QuerySet | Collection["get_user_model()"]: # pylint: disable=unsubscriptable-object """Given a request, returns an unsorted QuerySet of the members that the requesting user has permission to view. Args: @@ -1109,7 +1109,7 @@ def get_viewable_members_serializer(self, request) -> Union[QuerySet, Collection """ return self._get_viewable_members(request.user) - def get_hidden_members(self, user: "get_user_model()" = None) -> Union[QuerySet, Collection["get_user_model()"]]: # pylint: disable=unsubscriptable-object + def get_hidden_members(self, user: "get_user_model()" = None) -> QuerySet | Collection["get_user_model()"]: # pylint: disable=unsubscriptable-object """Returns a QuerySet of the members that you do not have permission to view. Args: user: The user who is attempting to view the member list. @@ -1224,7 +1224,7 @@ def set_code_mode(self, mode): def add_user( self, user: AbstractBaseUser, - request: Optional[HttpRequest] = None, + request: HttpRequest | None = None, force: bool = False, no_after_deadline: bool = False, add_to_waitlist: bool = False, @@ -1636,7 +1636,7 @@ def cancel(self): self.save(update_fields=["cancelled"]) if not self.is_both_blocks or self.block.block_letter != "B": - from .notifications import activity_cancelled_email # pylint: disable=import-outside-toplevel,cyclic-import + from .notifications import activity_cancelled_email # noqa: PLC0415 activity_cancelled_email(self) @@ -1699,7 +1699,7 @@ def create_signup(self, user: "get_user_model()", scheduled_activity: "EighthSch return signup - def get_absences(self) -> Union[QuerySet, Collection["EighthSignup"]]: # pylint: disable=unsubscriptable-object + def get_absences(self) -> QuerySet | Collection["EighthSignup"]: # pylint: disable=unsubscriptable-object """Returns all EighthSignups for which the student was marked as absent. Returns: A QuerySet of all the EighthSignups for which the student was marked as absent. @@ -1843,7 +1843,7 @@ def accept_pass(self): self.was_absent = False self.pass_accepted = True self.attendance_marked = True - self.save(update_fields=["was_absent", "pass_accepted"]) + self.save(update_fields=["was_absent", "pass_accepted", "attendance_marked"]) def reject_pass(self): """Rejects an eighth period pass for the EighthSignup object.""" @@ -1875,7 +1875,7 @@ class Meta: class EighthWaitlistManager(Manager): """Model manager for EighthWaitlist.""" - def get_next_waitlist(self, activity: EighthScheduledActivity) -> Union[QuerySet, Collection["EighthWaitlist"]]: # pylint: disable=unsubscriptable-object + def get_next_waitlist(self, activity: EighthScheduledActivity) -> QuerySet | Collection["EighthWaitlist"]: # pylint: disable=unsubscriptable-object """Returns a QuerySet of all the EighthWaitlist objects for the given activity, ordered by signup time. Args: diff --git a/intranet/apps/eighth/serializers.py b/intranet/apps/eighth/serializers.py index 6f187581b57..23795a5100a 100644 --- a/intranet/apps/eighth/serializers.py +++ b/intranet/apps/eighth/serializers.py @@ -10,6 +10,7 @@ from rest_framework.reverse import reverse from .models import EighthActivity, EighthBlock, EighthScheduledActivity, EighthSignup, EighthSponsor +from .views.attendance import check_attendance_open logger = logging.getLogger(__name__) @@ -139,6 +140,8 @@ def process_scheduled_activity( "name_with_flags_for_user": name_with_flags_for_user, "description": activity.description, "cancelled": scheduled_activity.cancelled, + "attendance_open": check_attendance_open(scheduled_activity) is None, + "attendance_url": reverse("student_attendance", args=[scheduled_activity.id]), "favorited": activity.id in favorited_activities, "subscribed_to": activity.id in subscribed_activities, "subscriptions_enabled": subscriptions_enabled, diff --git a/intranet/apps/eighth/tasks.py b/intranet/apps/eighth/tasks.py index 20d4326e92c..04634051f96 100644 --- a/intranet/apps/eighth/tasks.py +++ b/intranet/apps/eighth/tasks.py @@ -1,6 +1,6 @@ import calendar import datetime -from typing import Collection +from collections.abc import Collection from celery import shared_task from celery.utils.log import get_task_logger @@ -184,7 +184,7 @@ def eighth_admin_assign_hybrid_sticky_blocks(fmtdate: str) -> None: """ # Circular dependency - from .views.admin.blocks import perform_hybrid_block_signup # pylint: disable=import-outside-toplevel + from .views.admin.blocks import perform_hybrid_block_signup # noqa: PLC0415 perform_hybrid_block_signup(fmtdate, logger) @@ -203,7 +203,7 @@ def eighth_admin_signup_group_task(*, user_id: int, group_id: int, schact_id: in usually because they are stickied into another activity. """ # Circular dependency - from .views.admin.groups import eighth_admin_perform_group_signup # pylint: disable=import-outside-toplevel + from .views.admin.groups import eighth_admin_perform_group_signup # noqa: PLC0415 user = get_user_model().objects.get(id=user_id) @@ -252,7 +252,7 @@ def eighth_admin_signup_group_task_hybrid(*, user_id: int, group_id: int, schact """ # Circular dependency - from .views.admin.hybrid import eighth_admin_perform_group_signup # pylint: disable=import-outside-toplevel + from .views.admin.hybrid import eighth_admin_perform_group_signup # noqa: PLC0415 user = get_user_model().objects.get(id=user_id) diff --git a/intranet/apps/eighth/tests/admin/test_admin_general.py b/intranet/apps/eighth/tests/admin/test_admin_general.py index bc348dd83bb..2a71d9dafe8 100644 --- a/intranet/apps/eighth/tests/admin/test_admin_general.py +++ b/intranet/apps/eighth/tests/admin/test_admin_general.py @@ -35,18 +35,18 @@ def test_eighth_admin_dashboard_view(self): self.assertTemplateUsed(response, "eighth/admin/dashboard.html") self.assertEqual(response.context["start_date"], awaredate()) - self.assertQuerysetEqual( + self.assertQuerySetEqual( response.context["all_activities"], [repr(activity) for activity in EighthActivity.objects.all().order_by("name")], transform=repr ) - self.assertQuerysetEqual(response.context["blocks_after_start_date"], [repr(block) for block in EighthBlock.objects.all()], transform=repr) - self.assertQuerysetEqual(response.context["groups"], [repr(group) for group in Group.objects.all().order_by("name")], transform=repr) - self.assertQuerysetEqual(response.context["rooms"], [repr(room) for room in EighthRoom.objects.all()], transform=repr) - self.assertQuerysetEqual( + self.assertQuerySetEqual(response.context["blocks_after_start_date"], [repr(block) for block in EighthBlock.objects.all()], transform=repr) + self.assertQuerySetEqual(response.context["groups"], [repr(group) for group in Group.objects.all().order_by("name")], transform=repr) + self.assertQuerySetEqual(response.context["rooms"], [repr(room) for room in EighthRoom.objects.all()], transform=repr) + self.assertQuerySetEqual( response.context["sponsors"], [repr(sponsor) for sponsor in EighthSponsor.objects.order_by("last_name", "first_name").all()], transform=repr, ) - self.assertQuerysetEqual( + self.assertQuerySetEqual( response.context["blocks_next"], [repr(block) for block in EighthBlock.objects.filter(date="9001-4-20").all()], transform=repr ) self.assertEqual(response.context["blocks_next_date"], datetime.datetime(9001, 4, 20).date()) diff --git a/intranet/apps/eighth/tests/admin/test_admin_sponsors.py b/intranet/apps/eighth/tests/admin/test_admin_sponsors.py index e7a1869c689..3b017f8b391 100644 --- a/intranet/apps/eighth/tests/admin/test_admin_sponsors.py +++ b/intranet/apps/eighth/tests/admin/test_admin_sponsors.py @@ -31,7 +31,9 @@ def test_add_sponsor_view(self): # Test that error is raised and redirects self.assertTemplateUsed(response, "eighth/admin/add_sponsor.html") - self.assertFormError(response, "form", "user", "Select a valid choice. {} is not one of the available choices.".format(params["user"])) + self.assertFormError( + response.context["form"], "user", "Select a valid choice. {} is not one of the available choices.".format(params["user"]) + ) user = self.create_sponsor() params = { diff --git a/intranet/apps/eighth/tests/test_activities.py b/intranet/apps/eighth/tests/test_activities.py index df7bdb3735b..018a234f980 100644 --- a/intranet/apps/eighth/tests/test_activities.py +++ b/intranet/apps/eighth/tests/test_activities.py @@ -30,29 +30,29 @@ def test_past_activities_listed_properly(self): schact_past = self.schedule_activity(block_past.id, activity.id) response = self.client.get(reverse("eighth_activity", args=[activity.id])) - self.assertQuerysetEqual(response.context["scheduled_activities"], []) + self.assertQuerySetEqual(response.context["scheduled_activities"], []) response = self.client.get(reverse("eighth_activity", args=[activity.id]), {"show_all": 1}) - self.assertQuerysetEqual(response.context["scheduled_activities"], [repr(schact_past)], transform=repr) + self.assertQuerySetEqual(response.context["scheduled_activities"], [repr(schact_past)], transform=repr) block_today = self.add_block(date=today_date_str, block_letter="A") block_future = self.add_block(date=future_date_str, block_letter="A") response = self.client.get(reverse("eighth_activity", args=[activity.id])) - self.assertQuerysetEqual(response.context["scheduled_activities"], []) + self.assertQuerySetEqual(response.context["scheduled_activities"], []) response = self.client.get(reverse("eighth_activity", args=[activity.id]), {"show_all": 1}) - self.assertQuerysetEqual(response.context["scheduled_activities"], [repr(schact_past)], transform=repr) + self.assertQuerySetEqual(response.context["scheduled_activities"], [repr(schact_past)], transform=repr) schact_today = self.schedule_activity(block_today.id, activity.id) response = self.client.get(reverse("eighth_activity", args=[activity.id])) - self.assertQuerysetEqual(response.context["scheduled_activities"], [repr(schact_today)], transform=repr) + self.assertQuerySetEqual(response.context["scheduled_activities"], [repr(schact_today)], transform=repr) response = self.client.get(reverse("eighth_activity", args=[activity.id]), {"show_all": 1}) - self.assertQuerysetEqual(response.context["scheduled_activities"], [repr(schact_past), repr(schact_today)], transform=repr) + self.assertQuerySetEqual(response.context["scheduled_activities"], [repr(schact_past), repr(schact_today)], transform=repr) schact_future = self.schedule_activity(block_future.id, activity.id) response = self.client.get(reverse("eighth_activity", args=[activity.id])) - self.assertQuerysetEqual(response.context["scheduled_activities"], [repr(schact_today), repr(schact_future)], transform=repr) + self.assertQuerySetEqual(response.context["scheduled_activities"], [repr(schact_today), repr(schact_future)], transform=repr) response = self.client.get(reverse("eighth_activity", args=[activity.id]), {"show_all": 1}) - self.assertQuerysetEqual( + self.assertQuerySetEqual( response.context["scheduled_activities"], [repr(schact_past), repr(schact_today), repr(schact_future)], transform=repr ) diff --git a/intranet/apps/eighth/tests/test_general.py b/intranet/apps/eighth/tests/test_general.py index e038f3a8783..3bf586a11d2 100644 --- a/intranet/apps/eighth/tests/test_general.py +++ b/intranet/apps/eighth/tests/test_general.py @@ -68,7 +68,7 @@ def test_all_associated_rooms(self): schact1 = self.schedule_activity(act1.id, block1.id) schact1.rooms.add(room2) - self.assertQuerysetEqual(schact1.get_all_associated_rooms(), [repr(room1), repr(room2)], transform=repr) + self.assertQuerySetEqual(schact1.get_all_associated_rooms(), [repr(room1), repr(room2)], transform=repr) def test_room_use(self): """Make sure EighthScheduledActivities return the correct room.""" @@ -93,7 +93,7 @@ def test_room_formatting(self): """Make sure a room name formatting is correct.""" self.make_admin() room1 = self.add_room(name="999", capacity=1) - self.assertEqual("Rm. %s" % room1.name, room1.formatted_name) + self.assertEqual(f"Rm. {room1.name}", room1.formatted_name) room2 = self.add_room(name="Lab 999", capacity=1) self.assertEqual(room2.name, room2.formatted_name) room4 = self.add_room(name="Room 999", capacity=1) @@ -312,10 +312,10 @@ def test_active_schedulings(self): block_today = self.add_block(date=today, block_letter="A") block_future = self.add_block(date=year_future, block_letter="A") - self.assertQuerysetEqual(act.get_active_schedulings(), []) + self.assertQuerySetEqual(act.get_active_schedulings(), []) EighthScheduledActivity.objects.create(activity=act, block=block_past) - self.assertQuerysetEqual(act.get_active_schedulings(), []) + self.assertQuerySetEqual(act.get_active_schedulings(), []) schact_today = EighthScheduledActivity.objects.create(activity=act, block=block_today) - self.assertQuerysetEqual(act.get_active_schedulings(), [repr(schact_today)], transform=repr) + self.assertQuerySetEqual(act.get_active_schedulings(), [repr(schact_today)], transform=repr) EighthScheduledActivity.objects.create(activity=act, block=block_future) - self.assertQuerysetEqual(act.get_active_schedulings(), [repr(schact_today)], transform=repr) + self.assertQuerySetEqual(act.get_active_schedulings(), [repr(schact_today)], transform=repr) diff --git a/intranet/apps/eighth/urls.py b/intranet/apps/eighth/urls.py index 9040e336a18..a22df1a97ec 100644 --- a/intranet/apps/eighth/urls.py +++ b/intranet/apps/eighth/urls.py @@ -1,5 +1,5 @@ from django.conf import settings -from django.urls import include, re_path +from django.urls import include, path, re_path from .views import activities, attendance, monitoring, profile, routers, signup from .views.admin import activities as admin_activities @@ -8,27 +8,26 @@ from .views.admin import maintenance as admin_maintenance urlpatterns = [ - re_path(r"^$", routers.eighth_redirect_view, name="eighth_redirect"), + path("", routers.eighth_redirect_view, name="eighth_redirect"), # Students re_path(r"^/signup(?:/(?P\d+))?$", signup.eighth_signup_view, name="eighth_signup"), - re_path(r"^/leave$", signup.leave_waitlist_view, name="leave_waitlist"), - re_path(r"^/seen_feature$", signup.seen_new_feature_view, name="seen_new_feature"), - re_path(r"^/signup/multi$", signup.eighth_multi_signup_view, name="eighth_multi_signup"), - re_path(r"^/signup/subscribe/(?P\d+)$", signup.subscribe_to_club, name="subscribe_to_club"), - re_path(r"^/signup/unsubscribe/(?P\d+)$", signup.unsubscribe_from_club, name="unsubscribe_from_club"), - re_path(r"^/toggle_favorite$", signup.toggle_favorite_view, name="eighth_toggle_favorite"), - re_path(r"^/absences$", attendance.eighth_absences_view, name="eighth_absences"), - re_path(r"^/absences/(?P\d+)$", attendance.eighth_absences_view, name="eighth_absences"), - re_path(r"^/glance$", signup.eighth_location, name="eighth_location"), - re_path(r"^/student_attendance$", attendance.student_attendance_view, name="student_attendance"), - re_path(r"^/qr/(?P\w+)/(?P\w+)$", attendance.qr_attendance_view, name="qr_attendance"), + path("/leave", signup.leave_waitlist_view, name="leave_waitlist"), + path("/seen_feature", signup.seen_new_feature_view, name="seen_new_feature"), + path("/signup/multi", signup.eighth_multi_signup_view, name="eighth_multi_signup"), + path("/signup/subscribe/", signup.subscribe_to_club, name="subscribe_to_club"), + path("/signup/unsubscribe/", signup.unsubscribe_from_club, name="unsubscribe_from_club"), + path("/toggle_favorite", signup.toggle_favorite_view, name="eighth_toggle_favorite"), + path("/absences", attendance.eighth_absences_view, name="eighth_absences"), + path("/absences/", attendance.eighth_absences_view, name="eighth_absences"), + path("/glance", signup.eighth_location, name="eighth_location"), + re_path(r"^/student_attendance/(?P\d+)(?:/(?P[A-Za-z0-9]+))?$", attendance.student_attendance_view, name="student_attendance"), # Teachers - re_path(r"^/attendance$", attendance.teacher_choose_scheduled_activity_view, name="eighth_attendance_choose_scheduled_activity"), - re_path(r"^/attendance/(?P\d+)$", attendance.take_attendance_view, name="eighth_take_attendance"), - re_path(r"^/attendance/accept_pass/(?P\d+)$", attendance.accept_pass_view, name="eighth_accept_pass"), - re_path(r"^/attendance/accept_all_passes/(?P\d+)$", attendance.accept_all_passes_view, name="eighth_accept_all_passes"), - re_path(r"^/attendance/widget$", attendance.sponsor_schedule_widget_view, name="eighth_sponsor_schedule_widget"), - re_path(r"^/attendance/email/(?P\d+)$", attendance.email_students_view, name="eighth_email_students"), + path("/attendance", attendance.teacher_choose_scheduled_activity_view, name="eighth_attendance_choose_scheduled_activity"), + path("/attendance/", attendance.take_attendance_view, name="eighth_take_attendance"), + path("/attendance/accept_pass/", attendance.accept_pass_view, name="eighth_accept_pass"), + path("/attendance/accept_all_passes/", attendance.accept_all_passes_view, name="eighth_accept_all_passes"), + path("/attendance/widget", attendance.sponsor_schedule_widget_view, name="eighth_sponsor_schedule_widget"), + path("/attendance/email/", attendance.email_students_view, name="eighth_email_students"), # Profile re_path(r"^/profile(?:/(?P\d+))?$", profile.profile_view, name="eighth_profile"), re_path(r"^/profile(?:/(?P\d+))/signup/(?P\d+)?$", profile.profile_signup_view, name="eighth_profile_signup"), @@ -36,135 +35,131 @@ re_path(r"^/profile/history(?:/(?P\d+))?$", profile.profile_history_view, name="eighth_profile_history"), re_path(r"^/profile/often(?:/(?P\d+))?$", profile.profile_often_view, name="eighth_profile_often"), # Roster (for students/teachers) - re_path(r"^/roster/(?P\d+)$", attendance.roster_view, name="eighth_roster"), - re_path(r"^/roster/raw/(?P\d+)$", attendance.raw_roster_view, name="eighth_raw_roster"), - re_path(r"^/roster/raw/waitlist/(?P\d+)$", attendance.raw_waitlist_view, name="eighth_raw_waitlist"), + path("/roster/", attendance.roster_view, name="eighth_roster"), + path("/roster/raw/", attendance.raw_roster_view, name="eighth_raw_roster"), + path("/roster/raw/waitlist/", attendance.raw_waitlist_view, name="eighth_raw_waitlist"), # Activity Info (for students/teachers) - re_path(r"^/activity/(?P\d+)$", activities.activity_view, name="eighth_activity"), - re_path(r"^/activity/(?P\d+)/settings$", activities.settings_view, name="eighth_activity_settings"), - re_path(r"^/activity/statistics/global$", activities.stats_global_view, name="eighth_statistics_global"), - re_path(r"^/activity/statistics/multiple$", activities.stats_multiple_view, name="eighth_statistics_multiple"), - re_path(r"^/activity/statistics/(?P\d+)$", activities.stats_view, name="eighth_statistics"), + path("/activity/", activities.activity_view, name="eighth_activity"), + path("/activity//settings", activities.settings_view, name="eighth_activity_settings"), + path("/activity/statistics/global", activities.stats_global_view, name="eighth_statistics_global"), + path("/activity/statistics/multiple", activities.stats_multiple_view, name="eighth_statistics_multiple"), + path("/activity/statistics/", activities.stats_view, name="eighth_statistics"), # Admin - re_path(r"^/admin$", general.eighth_admin_dashboard_view, name="eighth_admin_dashboard"), - re_path(r"^/toggle_waitlist$", signup.toggle_waitlist_view, name="toggle_waitlist"), - re_path(r"^/prometheus-metrics$", monitoring.metrics_view, name="metrics"), + path("/admin", general.eighth_admin_dashboard_view, name="eighth_admin_dashboard"), + path("/toggle_waitlist", signup.toggle_waitlist_view, name="toggle_waitlist"), + path("/prometheus-metrics", monitoring.metrics_view, name="metrics"), ] eighth_admin_patterns = [ # Admin Activities - re_path(r"^activities/add$", admin_activities.add_activity_view, name="eighth_admin_add_activity"), - re_path(r"^activities/edit/(?P\d+)$", admin_activities.edit_activity_view, name="eighth_admin_edit_activity"), - re_path(r"^activities/delete/(?P\d+)$", admin_activities.delete_activity_view, name="eighth_admin_delete_activity"), - re_path(r"^history$", general.history_view, name="eighth_admin_history"), + path("activities/add", admin_activities.add_activity_view, name="eighth_admin_add_activity"), + path("activities/edit/", admin_activities.edit_activity_view, name="eighth_admin_edit_activity"), + path("activities/delete/", admin_activities.delete_activity_view, name="eighth_admin_delete_activity"), + path("history", general.history_view, name="eighth_admin_history"), # Maintenance tools - re_path(r"^maintenance$", admin_maintenance.index_view, name="eighth_admin_maintenance"), - re_path(r"^maintenance/clear_comments$", admin_maintenance.clear_comments_view, name="eighth_admin_maintenance_clear_comments"), - re_path(r"^maintenance/start_of_year$", admin_maintenance.start_of_year_view, name="eighth_admin_maintenance_start_of_year"), + path("maintenance", admin_maintenance.index_view, name="eighth_admin_maintenance"), + path("maintenance/clear_comments", admin_maintenance.clear_comments_view, name="eighth_admin_maintenance_clear_comments"), + path("maintenance/start_of_year", admin_maintenance.start_of_year_view, name="eighth_admin_maintenance_start_of_year"), # Blocks - re_path(r"^blocks/add$", blocks.add_block_view, name="eighth_admin_add_block"), - re_path(r"^blocks/print_rosters/(?P\d+)$", blocks.print_block_rosters_view, name="eighth_admin_print_block_rosters"), - re_path(r"^blocks/edit/(?P\d+)$", blocks.edit_block_view, name="eighth_admin_edit_block"), - re_path(r"^blocks/copy/(?P\d+)$", blocks.copy_block_view, name="eighth_admin_copy_block"), - re_path(r"^blocks/delete/(?P\d+)$", blocks.delete_block_view, name="eighth_admin_delete_block"), + path("blocks/add", blocks.add_block_view, name="eighth_admin_add_block"), + path("blocks/print_rosters/", blocks.print_block_rosters_view, name="eighth_admin_print_block_rosters"), + path("blocks/edit/", blocks.edit_block_view, name="eighth_admin_edit_block"), + path("blocks/copy/", blocks.copy_block_view, name="eighth_admin_copy_block"), + path("blocks/delete/", blocks.delete_block_view, name="eighth_admin_delete_block"), # Users - re_path(r"^users$", users.list_user_view, name="eighth_admin_manage_users"), - re_path(r"^users/non-graduated$", users.list_non_graduated_view, name="eighth_admin_manage_non_graduated"), + path("users", users.list_user_view, name="eighth_admin_manage_users"), + path("users/non-graduated", users.list_non_graduated_view, name="eighth_admin_manage_non_graduated"), re_path(r"^users/delete/(\d+)$", users.delete_user_view, name="eighth_admin_manage_users"), # Scheduling - re_path(r"^scheduling/schedule$", scheduling.schedule_activity_view, name="eighth_admin_schedule_activity"), - re_path(r"^scheduling/activity_schedule$", scheduling.show_activity_schedule_view, name="eighth_admin_view_activity_schedule"), - re_path(r"^scheduling/transfer_students$", scheduling.transfer_students_view, name="eighth_admin_transfer_students"), - re_path(r"^scheduling/transfer_students_action$", scheduling.transfer_students_action, name="eighth_admin_transfer_students_action"), - re_path(r"^scheduling/distribute_students$", scheduling.distribute_students_view, name="eighth_admin_distribute_students"), - re_path(r"^scheduling/unsignup_students$", scheduling.unsignup_students_view, name="eighth_admin_unsignup_students"), - re_path(r"^scheduling/remove_duplicates$", scheduling.remove_duplicates_view, name="eighth_admin_remove_duplicates"), + path("scheduling/schedule", scheduling.schedule_activity_view, name="eighth_admin_schedule_activity"), + path("scheduling/activity_schedule", scheduling.show_activity_schedule_view, name="eighth_admin_view_activity_schedule"), + path("scheduling/transfer_students", scheduling.transfer_students_view, name="eighth_admin_transfer_students"), + path("scheduling/transfer_students_action", scheduling.transfer_students_action, name="eighth_admin_transfer_students_action"), + path("scheduling/distribute_students", scheduling.distribute_students_view, name="eighth_admin_distribute_students"), + path("scheduling/unsignup_students", scheduling.unsignup_students_view, name="eighth_admin_unsignup_students"), + path("scheduling/remove_duplicates", scheduling.remove_duplicates_view, name="eighth_admin_remove_duplicates"), # Attendance - re_path(r"^attendance$", attendance.admin_choose_scheduled_activity_view, name="eighth_admin_attendance_choose_scheduled_activity"), - re_path(r"^attendance/(?P\d+)$", attendance.take_attendance_view, name="eighth_admin_take_attendance"), - re_path(r"^attendance/csv/(?P\d+)$", attendance.take_attendance_view, name="eighth_admin_export_attendance_csv"), - re_path(r"^attendance/delinquent_students$", admin_attendance.delinquent_students_view, name="eighth_admin_view_delinquent_students"), - re_path(r"^attendance/delinquent_students/csv$", admin_attendance.delinquent_students_view, name="eighth_admin_download_delinquent_students_csv"), - re_path(r"^attendance/after_deadline_signups$", admin_attendance.after_deadline_signup_view, name="eighth_admin_view_after_deadline_signups"), - re_path( - r"^attendance/after_deadline_signups/csv$", + path("attendance", attendance.admin_choose_scheduled_activity_view, name="eighth_admin_attendance_choose_scheduled_activity"), + path("attendance/", attendance.take_attendance_view, name="eighth_admin_take_attendance"), + path("attendance/csv/", attendance.take_attendance_view, name="eighth_admin_export_attendance_csv"), + path("attendance/delinquent_students", admin_attendance.delinquent_students_view, name="eighth_admin_view_delinquent_students"), + path("attendance/delinquent_students/csv", admin_attendance.delinquent_students_view, name="eighth_admin_download_delinquent_students_csv"), + path("attendance/after_deadline_signups", admin_attendance.after_deadline_signup_view, name="eighth_admin_view_after_deadline_signups"), + path( + "attendance/after_deadline_signups/csv", admin_attendance.after_deadline_signup_view, name="eighth_admin_download_after_deadline_signups_csv", ), - re_path( - r"^attendance/no_attendance$", admin_attendance.activities_without_attendance_view, name="eighth_admin_view_activities_without_attendance" - ), - re_path( - r"^attendance/migrate_outstanding_passes$", admin_attendance.migrate_outstanding_passes_view, name="eighth_admin_migrate_outstanding_passes" - ), - re_path( - r"^attendance/export_out_of_building_schedules$", + path("attendance/no_attendance", admin_attendance.activities_without_attendance_view, name="eighth_admin_view_activities_without_attendance"), + path("attendance/migrate_outstanding_passes", admin_attendance.migrate_outstanding_passes_view, name="eighth_admin_migrate_outstanding_passes"), + path( + "attendance/export_out_of_building_schedules", admin_attendance.out_of_building_schedules_view, name="eighth_admin_export_out_of_building_schedules", ), - re_path( - r"^attendance/export_out_of_building_schedules/csv/(?P\d+)$", + path( + "attendance/export_out_of_building_schedules/csv/", admin_attendance.out_of_building_schedules_view, name="eighth_admin_export_out_of_building_schedules_csv", ), - re_path(r"^attendance/clear_absences/(?P\d+)$", admin_attendance.clear_absence_view, name="eighth_admin_clear_absence"), - re_path(r"^attendance/open_passes$", admin_attendance.open_passes_view, name="eighth_admin_view_open_passes"), - re_path(r"^attendance/open_passes/csv$", admin_attendance.open_passes_view, name="eighth_admin_view_open_passes_csv"), - re_path(r"^attendance/no_signups/(?P\d+)$", admin_attendance.no_signups_roster, name="eighth_admin_no_signups_roster"), - re_path(r"^attendance/no_signups/csv/(?P\d+)$", admin_attendance.no_signups_roster, name="eighth_admin_no_signups_csv"), + path("attendance/clear_absences/", admin_attendance.clear_absence_view, name="eighth_admin_clear_absence"), + path("attendance/open_passes", admin_attendance.open_passes_view, name="eighth_admin_view_open_passes"), + path("attendance/open_passes/csv", admin_attendance.open_passes_view, name="eighth_admin_view_open_passes_csv"), + path("attendance/no_signups/", admin_attendance.no_signups_roster, name="eighth_admin_no_signups_roster"), + path("attendance/no_signups/csv/", admin_attendance.no_signups_roster, name="eighth_admin_no_signups_csv"), # Groups - re_path(r"^groups/add$", groups.add_group_view, name="eighth_admin_add_group"), - re_path(r"^groups/add_member/(?P\d+)$", groups.add_member_to_group_view, name="eighth_admin_add_member_to_group"), - re_path( - r"^groups/remove_member/(?P\d+)/(?P\d+)$", + path("groups/add", groups.add_group_view, name="eighth_admin_add_group"), + path("groups/add_member/", groups.add_member_to_group_view, name="eighth_admin_add_member_to_group"), + path( + "groups/remove_member//", groups.remove_member_from_group_view, name="eighth_admin_remove_member_from_group", ), - re_path(r"^groups/edit/(?P\d+)$", groups.edit_group_view, name="eighth_admin_edit_group"), - re_path(r"^groups/delete/(?P\d+)$", groups.delete_group_view, name="eighth_admin_delete_group"), - re_path(r"^groups/signup/(?P\d+)$", groups.eighth_admin_signup_group, name="eighth_admin_signup_group"), - re_path( - r"^groups/signup/action/(?P\d+)/(?P\d+)$", + path("groups/edit/", groups.edit_group_view, name="eighth_admin_edit_group"), + path("groups/delete/", groups.delete_group_view, name="eighth_admin_delete_group"), + path("groups/signup/", groups.eighth_admin_signup_group, name="eighth_admin_signup_group"), + path( + "groups/signup/action//", groups.eighth_admin_signup_group_action, name="eighth_admin_signup_group_action", ), - re_path(r"^groups/distribute/(?P\d+)$", groups.eighth_admin_distribute_group, name="eighth_admin_distribute_group"), - re_path(r"^groups/distribute/unsigned$", groups.eighth_admin_distribute_unsigned, name="eighth_admin_distribute_unsigned"), - re_path(r"^groups/distribute_action$", groups.eighth_admin_distribute_action, name="eighth_admin_distribute_action"), - re_path(r"^groups/download/(?P\d+)$", groups.download_group_csv_view, name="eighth_admin_download_group_csv"), - re_path(r"^groups/upload/(?P\d+)$", groups.upload_group_members_view, name="eighth_admin_upload_group_members"), - re_path(r"^groups/delete_empty$", groups.delete_empty_groups_view, name="eighth_admin_delete_empty_groups_view"), + path("groups/distribute/", groups.eighth_admin_distribute_group, name="eighth_admin_distribute_group"), + path("groups/distribute/unsigned", groups.eighth_admin_distribute_unsigned, name="eighth_admin_distribute_unsigned"), + path("groups/distribute_action", groups.eighth_admin_distribute_action, name="eighth_admin_distribute_action"), + path("groups/download/", groups.download_group_csv_view, name="eighth_admin_download_group_csv"), + path("groups/upload/", groups.upload_group_members_view, name="eighth_admin_upload_group_members"), + path("groups/delete_empty", groups.delete_empty_groups_view, name="eighth_admin_delete_empty_groups_view"), # Rooms - re_path(r"^rooms/add$", rooms.add_room_view, name="eighth_admin_add_room"), - re_path(r"^rooms/edit/(?P\d+)$", rooms.edit_room_view, name="eighth_admin_edit_room"), - re_path(r"^rooms/delete/(?P\d+)$", rooms.delete_room_view, name="eighth_admin_delete_room"), - re_path(r"^rooms/sanity_check$", rooms.room_sanity_check_view, name="eighth_admin_room_sanity_check"), - re_path(r"^rooms/block_utilization$", rooms.room_utilization_for_block_view, name="eighth_admin_room_utilization_for_block"), - re_path(r"^rooms/block_utilization/csv$", rooms.room_utilization_for_block_view, name="eighth_admin_room_utilization_for_block_csv"), - re_path(r"^rooms/utilization$", rooms.room_utilization_view, name="eighth_admin_room_utilization"), - re_path(r"^rooms/utilization/(?P\d+)/(?P\d+)$", rooms.room_utilization_action, name="eighth_admin_room_utilization"), - re_path(r"^rooms/utilization/(?P\d+)/(?P\d+)/csv$", rooms.room_utilization_action, name="eighth_admin_room_utilization_csv"), + path("rooms/add", rooms.add_room_view, name="eighth_admin_add_room"), + path("rooms/edit/", rooms.edit_room_view, name="eighth_admin_edit_room"), + path("rooms/delete/", rooms.delete_room_view, name="eighth_admin_delete_room"), + path("rooms/sanity_check", rooms.room_sanity_check_view, name="eighth_admin_room_sanity_check"), + path("rooms/block_utilization", rooms.room_utilization_for_block_view, name="eighth_admin_room_utilization_for_block"), + path("rooms/block_utilization/csv", rooms.room_utilization_for_block_view, name="eighth_admin_room_utilization_for_block_csv"), + path("rooms/utilization", rooms.room_utilization_view, name="eighth_admin_room_utilization"), + path("rooms/utilization//", rooms.room_utilization_action, name="eighth_admin_room_utilization"), + path("rooms/utilization///csv", rooms.room_utilization_action, name="eighth_admin_room_utilization_csv"), # Sponsors - re_path(r"^sponsors/add$", sponsors.add_sponsor_view, name="eighth_admin_add_sponsor"), + path("sponsors/add", sponsors.add_sponsor_view, name="eighth_admin_add_sponsor"), re_path(r"^sponsors/list_activities", sponsors.list_sponsor_activity_view, name="eighth_admin_list_sponsor_activity"), - re_path(r"^sponsors/list$", sponsors.list_sponsor_view, name="eighth_admin_list_sponsor"), - re_path(r"^sponsors/list/csv$", sponsors.list_sponsor_view, name="eighth_admin_list_sponsor_csv"), - re_path(r"^sponsors/edit/(?P\d+)$", sponsors.edit_sponsor_view, name="eighth_admin_edit_sponsor"), - re_path(r"^sponsors/delete/(?P\d+)$", sponsors.delete_sponsor_view, name="eighth_admin_delete_sponsor"), - re_path(r"^sponsors/sanity_check$", sponsors.sponsor_sanity_check_view, name="eighth_admin_sponsor_sanity_check"), - re_path(r"^sponsors/schedule/(?P\d+)$", sponsors.sponsor_schedule_view, name="eighth_admin_sponsor_schedule"), - re_path(r"^startdate$", general.edit_start_date_view, name="eighth_admin_edit_start_date"), - re_path(r"^cache$", general.cache_view, name="eighth_admin_cache"), + path("sponsors/list", sponsors.list_sponsor_view, name="eighth_admin_list_sponsor"), + path("sponsors/list/csv", sponsors.list_sponsor_view, name="eighth_admin_list_sponsor_csv"), + path("sponsors/edit/", sponsors.edit_sponsor_view, name="eighth_admin_edit_sponsor"), + path("sponsors/delete/", sponsors.delete_sponsor_view, name="eighth_admin_delete_sponsor"), + path("sponsors/sanity_check", sponsors.sponsor_sanity_check_view, name="eighth_admin_sponsor_sanity_check"), + path("sponsors/schedule/", sponsors.sponsor_schedule_view, name="eighth_admin_sponsor_schedule"), + path("startdate", general.edit_start_date_view, name="eighth_admin_edit_start_date"), + path("cache", general.cache_view, name="eighth_admin_cache"), ] ####### if settings.ENABLE_HYBRID_EIGHTH: hybrid_patterns = [ - re_path(r"^hybrid/list$", hybrid.list_sponsor_view, name="eighth_admin_list_sponsor_hybrid"), - re_path(r"^hybrid/no_attendance$", hybrid.activities_without_attendance_view, name="eighth_admin_view_activities_without_attendance_hybrid"), - re_path(r"^hybrid/groups/signup/(?P\d+)$", hybrid.eighth_admin_signup_group_hybrid_view, name="eighth_admin_signup_group_hybrid"), - re_path( - r"^hybrid/groups/signup/action/(?P\d+)/(?P\d+)/(?P\d+)$", + path("hybrid/list", hybrid.list_sponsor_view, name="eighth_admin_list_sponsor_hybrid"), + path("hybrid/no_attendance", hybrid.activities_without_attendance_view, name="eighth_admin_view_activities_without_attendance_hybrid"), + path("hybrid/groups/signup/", hybrid.eighth_admin_signup_group_hybrid_view, name="eighth_admin_signup_group_hybrid"), + path( + "hybrid/groups/signup/action///", hybrid.eighth_admin_signup_group_action_hybrid, name="eighth_admin_signup_group_action_hybrid", ), @@ -172,4 +167,4 @@ eighth_admin_patterns.extend(hybrid_patterns) ####### -urlpatterns += [re_path(r"^/admin/", include(eighth_admin_patterns))] +urlpatterns += [path("/admin/", include(eighth_admin_patterns))] diff --git a/intranet/apps/eighth/views/admin/blocks.py b/intranet/apps/eighth/views/admin/blocks.py index 7060b604343..bd07ae3a1a3 100644 --- a/intranet/apps/eighth/views/admin/blocks.py +++ b/intranet/apps/eighth/views/admin/blocks.py @@ -318,7 +318,7 @@ def delete_block_view(request, block_id): context = { "admin_page_title": "Delete Block", "item_name": str(block), - "help_text": "Deleting this block will remove all records " "of it related to eighth period.", + "help_text": "Deleting this block will remove all records of it related to eighth period.", } return render(request, "eighth/admin/delete_form.html", context) diff --git a/intranet/apps/eighth/views/admin/groups.py b/intranet/apps/eighth/views/admin/groups.py index 55f82c79e51..b305221e4ee 100644 --- a/intranet/apps/eighth/views/admin/groups.py +++ b/intranet/apps/eighth/views/admin/groups.py @@ -1,7 +1,6 @@ import csv import logging import re -from typing import List, Optional from cacheops import invalidate_model, invalidate_obj from django import http @@ -155,7 +154,7 @@ def get_file_string(fileobj): return filetext -def get_user_info(key: str, val) -> Optional[List[User]]: +def get_user_info(key: str, val) -> list[User] | None: if key in ["username", "id"]: try: u = get_user_model().objects.filter(**{key: val}) @@ -199,7 +198,7 @@ def handle_group_input(filetext: str): return find_users_input(lines) -def find_users_input(lines: List[str]): +def find_users_input(lines: list[str]): sure_users = [] unsure_users = [] for line in lines: @@ -353,7 +352,7 @@ def delete_group_view(request, group_id): context = { "admin_page_title": "Delete Group", "item_name": str(group), - "help_text": "Deleting this group will remove all records " "of it related to eighth period.", + "help_text": "Deleting this group will remove all records of it related to eighth period.", } return render(request, "eighth/admin/delete_form.html", context) @@ -486,7 +485,7 @@ def eighth_admin_signup_group_action(request, group_id, schact_id): ) -def eighth_admin_perform_group_signup(*, group_id: int, schact_id: int, request: Optional[http.HttpRequest], skip_users: set): +def eighth_admin_perform_group_signup(*, group_id: int, schact_id: int, request: http.HttpRequest | None, skip_users: set): """Performs sign up of all users in a specific group up for a specific scheduled activity. diff --git a/intranet/apps/eighth/views/admin/hybrid.py b/intranet/apps/eighth/views/admin/hybrid.py index 9045ec3a625..3726df45e0a 100644 --- a/intranet/apps/eighth/views/admin/hybrid.py +++ b/intranet/apps/eighth/views/admin/hybrid.py @@ -1,5 +1,4 @@ import logging -from typing import Optional from django import http from django.contrib import messages @@ -212,7 +211,7 @@ def eighth_admin_signup_group_action_hybrid(request, group_id, schact_virtual_id ) -def eighth_admin_perform_group_signup(*, group_id: int, schact_virtual_id: int, schact_person_id: int, request: Optional[http.HttpRequest]): +def eighth_admin_perform_group_signup(*, group_id: int, schact_virtual_id: int, schact_person_id: int, request: http.HttpRequest | None): """Performs sign up of all users in a specific group up for a specific scheduled activity. diff --git a/intranet/apps/eighth/views/admin/rooms.py b/intranet/apps/eighth/views/admin/rooms.py index 45b2a98742f..e2fd9f722ff 100644 --- a/intranet/apps/eighth/views/admin/rooms.py +++ b/intranet/apps/eighth/views/admin/rooms.py @@ -87,7 +87,7 @@ def delete_room_view(request, room_id): context = { "admin_page_title": "Delete Room", "item_name": str(room), - "help_text": "Deleting this room will remove all records " "of it related to eighth period.", + "help_text": "Deleting this room will remove all records of it related to eighth period.", } return render(request, "eighth/admin/delete_form.html", context) @@ -204,8 +204,8 @@ def room_utilization_action(request, start_id, end_id): show_opts_defaults = ["block", "rooms", "capacity", "signups", "aid", "activity", "comments", "sponsors"] show_opts_hidden = ["admin_comments"] if not show_vals: - show = {name: True for name in show_opts_defaults} - show.update({name: False for name in show_opts_hidden}) + show = dict.fromkeys(show_opts_defaults, True) + show.update(dict.fromkeys(show_opts_hidden, False)) else: show = {name: name in show_vals for name in show_opts} diff --git a/intranet/apps/eighth/views/admin/scheduling.py b/intranet/apps/eighth/views/admin/scheduling.py index 0c2c895a062..66fed30ff9f 100644 --- a/intranet/apps/eighth/views/admin/scheduling.py +++ b/intranet/apps/eighth/views/admin/scheduling.py @@ -245,7 +245,7 @@ def schedule_activity_view(request): # There must be an error in the form if this is reached formset = ScheduledActivityFormset(initial=initial_formset_data) context["formset"] = formset - context["rows"] = list(zip(blocks, formset)) + context["rows"] = list(zip(blocks, formset, strict=False)) context["default_rooms"] = activity.rooms.all() context["default_sponsors"] = activity.sponsors.all() diff --git a/intranet/apps/eighth/views/attendance.py b/intranet/apps/eighth/views/attendance.py index 65bfa0b58e5..f88841d7673 100644 --- a/intranet/apps/eighth/views/attendance.py +++ b/intranet/apps/eighth/views/attendance.py @@ -23,17 +23,17 @@ from reportlab.lib.units import inch from reportlab.platypus import PageBreak, Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle +from ....settings.__init__ import ATTENDANCE_CODE_BUFFER from ....utils.date import get_date_range_this_year from ...auth.decorators import attendance_taker_required, deny_restricted, eighth_admin_required from ...dashboard.views import gen_sponsor_schedule -from ...schedule.models import Day +from ...schedule.models import Block, Day, shift_time from ...schedule.views import decode_date from ..forms.admin.activities import ActivitySelectionForm from ..forms.admin.blocks import BlockSelectionForm from ..models import EighthActivity, EighthBlock, EighthScheduledActivity, EighthSignup, EighthSponsor, EighthWaitlist from ..tasks import email_scheduled_activity_students_task from ..utils import get_start_date -from .signup import shift_time logger = logging.getLogger(__name__) @@ -290,6 +290,11 @@ def take_attendance_view(request, scheduled_activity_id): scheduled_activity.save() invalidate_obj(scheduled_activity) + for signup in EighthSignup.objects.filter(scheduled_activity=scheduled_activity): + signup.attendance_marked = False + signup.save() + invalidate_obj(signup) + messages.success(request, f"Attendance bit cleared for {scheduled_activity}") redirect_url = reverse(url_name, args=[scheduled_activity.id]) @@ -420,6 +425,21 @@ def take_attendance_view(request, scheduled_activity_id): members.sort(key=lambda m: m["name"]) + auto_time_string = "" + try: + day_block = ( + Day.objects.select_related("day_type") + .get(date=scheduled_activity.block.date) + .day_type.blocks.get(name="8" + scheduled_activity.block.block_letter) + ) + start_time = shift_time(tm(hour=day_block.start.hour, minute=day_block.start.minute), -ATTENDANCE_CODE_BUFFER) + end_time = shift_time(tm(hour=day_block.end.hour, minute=day_block.end.minute), ATTENDANCE_CODE_BUFFER) + auto_time_string = f"({start_time.strftime('%-I:%M')} - {end_time.strftime('%-I:%M %p')})" + except Day.DoesNotExist: + auto_time_string = "" + except Block.DoesNotExist: + auto_time_string = "" + print(auto_time_string) context = { "scheduled_activity": scheduled_activity, "passes": passes, @@ -430,7 +450,8 @@ def take_attendance_view(request, scheduled_activity_id): "show_checkboxes": (scheduled_activity.block.locked or request.user.is_eighth_admin), "show_icons": (scheduled_activity.block.locked and scheduled_activity.block.attendance_locked() and not request.user.is_eighth_admin), "bbcu_script": settings.BBCU_SCRIPT, - "qr_url": request.build_absolute_uri(reverse("qr_attendance", args=[scheduled_activity.id, scheduled_activity.attendance_code])), + "qr_url": request.build_absolute_uri(reverse("student_attendance", args=[scheduled_activity.id, scheduled_activity.attendance_code])), + "auto_time_string": auto_time_string, } if request.user.is_eighth_admin: @@ -501,7 +522,7 @@ def accept_pass_view(request, signup_id): sponsor = request.user.get_eighth_sponsor() can_accept = signup.scheduled_activity.block.locked and ( - sponsor and (sponsor in signup.scheduled_activity.get_true_sponsors()) or request.user.is_eighth_admin + (sponsor and (sponsor in signup.scheduled_activity.get_true_sponsors())) or request.user.is_eighth_admin ) if not can_accept: @@ -535,7 +556,9 @@ def accept_all_passes_view(request, scheduled_activity_id): raise http.Http404 from e sponsor = request.user.get_eighth_sponsor() - can_accept = scheduled_activity.block.locked and (sponsor and (sponsor in scheduled_activity.get_true_sponsors()) or request.user.is_eighth_admin) + can_accept = scheduled_activity.block.locked and ( + (sponsor and (sponsor in scheduled_activity.get_true_sponsors())) or request.user.is_eighth_admin + ) if not can_accept: return render(request, "error/403.html", {"reason": "You do not have permission to take accept these passes."}, status=403) @@ -776,143 +799,121 @@ def email_students_view(request, scheduled_activity_id): @login_required @deny_restricted -def student_attendance_view(request): - blocks = EighthBlock.objects.get_blocks_today() - mark_block = None - mark_result = None - if request.method == "POST": - now = timezone.localtime() - try: - day_blocks = Day.objects.select_related("day_type").get(date=now).day_type.blocks - except Day.DoesNotExist: - messages.error(request, "Error. Attendance is only available on school days.") - return redirect("index") - for block in blocks: - block_letter = block.block_letter - code = request.POST.get(block_letter) - if code is None: - continue - act = request.user.eighthscheduledactivity_set.get(block=block) - if act.get_code_mode_display() == "Auto": - try: - day_block = day_blocks.get(name="8" + block_letter) - except Exception: - mark_result = "invalid_time" - mark_block = block - break - start_time = shift_time(tm(hour=day_block.start.hour, minute=day_block.start.minute), -20) - end_time = shift_time(tm(hour=day_block.end.hour, minute=day_block.end.minute), 20) - if not start_time <= now.time() <= end_time: - mark_result = "invalid_time" - mark_block = block - break - elif act.get_code_mode_display() == "Closed": - mark_result = "code_closed" - mark_block = block - break - code = code.upper() - if code == act.attendance_code: - try: - present = EighthSignup.objects.get(scheduled_activity=act, user__in=[request.user.id]) - present.was_absent = False - present.attendance_marked = True - present.save() - invalidate_obj(present) - act.attendance_taken = True - act.save() - invalidate_obj(act) - mark_result = "code_correct" - mark_block = block - except Exception: - mark_result = "code_fail" - mark_block = block - break - else: - mark_result = "code_fail" - mark_block = block - break - return student_frontend(request, mark_block, mark_result) - +def student_attendance_view(request, sch_act_id, code=None): + """Handles the initial steps for code or QR code student-based attendance + Args: + request: the user's request + sch_act_id: the id of the EighthScheduledActivity for which the user attempts to take attendance. It is from url, required. + code: the code which the user is attempting to take attendance. From url, optional (if the user just wants to access the page) + Returns: + An HttpResponse rendering the student attendance page for the specified EighthScheduledActivity (if it exists). + Otherwise, redirects to index / dashboard. + """ + result = None # could end as None (no attendance attempt), no_signup, no_school, invalid_time, code_closed, code_fail, or marked + try: + sch_act = EighthScheduledActivity.objects.get(id=sch_act_id) + except EighthScheduledActivity.DoesNotExist: + messages.error(request, "Error marking attendance.") + return redirect("index") -@login_required -@deny_restricted -def student_frontend(request, mark_block: EighthBlock = None, mark_result: str = None): - blocks = EighthBlock.objects.get_blocks_today() - if blocks: - sch_acts = [] - att_marked = [] - for block in blocks: - try: - act = request.user.eighthscheduledactivity_set.get(block=block) - if act.activity.name != "z - Hybrid Sticky": - sch_acts.append( - [block, act, ", ".join([r.name for r in act.get_true_rooms()]), ", ".join([s.name for s in act.get_true_sponsors()])] - ) - signup = EighthSignup.objects.get(user=request.user, scheduled_activity=act) - if (not signup.was_absent) and signup.attendance_marked and act.attendance_taken: - att_marked.append(block) - except EighthScheduledActivity.DoesNotExist: - sch_acts.append([block, None]) - results = { - "code_correct": "", - "code_closed": '

Error. Ask your teacher to open the attendance code.

', - "code_fail": '

Invalid Code.

', - "invalid_time": f'

Invalid time. Please fill this out during {block.block_letter} block.

', - } - response = render( - request, - "eighth/student_submit_attendance.html", - context={"sch_acts": sch_acts, "att_marked": att_marked, "mark_block": mark_block, "attendance_result": results.get(mark_result)}, - ) + try: + user_signup = EighthSignup.objects.get(user=request.user, scheduled_activity=sch_act) + if user_signup.attendance_marked and not user_signup.was_absent: + return student_frontend(request, sch_act_id, result="marked") + except EighthSignup.DoesNotExist: + result = "no_signup" + return student_frontend(request, sch_act_id, result) + + is_open = check_attendance_open(sch_act) # returns None if attendance is open, error string if closed + if is_open is None: + if code is not None: + result = mark_attendance(request, sch_act, code) else: - messages.error(request, "There are no eighth period blocks scheduled today.") - response = redirect("index") - return response + result = is_open + + return student_frontend(request, sch_act_id, result) @login_required @deny_restricted -def qr_attendance_view(request, act_id, code): - act = get_object_or_404(EighthScheduledActivity, id=act_id) - error = False +def student_frontend(request, sch_act_id, result): + """Handles the process of rendering the frontend UI for student-based attendance. + It is only called by the student_attendance_view function + Args: + request: the user's request + sch_act_id: the EighthScheduledActivity for which the user attempts to take attendance + result: The result of the student's attempt to take attendance (if there was an attempt) + Returns: + An HttpResponse which renders the UI with a corresponding message. Returns this to student_attendance_view. + """ + + result_to_html = { + None: "", + "no_signup": '

Error: You are not signed up for this activity.

', + "no_school": '

Error: Attendance is only available on school days.

', + "invalid_time": '

Error: Please fill out attendance during the timeframe of this activity.

', + "code_closed": '

Error: Your activity sponsor has closed the attendance code.

', + "code_fail": '

Error: Invalid Code.

', + "marked": '

Attendance marked.

', + } + html_text = result_to_html[result] + sch_act = EighthScheduledActivity.objects.get(id=sch_act_id) + date_string = sch_act.block.date.strftime("%b %d") + show_form = result in (None, "code_fail") + return render( + request, + "eighth/student_submit_attendance.html", + context={"result": result, "result_html": html_text, "show_form": show_form, "sch_act": sch_act, "date_string": date_string}, + ) + + +def mark_attendance(request, act, code): + """Handles the process of checking the code and marking attendance for the user. + Args: + request: the user's request + act: the EighthScheduledActivity for which the user attempts to take attendance + code: the code which the user is attempting to take attendance + Returns: + A string reporting the result of the attempt. Either "code_correct" or "code_fail" + """ + if code.upper() == act.attendance_code: + try: + present = EighthSignup.objects.get(scheduled_activity=act, user__in=[request.user.id]) + present.was_absent = False + present.attendance_marked = True + present.save() + invalidate_obj(present) + act.attendance_taken = True + act.save() + invalidate_obj(act) + return "marked" + except EighthSignup.DoesNotExist: + return "code_fail" + return "code_fail" + + +def check_attendance_open(act: EighthScheduledActivity): + """Checks whether attendance is open for an EighthScheduledActivity. + Args: + act: the EighthScheduledActivity in question + day_blocks: the set of Block objects in the current day's day_type (defined in student_attendance_view) + Returns: + mark_result: a string, either "no_school", "invalid_time" or "code_closed", or None if attendance is open. + """ block = act.block mark_result = None if act.get_code_mode_display() == "Auto": now = timezone.localtime() - day_blocks = Day.objects.select_related("day_type").get(date=now).day_type.blocks + try: + day_blocks = Day.objects.select_related("day_type").get(date=now).day_type.blocks + except Day.DoesNotExist: + return "no_school" try: day_block = day_blocks.get(name="8" + block.block_letter) - start_time = shift_time(tm(hour=day_block.start.hour, minute=day_block.start.minute), -20) - end_time = shift_time(tm(hour=day_block.end.hour, minute=day_block.end.minute), 20) - if not start_time <= now.time() <= end_time: - mark_result = "invalid_time" - error = True - except Exception: + except Block.DoesNotExist: + mark_result = "invalid_time" + if not day_block.eighth_auto_open <= timezone.localtime().time() <= day_block.eighth_auto_close: mark_result = "invalid_time" - error = True elif act.get_code_mode_display() == "Closed": mark_result = "code_closed" - error = True - if not error: - code = code.upper() - if code == act.attendance_code: - try: - present = EighthSignup.objects.get(scheduled_activity=act, user__in=[request.user.id]) - present.was_absent = False - present.attendance_marked = True - present.save() - invalidate_obj(present) - act.attendance_taken = True - act.save() - invalidate_obj(act) - mark_result = "code_correct" - messages.success(request, "Attendance marked.") - except Exception: - mark_result = "code_fail" - messages.error(request, "Failed to mark attendance.") - else: - mark_result = "code_fail" - messages.error(request, "Failed to mark attendance.") - else: - messages.error(request, "Failed to mark attendance.") - return student_frontend(request, block, mark_result) + return mark_result diff --git a/intranet/apps/eighth/views/monitoring.py b/intranet/apps/eighth/views/monitoring.py index d128056dc82..ed1983d8bf7 100644 --- a/intranet/apps/eighth/views/monitoring.py +++ b/intranet/apps/eighth/views/monitoring.py @@ -6,7 +6,7 @@ def metrics_view(request): - remote_addr = request.META["HTTP_X_REAL_IP"] if "HTTP_X_REAL_IP" in request.META else request.META.get("REMOTE_ADDR", "") + remote_addr = request.headers["x-real-ip"] if "x-real-ip" in request.headers else request.META.get("REMOTE_ADDR", "") is_admin = request.user.is_authenticated and not request.user.is_restricted and request.user.has_admin_permission("eighth") # If they're not from an IP on the white list and they're not an eighth admin, deny access diff --git a/intranet/apps/eighth/views/signup.py b/intranet/apps/eighth/views/signup.py index 8ddab3d1836..24feb991c8b 100644 --- a/intranet/apps/eighth/views/signup.py +++ b/intranet/apps/eighth/views/signup.py @@ -1,8 +1,6 @@ import datetime import logging import time -from datetime import time as tm -from datetime import timedelta from django import http from django.conf import settings @@ -20,7 +18,6 @@ from ....utils.locking import lock_on from ....utils.serialization import safe_json from ...auth.decorators import deny_restricted, eighth_admin_required -from ...schedule.models import Day from ..exceptions import SignupException from ..models import EighthActivity, EighthBlock, EighthScheduledActivity, EighthSignup, EighthWaitlist from ..serializers import EighthBlockDetailSerializer @@ -31,11 +28,6 @@ eighth_signup_submits = Summary("intranet_eighth_signup_submits", "Number of eighth period signups performed from the eighth signup view") -def shift_time(time, minutes): - dt = datetime.datetime.combine(datetime.datetime.today(), time) - return (dt + timedelta(minutes=minutes)).time() - - @login_required @deny_restricted def eighth_signup_view(request, block_id=None): @@ -223,30 +215,6 @@ def eighth_signup_view(request, block_id=None): except KeyError: active_block_current_signup = None - attendance_open = False - try: - now = timezone.localtime() - dayblks = Day.objects.select_related("day_type").get(date=now).day_type.blocks.all() - earliest = None - latest = None - for blk in dayblks: - name = blk.name - if name is None: - continue - if "8" in name: - start = tm(hour=blk.start.hour, minute=blk.start.minute) - end = tm(hour=blk.end.hour, minute=blk.end.minute) - if earliest is None or start <= earliest: - earliest = start - if latest is None or end >= latest: - latest = end - if earliest is not None: - earliest = shift_time(earliest, -20) - latest = shift_time(latest, 20) - if earliest <= now.time() <= latest: - attendance_open = True - except Exception: - attendance_open = False context = { "user": user, "real_user": request.user, @@ -254,7 +222,6 @@ def eighth_signup_view(request, block_id=None): "activities_list": safe_json(block_info["activities"]), "active_block": block, "active_block_current_signup": active_block_current_signup, - "attendance_open": attendance_open, } ####### @@ -433,7 +400,7 @@ def subscribe_to_club(request, activity_id): activity.subscribers.add(request.user) - return redirect(request.META.get("HTTP_REFERER", "/")) + return redirect(request.headers.get("referer", "/")) @login_required @@ -448,7 +415,7 @@ def unsubscribe_from_club(request, activity_id): if request.user in activity.subscribers.all(): activity.subscribers.remove(request.user) - return redirect(request.META.get("HTTP_REFERER", "/")) + return redirect(request.headers.get("referer", "/")) @login_required @@ -484,33 +451,7 @@ def eighth_location(request): 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()])]) except EighthScheduledActivity.DoesNotExist: sch_acts.append([b, None]) - attendance_open = False - try: - now = timezone.localtime() - dayblks = Day.objects.select_related("day_type").get(date=now).day_type.blocks.all() - earliest = None - latest = None - for blk in dayblks: - name = blk.name - if name is None: - continue - if "8" in name: - start = tm(hour=blk.start.hour, minute=blk.start.minute) - end = tm(hour=blk.end.hour, minute=blk.end.minute) - if earliest is None or start <= earliest: - earliest = start - if latest is None or end >= latest: - latest = end - if earliest is not None: - earliest = shift_time(earliest, -20) - latest = shift_time(latest, 20) - if earliest <= now.time() <= latest: - attendance_open = True - except Exception: - attendance_open = False - response = render( - request, "eighth/location.html", context={"sch_acts": sch_acts, "real_user": request.user, "attendance_open": attendance_open} - ) + response = render(request, "eighth/location.html", context={"sch_acts": sch_acts, "real_user": request.user}) else: messages.error(request, "There are no eighth period blocks scheduled today.") response = redirect("index") diff --git a/intranet/apps/emailfwd/urls.py b/intranet/apps/emailfwd/urls.py index ebe770cc8f7..9aa755c4101 100644 --- a/intranet/apps/emailfwd/urls.py +++ b/intranet/apps/emailfwd/urls.py @@ -1,5 +1,5 @@ -from django.urls import re_path +from django.urls import path from . import views -urlpatterns = [re_path(r"^/senior$", views.senior_email_forward_view, name="senior_emailfwd")] +urlpatterns = [path("/senior", views.senior_email_forward_view, name="senior_emailfwd")] diff --git a/intranet/apps/emerg/views.py b/intranet/apps/emerg/views.py index c525e10d9c6..3423cc2a568 100644 --- a/intranet/apps/emerg/views.py +++ b/intranet/apps/emerg/views.py @@ -1,12 +1,13 @@ import logging import time -from typing import Tuple import requests from bs4 import BeautifulSoup from django.conf import settings from django.core.cache import cache from django.utils import timezone +from requests.adapters import HTTPAdapter +from urllib3 import Retry from ...utils.html import get_domain_name, safe_fcps_emerg_html @@ -122,7 +123,7 @@ def update_emerg_cache(*, custom_logger=None) -> None: cache.set(key, result, timeout=settings.CACHE_AGE["emerg"]) -def get_csl_status() -> Tuple[str, bool]: +def get_csl_status() -> tuple[str, bool]: """Get the cached status of the TJCSL status page. Returns: @@ -137,18 +138,22 @@ def get_csl_status() -> Tuple[str, bool]: updated = False if not status: - response = requests.get(settings.CSL_STATUS_PAGE) - if response.status_code != 200: + session = requests.Session() + adapter = HTTPAdapter( + max_retries=Retry( + total=settings.CSL_STATUS_PAGE_MAX_RETRIES, backoff_factor=0.3, status_forcelist=[500, 502, 503, 504], allowed_methods=["GET"] + ) + ) + session.mount("https://", adapter) + + try: + response = session.get(settings.CSL_STATUS_PAGE, timeout=settings.CSL_STATUS_PAGE_TIMEOUT) + response.raise_for_status() + status = response.json()["data"]["attributes"]["aggregate_state"] + updated = True + except Exception as ex: status = "error" - logger.error("Could not fetch status page") - - else: - try: - status = response.json()["data"]["attributes"]["aggregate_state"] - updated = True - except KeyError as e: - status = "error" - logger.error("Unexpected status page JSON format. %s", e) + logger.error(f"Could not fetch status page or incorrect status page JSON format: {ex}") cache.set("emerg:csl_status", status, settings.CACHE_AGE["csl_status"]) diff --git a/intranet/apps/enrichment/admin.py b/intranet/apps/enrichment/admin.py index 480fb00dbb6..f08d31f1117 100644 --- a/intranet/apps/enrichment/admin.py +++ b/intranet/apps/enrichment/admin.py @@ -3,11 +3,9 @@ from .models import EnrichmentActivity +@admin.register(EnrichmentActivity) class EnrichmentActivityAdmin(admin.ModelAdmin): list_display = ("title", "location", "time", "capacity", "presign", "attendance_taken") list_filter = ("time", "presign", "attendance_taken") ordering = ("-time",) search_fields = ("title", "description", "groups_allowed__name", "groups_blacklisted__name") - - -admin.site.register(EnrichmentActivity, EnrichmentActivityAdmin) diff --git a/intranet/apps/enrichment/urls.py b/intranet/apps/enrichment/urls.py index 54a89249e28..f166cd14ace 100644 --- a/intranet/apps/enrichment/urls.py +++ b/intranet/apps/enrichment/urls.py @@ -1,5 +1,5 @@ from django.conf import settings -from django.urls import re_path +from django.urls import path from . import views @@ -7,11 +7,11 @@ if settings.ENABLE_ENRICHMENT_APP: urlpatterns = [ - re_path(r"^$", views.enrichment_view, name="enrichment"), - re_path(r"^/add$", views.add_enrichment_view, name="add_enrichment"), - re_path(r"^/modify/(?P\d+)$", views.modify_enrichment_view, name="modify_enrichment"), - re_path(r"^/delete/(?P\d+)$", views.delete_enrichment_view, name="delete_enrichment"), - re_path(r"^/join/(?P\d+)$", views.enrichment_signup_view, name="enrichment_signup"), - re_path(r"^/roster/(?P\d+)$", views.enrichment_roster_view, name="enrichment_roster"), - re_path(r"^/(?P\d+)$", views.enrichment_roster_view, name="enrichment"), + path("", views.enrichment_view, name="enrichment"), + path("/add", views.add_enrichment_view, name="add_enrichment"), + path("/modify/", views.modify_enrichment_view, name="modify_enrichment"), + path("/delete/", views.delete_enrichment_view, name="delete_enrichment"), + path("/join/", views.enrichment_signup_view, name="enrichment_signup"), + path("/roster/", views.enrichment_roster_view, name="enrichment_roster"), + path("/", views.enrichment_roster_view, name="enrichment"), ] diff --git a/intranet/apps/enrichment/views.py b/intranet/apps/enrichment/views.py index 81ad5fbb6d0..99473cc60ba 100644 --- a/intranet/apps/enrichment/views.py +++ b/intranet/apps/enrichment/views.py @@ -220,7 +220,7 @@ def enrichment_signup_view(request, enrichment_id): if too_early_to_signup[0]: messages.error( request, - "You may not sign up for this enrichment activity until " f"{too_early_to_signup[1].strftime('%A, %B %-d at %-I:%M %p')}.", + f"You may not sign up for this enrichment activity until {too_early_to_signup[1].strftime('%A, %B %-d at %-I:%M %p')}.", ) return redirect("enrichment") diff --git a/intranet/apps/events/admin.py b/intranet/apps/events/admin.py index 02dfd0fe2ba..340a1cd50d3 100644 --- a/intranet/apps/events/admin.py +++ b/intranet/apps/events/admin.py @@ -3,9 +3,9 @@ from .models import Event, Link +@admin.register(Event) class EventAdmin(admin.ModelAdmin): raw_id_fields = ("scheduled_activity", "announcement") -admin.site.register(Event, EventAdmin) admin.site.register(Link) diff --git a/intranet/apps/events/tests.py b/intranet/apps/events/tests.py index 60f6d94af89..f6cdd681c67 100644 --- a/intranet/apps/events/tests.py +++ b/intranet/apps/events/tests.py @@ -111,7 +111,7 @@ def test_view_roster(self): expected_context = {"event": event, "viewable_roster": [], "num_hidden_members": 0, "is_events_admin": False} for key, item in expected_context.items(): self.assertEqual(response.context[key], item) - self.assertQuerysetEqual(response.context["full_roster"], get_user_model().objects.none()) + self.assertQuerySetEqual(response.context["full_roster"], get_user_model().objects.none()) # Test with a few attendees num_users = 5 @@ -217,7 +217,7 @@ def test_modify_event(self): # Test GET of valid event id response = self.client.get(reverse("modify_event", args=[event.id])) - expected_context = {"action": "modify", "action_title": "Modify", "id": str(event.id), "is_events_admin": True} + expected_context = {"action": "modify", "action_title": "Modify", "id": event.id, "is_events_admin": True} for key, item in expected_context.items(): self.assertEqual(response.context[key], item) diff --git a/intranet/apps/events/urls.py b/intranet/apps/events/urls.py index cbb09e02be1..2caf8149f15 100644 --- a/intranet/apps/events/urls.py +++ b/intranet/apps/events/urls.py @@ -1,16 +1,16 @@ -from django.urls import re_path +from django.urls import path from . import views urlpatterns = [ - re_path(r"^$", views.events_view, name="events"), - re_path(r"^/add$", views.add_event_view, name="add_event"), - re_path(r"^/request$", views.request_event_view, name="request_event"), - re_path(r"^/modify/(?P\d+)$", views.modify_event_view, name="modify_event"), - re_path(r"^/delete/(?P\d+)$", views.delete_event_view, name="delete_event"), - re_path(r"^/join/(?P\d+)$", views.join_event_view, name="join_event"), - re_path(r"^/roster/(?P\d+)$", views.event_roster_view, name="event_roster"), - re_path(r"^/show$", views.show_event_view, name="show_event"), - re_path(r"^/hide$", views.hide_event_view, name="hide_event"), - re_path(r"^/(?P\d+)$", views.event_roster_view, name="event"), + path("", views.events_view, name="events"), + path("/add", views.add_event_view, name="add_event"), + path("/request", views.request_event_view, name="request_event"), + path("/modify/", views.modify_event_view, name="modify_event"), + path("/delete/", views.delete_event_view, name="delete_event"), + path("/join/", views.join_event_view, name="join_event"), + path("/roster/", views.event_roster_view, name="event_roster"), + path("/show", views.show_event_view, name="show_event"), + path("/hide", views.hide_event_view, name="hide_event"), + path("/", views.event_roster_view, name="event"), ] diff --git a/intranet/apps/features/helpers.py b/intranet/apps/features/helpers.py index 92c588e4a31..0f8af697774 100644 --- a/intranet/apps/features/helpers.py +++ b/intranet/apps/features/helpers.py @@ -1,7 +1,4 @@ -from typing import Optional - - -def get_feature_context(request) -> Optional[str]: +def get_feature_context(request) -> str | None: """Given a Django request, returns the 'context' that should be used to select feature announcements to display (one of ``dashboard``, ``login``, ``eighth_signup``, or ``None``). diff --git a/intranet/apps/features/tests.py b/intranet/apps/features/tests.py index 9077388ee26..9cff1bb411c 100644 --- a/intranet/apps/features/tests.py +++ b/intranet/apps/features/tests.py @@ -17,7 +17,7 @@ def test_anonymous_feature_list(self): for view_name in ["index", "login"]: response = self.client.get(reverse(view_name)) self.assertEqual(response.status_code, 200) - self.assertQuerysetEqual(response.context["feature_announcements"], []) + self.assertQuerySetEqual(response.context["feature_announcements"], []) today = timezone.localdate() yesterday = today - datetime.timedelta(days=1) @@ -34,7 +34,7 @@ def test_anonymous_feature_list(self): for view_name in ["index", "login"]: response = self.client.get(reverse(view_name)) self.assertEqual(response.status_code, 200) - self.assertQuerysetEqual(response.context["feature_announcements"], list(map(repr, [fa2, fa3, fa4])), transform=repr, ordered=False) + self.assertQuerySetEqual(response.context["feature_announcements"], list(map(repr, [fa2, fa3, fa4])), transform=repr, ordered=False) def test_login_feature_list(self): """Tests listing features on the login/dashboard/eighth signup pages as an authenticated user.""" @@ -45,7 +45,7 @@ def test_login_feature_list(self): for view_name in ["index", "login", "eighth_signup"]: response = self.client.get(reverse(view_name)) self.assertEqual(response.status_code, 200) - self.assertQuerysetEqual(response.context["feature_announcements"], []) + self.assertQuerySetEqual(response.context["feature_announcements"], []) today = timezone.localdate() yesterday = today - datetime.timedelta(days=1) @@ -65,7 +65,7 @@ def test_login_feature_list(self): response = self.client.get(reverse(view_name)) self.assertEqual(response.status_code, 200) - self.assertQuerysetEqual(response.context["feature_announcements"], [repr(announcement)], transform=repr, ordered=False) + self.assertQuerySetEqual(response.context["feature_announcements"], [repr(announcement)], transform=repr, ordered=False) self.assertTrue(announcement.users_seen.filter(id=user.id).exists()) @@ -75,4 +75,4 @@ def test_login_feature_list(self): response = self.client.get(reverse(view_name)) self.assertEqual(response.status_code, 200) - self.assertQuerysetEqual(response.context["feature_announcements"], [], ordered=False) + self.assertQuerySetEqual(response.context["feature_announcements"], [], ordered=False) diff --git a/intranet/apps/features/urls.py b/intranet/apps/features/urls.py index ffaec2836fa..0940ae4b3a6 100644 --- a/intranet/apps/features/urls.py +++ b/intranet/apps/features/urls.py @@ -1,9 +1,7 @@ -from django.urls import re_path +from django.urls import path from . import views app_name = "features" -urlpatterns = [ - re_path(r"^/dismiss-announcement/(?P\d+)$", views.dismiss_feat_announcement_view, name="dismiss_feat_announcement") -] +urlpatterns = [path("/dismiss-announcement/", views.dismiss_feat_announcement_view, name="dismiss_feat_announcement")] diff --git a/intranet/apps/feedback/admin.py b/intranet/apps/feedback/admin.py index 2ec4d9cb258..b03da7d0c26 100644 --- a/intranet/apps/feedback/admin.py +++ b/intranet/apps/feedback/admin.py @@ -3,10 +3,8 @@ from .models import Feedback +@admin.register(Feedback) class FeedbackAdmin(admin.ModelAdmin): list_display = ("user", "date", "comments") ordering = ("-date",) raw_id_fields = ("user",) - - -admin.site.register(Feedback, FeedbackAdmin) diff --git a/intranet/apps/feedback/urls.py b/intranet/apps/feedback/urls.py index 71cc22a824e..2b0eac2962b 100644 --- a/intranet/apps/feedback/urls.py +++ b/intranet/apps/feedback/urls.py @@ -1,5 +1,5 @@ -from django.urls import re_path +from django.urls import path from . import views -urlpatterns = [re_path(r"^$", views.send_feedback_view, name="send_feedback")] +urlpatterns = [path("", views.send_feedback_view, name="send_feedback")] diff --git a/intranet/apps/feedback/views.py b/intranet/apps/feedback/views.py index f0db23569b6..46fb8451f3a 100644 --- a/intranet/apps/feedback/views.py +++ b/intranet/apps/feedback/views.py @@ -16,8 +16,8 @@ def send_feedback_email(request, data): data["user"] = request.user email = request.user.tj_email if request.user.is_authenticated else f"unknown-{request.user}@tjhsst.edu" data["email"] = email - data["remote_ip"] = request.META["HTTP_X_REAL_IP"] if "HTTP_X_REAL_IP" in request.META else request.META.get("REMOTE_ADDR", "") - data["user_agent"] = request.META.get("HTTP_USER_AGENT") + data["remote_ip"] = request.headers["x-real-ip"] if "x-real-ip" in request.headers else request.META.get("REMOTE_ADDR", "") + data["user_agent"] = request.headers.get("user-agent") headers = {"Reply-To": f"{email}; {settings.FEEDBACK_EMAIL}"} email_send_task.delay("feedback/email.txt", "feedback/email.html", data, f"Feedback from {request.user}", [settings.FEEDBACK_EMAIL], headers) diff --git a/intranet/apps/files/urls.py b/intranet/apps/files/urls.py index 6c11ef3feea..8f8130251f9 100644 --- a/intranet/apps/files/urls.py +++ b/intranet/apps/files/urls.py @@ -1,10 +1,10 @@ -from django.urls import re_path +from django.urls import path, re_path from . import views urlpatterns = [ - re_path(r"^$", views.files_view, name="files"), - re_path(r"^/auth$", views.files_auth, name="files_auth"), + path("", views.files_view, name="files"), + path("/auth", views.files_auth, name="files_auth"), re_path(r"^/(?P\w+)$", views.files_type, name="files_type"), re_path(r"^/(?P\w+)/upload$", views.files_upload, name="files_upload"), re_path(r"^/(?P\w+)/delete$", views.files_delete, name="files_delete"), diff --git a/intranet/apps/files/views.py b/intranet/apps/files/views.py index 0c77ed90b2b..27d5bc9b32b 100644 --- a/intranet/apps/files/views.py +++ b/intranet/apps/files/views.py @@ -247,9 +247,10 @@ def can_access_path(fsdir): dirbase_escaped = os.path.basename(fsdir) dirbase_escaped = slugify(dirbase_escaped) - with tempfile.TemporaryDirectory(prefix=f"ion_filecenter_{request.user.username}_{dirbase_escaped}_zip") as tmpdir, tempfile.TemporaryFile( - prefix=f"ion_filecenter_{request.user.username}_{dirbase_escaped}" - ) as tmpfile: + with ( + tempfile.TemporaryDirectory(prefix=f"ion_filecenter_{request.user.username}_{dirbase_escaped}_zip") as tmpdir, + tempfile.TemporaryFile(prefix=f"ion_filecenter_{request.user.username}_{dirbase_escaped}") as tmpfile, + ): remote_directories = [fsdir] totalsize = 0 while remote_directories: diff --git a/intranet/apps/groups/tests.py b/intranet/apps/groups/tests.py index cde77652baa..a684598cb33 100644 --- a/intranet/apps/groups/tests.py +++ b/intranet/apps/groups/tests.py @@ -20,19 +20,19 @@ def test_groups_view(self): response = self.client.get(reverse("groups")) self.assertEqual(response.status_code, 200) self.assertEqual(response.context["group_admin"], False) - self.assertQuerysetEqual(response.context["all_groups"], list(map(repr, Group.objects.all())), transform=repr, ordered=False) + self.assertQuerySetEqual(response.context["all_groups"], list(map(repr, Group.objects.all())), transform=repr, ordered=False) user.groups.set([admin_all_group]) response = self.client.get(reverse("groups")) self.assertEqual(response.status_code, 200) self.assertEqual(response.context["group_admin"], True) - self.assertQuerysetEqual(response.context["all_groups"], list(map(repr, Group.objects.all())), transform=repr, ordered=False) + self.assertQuerySetEqual(response.context["all_groups"], list(map(repr, Group.objects.all())), transform=repr, ordered=False) user.groups.set([admin_groups_group]) response = self.client.get(reverse("groups")) self.assertEqual(response.status_code, 200) self.assertEqual(response.context["group_admin"], True) - self.assertQuerysetEqual(response.context["all_groups"], list(map(repr, Group.objects.all())), transform=repr, ordered=False) + self.assertQuerySetEqual(response.context["all_groups"], list(map(repr, Group.objects.all())), transform=repr, ordered=False) def test_add_group_view(self): admin_all_group = Group.objects.get_or_create(name="admin_all")[0] diff --git a/intranet/apps/groups/urls.py b/intranet/apps/groups/urls.py index be9546fabb2..82e9da71d68 100644 --- a/intranet/apps/groups/urls.py +++ b/intranet/apps/groups/urls.py @@ -1,5 +1,5 @@ -from django.urls import re_path +from django.urls import path from . import views -urlpatterns = [re_path(r"^$", views.groups_view, name="groups"), re_path(r"^/add$", views.add_group_view, name="add_groups")] +urlpatterns = [path("", views.groups_view, name="groups"), path("/add", views.add_group_view, name="add_groups")] diff --git a/intranet/apps/itemreg/admin.py b/intranet/apps/itemreg/admin.py index c8e597f2a49..978429c2b35 100644 --- a/intranet/apps/itemreg/admin.py +++ b/intranet/apps/itemreg/admin.py @@ -3,6 +3,7 @@ from .models import CalculatorRegistration, ComputerRegistration, PhoneRegistration +@admin.register(CalculatorRegistration) class CalculatorRegistrationAdmin(admin.ModelAdmin): list_display = ("calc_type", "calc_serial", "calc_id", "user", "added") list_filter = ("calc_type", "added") @@ -10,6 +11,7 @@ class CalculatorRegistrationAdmin(admin.ModelAdmin): raw_id_fields = ("user",) +@admin.register(ComputerRegistration) class ComputerRegistrationAdmin(admin.ModelAdmin): list_display = ("manufacturer", "model", "serial", "description", "user", "added") list_filter = ("added", "manufacturer") @@ -17,13 +19,9 @@ class ComputerRegistrationAdmin(admin.ModelAdmin): raw_id_fields = ("user",) +@admin.register(PhoneRegistration) class PhoneRegistrationAdmin(admin.ModelAdmin): list_display = ("manufacturer", "model", "imei", "description", "user", "added") list_filter = ("added", "manufacturer") ordering = ("-added",) raw_id_fields = ("user",) - - -admin.site.register(CalculatorRegistration, CalculatorRegistrationAdmin) -admin.site.register(ComputerRegistration, ComputerRegistrationAdmin) -admin.site.register(PhoneRegistration, PhoneRegistrationAdmin) diff --git a/intranet/apps/itemreg/urls.py b/intranet/apps/itemreg/urls.py index f7220248c26..69f9eb8137e 100644 --- a/intranet/apps/itemreg/urls.py +++ b/intranet/apps/itemreg/urls.py @@ -1,10 +1,10 @@ -from django.urls import re_path +from django.urls import path, re_path from . import views urlpatterns = [ - re_path(r"^$", views.home_view, name="itemreg"), - re_path(r"^/search$", views.search_view, name="itemreg_search"), + path("", views.home_view, name="itemreg"), + path("/search", views.search_view, name="itemreg_search"), re_path(r"^/register/(?P\w+)$", views.register_view, name="itemreg_register"), re_path(r"^/delete/(?P\w+)/(?P\d+)?$", views.register_delete_view, name="itemreg_delete"), ] diff --git a/intranet/apps/logs/admin.py b/intranet/apps/logs/admin.py index a613b1f00ed..37883bcf8ba 100644 --- a/intranet/apps/logs/admin.py +++ b/intranet/apps/logs/admin.py @@ -20,7 +20,7 @@ def lookups(self, request, model_admin): paths = model_admin.model.objects.order_by("path").values_list("path", flat=True).distinct() truncated_paths = {path if len(path) < 40 else path[:40] + "..." for path in paths} truncated_paths = sorted(truncated_paths) - return zip(truncated_paths, gettext_lazy(truncated_paths)) + return zip(truncated_paths, gettext_lazy(truncated_paths), strict=False) def queryset(self, request, queryset): if self.value(): @@ -38,7 +38,7 @@ def lookups(self, request, model_admin): paths = model_admin.model.objects.order_by("user_agent").values_list("user_agent", flat=True).distinct() truncated_paths = {path if len(path) < 40 else path[:40] + "..." for path in paths} truncated_paths = sorted(truncated_paths) - return zip(truncated_paths, gettext_lazy(truncated_paths)) + return zip(truncated_paths, gettext_lazy(truncated_paths), strict=False) def queryset(self, request, queryset): if self.value(): @@ -54,7 +54,7 @@ class MethodFilter(admin.SimpleListFilter): methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"] def lookups(self, request, model_admin): - return zip(self.methods, gettext_lazy(self.methods)) + return zip(self.methods, gettext_lazy(self.methods), strict=False) def queryset(self, request, queryset): if self.value(): @@ -94,7 +94,7 @@ def lookups(self, request, model_admin): self.url_paths_looked_up.append(reverse(path)) except Exception as e: logger.exception("Failed to lookup path: %s: %s", path, e) - return zip(self.url_paths_looked_up, gettext_lazy(self.url_paths_looked_up)) + return zip(self.url_paths_looked_up, gettext_lazy(self.url_paths_looked_up), strict=False) def queryset(self, request, queryset): if self.value(): @@ -102,6 +102,7 @@ def queryset(self, request, queryset): return queryset +@admin.register(Request) class RequestAdmin(admin.ModelAdmin): def truncated_path(self): return self.path[:80] + "..." if len(self.path) > 80 else self.path # pylint: disable=no-member @@ -178,6 +179,3 @@ def flag_requests(self, request, queryset): class Media: css = {"all": ("css/admin.css",)} - - -admin.site.register(Request, RequestAdmin) diff --git a/intranet/apps/logs/tests.py b/intranet/apps/logs/tests.py index ed185b66037..f70659ff377 100644 --- a/intranet/apps/logs/tests.py +++ b/intranet/apps/logs/tests.py @@ -80,7 +80,7 @@ def test_logs_request_object_string(self): request_string = str(Request.objects.first()) self.assertIn(request.user.username, request_string) - self.assertIn(request.META["HTTP_X_REAL_IP"], request_string) + self.assertIn(request.headers["x-real-ip"], request_string) def test_logs_request_object_path(self): self.create_logs_request() @@ -135,20 +135,20 @@ def setUpTestData(cls): def test_logs_user_search(self): response = self.client.get(reverse("logs"), data={"user": ["anonymous", self.main_user.username]}) - self.assertQuerysetEqual(Request.objects.filter(user__isnull=True), response.context["rqs"]) # Should only show anonymous user logs + self.assertQuerySetEqual(Request.objects.filter(user__isnull=True), response.context["rqs"]) # Should only show anonymous user logs response = self.client.get(reverse("logs"), data={"user": self.main_user.username}) - self.assertQuerysetEqual(Request.objects.filter(user=self.main_user), response.context["rqs"]) # Should only show anonymous user logs + self.assertQuerySetEqual(Request.objects.filter(user=self.main_user), response.context["rqs"]) # Should only show anonymous user logs response = self.client.get(reverse("logs"), data={"user": [self.main_user.username, self.test_user.username]}) - self.assertQuerysetEqual( + self.assertQuerySetEqual( Request.objects.filter(Q(user=self.main_user) | Q(user=self.test_user)), response.context["rqs"] ) # Should only show anonymous user logs def test_logs_ip_search(self): response = self.client.get(reverse("logs"), data={"ip": "0.0.0.0/24"}) - self.assertQuerysetEqual(Request.objects.filter(Q(ip="0.0.0.1") | Q(ip="0.0.0.2")), response.context["rqs"]) + self.assertQuerySetEqual(Request.objects.filter(Q(ip="0.0.0.1") | Q(ip="0.0.0.2")), response.context["rqs"]) response = self.client.get(reverse("logs"), data={"ip": "1.1.1.1/15"}) self.assertContains(response, "Subnet too large") @@ -158,15 +158,15 @@ def test_logs_ip_search(self): def test_logs_method_search(self): response = self.client.get(reverse("logs"), data={"method": "GET"}) - self.assertQuerysetEqual(Request.objects.filter(method="GET"), response.context["rqs"]) + self.assertQuerySetEqual(Request.objects.filter(method="GET"), response.context["rqs"]) response = self.client.get(reverse("logs"), data={"method": ["GET", "POST"]}) - self.assertQuerysetEqual(Request.objects.filter(Q(method="GET") | Q(method="POST")), response.context["rqs"]) + self.assertQuerySetEqual(Request.objects.filter(Q(method="GET") | Q(method="POST")), response.context["rqs"]) def test_logs_from_search(self): response = self.client.get(reverse("logs"), data={"from": "2087-03-05 11:0:0"}) - self.assertQuerysetEqual(Request.objects.filter(timestamp=datetime.datetime(2087, 3, 5, 12, 0, 0)), response.context["rqs"]) + self.assertQuerySetEqual(Request.objects.filter(timestamp=datetime.datetime(2087, 3, 5, 12, 0, 0)), response.context["rqs"]) response = self.client.get(reverse("logs"), data={"from": "asdjkasdaoij4"}) self.assertContains(response, "Invalid from time") @@ -174,41 +174,41 @@ def test_logs_from_search(self): def test_logs_to_search(self): response = self.client.get(reverse("logs"), data={"to": "2087-03-05 11:0:0"}) - self.assertQuerysetEqual(Request.objects.exclude(timestamp=datetime.datetime(2087, 3, 5, 12, 0, 0)), response.context["rqs"]) + self.assertQuerySetEqual(Request.objects.exclude(timestamp=datetime.datetime(2087, 3, 5, 12, 0, 0)), response.context["rqs"]) response = self.client.get(reverse("logs"), data={"to": "teojdaosdj"}) self.assertContains(response, "Invalid to time") def test_logs_path_type_and_path_search(self): response = self.client.get(reverse("logs"), data={"path-type": "contains", "path": "and"}) - self.assertQuerysetEqual(Request.objects.filter(path__contains="and"), response.context["rqs"]) + self.assertQuerySetEqual(Request.objects.filter(path__contains="and"), response.context["rqs"]) response = self.client.get(reverse("logs"), data={"path-type": "starts", "path": "rand"}) - self.assertQuerysetEqual(Request.objects.filter(path__startswith="rand"), response.context["rqs"]) + self.assertQuerySetEqual(Request.objects.filter(path__startswith="rand"), response.context["rqs"]) response = self.client.get(reverse("logs"), data={"path-type": "ends", "path": "dom"}) - self.assertQuerysetEqual(Request.objects.filter(path__endswith="dom"), response.context["rqs"]) + self.assertQuerySetEqual(Request.objects.filter(path__endswith="dom"), response.context["rqs"]) response = self.client.get(reverse("logs"), data={"path-type": "none", "path": "/"}) - self.assertQuerysetEqual(Request.objects.filter(path="/"), response.context["rqs"]) + self.assertQuerySetEqual(Request.objects.filter(path="/"), response.context["rqs"]) def test_logs_user_agent_type_and_user_agent_search(self): response = self.client.get(reverse("logs"), data={"user-agent-type": "contains", "user-agent": "ql"}) - self.assertQuerysetEqual(Request.objects.filter(user_agent__contains="ql"), response.context["rqs"]) + self.assertQuerySetEqual(Request.objects.filter(user_agent__contains="ql"), response.context["rqs"]) response = self.client.get(reverse("logs"), data={"user-agent-type": "starts", "user-agent": "uni"}) - self.assertQuerysetEqual(Request.objects.filter(user_agent__startswith="uni"), response.context["rqs"]) + self.assertQuerySetEqual(Request.objects.filter(user_agent__startswith="uni"), response.context["rqs"]) response = self.client.get(reverse("logs"), data={"user-agent-type": "ends", "user-agent": "qlo"}) - self.assertQuerysetEqual(Request.objects.filter(user_agent__endswith="qlo"), response.context["rqs"]) + self.assertQuerySetEqual(Request.objects.filter(user_agent__endswith="qlo"), response.context["rqs"]) response = self.client.get(reverse("logs"), data={"user-agent-type": "none", "user-agent": "test"}) - self.assertQuerysetEqual(Request.objects.filter(user_agent="test"), response.context["rqs"]) + self.assertQuerySetEqual(Request.objects.filter(user_agent="test"), response.context["rqs"]) def test_logs_no_search(self): response = self.client.get(reverse("logs")) - self.assertQuerysetEqual(Request.objects.all(), response.context["rqs"]) + self.assertQuerySetEqual(Request.objects.all(), response.context["rqs"]) def test_logs_page_count_invalid(self): response = self.client.get(reverse("logs"), data={"page": "abc"}) - self.assertQuerysetEqual(Request.objects.all(), response.context["rqs"]) + self.assertQuerySetEqual(Request.objects.all(), response.context["rqs"]) diff --git a/intranet/apps/logs/urls.py b/intranet/apps/logs/urls.py index 442446858d3..5a5236fd964 100644 --- a/intranet/apps/logs/urls.py +++ b/intranet/apps/logs/urls.py @@ -1,8 +1,8 @@ -from django.urls import re_path +from django.urls import path from . import views urlpatterns = [ - re_path(r"^$", views.logs_view, name="logs"), - re_path(r"^/request/(?P\d+)$", views.request_view, name="request"), + path("", views.logs_view, name="logs"), + path("/request/", views.request_view, name="request"), ] diff --git a/intranet/apps/lostfound/admin.py b/intranet/apps/lostfound/admin.py index 8b118e2845f..e24cfdcd64e 100644 --- a/intranet/apps/lostfound/admin.py +++ b/intranet/apps/lostfound/admin.py @@ -3,6 +3,7 @@ from .models import FoundItem, LostItem +@admin.register(LostItem) class LostItemAdmin(admin.ModelAdmin): list_display = ("title", "description", "user", "last_seen", "added") list_filter = ("added", "last_seen") @@ -10,12 +11,9 @@ class LostItemAdmin(admin.ModelAdmin): raw_id_fields = ("user",) +@admin.register(FoundItem) class FoundItemAdmin(admin.ModelAdmin): list_display = ("title", "description", "user", "found", "added") list_filter = ("added", "found") ordering = ("-added",) raw_id_fields = ("user",) - - -admin.site.register(LostItem, LostItemAdmin) -admin.site.register(FoundItem, FoundItemAdmin) diff --git a/intranet/apps/lostfound/migrations/0003_auto_20250517_1958.py b/intranet/apps/lostfound/migrations/0003_auto_20250517_1958.py new file mode 100644 index 00000000000..1fed7bb8537 --- /dev/null +++ b/intranet/apps/lostfound/migrations/0003_auto_20250517_1958.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.25 on 2025-05-17 23:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('lostfound', '0002_auto_20160828_2058'), + ] + + operations = [ + migrations.AddField( + model_name='founditem', + name='expired', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='lostitem', + name='expired', + field=models.BooleanField(default=False), + ), + ] diff --git a/intranet/apps/lostfound/models.py b/intranet/apps/lostfound/models.py index 05ddaee9b66..402755bd54d 100644 --- a/intranet/apps/lostfound/models.py +++ b/intranet/apps/lostfound/models.py @@ -9,6 +9,7 @@ class LostItem(models.Model): last_seen = models.DateField() added = models.DateTimeField(auto_now_add=True) found = models.BooleanField(default=False) + expired = models.BooleanField(default=False) def __str__(self): return str(self.title) @@ -24,6 +25,7 @@ class FoundItem(models.Model): found = models.DateField() added = models.DateTimeField(auto_now_add=True) retrieved = models.BooleanField(default=False) + expired = models.BooleanField(default=False) def __str__(self): return str(self.title) diff --git a/intranet/apps/lostfound/tasks.py b/intranet/apps/lostfound/tasks.py new file mode 100644 index 00000000000..6406c662df4 --- /dev/null +++ b/intranet/apps/lostfound/tasks.py @@ -0,0 +1,18 @@ +from datetime import timedelta + +from celery import shared_task +from celery.utils.log import get_task_logger +from django.utils import timezone + +from ...settings.__init__ import LOSTFOUND_EXPIRATION +from .models import FoundItem, LostItem + +logger = get_task_logger(__name__) + + +@shared_task +def remove_old_lostfound(): + expired_date = timezone.now() - timedelta(days=LOSTFOUND_EXPIRATION) + LostItem.objects.filter(added__lt=expired_date).update(expired=True) + FoundItem.objects.filter(added__lt=expired_date).update(expired=True) + logger.info("Lost and found items added before " + expired_date.strftime("%Y-%m-%d %H:%M:%S") + " have expired.") diff --git a/intranet/apps/lostfound/urls.py b/intranet/apps/lostfound/urls.py index 400cb58f152..aa49dad7d1a 100644 --- a/intranet/apps/lostfound/urls.py +++ b/intranet/apps/lostfound/urls.py @@ -1,14 +1,14 @@ -from django.urls import re_path +from django.urls import path, re_path from . import views urlpatterns = [ - re_path(r"^$", views.home_view, name="lostfound"), - re_path(r"^/lost/add$", views.lostitem_add_view, name="lostitem_add"), + path("", views.home_view, name="lostfound"), + path("/lost/add", views.lostitem_add_view, name="lostitem_add"), re_path(r"^/lost/modify/(?P\d+)?$", views.lostitem_modify_view, name="lostitem_modify"), re_path(r"^/lost/delete/(?P\d+)?$", views.lostitem_delete_view, name="lostitem_delete"), re_path(r"^/lost/(?P\d+)?$", views.lostitem_view, name="lostitem_view"), - re_path(r"^/found/add$", views.founditem_add_view, name="founditem_add"), + path("/found/add", views.founditem_add_view, name="founditem_add"), re_path(r"^/found/modify/(?P\d+)?$", views.founditem_modify_view, name="founditem_modify"), re_path(r"^/found/delete/(?P\d+)?$", views.founditem_delete_view, name="founditem_delete"), re_path(r"^/found/(?P\d+)?$", views.founditem_view, name="founditem_view"), diff --git a/intranet/apps/lostfound/views.py b/intranet/apps/lostfound/views.py index de6a26c9808..776f8eab876 100644 --- a/intranet/apps/lostfound/views.py +++ b/intranet/apps/lostfound/views.py @@ -17,10 +17,10 @@ @login_required @deny_restricted def home_view(request): - lost_all = LostItem.objects.exclude(found=True).order_by("id") + lost_all = LostItem.objects.exclude(found=True).exclude(expired=True) lost_pg = Paginator(lost_all, 20) - found_all = FoundItem.objects.exclude(retrieved=True).order_by("id") + found_all = FoundItem.objects.exclude(retrieved=True).exclude(expired=True) found_pg = Paginator(found_all, 20) page = request.GET.get("page", 1) diff --git a/intranet/apps/notifications/emails.py b/intranet/apps/notifications/emails.py index 662051382fa..2027318b456 100644 --- a/intranet/apps/notifications/emails.py +++ b/intranet/apps/notifications/emails.py @@ -1,5 +1,5 @@ import logging -from typing import Collection, Mapping +from collections.abc import Collection, Mapping from django.conf import settings from django.core.mail import EmailMultiAlternatives diff --git a/intranet/apps/notifications/urls.py b/intranet/apps/notifications/urls.py index 8fb3cb99808..4926bdb254b 100644 --- a/intranet/apps/notifications/urls.py +++ b/intranet/apps/notifications/urls.py @@ -1,11 +1,11 @@ -from django.urls import re_path +from django.urls import path from . import views urlpatterns = [ - re_path(r"^/android/setup$", views.android_setup_view, name="notif_android_setup"), - re_path(r"^/chrome/setup$", views.chrome_setup_view, name="notif_chrome_setup"), - re_path(r"^/chrome/getdata$", views.chrome_getdata_view, name="notif_chrome_getdata"), - re_path(r"^/gcm/post$", views.gcm_post_view, name="notif_gcm_post"), - re_path(r"^/gcm/list$", views.gcm_list_view, name="notif_gcm_list"), + path("/android/setup", views.android_setup_view, name="notif_android_setup"), + path("/chrome/setup", views.chrome_setup_view, name="notif_chrome_setup"), + path("/chrome/getdata", views.chrome_getdata_view, name="notif_chrome_getdata"), + path("/gcm/post", views.gcm_post_view, name="notif_gcm_post"), + path("/gcm/list", views.gcm_list_view, name="notif_gcm_list"), ] diff --git a/intranet/apps/oauth/migrations/0010_cslapplication_allowed_origins_and_more.py b/intranet/apps/oauth/migrations/0010_cslapplication_allowed_origins_and_more.py new file mode 100644 index 00000000000..18506e19418 --- /dev/null +++ b/intranet/apps/oauth/migrations/0010_cslapplication_allowed_origins_and_more.py @@ -0,0 +1,36 @@ +# Generated by Django 5.2.4 on 2025-07-13 18:25 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('oauth', '0009_cslapplication_post_logout_redirect_uris'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='cslapplication', + name='allowed_origins', + field=models.TextField(blank=True, default='', help_text='Allowed origins list to enable CORS, space separated'), + ), + migrations.AddField( + model_name='cslapplication', + name='hash_client_secret', + field=models.BooleanField(default=True), + ), + migrations.AlterField( + model_name='cslapplication', + name='post_logout_redirect_uris', + field=models.TextField(blank=True, default='', help_text='Allowed Post Logout URIs list, space separated'), + ), + migrations.AlterField( + model_name='cslapplication', + name='user', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/intranet/apps/oauth/views.py b/intranet/apps/oauth/views.py index aef24ce2fcb..bb9fd735567 100644 --- a/intranet/apps/oauth/views.py +++ b/intranet/apps/oauth/views.py @@ -35,7 +35,7 @@ def get_form_class(self): ), help_texts={ "client_secret": ( - "Save this now, as you will not be able to access it again. " "Keep it secure - it acts as your application's password." + "Save this now, as you will not be able to access it again. Keep it secure - it acts as your application's password." ), "redirect_uris": "Space or line separated list", }, diff --git a/intranet/apps/parking/admin.py b/intranet/apps/parking/admin.py index 67d2d3e37aa..e8c3d30cb1d 100644 --- a/intranet/apps/parking/admin.py +++ b/intranet/apps/parking/admin.py @@ -20,6 +20,7 @@ """ +@admin.register(ParkingApplication) class ParkingAdmin(admin.ModelAdmin): def get_user(self, obj): u = obj.user @@ -56,12 +57,9 @@ def get_cars(self, obj): actions = [export_csv_action()] +@admin.register(CarApplication) class CarAdmin(admin.ModelAdmin): list_display = ("license_plate", "user", "make", "model", "year") list_filter = ("added", "updated") ordering = ("-added",) raw_id_fields = ("user",) - - -admin.site.register(ParkingApplication, ParkingAdmin) -admin.site.register(CarApplication, CarAdmin) diff --git a/intranet/apps/parking/migrations/0003_auto_20160606_1448.py b/intranet/apps/parking/migrations/0003_auto_20160606_1448.py index c4cd5ed1137..502baca662e 100644 --- a/intranet/apps/parking/migrations/0003_auto_20160606_1448.py +++ b/intranet/apps/parking/migrations/0003_auto_20160606_1448.py @@ -4,8 +4,7 @@ import datetime from django.db import migrations, models -from django.utils.timezone import utc - +from datetime import timezone class Migration(migrations.Migration): @@ -15,25 +14,25 @@ class Migration(migrations.Migration): migrations.AddField( model_name='carapplication', name='added', - field=models.DateTimeField(auto_now=True, default=datetime.datetime(2016, 6, 6, 18, 47, 49, 818264, tzinfo=utc)), + field=models.DateTimeField(auto_now=True, default=datetime.datetime(2016, 6, 6, 18, 47, 49, 818264, tzinfo=timezone.utc)), preserve_default=False, ), migrations.AddField( model_name='carapplication', name='updated', - field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2016, 6, 6, 18, 47, 52, 898429, tzinfo=utc)), + field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2016, 6, 6, 18, 47, 52, 898429, tzinfo=timezone.utc)), preserve_default=False, ), migrations.AddField( model_name='parkingapplication', name='added', - field=models.DateTimeField(auto_now=True, default=datetime.datetime(2016, 6, 6, 18, 47, 57, 841492, tzinfo=utc)), + field=models.DateTimeField(auto_now=True, default=datetime.datetime(2016, 6, 6, 18, 47, 57, 841492, tzinfo=timezone.utc)), preserve_default=False, ), migrations.AddField( model_name='parkingapplication', name='updated', - field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2016, 6, 6, 18, 48, 0, 922245, tzinfo=utc)), + field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2016, 6, 6, 18, 48, 0, 922245, tzinfo=timezone.utc)), preserve_default=False, ), ] diff --git a/intranet/apps/parking/urls.py b/intranet/apps/parking/urls.py index 3121e30bd1d..bda80c6333c 100644 --- a/intranet/apps/parking/urls.py +++ b/intranet/apps/parking/urls.py @@ -1,10 +1,10 @@ -from django.urls import re_path +from django.urls import path from . import views urlpatterns = [ - re_path(r"^$", views.parking_intro_view, name="parking"), - re_path(r"^/form$", views.parking_form_view, name="parking_form"), - re_path(r"^/car$", views.parking_car_view, name="parking_car"), - re_path(r"^/joint$", views.parking_joint_view, name="parking_joint"), + path("", views.parking_intro_view, name="parking"), + path("/form", views.parking_form_view, name="parking_form"), + path("/car", views.parking_car_view, name="parking_car"), + path("/joint", views.parking_joint_view, name="parking_joint"), ] diff --git a/intranet/apps/polls/urls.py b/intranet/apps/polls/urls.py index 7e566794a40..e38ad1550a4 100644 --- a/intranet/apps/polls/urls.py +++ b/intranet/apps/polls/urls.py @@ -1,15 +1,15 @@ -from django.urls import re_path +from django.urls import path from . import views urlpatterns = [ - re_path(r"^$", views.polls_view, name="polls"), - re_path(r"^/vote/(?P\d+)$", views.poll_vote_view, name="poll_vote"), - re_path(r"^/results/(?P\d+)$", views.poll_results_view, name="poll_results"), - re_path(r"^/add$", views.add_poll_view, name="add_poll"), - re_path(r"^/modify/(?P\d+)$", views.modify_poll_view, name="modify_poll"), - re_path(r"^/delete/(?P\d+)$", views.delete_poll_view, name="delete_poll"), - re_path(r"^/download/(?P\d+)$", views.csv_results, name="poll_csv_results"), - re_path(r"^/ranked-choice-download/(?P\d+)$", views.ranked_choice_results, name="ranked_choice_results"), - re_path(r"^/winner/(?P\d+)$", views.election_winners_view, name="election_winner"), + path("", views.polls_view, name="polls"), + path("/vote/", views.poll_vote_view, name="poll_vote"), + path("/results/", views.poll_results_view, name="poll_results"), + path("/add", views.add_poll_view, name="add_poll"), + path("/modify/", views.modify_poll_view, name="modify_poll"), + path("/delete/", views.delete_poll_view, name="delete_poll"), + path("/download/", views.csv_results, name="poll_csv_results"), + path("/ranked-choice-download/", views.ranked_choice_results, name="ranked_choice_results"), + path("/winner/", views.election_winners_view, name="election_winner"), ] diff --git a/intranet/apps/polls/views.py b/intranet/apps/polls/views.py index f9274628039..172c1e97c00 100644 --- a/intranet/apps/polls/views.py +++ b/intranet/apps/polls/views.py @@ -593,7 +593,7 @@ def __str__(self): return self.name def __repr__(self): - return "" % self.name + return f"" def __hash__(self): return hash(self.name) diff --git a/intranet/apps/preferences/fields.py b/intranet/apps/preferences/fields.py index e9051a9b605..552f8aa19f9 100644 --- a/intranet/apps/preferences/fields.py +++ b/intranet/apps/preferences/fields.py @@ -1,7 +1,7 @@ from django import forms from django.core import validators from django.db import models -from django.utils.encoding import force_text +from django.utils.encoding import force_str class PhoneField(models.Field): @@ -39,7 +39,7 @@ def to_python(self, value): """Returns a Unicode object.""" if value in self.empty_values: return "" - value = force_text(value).strip() + value = force_str(value).strip() return value @staticmethod diff --git a/intranet/apps/preferences/tests.py b/intranet/apps/preferences/tests.py index c895be7a198..5a5fa13b7a7 100644 --- a/intranet/apps/preferences/tests.py +++ b/intranet/apps/preferences/tests.py @@ -184,8 +184,8 @@ def test_privacy_options_view(self): prefix: [name[len(prefix) + 1 :] for name in dir(UserProperties) if name.startswith(prefix + "_")] for prefix in ["self", "parent"] } - for permission_type in PERMISSIONS_NAMES.keys(): - for permission in PERMISSIONS_NAMES[permission_type]: + for permission_type, permissions in PERMISSIONS_NAMES.items(): + for permission in permissions: if permission_type == "self": self.assertIn(f"{permission}-{permission_type}", options.keys()) self.assertFalse(options[f"{permission}-{permission_type}"]) diff --git a/intranet/apps/preferences/urls.py b/intranet/apps/preferences/urls.py index 92c7856eb70..15eb3ff7c80 100644 --- a/intranet/apps/preferences/urls.py +++ b/intranet/apps/preferences/urls.py @@ -1,5 +1,5 @@ -from django.urls import re_path +from django.urls import path from . import views -urlpatterns = [re_path(r"^$", views.preferences_view, name="preferences"), re_path(r"^/privacy$", views.privacy_options_view, name="privacy_options")] +urlpatterns = [path("", views.preferences_view, name="preferences"), path("/privacy", views.privacy_options_view, name="privacy_options")] diff --git a/intranet/apps/printing/admin.py b/intranet/apps/printing/admin.py index d0e9724f1b8..3806b7e094c 100644 --- a/intranet/apps/printing/admin.py +++ b/intranet/apps/printing/admin.py @@ -3,6 +3,7 @@ from .models import PrintJob +@admin.register(PrintJob) class PrintJobAdmin(admin.ModelAdmin): @staticmethod def formatted_page_range(obj): @@ -17,6 +18,3 @@ def formatted_page_range(obj): list_filter = ("time", "printer", "num_pages") ordering = ("-time",) raw_id_fields = ("user",) - - -admin.site.register(PrintJob, PrintJobAdmin) diff --git a/intranet/apps/printing/forms.py b/intranet/apps/printing/forms.py index e7434753791..762ba6881ed 100644 --- a/intranet/apps/printing/forms.py +++ b/intranet/apps/printing/forms.py @@ -25,13 +25,99 @@ def validate_size(self): if filesize > settings.FILES_MAX_UPLOAD_SIZE: raise forms.ValidationError(f"The file uploaded is above the maximum upload size ({settings.FILES_MAX_UPLOAD_SIZE / 1024 / 1024}MB). ") + def validate_page_range(self): + """ + Expects string that only includes commas, digits, and dashes (already validated by regex) + Validates that the page_range field's ranges and numbers correctly selects any pages from 1 to infinity. + """ + # Return if user specifies all pages. + if self.strip() == "": + return + + # Gets number with suffix for clean error handling, e.g 1st, 2nd, 3rd + def get_number_ordinal(n: int) -> str: + if 11 <= (n % 100) <= 20: + suffix = "th" + else: + # Codespell thinks 'nd' is misspelled + suffix = ["th", "st", "nd", "rd", "th"][min(n % 10, 4)] # codespell:ignore + return str(n) + suffix + + range_list = [r.strip() for r in self.split(",")] + prev_max = 0 + page_set = set() + for i, single_range in enumerate(range_list, start=1): + # Present a clean error message if there's multiple items or or not. + error_prefix = "Input is invalid, " if len(range_list) == 1 else f"The {get_number_ordinal(i)} item is invalid, " + + if "-" in single_range: # Handling a range. + input_range = single_range.split("-") + if len(input_range) != 2: + raise forms.ValidationError(error_prefix + "make sure the range is properly formatted (x-y).") + + try: + range_from, range_to = map(int, input_range) + except ValueError as e: + raise forms.ValidationError(error_prefix + "you must specify two numbers around a dash for ranges.") from e + + if range_from <= 0 or range_to <= 0: + raise forms.ValidationError(error_prefix + "pages are numbered starting at 1, not 0.") + + if range_from > range_to: + raise forms.ValidationError(error_prefix + "the lower bound should be smaller than the upper bound.") + + if range_to < prev_max: + raise forms.ValidationError( + error_prefix + "please make sure your ranges and page numbers are in ascending order and not duplicating." + ) # LPR won't accept non ascending ranges. + + for page in range(range_from, range_to + 1): + if page in page_set: + raise forms.ValidationError( + error_prefix + "you may not specify duplicate page numbers." + ) # CUPS will ignore duplicating page numbers, user should be notified. + else: + page_set.add(page) + + prev_max = range_to + else: # Handling a single number. + try: + page_num = int(single_range) + except ValueError as e: + raise forms.ValidationError(error_prefix + "you may only specify numbers.") from e + + if page_num == 0: + raise forms.ValidationError(error_prefix + "pages are numbered starting at 1, not 0.") + + if page_num < prev_max: + raise forms.ValidationError( + error_prefix + "please make sure your ranges and page numbers are in ascending order and not duplicating." + ) # LPR won't accept non ascending ranges. + + if page_num in page_set: + raise forms.ValidationError( + error_prefix + "you may not specify duplicate page numbers." + ) # CUPS will ignore duplicating page numbers, user should be notified. + else: + page_set.add(page_num) + + prev_max = page_num + + return + file = forms.FileField(validators=[validate_size]) printer = forms.ChoiceField() page_range = forms.RegexField( max_length=100, required=False, regex=r"^[0-9,\- ]*$", - error_messages={"invalid": "This field must contain only numbers or ranges of numbers."}, + error_messages={"invalid": "This field must contain only a list of comma separated numbers and/or ranges separated by dashes."}, + help_text=( + "Specify pages to print as a comma-separated list of ascending numbers and ranges (inclusive), e.g. '1-3, 5, 7'." + "
You may not specify a page to print multiple times." + "
Leave blank for all pages." + ), + validators=[validate_page_range], widget=forms.TextInput(attrs={"placeholder": "All Pages"}), ) diff --git a/intranet/apps/printing/urls.py b/intranet/apps/printing/urls.py index d4711b6cf06..198fad93100 100644 --- a/intranet/apps/printing/urls.py +++ b/intranet/apps/printing/urls.py @@ -1,5 +1,5 @@ -from django.urls import re_path +from django.urls import path from . import views -urlpatterns = [re_path(r"^$", views.print_view, name="printing")] +urlpatterns = [path("", views.print_view, name="printing")] diff --git a/intranet/apps/printing/views.py b/intranet/apps/printing/views.py index 3ee4b054da3..aa6e9df6b38 100644 --- a/intranet/apps/printing/views.py +++ b/intranet/apps/printing/views.py @@ -6,7 +6,6 @@ import subprocess import tempfile from io import BytesIO -from typing import Dict, List, Optional, Tuple import magic from django.conf import settings @@ -63,7 +62,7 @@ def set_user_ratelimit_status(username: str) -> None: cache.incr(cache_key) -def parse_alerts(alerts: str) -> Tuple[str, str]: +def parse_alerts(alerts: str) -> tuple[str, str]: known_alerts = { "paused": "unavailable", "media-empty-error": "out of paper", @@ -88,7 +87,7 @@ def parse_alerts(alerts: str) -> Tuple[str, str]: return alerts_text, printer_class -def get_printers() -> Dict[str, List[str]]: +def get_printers() -> dict[str, list[str]]: """Returns a dictionary mapping name:description for available printers. This requires that a CUPS client be configured on the server. @@ -150,7 +149,7 @@ def get_printers() -> Dict[str, List[str]]: return printers -def convert_soffice(tmpfile_name: str) -> Optional[str]: +def convert_soffice(tmpfile_name: str) -> str | None: """Converts a doc or docx to a PDF with soffice. Args: @@ -181,7 +180,7 @@ def convert_soffice(tmpfile_name: str) -> Optional[str]: return None -def convert_pdf(tmpfile_name: str, cmdname: str = "ps2pdf") -> Optional[str]: +def convert_pdf(tmpfile_name: str, cmdname: str = "ps2pdf") -> str | None: new_name = f"{tmpfile_name}.pdf" try: output = subprocess.check_output([cmdname, tmpfile_name, new_name], stderr=subprocess.STDOUT, universal_newlines=True) @@ -243,7 +242,7 @@ def get_mimetype(tmpfile_name: str) -> str: return mimetype -def convert_file(tmpfile_name: str, orig_fname: str) -> Optional[str]: +def convert_file(tmpfile_name: str, orig_fname: str) -> str | None: detected = get_mimetype(tmpfile_name) add_breadcrumb(category="printing", message=f"Detected file type {detected}", level="debug") @@ -275,42 +274,38 @@ def convert_file(tmpfile_name: str, orig_fname: str) -> Optional[str]: raise InvalidInputPrintingError(f"Invalid file type {detected}") -def check_page_range(page_range: str, max_pages: int) -> Optional[int]: - """Returns the number of pages included in the range, or None if it is an invalid range. +def check_page_range(page_range: str, max_pages: int) -> int | None: + """Returns the number of pages included in the range, or None if the range exceeds max_pages. Args: - page_range: The page range as a string, such as "1-5" or "1,2,3". + page_range: The page range as a string, such as "1-5" or "1,2,3". It has already been validated as + syntantically correct by the form validator. max_pages: The number of pages in the submitted document. If the number of pages in the given range exceeds this, it will be considered invalid. Returns: - The number of pages in the range, or None if it is an invalid range. + The number of pages in the range, or None if it's higher than max_pages. """ pages = 0 try: - for single_range in page_range.split(","): # check all ranges separated by commas + for single_range in page_range.split(","): if "-" in single_range: - if single_range.count("-") > 1: - return None - range_low, range_high = map(int, single_range.split("-")) - # check in page range - if range_low <= 0 or range_high <= 0 or range_low > max_pages or range_high > max_pages: - return None - - if range_low > range_high: # check lower bound <= upper bound + # Check the page range. + if range_low > max_pages or range_high > max_pages: return None pages += range_high - range_low + 1 + else: single_range = int(single_range) - if single_range <= 0 or single_range > max_pages: # check in page range + if single_range > max_pages: # Check the page range return None pages += 1 - except ValueError: # catch int parse fail + except ValueError: # Form has been validated, so int parse error shouldn't occur. return None return pages @@ -413,7 +408,7 @@ def print_job(obj: PrintJob, do_print: bool = True): if obj.page_range: if not range_count: - raise InvalidInputPrintingError("You specified an invalid page range.") + raise InvalidInputPrintingError("You specified a page range that exceeds the amount of pages in your document.") elif range_count > settings.PRINTING_PAGES_LIMIT_TEACHERS and (obj.user.is_teacher or obj.user.is_printing_admin): raise InvalidInputPrintingError( f"You specified a range of {range_count} pages. " @@ -428,12 +423,12 @@ def print_job(obj: PrintJob, do_print: bool = True): elif num_pages > settings.PRINTING_PAGES_LIMIT_TEACHERS and (obj.user.is_teacher or obj.user.is_printing_admin): raise InvalidInputPrintingError( - f"This file contains {num_pages} pages. " f"You may only print up to {settings.PRINTING_PAGES_LIMIT_TEACHERS} pages using this tool." + f"This file contains {num_pages} pages. You may only print up to {settings.PRINTING_PAGES_LIMIT_TEACHERS} pages using this tool." ) elif num_pages > settings.PRINTING_PAGES_LIMIT_STUDENTS: raise InvalidInputPrintingError( - f"This file contains {num_pages} pages. " f"You may only print up to {settings.PRINTING_PAGES_LIMIT_STUDENTS} pages using this tool." + f"This file contains {num_pages} pages. You may only print up to {settings.PRINTING_PAGES_LIMIT_STUDENTS} pages using this tool." ) if get_user_ratelimit_status(obj.user.username): diff --git a/intranet/apps/schedule/migrations/0012_attendance_auto_times.py b/intranet/apps/schedule/migrations/0012_attendance_auto_times.py new file mode 100644 index 00000000000..d4a8a36b1e5 --- /dev/null +++ b/intranet/apps/schedule/migrations/0012_attendance_auto_times.py @@ -0,0 +1,37 @@ +from django.db import migrations, models +import datetime +from ..models import shift_time +from ....settings.__init__ import ATTENDANCE_CODE_BUFFER + +def populate_eighth_auto_fields(apps, schema_editor): + Block = apps.get_model('schedule', 'Block') + for obj in Block.objects.all(): + if "8" in obj.name: + start = datetime.time(hour=obj.start.hour, minute=obj.start.minute) + end = datetime.time(hour=obj.end.hour, minute=obj.end.minute) + obj.eighth_auto_open = shift_time(start, -ATTENDANCE_CODE_BUFFER) + obj.eighth_auto_close = shift_time(end, ATTENDANCE_CODE_BUFFER) + else: + obj.eighth_auto_open = None + obj.eighth_auto_close = None + obj.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('schedule', '0011_day_comment'), + ] + + operations = [ + migrations.AddField( + model_name='block', + name='eighth_auto_close', + field=models.TimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='block', + name='eighth_auto_open', + field=models.TimeField(blank=True, null=True), + ), + migrations.RunPython(populate_eighth_auto_fields, reverse_code=migrations.RunPython.noop), + ] diff --git a/intranet/apps/schedule/models.py b/intranet/apps/schedule/models.py index 95cfd3a2794..f43cc44865c 100644 --- a/intranet/apps/schedule/models.py +++ b/intranet/apps/schedule/models.py @@ -3,6 +3,30 @@ from django.db import models from django.utils import timezone +from ...settings.__init__ import ATTENDANCE_CODE_BUFFER + + +def shift_time(time: datetime.time, minutes: int) -> datetime.time: + """Shifts the given time field by a certain number of minutes, with safeties: if shifting under 0,0,0 or past 23,59,59, + it returns 0,0,0 or 23,59,59, respectively. + Args: + time: A datetime.time object + minutes: Number of minutes (int) + Returns: + A datetime.time object which is the time argument shifted by minutes (if minutes is negative, shifts backwards) + """ + today = datetime.datetime.today() + if minutes < 0: + t = datetime.time(0, -minutes, 0) + if time < t: + return datetime.time(0, 0, 0) + else: + delta = datetime.datetime.combine(today, datetime.time(23, 59, 59)) - datetime.datetime.combine(today, time) + if minutes > delta.total_seconds() / 60: + return datetime.time(23, 59, 59) + dt = datetime.datetime.combine(today, time) + return (dt + datetime.timedelta(minutes=minutes)).time() + class Time(models.Model): hour = models.IntegerField() @@ -30,10 +54,30 @@ class Block(models.Model): start = models.ForeignKey("Time", related_name="blockstart", on_delete=models.CASCADE) end = models.ForeignKey("Time", related_name="blockend", on_delete=models.CASCADE) order = models.IntegerField(default=0) + eighth_auto_open = models.TimeField( + null=True, blank=True + ) # time when attendance is opened for sch_acts with "auto" mode set, 11:59:59 if not eighth period + eighth_auto_close = models.TimeField(null=True, blank=True) # 00:00:00 if not eighth period def __str__(self): return f"{self.name}: {self.start} - {self.end}" + def calculate_eighth_auto_times(self): + """Generates the times when eighth-block code attendance opens and closes automatically.""" + if "8" in self.name: + start = datetime.time(hour=self.start.hour, minute=self.start.minute) + end = datetime.time(hour=self.end.hour, minute=self.end.minute) + self.eighth_auto_open = shift_time(start, -ATTENDANCE_CODE_BUFFER) + self.eighth_auto_close = shift_time(end, ATTENDANCE_CODE_BUFFER) + else: + self.eighth_auto_open = None + self.eighth_auto_close = None + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + self.calculate_eighth_auto_times() + super().save(update_fields=["eighth_auto_open", "eighth_auto_close"]) + class Meta: unique_together = ("order", "name", "start", "end") ordering = ("order", "name", "start", "end") diff --git a/intranet/apps/schedule/urls.py b/intranet/apps/schedule/urls.py index 11d1610fdff..002f8a7d63f 100644 --- a/intranet/apps/schedule/urls.py +++ b/intranet/apps/schedule/urls.py @@ -1,15 +1,15 @@ -from django.urls import re_path +from django.urls import path, re_path from . import views urlpatterns = [ # Admin - re_path(r"^$", views.calendar_view, name="calendar"), + path("", views.calendar_view, name="calendar"), re_path(r"^/admin", views.admin_home_view, name="schedule_admin"), - re_path(r"^/view$", views.schedule_view, name="schedule"), - re_path(r"^/embed$", views.schedule_embed, name="schedule_embed"), - re_path(r"^/widget$", views.schedule_widget_view, name="schedule_widget"), + path("/view", views.schedule_view, name="schedule"), + path("/embed", views.schedule_embed, name="schedule_embed"), + path("/widget", views.schedule_widget_view, name="schedule_widget"), re_path(r"^/daytype(?:/(?P\d+))?$", views.admin_daytype_view, name="schedule_daytype"), - re_path(r"^/add$", views.admin_add_view, name="schedule_add"), - re_path(r"^/comment$", views.admin_comment_view, name="schedule_comment"), + path("/add", views.admin_add_view, name="schedule_add"), + path("/comment", views.admin_comment_view, name="schedule_comment"), ] diff --git a/intranet/apps/schedule/views.py b/intranet/apps/schedule/views.py index de97ef4ebe4..3ba36eac97e 100644 --- a/intranet/apps/schedule/views.py +++ b/intranet/apps/schedule/views.py @@ -417,6 +417,7 @@ def admin_daytype_view(request, daytype_id=None): request.POST.getlist("block_name"), [[int(j) if j else 0 for j in i.split(":")] if ":" in i else [9, 0] for i in request.POST.getlist("block_start")], [[int(j) if j else 0 for j in i.split(":")] if ":" in i else [10, 0] for i in request.POST.getlist("block_end")], + strict=False, ) model.blocks.all().delete() for blk in blocks: diff --git a/intranet/apps/search/tests.py b/intranet/apps/search/tests.py index b75bd07b8ff..51bd62f967b 100644 --- a/intranet/apps/search/tests.py +++ b/intranet/apps/search/tests.py @@ -66,7 +66,7 @@ def test_grade_level_search(self): user_type="student", )[0] user2 = get_user_model().objects.get_or_create( - username=f"{get_senior_graduation_year()+1}hthere", + username=f"{get_senior_graduation_year() + 1}hthere", student_id=1234568, first_name="Hello", last_name="There", diff --git a/intranet/apps/search/urls.py b/intranet/apps/search/urls.py index 0b7c9600a34..4111df3c547 100644 --- a/intranet/apps/search/urls.py +++ b/intranet/apps/search/urls.py @@ -1,5 +1,5 @@ -from django.urls import re_path +from django.urls import path from . import views -urlpatterns = [re_path(r"^$", views.search_view, name="search")] +urlpatterns = [path("", views.search_view, name="search")] diff --git a/intranet/apps/search/utils.py b/intranet/apps/search/utils.py index a61a8342174..c45593ce7dd 100644 --- a/intranet/apps/search/utils.py +++ b/intranet/apps/search/utils.py @@ -27,7 +27,7 @@ def get_query(query_string, search_fields): for term in terms: or_query = None # Query to search for a given term in each field for field_name in search_fields: - q = Q(**{"%s__icontains" % field_name: term}) + q = Q(**{f"{field_name}__icontains": term}) if or_query is None: or_query = q else: diff --git a/intranet/apps/seniors/urls.py b/intranet/apps/seniors/urls.py index 7838a16111d..fcc23ddf1f7 100644 --- a/intranet/apps/seniors/urls.py +++ b/intranet/apps/seniors/urls.py @@ -1,9 +1,9 @@ from django.conf import settings -from django.urls import re_path +from django.urls import path from . import views urlpatterns = [] if settings.ENABLE_SENIOR_DESTINATIONS: - urlpatterns = [re_path(r"^$", views.seniors_home_view, name="seniors"), re_path(r"^/add$", views.seniors_add_view, name="seniors_add")] + urlpatterns = [path("", views.seniors_home_view, name="seniors"), path("/add", views.seniors_add_view, name="seniors_add")] diff --git a/intranet/apps/sessionmgmt/admin.py b/intranet/apps/sessionmgmt/admin.py index ea0258e79ee..60ef9b27114 100644 --- a/intranet/apps/sessionmgmt/admin.py +++ b/intranet/apps/sessionmgmt/admin.py @@ -3,10 +3,8 @@ from .models import TrustedSession +@admin.register(TrustedSession) class TrustedSessionAdmin(admin.ModelAdmin): list_display = ("user", "description", "device_type", "first_trusted_date") list_filter = ("device_type", "description", "first_trusted_date") search_fields = ("user__username", "user__first_name", "user__last_name", "description") - - -admin.site.register(TrustedSession, TrustedSessionAdmin) diff --git a/intranet/apps/sessionmgmt/urls.py b/intranet/apps/sessionmgmt/urls.py index 23b92a1c9e3..31841318f1f 100644 --- a/intranet/apps/sessionmgmt/urls.py +++ b/intranet/apps/sessionmgmt/urls.py @@ -1,10 +1,10 @@ -from django.urls import re_path +from django.urls import path from . import views urlpatterns = [ - re_path(r"^$", views.index_view, name="sessionmgmt"), - re_path(r"^/trust$", views.trust_session_view, name="trust_session"), - re_path(r"^/revoke$", views.revoke_session_view, name="revoke_session"), - re_path(r"^/global-logout$", views.global_logout_view, name="global_logout"), + path("", views.index_view, name="sessionmgmt"), + path("/trust", views.trust_session_view, name="trust_session"), + path("/revoke", views.revoke_session_view, name="revoke_session"), + path("/global-logout", views.global_logout_view, name="global_logout"), ] diff --git a/intranet/apps/signage/admin.py b/intranet/apps/signage/admin.py index 33497313538..743b7f53352 100644 --- a/intranet/apps/signage/admin.py +++ b/intranet/apps/signage/admin.py @@ -7,6 +7,7 @@ from .models import Page, Sign +@admin.register(Sign) class SignAdmin(admin.ModelAdmin): list_display = ( "name", @@ -24,6 +25,7 @@ def page_list(self, obj): return ", ".join(str(p) for p in obj.pages.all()) +@admin.register(Page) class PageAdmin(admin.ModelAdmin): list_display = ( "name", @@ -151,7 +153,3 @@ def set_custom_switch_time_for_all_signs(self, request, queryset): } ) return render(request, "signage/admin/set_custom_switch_time_for_all_signs.html", {"form": form}) - - -admin.site.register(Sign, SignAdmin) -admin.site.register(Page, PageAdmin) diff --git a/intranet/apps/signage/urls.py b/intranet/apps/signage/urls.py index a4614f0fca9..cd01a5d9182 100644 --- a/intranet/apps/signage/urls.py +++ b/intranet/apps/signage/urls.py @@ -1,4 +1,4 @@ -from django.urls import re_path +from django.urls import path, re_path from django.views.generic.base import RedirectView from . import views @@ -7,6 +7,6 @@ re_path(r"^/serviceworker\.js$", RedirectView.as_view(url="/static/signage/serviceworker.js"), name="signage-serviceworker"), # This MUST match the SignageConsumer entry in intranet/routing.py re_path(r"^/display/(?P[\w_-]+)?$", views.signage_display, name="signage_display"), - re_path(r"^/eighth$", views.eighth, name="eighth"), - re_path(r"^/prometheus-metrics$", views.prometheus_metrics, name="prometheus_metrics"), + path("/eighth", views.eighth, name="eighth"), + path("/prometheus-metrics", views.prometheus_metrics, name="prometheus_metrics"), ] diff --git a/intranet/apps/signage/views.py b/intranet/apps/signage/views.py index 077981b8273..21a6b297db8 100644 --- a/intranet/apps/signage/views.py +++ b/intranet/apps/signage/views.py @@ -1,6 +1,5 @@ import datetime import logging -from typing import Optional from django import http from django.conf import settings @@ -20,7 +19,7 @@ logger = logging.getLogger(__name__) -def check_internal_ip(request) -> Optional[HttpResponse]: +def check_internal_ip(request) -> HttpResponse | None: """ A method to determine if a request is allowed to load a signage page. @@ -30,7 +29,7 @@ def check_internal_ip(request) -> Optional[HttpResponse]: Returns: a 403 if the request is unauthorized or None if the request is authorized """ - remote_addr = request.META["HTTP_X_REAL_IP"] if "HTTP_X_REAL_IP" in request.META else request.META.get("REMOTE_ADDR", "") + remote_addr = request.headers["x-real-ip"] if "x-real-ip" in request.headers else request.META.get("REMOTE_ADDR", "") if (not request.user.is_authenticated or request.user.is_restricted) and remote_addr not in settings.TJ_IPS: return render(request, "error/403.html", {"reason": "You are not authorized to view this page."}, status=403) @@ -146,7 +145,7 @@ def eighth(request): def prometheus_metrics(request): """Prometheus metrics for signage displays. Currently just whether or not they are online.""" - remote_addr = request.META["HTTP_X_REAL_IP"] if "HTTP_X_REAL_IP" in request.META else request.META.get("REMOTE_ADDR", "") + remote_addr = request.headers["x-real-ip"] if "x-real-ip" in request.headers else request.META.get("REMOTE_ADDR", "") is_admin = request.user.is_authenticated and not request.user.is_restricted and request.user.is_superuser # If they're not from an IP on the white list and they're not an admin, deny access diff --git a/intranet/apps/templatetags/paginate.py b/intranet/apps/templatetags/paginate.py index 22eed86ca88..1a8f96d4f5d 100644 --- a/intranet/apps/templatetags/paginate.py +++ b/intranet/apps/templatetags/paginate.py @@ -1,5 +1,3 @@ -from typing import List, Union - from django import template register = template.Library() @@ -14,7 +12,7 @@ def query_transform(request, **kwargs): @register.filter # TODO: replace return type with list[int | None] -def page_list(paginator, current_page) -> List[Union[int, None]]: +def page_list(paginator, current_page) -> list[int | None]: """Pagination If there is a ``None`` in the output, it should be replaced diff --git a/intranet/apps/templatetags/svg.py b/intranet/apps/templatetags/svg.py new file mode 100644 index 00000000000..878851a7bb8 --- /dev/null +++ b/intranet/apps/templatetags/svg.py @@ -0,0 +1,55 @@ +import logging +import os + +from django import template +from django.contrib.staticfiles import finders +from django.core.exceptions import ImproperlyConfigured +from django.utils.safestring import mark_safe + +from intranet import settings + +logger = logging.getLogger(__name__) +register = template.Library() + +# An updated version of django-inline-svg that works with django 5+ + + +class SVGNotFound(FileNotFoundError): + pass + + +@register.simple_tag +def svg(filename): + SVG_DIRS = getattr(settings, "SVG_DIRS", []) + + if not isinstance(SVG_DIRS, list): + raise ImproperlyConfigured("SVG_DIRS setting must be a list") + + path = None + + if SVG_DIRS: + for directory in SVG_DIRS: + svg_path = os.path.join(directory, f"{filename}.svg") + if os.path.isfile(svg_path): + path = svg_path + else: + path = finders.find(os.path.join("svg", f"{filename}.svg")) + + if not path: + message = f"SVG '{filename}.svg' not found" + + # Raise exception if DEBUG is True, else just log a warning. + if settings.DEBUG: + raise SVGNotFound(message) + else: + logger.warning(message) + return "" + + # Sometimes path can be a list/tuple if there's more than one file found + if isinstance(path, list | tuple): + path = path[0] + + with open(path) as svg_file: + svg = mark_safe(svg_file.read()) + + return svg diff --git a/intranet/apps/users/admin.py b/intranet/apps/users/admin.py index 3d961838cff..c4d39bbd1e9 100644 --- a/intranet/apps/users/admin.py +++ b/intranet/apps/users/admin.py @@ -3,6 +3,7 @@ from ..users.models import Course, Section, User, UserProperties +@admin.register(User) class UserAdmin(admin.ModelAdmin): # Render is_active using checkmarks or crosses def user_active(self, obj): @@ -43,7 +44,6 @@ def user_active(self, obj): ) -admin.site.register(User, UserAdmin) admin.site.register(UserProperties) admin.site.register(Course) admin.site.register(Section) diff --git a/intranet/apps/users/courses_urls.py b/intranet/apps/users/courses_urls.py index ce94d5cf19e..1a325c0e26f 100644 --- a/intranet/apps/users/courses_urls.py +++ b/intranet/apps/users/courses_urls.py @@ -1,11 +1,11 @@ -from django.urls import re_path +from django.urls import path, re_path from . import views urlpatterns = [ - re_path(r"^$", views.all_courses_view, name="all_courses"), + path("", views.all_courses_view, name="all_courses"), re_path(r"^/room/(?P.*)$", views.sections_by_room_view, name="room_sections"), - re_path(r"^/period/(?P\d+)$", views.courses_by_period_view, name="period_courses"), + path("/period/", views.courses_by_period_view, name="period_courses"), re_path(r"^/course/(?P.*)$", views.course_info_view, name="course_sections"), re_path(r"^/section/(?P.*)$", views.section_view, name="section_info"), ] diff --git a/intranet/apps/users/models.py b/intranet/apps/users/models.py index 4ff84fe0bde..a4a5212abf7 100644 --- a/intranet/apps/users/models.py +++ b/intranet/apps/users/models.py @@ -1,8 +1,9 @@ # pylint: disable=too-many-lines; Allow more than 1000 lines import logging from base64 import b64encode +from collections.abc import Collection from datetime import timedelta -from typing import Collection, Dict, Optional, Union +from typing import Optional from dateutil.relativedelta import relativedelta from django.conf import settings @@ -41,14 +42,14 @@ class UserManager(DjangoUserManager): """ - def user_with_student_id(self, student_id: Union[int, str]) -> Optional["User"]: + def user_with_student_id(self, student_id: int | str) -> Optional["User"]: """Get a unique user object by FCPS student ID. (Ex. 1624472)""" results = User.objects.filter(student_id=str(student_id)) if len(results) == 1: return results.first() return None - def user_with_ion_id(self, student_id: Union[int, str]) -> Optional["User"]: + def user_with_ion_id(self, student_id: int | str) -> Optional["User"]: """Get a unique user object by Ion ID. (Ex. 489)""" if isinstance(student_id, str) and not is_entirely_digit(student_id): return None @@ -57,11 +58,11 @@ def user_with_ion_id(self, student_id: Union[int, str]) -> Optional["User"]: return results.first() return None - def users_in_year(self, year: int) -> Union[Collection["User"], QuerySet]: # pylint: disable=unsubscriptable-object + def users_in_year(self, year: int) -> Collection["User"] | QuerySet: # pylint: disable=unsubscriptable-object """Get a list of users in a specific graduation year.""" return User.objects.filter(graduation_year=year) - def user_with_name(self, given_name: Optional[str] = None, last_name: Optional[str] = None) -> "User": # pylint: disable=unsubscriptable-object + def user_with_name(self, given_name: str | None = None, last_name: str | None = None) -> "User": # pylint: disable=unsubscriptable-object """Get a unique user object by given name (first/nickname) and/or last name. Args: @@ -85,14 +86,14 @@ def user_with_name(self, given_name: Optional[str] = None, last_name: Optional[s except (User.DoesNotExist, User.MultipleObjectsReturned): return None - def get_students(self) -> Union[Collection["User"], QuerySet]: # pylint: disable=unsubscriptable-object + def get_students(self) -> Collection["User"] | QuerySet: # pylint: disable=unsubscriptable-object """Get user objects that are students (quickly).""" users = User.objects.filter(user_type="student", graduation_year__gte=get_senior_graduation_year()) users = users.exclude(id__in=EXTRA) return users - def get_teachers(self) -> Union[Collection["User"], QuerySet]: # pylint: disable=unsubscriptable-object + def get_teachers(self) -> Collection["User"] | QuerySet: # pylint: disable=unsubscriptable-object """Get user objects that are teachers (quickly).""" users = User.objects.filter(user_type="teacher") users = users.exclude(id__in=EXTRA) @@ -120,7 +121,7 @@ def get_teachers_attendance_users(self) -> "QuerySet[User]": return users - def get_teachers_sorted(self) -> Union[Collection["User"], QuerySet]: # pylint: disable=unsubscriptable-object + def get_teachers_sorted(self) -> Collection["User"] | QuerySet: # pylint: disable=unsubscriptable-object """Returns a ``QuerySet`` of teachers sorted by last name, then first name. Returns: @@ -170,8 +171,8 @@ def get_approve_announcements_users_sorted(self) -> "QuerySet[User]": def exclude_from_search( self, - existing_queryset: Optional[Union[Collection["User"], QuerySet]] = None, # pylint: disable=unsubscriptable-object - ) -> Union[Collection["User"], QuerySet]: # pylint: disable=unsubscriptable-object + existing_queryset: Collection["User"] | QuerySet | None = None, # pylint: disable=unsubscriptable-object + ) -> Collection["User"] | QuerySet: # pylint: disable=unsubscriptable-object if existing_queryset is None: existing_queryset = self @@ -271,7 +272,7 @@ def address(self) -> Optional["Address"]: return self.properties.address @property - def schedule(self) -> Optional[Union[QuerySet, Collection["Section"]]]: # pylint: disable=unsubscriptable-object + def schedule(self) -> QuerySet | Collection["Section"] | None: # pylint: disable=unsubscriptable-object """Returns a QuerySet of the ``Section`` objects representing the classes this student is in, or ``None`` if the current user does not have permission to list this student's classes. @@ -283,7 +284,7 @@ def schedule(self) -> Optional[Union[QuerySet, Collection["Section"]]]: # pylin """ return self.properties.schedule - def member_of(self, group: Union[Group, str]) -> bool: + def member_of(self, group: Group | str) -> bool: """Returns whether a user is a member of a certain group. Args: @@ -401,7 +402,7 @@ def get_short_name(self) -> str: return self.short_name @property - def primary_email_address(self) -> Optional[str]: + def primary_email_address(self) -> str | None: try: return self.primary_email.address if self.primary_email else None except Email.DoesNotExist: @@ -433,7 +434,7 @@ def tj_email(self) -> str: return f"{self.username}@{domain}" @property - def non_tj_email(self) -> Optional[str]: + def non_tj_email(self) -> str | None: """ Returns the user's first non-TJ email found, or None if none is found. @@ -475,7 +476,7 @@ def notification_email(self) -> str: return email.address if email and email.address else self.tj_email @property - def default_photo(self) -> Optional[bytes]: + def default_photo(self) -> bytes | None: """Returns default photo (in binary) that should be used Returns: @@ -510,7 +511,7 @@ def grade(self) -> "Grade": return Grade(self.graduation_year) @property - def permissions(self) -> Dict[str, bool]: + def permissions(self) -> dict[str, bool]: """Dynamically generate dictionary of privacy options. Returns: @@ -520,9 +521,9 @@ def permissions(self) -> Dict[str, bool]: """ permissions_dict = {} - for prefix in PERMISSIONS_NAMES: + for prefix, suffixes in PERMISSIONS_NAMES.items(): permissions_dict[prefix] = {} - for suffix in PERMISSIONS_NAMES[prefix]: + for suffix in suffixes: permissions_dict[prefix][suffix] = getattr(self.properties, prefix + "_" + suffix) return permissions_dict @@ -752,7 +753,7 @@ def is_global_admin(self) -> bool: return self.member_of("admin_all") and self.is_staff and self.is_superuser - def can_manage_group(self, group: Union[Group, str]) -> bool: + def can_manage_group(self, group: Group | str) -> bool: """Checks whether this user has permission to edit/manage the given group (either a Group or a group name). @@ -1085,7 +1086,7 @@ def absence_info(self): def handle_delete(self): """Handle a graduated user being deleted.""" - from intranet.apps.eighth.models import EighthScheduledActivity # pylint: disable=import-outside-toplevel + from intranet.apps.eighth.models import EighthScheduledActivity # noqa: PLC0415 EighthScheduledActivity.objects.filter(eighthsignup_set__user=self).update(archived_member_count=F("archived_member_count") + 1) @@ -1396,7 +1397,7 @@ def __getattr__(self, name): raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}") @cached_property - def base64(self) -> Optional[bytes]: + def base64(self) -> bytes | None: """Returns base64 encoded binary data for a user's picture. Returns: @@ -1462,7 +1463,7 @@ def text(self) -> str: return self._name @staticmethod - def number_from_name(name: str) -> Optional[int]: + def number_from_name(name: str) -> int | None: if name in Grade.names: return Grade.names.index(name) + 9 return None diff --git a/intranet/apps/users/tests.py b/intranet/apps/users/tests.py index 10093859389..35bd21bf23c 100644 --- a/intranet/apps/users/tests.py +++ b/intranet/apps/users/tests.py @@ -111,7 +111,7 @@ def test_get_teachers(self): ] ] - self.assertQuerysetEqual(get_user_model().objects.get_teachers(), list(map(repr, users)), transform=repr, ordered=False) + self.assertQuerySetEqual(get_user_model().objects.get_teachers(), list(map(repr, users)), transform=repr, ordered=False) self.assertEqual(list(get_user_model().objects.get_teachers_sorted()), sorted(users, key=lambda u: (u.last_name, u.first_name))) diff --git a/intranet/apps/welcome/urls.py b/intranet/apps/welcome/urls.py index 11c4a8e168a..63dc0f1c6cc 100644 --- a/intranet/apps/welcome/urls.py +++ b/intranet/apps/welcome/urls.py @@ -1,10 +1,10 @@ -from django.urls import re_path +from django.urls import path from . import views urlpatterns = [ - re_path(r"^/student$", views.student_welcome_view, name="welcome_student"), - re_path(r"^/teacher$", views.teacher_welcome_view, name="welcome_teacher"), - re_path(r"^/done$", views.done_welcome_view, name="welcome_done"), - re_path(r"^$", views.welcome_view, name="welcome"), + path("/student", views.student_welcome_view, name="welcome_student"), + path("/teacher", views.teacher_welcome_view, name="welcome_teacher"), + path("/done", views.done_welcome_view, name="welcome_done"), + path("", views.welcome_view, name="welcome"), ] diff --git a/intranet/middleware/access_log.py b/intranet/middleware/access_log.py index 16b0e993d1b..85dafced7e5 100644 --- a/intranet/middleware/access_log.py +++ b/intranet/middleware/access_log.py @@ -23,15 +23,15 @@ def __call__(self, request): else: username = request.user.username - if "HTTP_X_REAL_IP" in request.META: - ip = request.META["HTTP_X_REAL_IP"] + if "x-real-ip" in request.headers: + ip = request.headers["x-real-ip"] else: ip = (request.META.get("REMOTE_ADDR", ""),) if isinstance(ip, set): ip = ip[0] - user_agent = request.META.get("HTTP_USER_AGENT", "") + user_agent = request.headers.get("user-agent", "") path = request.get_full_path() if user_agent and not ( diff --git a/intranet/middleware/monitoring.py b/intranet/middleware/monitoring.py index 7b4034a07d0..33e0e8dc31b 100644 --- a/intranet/middleware/monitoring.py +++ b/intranet/middleware/monitoring.py @@ -13,7 +13,7 @@ def __init__(self, get_response): def __call__(self, request): # We would like to be able to just check request.resolver_match.app_name. Unfortunately, URL resolving has not taken place yet, so we can't. if request.path == "/prometheus" or request.path.startswith("/prometheus/"): - remote_addr = request.META["HTTP_X_REAL_IP"] if "HTTP_X_REAL_IP" in request.META else request.META.get("REMOTE_ADDR", "") + remote_addr = request.headers["x-real-ip"] if "x-real-ip" in request.headers else request.META.get("REMOTE_ADDR", "") is_superuser = request.user.is_authenticated and request.user.is_superuser # If they're not from an IP on the white list and they're not a superuser, deny access diff --git a/intranet/middleware/same_origin.py b/intranet/middleware/same_origin.py index 2226d6cda12..3906995f881 100644 --- a/intranet/middleware/same_origin.py +++ b/intranet/middleware/same_origin.py @@ -12,8 +12,8 @@ def __init__(self, get_response): self.get_response = get_response def __call__(self, request): - host = request.META.get("HTTP_HOST") - origin = request.META.get("HTTP_ORIGIN") + host = request.headers.get("host") + origin = request.headers.get("origin") # Note: The "Origin" header is not sent on the main page request, so we need to explicitly # handle it being None. diff --git a/intranet/routing.py b/intranet/routing.py index f0545d14bd0..bbe97e3a56b 100644 --- a/intranet/routing.py +++ b/intranet/routing.py @@ -10,6 +10,7 @@ from channels.routing import ProtocolTypeRouter, URLRouter from django.core.asgi import get_asgi_application +from django.urls import path from django.urls import re_path from .apps.bus.consumers import BusConsumer @@ -34,7 +35,7 @@ def disconnect(self, code): "websocket": AuthMiddlewareStack( URLRouter( [ - re_path(r"^bus/$", BusConsumer.as_asgi()), + path("bus/", BusConsumer.as_asgi()), # This MUST match the signage_display entry in intranet/apps/signage/urls.py re_path(r"^signage/display/(?P[-_\w]+)?$", SignageConsumer.as_asgi()), re_path(r"^.*$", WebsocketCloseConsumer.as_asgi()), diff --git a/intranet/settings/__init__.py b/intranet/settings/__init__.py index 2cf7a826416..05ab7298538 100644 --- a/intranet/settings/__init__.py +++ b/intranet/settings/__init__.py @@ -54,14 +54,14 @@ # Date of tjSTAR TJSTAR_DATE = datetime.date(end_school_year, - 5, 21 # UPDATE THIS! Value when last updated: May 21, 2024 # noqa: E128 + 5, 21 # UPDATE THIS! Value when last updated: May 21, 2025 # noqa: E128 ) # noqa: E124 # When to start showing the tjSTAR banner TJSTAR_BANNER_START_DATE = TJSTAR_DATE - datetime.timedelta(days=4) # Senior Destinations Banner -ENABLE_SENIOR_DESTS_BANNER = True +ENABLE_SENIOR_DESTS_BANNER = False SENIOR_DESTS_BANNER_TEXT = "Congratulations seniors! The tjTODAY Senior Issue form is out! Please submit your " \ "senior destinations, quotes, and superlatives to the form attached below by May 2" SENIOR_DESTS_BANNER_LINK = "https://tinyurl.com/tjseniors2025" @@ -587,9 +587,11 @@ def get_month_seconds(): # extension of django.core.cache.backends.dummy.DummyCache else: CACHES["default"] = { - "BACKEND": "redis_cache.RedisCache", + "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "127.0.0.1:6379", - "OPTIONS": {"PARSER_CLASS": "redis.connection.HiredisParser", "PICKLE_VERSION": 4}, + "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient", + "PICKLE_VERSION": 4 + }, "KEY_PREFIX": "ion", } @@ -695,7 +697,6 @@ def get_month_seconds(): "oauth2_provider", # django-oauth-toolkit "corsheaders", # django-cors-headers "cacheops", # django-cacheops - "svg", # django-inline-svg "simple_history", # django-simple-history "django_referrer_policy", "django_user_agents", @@ -885,6 +886,10 @@ def get_log(name): # pylint: disable=redefined-outer-name; 'name' is used as th # taking attendance (10PM) ATTENDANCE_LOCK_HOUR = 22 +# The time buffer (in minutes) before the start or end of an eighth block (times from the Block model in schedule) during which +# code/qr attendance is allowed, if the code mode is "auto" +ATTENDANCE_CODE_BUFFER = 20 + # The number of days to show an absence message (2 weeks) CLEAR_ABSENCE_DAYS = 14 @@ -894,6 +899,12 @@ def get_log(name): # pylint: disable=redefined-outer-name; 'name' is used as th # The address for the CSL's BetterUptime status page CSL_STATUS_PAGE = "https://status.tjhsst.edu/index.json" +# Number of times to retry accessing the status page above +CSL_STATUS_PAGE_MAX_RETRIES = 5 + +# Timeout for accessing CSL_STATUS_PAGE (in seconds) +CSL_STATUS_PAGE_TIMEOUT = 15 + # The timeout for the request to FCPS' emergency page (in seconds) EMERGENCY_TIMEOUT = 5 @@ -910,6 +921,9 @@ def get_log(name): # pylint: disable=redefined-outer-name; 'name' is used as th # Time that the bus page should change from morning to afternoon display BUS_PAGE_CHANGEOVER_HOUR = 12 +# Age (in days) of a lost and found entry until it is hidden +LOSTFOUND_EXPIRATION = 180 + # Substrings of paths to not log in the Ion access logs NONLOGGABLE_PATH_BEGINNINGS = ["/static"] NONLOGGABLE_PATH_ENDINGS = [".png", ".jpg", ".jpeg", ".gif", ".css", ".js", ".ico", "jsi18n/"] @@ -946,6 +960,11 @@ def get_log(name): # pylint: disable=redefined-outer-name; 'name' is used as th "schedule": celery.schedules.crontab(day_of_month=3, hour=1), "args": (), }, + "remove-old-lostfound-entries": { + "task": "intranet.apps.lostfound.tasks.remove_old_lostfound", + "schedule": celery.schedules.crontab(day_of_month=1, hour=1), + "args": (), + }, } MAINTENANCE_MODE = False diff --git a/intranet/static/css/bus.scss b/intranet/static/css/bus.scss index 0317d5db18b..8572eca3192 100644 --- a/intranet/static/css/bus.scss +++ b/intranet/static/css/bus.scss @@ -4,7 +4,7 @@ body { overflow-x: hidden; } -svg:not(#status-icon-svg) { +svg:not(.status-icon-svg) { width: 100%; max-height: 80vh; margin-top: 15px; @@ -323,4 +323,4 @@ svg text { transform: rotate(180deg) translate(87%, 18%); transform-origin: center; } -*/ \ No newline at end of file +*/ diff --git a/intranet/static/css/dashboard.widgets.scss b/intranet/static/css/dashboard.widgets.scss index e2d1c018e15..b36c9a2d260 100644 --- a/intranet/static/css/dashboard.widgets.scss +++ b/intranet/static/css/dashboard.widgets.scss @@ -414,7 +414,6 @@ a#eighth-sponsor { .btn-link { text-decoration: none; color: $grey; - display: inline-block; line-height: 18px; padding: 0 3px; margin: 5px 0; diff --git a/intranet/static/css/page_base.scss b/intranet/static/css/page_base.scss index 231e54f9a6e..d6d81e64e40 100644 --- a/intranet/static/css/page_base.scss +++ b/intranet/static/css/page_base.scss @@ -215,17 +215,20 @@ h1 { align-items: center; border-radius: 4px; flex-shrink: 1; - height: 30px; + height: 25px; text-decoration: none; display: inline-flex; vertical-align: middle; margin-bottom: 3px; margin-right: 10px; + padding-left: 3px; + padding-right: 3px; color: white; } .status-link:hover { - background-color: rgba(120, 211, 246, 0.4); + background-color: rgba(0, 0, 0, .2); + text-decoration: none; } .status-icon { @@ -235,6 +238,7 @@ h1 { text-align: center; width: 22px; height: 22px; + color: white; } .header .username { @@ -444,6 +448,7 @@ ul.dropdown-menu.absence-notification { } .user-name { margin: 0 3px; + color: #fff; } .user-name-eighth { @@ -504,14 +509,77 @@ ul.dropdown-menu.absence-notification { border-top: 2px solid rgb(161, 161, 161); } } - .selected { - background-color: rgb(252, 252, 252); a { color: $grey; } } + + .nav-status-indicator { + margin-top: 10px; + display: none; // only show when the nav bar is collapsed + border-radius: 8px; + margin-left: auto; + margin-right: auto; + height: 23px; + padding: 3px 4px; + &.selected { + background-color: rgb(223 222 222); + + a { + color: blue; + background: none; + -webkit-background-clip: initial; + background-clip: initial; + } + } + a { + all: unset; + margin-left: 5px; + display: inline; + margin-top: 2px; + + background: linear-gradient( + to right, + #484848, + #939393, + #484848 + ); + background-size: 200% auto; + background-clip: text; + -webkit-background-clip: text; + color: transparent; + animation: glow-move 4s linear infinite; + text-decoration: none; + } + + svg > circle { + animation: pulse 2.8s infinite; + transform-origin: center; + } + + @keyframes glow-move { + 0% { + background-position: 200% center; + } + 100% { + background-position: -200% center; + } + } + + @keyframes pulse { + 0% { + transform: scale(0.8); + } + 50% { + transform: scale(1); + } + 100% { + transform: scale(0.8); + } + } + } } diff --git a/intranet/static/css/responsive.core.scss b/intranet/static/css/responsive.core.scss index 4addc3145bb..34fdb00b601 100644 --- a/intranet/static/css/responsive.core.scss +++ b/intranet/static/css/responsive.core.scss @@ -46,6 +46,15 @@ body.disable-scroll { top: 4px; left: 60px; } + + .status-icon { + margin-top: 6px; + } + + .nav .nav-status-indicator { + display: flex; + justify-content: center; + } } @media (max-width: 960px) { diff --git a/intranet/static/css/tjstar_ribbon.scss b/intranet/static/css/tjstar_ribbon.scss index aba254f8440..06b49ae58a4 100644 --- a/intranet/static/css/tjstar_ribbon.scss +++ b/intranet/static/css/tjstar_ribbon.scss @@ -5,6 +5,9 @@ padding: 1rem; border: 1px solid #e3e3e3; margin-bottom: 6px; + @media (max-width: 992px) { + margin-top: 40px; + } } .tjstar-ribbon-toggle { @@ -14,6 +17,9 @@ margin-bottom: 6px; padding-left: 1rem; cursor: pointer; + @media (max-width: 992px) { + margin-top: 40px; + } } .tjstar-ribbon-toggle-text { diff --git a/intranet/static/vendor/qr-code-styling.js b/intranet/static/vendor/qr-code-styling.js new file mode 100644 index 00000000000..a7a79d2e53e --- /dev/null +++ b/intranet/static/vendor/qr-code-styling.js @@ -0,0 +1,2 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.QRCodeStyling=e():t.QRCodeStyling=e()}(self,(function(){return(()=>{var t={192:(t,e)=>{var r,n,o=function(){var t=function(t,e){var r=t,n=a[e],o=null,i=0,u=null,v=[],y={},_=function(t,e){o=function(t){for(var e=new Array(t),r=0;r=7&&M(t),null==u&&(u=P(r,n,v)),C(u,e)},m=function(t,e){for(var r=-1;r<=7;r+=1)if(!(t+r<=-1||i<=t+r))for(var n=-1;n<=7;n+=1)e+n<=-1||i<=e+n||(o[t+r][e+n]=0<=r&&r<=6&&(0==n||6==n)||0<=n&&n<=6&&(0==r||6==r)||2<=r&&r<=4&&2<=n&&n<=4)},b=function(){for(var t=8;t>n&1);o[Math.floor(n/3)][n%3+i-8-3]=a}for(n=0;n<18;n+=1)a=!t&&1==(e>>n&1),o[n%3+i-8-3][Math.floor(n/3)]=a},S=function(t,e){for(var r=n<<3|e,a=s.getBCHTypeInfo(r),u=0;u<15;u+=1){var c=!t&&1==(a>>u&1);u<6?o[u][8]=c:u<8?o[u+1][8]=c:o[i-15+u][8]=c}for(u=0;u<15;u+=1)c=!t&&1==(a>>u&1),u<8?o[8][i-u-1]=c:u<9?o[8][15-u-1+1]=c:o[8][15-u-1]=c;o[i-8][8]=!t},C=function(t,e){for(var r=-1,n=i-1,a=7,u=0,c=s.getMaskFunction(e),h=i-1;h>0;h-=2)for(6==h&&(h-=1);;){for(var d=0;d<2;d+=1)if(null==o[n][h-d]){var l=!1;u>>a&1)),c(n,h-d)&&(l=!l),o[n][h-d]=l,-1==(a-=1)&&(u+=1,a=7)}if((n+=r)<0||i<=n){n-=r,r=-r;break}}},P=function(t,e,r){for(var n=h.getRSBlocks(t,e),o=d(),i=0;i8*u)throw"code length overflow. ("+o.getLengthInBits()+">"+8*u+")";for(o.getLengthInBits()+4<=8*u&&o.put(0,4);o.getLengthInBits()%8!=0;)o.putBit(!1);for(;!(o.getLengthInBits()>=8*u||(o.put(236,8),o.getLengthInBits()>=8*u));)o.put(17,8);return function(t,e){for(var r=0,n=0,o=0,i=new Array(e.length),a=new Array(e.length),u=0;u=0?p.getAt(g):0}}var v=0;for(l=0;ln)&&(t=n,e=r)}return e}())},y.createTableTag=function(t,e){t=t||2;var r="";r+='',r+="";for(var n=0;n";for(var o=0;o';r+=""}return(r+="")+"
"},y.createSvgTag=function(t,e,r,n){var o={};"object"==typeof arguments[0]&&(t=(o=arguments[0]).cellSize,e=o.margin,r=o.alt,n=o.title),t=t||2,e=void 0===e?4*t:e,(r="string"==typeof r?{text:r}:r||{}).text=r.text||null,r.id=r.text?r.id||"qrcode-description":null,(n="string"==typeof n?{text:n}:n||{}).text=n.text||null,n.id=n.text?n.id||"qrcode-title":null;var i,a,s,u,c=y.getModuleCount()*t+2*e,h="";for(u="l"+t+",0 0,"+t+" -"+t+",0 0,-"+t+"z ",h+=''+O(n.text)+"":"",h+=r.text?''+O(r.text)+"":"",h+='',h+='"},y.createDataURL=function(t,e){t=t||2,e=void 0===e?4*t:e;var r=y.getModuleCount()*t+2*e,n=e,o=r-e;return w(r,r,(function(e,r){if(n<=e&&e"};var O=function(t){for(var e="",r=0;r":e+=">";break;case"&":e+="&";break;case'"':e+=""";break;default:e+=n}}return e};return y.createASCII=function(t,e){if((t=t||1)<2)return function(t){t=void 0===t?2:t;var e,r,n,o,i,a=1*y.getModuleCount()+2*t,s=t,u=a-t,c={"██":"█","█ ":"▀"," █":"▄"," ":" "},h={"██":"▀","█ ":"▀"," █":" "," ":" "},d="";for(e=0;e=u?h[i]:c[i];d+="\n"}return a%2&&t>0?d.substring(0,d.length-a-1)+Array(a+1).join("▀"):d.substring(0,d.length-1)}(e);t-=1,e=void 0===e?2*t:e;var r,n,o,i,a=y.getModuleCount()*t+2*e,s=e,u=a-e,c=Array(t+1).join("██"),h=Array(t+1).join(" "),d="",l="";for(r=0;r>>8),e.push(255&a)):e.push(n)}}return e}};var e,r,n,o,i,a={L:1,M:0,Q:3,H:2},s=(e=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],r=1335,n=7973,i=function(t){for(var e=0;0!=t;)e+=1,t>>>=1;return e},(o={}).getBCHTypeInfo=function(t){for(var e=t<<10;i(e)-i(r)>=0;)e^=r<=0;)e^=n<5&&(r+=3+i-5)}for(n=0;n=256;)e-=255;return t[e]}}}();function c(t,e){if(void 0===t.length)throw t.length+"/"+e;var r=function(){for(var r=0;r>>7-e%8&1)},put:function(t,e){for(var n=0;n>>e-n-1&1))},getLengthInBits:function(){return e},putBit:function(r){var n=Math.floor(e/8);t.length<=n&&t.push(0),r&&(t[n]|=128>>>e%8),e+=1}};return r},l=function(t){var e=t,r={getMode:function(){return 1},getLength:function(t){return e.length},write:function(t){for(var r=e,o=0;o+2>>8&255)+(255&o),t.put(o,13),r+=2}if(r>>8)},writeBytes:function(t,r,n){r=r||0,n=n||t.length;for(var o=0;o0&&(e+=","),e+=t[r];return e+"]"}};return e},y=function(t){var e=t,r=0,n=0,o=0,i={read:function(){for(;o<8;){if(r>=e.length){if(0==o)return-1;throw"unexpected end of file./"+o}var t=e.charAt(r);if(r+=1,"="==t)return o=0,-1;t.match(/^\s$/)||(n=n<<6|a(t.charCodeAt(0)),o+=6)}var i=n>>>o-8&255;return o-=8,i}},a=function(t){if(65<=t&&t<=90)return t-65;if(97<=t&&t<=122)return t-97+26;if(48<=t&&t<=57)return t-48+52;if(43==t)return 62;if(47==t)return 63;throw"c:"+t};return i},w=function(t,e,r){for(var n=function(t,e){var r=t,n=e,o=new Array(t*e),i={setPixel:function(t,e,n){o[e*r+t]=n},write:function(t){t.writeString("GIF87a"),t.writeShort(r),t.writeShort(n),t.writeByte(128),t.writeByte(0),t.writeByte(0),t.writeByte(0),t.writeByte(0),t.writeByte(0),t.writeByte(255),t.writeByte(255),t.writeByte(255),t.writeString(","),t.writeShort(0),t.writeShort(0),t.writeShort(r),t.writeShort(n),t.writeByte(0);var e=a(2);t.writeByte(2);for(var o=0;e.length-o>255;)t.writeByte(255),t.writeBytes(e,o,255),o+=255;t.writeByte(e.length-o),t.writeBytes(e,o,e.length-o),t.writeByte(0),t.writeString(";")}},a=function(t){for(var e=1<>>e!=0)throw"length over";for(;c+e>=8;)u.writeByte(255&(t<>>=8-c,h=0,c=0;h|=t<0&&u.writeByte(h)}});l.write(e,n);var f=0,p=String.fromCharCode(o[f]);for(f+=1;f=6;)i(t>>>e-6),e-=6},o.flush=function(){if(e>0&&(i(t<<6-e),t=0,e=0),r%3!=0)for(var o=3-r%3,a=0;a>6,128|63&n):n<55296||n>=57344?e.push(224|n>>12,128|n>>6&63,128|63&n):(r++,n=65536+((1023&n)<<10|1023&t.charCodeAt(r)),e.push(240|n>>18,128|n>>12&63,128|n>>6&63,128|63&n))}return e}(t)},void 0===(n="function"==typeof(r=function(){return o})?r.apply(e,[]):r)||(t.exports=n)},796:(t,e,r)=>{"use strict";r.d(e,{default:()=>W});var n=function(){return(n=Object.assign||function(t){for(var e,r=1,n=arguments.length;rn||o&&o2||a&&s||u&&c)this._basicSquare({x:e,y:r,size:n,context:o,rotation:0});else{if(2===h){var d=0;return a&&u?d=Math.PI/2:u&&s?d=Math.PI:s&&c&&(d=-Math.PI/2),void this._basicCornerRounded({x:e,y:r,size:n,context:o,rotation:d})}if(1===h)return d=0,u?d=Math.PI/2:s?d=Math.PI:c&&(d=-Math.PI/2),void this._basicSideRounded({x:e,y:r,size:n,context:o,rotation:d})}else this._basicDot({x:e,y:r,size:n,context:o,rotation:0})},t.prototype._drawExtraRounded=function(t){var e=t.x,r=t.y,n=t.size,o=t.context,i=t.getNeighbor,a=i?+i(-1,0):0,s=i?+i(1,0):0,u=i?+i(0,-1):0,c=i?+i(0,1):0,h=a+s+u+c;if(0!==h)if(h>2||a&&s||u&&c)this._basicSquare({x:e,y:r,size:n,context:o,rotation:0});else{if(2===h){var d=0;return a&&u?d=Math.PI/2:u&&s?d=Math.PI:s&&c&&(d=-Math.PI/2),void this._basicCornerExtraRounded({x:e,y:r,size:n,context:o,rotation:d})}if(1===h)return d=0,u?d=Math.PI/2:s?d=Math.PI:c&&(d=-Math.PI/2),void this._basicSideRounded({x:e,y:r,size:n,context:o,rotation:d})}else this._basicDot({x:e,y:r,size:n,context:o,rotation:0})},t.prototype._drawClassy=function(t){var e=t.x,r=t.y,n=t.size,o=t.context,i=t.getNeighbor,a=i?+i(-1,0):0,s=i?+i(1,0):0,u=i?+i(0,-1):0,c=i?+i(0,1):0;0!==a+s+u+c?a||u?s||c?this._basicSquare({x:e,y:r,size:n,context:o,rotation:0}):this._basicCornerRounded({x:e,y:r,size:n,context:o,rotation:Math.PI/2}):this._basicCornerRounded({x:e,y:r,size:n,context:o,rotation:-Math.PI/2}):this._basicCornersRounded({x:e,y:r,size:n,context:o,rotation:Math.PI/2})},t.prototype._drawClassyRounded=function(t){var e=t.x,r=t.y,n=t.size,o=t.context,i=t.getNeighbor,a=i?+i(-1,0):0,s=i?+i(1,0):0,u=i?+i(0,-1):0,c=i?+i(0,1):0;0!==a+s+u+c?a||u?s||c?this._basicSquare({x:e,y:r,size:n,context:o,rotation:0}):this._basicCornerExtraRounded({x:e,y:r,size:n,context:o,rotation:Math.PI/2}):this._basicCornerExtraRounded({x:e,y:r,size:n,context:o,rotation:-Math.PI/2}):this._basicCornersRounded({x:e,y:r,size:n,context:o,rotation:Math.PI/2})},t}(),w="square",_="extra-rounded";var m=function(){return(m=Object.assign||function(t){for(var e,r=1,n=arguments.length;r0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]=(e-o.hideXDots)/2&&t<(e+o.hideXDots)/2&&r>=(e-o.hideYDots)/2&&r<(e+o.hideYDots)/2||(null===(n=P[t])||void 0===n?void 0:n[r])||(null===(i=P[t-e+7])||void 0===i?void 0:i[r])||(null===(a=P[t])||void 0===a?void 0:a[r-e+7])||(null===(s=O[t])||void 0===s?void 0:s[r])||(null===(u=O[t-e+7])||void 0===u?void 0:u[r])||(null===(c=O[t])||void 0===c?void 0:c[r-e+7]))})),this.drawCorners(),this._options.image&&this.drawImage({width:o.width,height:o.height,count:e,dotSize:n}),[2]}}))},new((n=void 0)||(n=Promise))((function(t,i){function a(t){try{u(o.next(t))}catch(t){i(t)}}function s(t){try{u(o.throw(t))}catch(t){i(t)}}function u(e){var r;e.done?t(e.value):(r=e.value,r instanceof n?r:new n((function(t){t(r)}))).then(a,s)}u((o=o.apply(e,r||[])).next())}));var e,r,n,o},t.prototype.drawBackground=function(){var t=this.context,e=this._options;if(t){if(e.backgroundOptions.gradient){var r=e.backgroundOptions.gradient,n=this._createGradient({context:t,options:r,additionalRotation:0,x:0,y:0,size:this._canvas.width>this._canvas.height?this._canvas.width:this._canvas.height});r.colorStops.forEach((function(t){var e=t.offset,r=t.color;n.addColorStop(e,r)})),t.fillStyle=n}else e.backgroundOptions.color&&(t.fillStyle=e.backgroundOptions.color);t.fillRect(0,0,this._canvas.width,this._canvas.height)}},t.prototype.drawDots=function(t){var e=this;if(!this._qr)throw"QR code is not defined";var r=this.context;if(!r)throw"QR code is not defined";var n=this._options,o=this._qr.getModuleCount();if(o>n.width||o>n.height)throw"The canvas is too small.";var i=Math.min(n.width,n.height)-2*n.margin,a=Math.floor(i/o),s=Math.floor((n.width-o*a)/2),u=Math.floor((n.height-o*a)/2),c=new y({context:r,type:n.dotsOptions.type});r.beginPath();for(var h=function(r){for(var n=function(n){return t&&!t(r,n)?"continue":d._qr.isDark(r,n)?void c.draw(s+r*a,u+n*a,a,(function(i,a){return!(r+i<0||n+a<0||r+i>=o||n+a>=o)&&!(t&&!t(r+i,n+a))&&!!e._qr&&e._qr.isDark(r+i,n+a)})):"continue"},i=0;i=0&&c<=.25*Math.PI||c>1.75*Math.PI&&c<=2*Math.PI?(h-=s/2,d-=s/2*Math.tan(u),l+=s/2,f+=s/2*Math.tan(u)):c>.25*Math.PI&&c<=.75*Math.PI?(d-=s/2,h-=s/2/Math.tan(u),f+=s/2,l+=s/2/Math.tan(u)):c>.75*Math.PI&&c<=1.25*Math.PI?(h+=s/2,d+=s/2*Math.tan(u),l-=s/2,f-=s/2*Math.tan(u)):c>1.25*Math.PI&&c<=1.75*Math.PI&&(d+=s/2,h+=s/2/Math.tan(u),f-=s/2,l-=s/2/Math.tan(u)),e=r.createLinearGradient(Math.round(h),Math.round(d),Math.round(l),Math.round(f))}return e},t}();var D=function(){return(D=Object.assign||function(t){for(var e,r=1,n=arguments.length;r2||i&&a||s&&u)this._basicSquare({x:e,y:r,size:n,rotation:0});else{if(2===c){var h=0;return i&&s?h=Math.PI/2:s&&a?h=Math.PI:a&&u&&(h=-Math.PI/2),void this._basicCornerRounded({x:e,y:r,size:n,rotation:h})}if(1===c)return h=0,s?h=Math.PI/2:a?h=Math.PI:u&&(h=-Math.PI/2),void this._basicSideRounded({x:e,y:r,size:n,rotation:h})}else this._basicDot({x:e,y:r,size:n,rotation:0})},t.prototype._drawExtraRounded=function(t){var e=t.x,r=t.y,n=t.size,o=t.getNeighbor,i=o?+o(-1,0):0,a=o?+o(1,0):0,s=o?+o(0,-1):0,u=o?+o(0,1):0,c=i+a+s+u;if(0!==c)if(c>2||i&&a||s&&u)this._basicSquare({x:e,y:r,size:n,rotation:0});else{if(2===c){var h=0;return i&&s?h=Math.PI/2:s&&a?h=Math.PI:a&&u&&(h=-Math.PI/2),void this._basicCornerExtraRounded({x:e,y:r,size:n,rotation:h})}if(1===c)return h=0,s?h=Math.PI/2:a?h=Math.PI:u&&(h=-Math.PI/2),void this._basicSideRounded({x:e,y:r,size:n,rotation:h})}else this._basicDot({x:e,y:r,size:n,rotation:0})},t.prototype._drawClassy=function(t){var e=t.x,r=t.y,n=t.size,o=t.getNeighbor,i=o?+o(-1,0):0,a=o?+o(1,0):0,s=o?+o(0,-1):0,u=o?+o(0,1):0;0!==i+a+s+u?i||s?a||u?this._basicSquare({x:e,y:r,size:n,rotation:0}):this._basicCornerRounded({x:e,y:r,size:n,rotation:Math.PI/2}):this._basicCornerRounded({x:e,y:r,size:n,rotation:-Math.PI/2}):this._basicCornersRounded({x:e,y:r,size:n,rotation:Math.PI/2})},t.prototype._drawClassyRounded=function(t){var e=t.x,r=t.y,n=t.size,o=t.getNeighbor,i=o?+o(-1,0):0,a=o?+o(1,0):0,s=o?+o(0,-1):0,u=o?+o(0,1):0;0!==i+a+s+u?i||s?a||u?this._basicSquare({x:e,y:r,size:n,rotation:0}):this._basicCornerExtraRounded({x:e,y:r,size:n,rotation:Math.PI/2}):this._basicCornerExtraRounded({x:e,y:r,size:n,rotation:-Math.PI/2}):this._basicCornersRounded({x:e,y:r,size:n,rotation:Math.PI/2})},t}();var A=function(){return(A=Object.assign||function(t){for(var e,r=1,n=arguments.length;r0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]=(e-o.hideXDots)/2&&t<(e+o.hideXDots)/2&&r>=(e-o.hideYDots)/2&&r<(e+o.hideYDots)/2||(null===(n=B[t])||void 0===n?void 0:n[r])||(null===(i=B[t-e+7])||void 0===i?void 0:i[r])||(null===(a=B[t])||void 0===a?void 0:a[r-e+7])||(null===(s=E[t])||void 0===s?void 0:s[r])||(null===(u=E[t-e+7])||void 0===u?void 0:u[r])||(null===(c=E[t])||void 0===c?void 0:c[r-e+7]))})),this.drawCorners(),this._options.image&&this.drawImage({width:o.width,height:o.height,count:e,dotSize:n}),[2]}}))},new((n=void 0)||(n=Promise))((function(t,i){function a(t){try{u(o.next(t))}catch(t){i(t)}}function s(t){try{u(o.throw(t))}catch(t){i(t)}}function u(e){var r;e.done?t(e.value):(r=e.value,r instanceof n?r:new n((function(t){t(r)}))).then(a,s)}u((o=o.apply(e,r||[])).next())}));var e,r,n,o},t.prototype.drawBackground=function(){var t,e,r=this._element,n=this._options;if(r){var o=null===(t=n.backgroundOptions)||void 0===t?void 0:t.gradient,i=null===(e=n.backgroundOptions)||void 0===e?void 0:e.color;(o||i)&&this._createColor({options:o,color:i,additionalRotation:0,x:0,y:0,height:n.height,width:n.width,name:"background-color"})}},t.prototype.drawDots=function(t){var e,r,n=this;if(!this._qr)throw"QR code is not defined";var o=this._options,i=this._qr.getModuleCount();if(i>o.width||i>o.height)throw"The canvas is too small.";var a=Math.min(o.width,o.height)-2*o.margin,s=Math.floor(a/i),u=Math.floor((o.width-i*s)/2),c=Math.floor((o.height-i*s)/2),h=new I({svg:this._element,type:o.dotsOptions.type});this._dotsClipPath=document.createElementNS("http://www.w3.org/2000/svg","clipPath"),this._dotsClipPath.setAttribute("id","clip-path-dot-color"),this._defs.appendChild(this._dotsClipPath),this._createColor({options:null===(e=o.dotsOptions)||void 0===e?void 0:e.gradient,color:o.dotsOptions.color,additionalRotation:0,x:u,y:c,height:i*s,width:i*s,name:"dot-color"});for(var d=function(e){for(var o=function(o){return t&&!t(e,o)?"continue":(null===(r=l._qr)||void 0===r?void 0:r.isDark(e,o))?(h.draw(u+e*s,c+o*s,s,(function(r,a){return!(e+r<0||o+a<0||e+r>=i||o+a>=i)&&!(t&&!t(e+r,o+a))&&!!n._qr&&n._qr.isDark(e+r,o+a)})),void(h._element&&l._dotsClipPath&&l._dotsClipPath.appendChild(h._element))):"continue"},a=0;aa?s:a,h=document.createElementNS("http://www.w3.org/2000/svg","rect");if(h.setAttribute("x",String(o)),h.setAttribute("y",String(i)),h.setAttribute("height",String(a)),h.setAttribute("width",String(s)),h.setAttribute("clip-path","url('#clip-path-"+u+"')"),e){var d;if(e.type===C)(d=document.createElementNS("http://www.w3.org/2000/svg","radialGradient")).setAttribute("id",u),d.setAttribute("gradientUnits","userSpaceOnUse"),d.setAttribute("fx",String(o+s/2)),d.setAttribute("fy",String(i+a/2)),d.setAttribute("cx",String(o+s/2)),d.setAttribute("cy",String(i+a/2)),d.setAttribute("r",String(c/2));else{var l=((e.rotation||0)+n)%(2*Math.PI),f=(l+2*Math.PI)%(2*Math.PI),p=o+s/2,g=i+a/2,v=o+s/2,y=i+a/2;f>=0&&f<=.25*Math.PI||f>1.75*Math.PI&&f<=2*Math.PI?(p-=s/2,g-=a/2*Math.tan(l),v+=s/2,y+=a/2*Math.tan(l)):f>.25*Math.PI&&f<=.75*Math.PI?(g-=a/2,p-=s/2/Math.tan(l),y+=a/2,v+=s/2/Math.tan(l)):f>.75*Math.PI&&f<=1.25*Math.PI?(p+=s/2,g+=a/2*Math.tan(l),v-=s/2,y-=a/2*Math.tan(l)):f>1.25*Math.PI&&f<=1.75*Math.PI&&(g+=a/2,p+=s/2/Math.tan(l),y-=a/2,v-=s/2/Math.tan(l)),(d=document.createElementNS("http://www.w3.org/2000/svg","linearGradient")).setAttribute("id",u),d.setAttribute("gradientUnits","userSpaceOnUse"),d.setAttribute("x1",String(Math.round(p))),d.setAttribute("y1",String(Math.round(g))),d.setAttribute("x2",String(Math.round(v))),d.setAttribute("y2",String(Math.round(y)))}e.colorStops.forEach((function(t){var e=t.offset,r=t.color,n=document.createElementNS("http://www.w3.org/2000/svg","stop");n.setAttribute("offset",100*e+"%"),n.setAttribute("stop-color",r),d.appendChild(n)})),h.setAttribute("fill","url('#"+u+"')"),this._defs.appendChild(d)}else r&&h.setAttribute("fill",r);this._element.appendChild(h)},t}(),N="canvas";for(var T={},j=0;j<=40;j++)T[j]=j;const F={type:N,width:300,height:300,data:"",margin:0,qrOptions:{typeNumber:T[0],mode:void 0,errorCorrectionLevel:"Q"},imageOptions:{hideBackgroundDots:!0,imageSize:.4,crossOrigin:void 0,margin:0},dotsOptions:{type:"square",color:"#000"},backgroundOptions:{color:"#fff"}};var Q=function(){return(Q=Object.assign||function(t){for(var e,r=1,n=arguments.length;rMath.min(e.width,e.height)&&(e.margin=Math.min(e.width,e.height)),e.dotsOptions=Q({},e.dotsOptions),e.dotsOptions.gradient&&(e.dotsOptions.gradient=H(e.dotsOptions.gradient)),e.cornersSquareOptions&&(e.cornersSquareOptions=Q({},e.cornersSquareOptions),e.cornersSquareOptions.gradient&&(e.cornersSquareOptions.gradient=H(e.cornersSquareOptions.gradient))),e.cornersDotOptions&&(e.cornersDotOptions=Q({},e.cornersDotOptions),e.cornersDotOptions.gradient&&(e.cornersDotOptions.gradient=H(e.cornersDotOptions.gradient))),e.backgroundOptions&&(e.backgroundOptions=Q({},e.backgroundOptions),e.backgroundOptions.gradient&&(e.backgroundOptions.gradient=H(e.backgroundOptions.gradient))),e}var X=r(192),U=r.n(X),Y=function(t,e,r,n){return new(r||(r=Promise))((function(o,i){function a(t){try{u(n.next(t))}catch(t){i(t)}}function s(t){try{u(n.throw(t))}catch(t){i(t)}}function u(t){var e;t.done?o(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e)}))).then(a,s)}u((n=n.apply(t,e||[])).next())}))},$=function(t,e){var r,n,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(r)throw new TypeError("Generator is already executing.");for(;a;)try{if(r=1,n&&(o=2&i[0]?n.return:i[0]?n.throw||((o=n.return)&&o.call(n),0):n.next)&&!(o=o.call(n,i[1])).done)return o;switch(n=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,n=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]\r\n'+n],{type:"image/svg+xml"})]):[2,new Promise((function(r){return e.getCanvas().toBlob(r,"image/"+t,1)}))]}}))}))},t.prototype.download=function(t){return Y(this,void 0,void 0,(function(){var e,r,n,o,i;return $(this,(function(a){switch(a.label){case 0:if(!this._qr)throw"QR code is empty";return e="png",r="qr","string"==typeof t?(e=t,console.warn("Extension is deprecated as argument for 'download' method, please pass object { name: '...', extension: '...' } as argument")):"object"==typeof t&&null!==t&&(t.name&&(r=t.name),t.extension&&(e=t.extension)),[4,this._getQRStylingElement(e)];case 1:return n=a.sent(),"svg"===e.toLowerCase()?(o=new XMLSerializer,i='\r\n'+(i=o.serializeToString(n.getElement())),s("data:image/svg+xml;charset=utf-8,"+encodeURIComponent(i),r+".svg")):s(n.getCanvas().toDataURL("image/"+e),r+"."+e),[2]}}))}))},t}()}},e={};function r(n){if(e[n])return e[n].exports;var o=e[n]={exports:{}};return t[n](o,o.exports,r),o.exports}return r.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return r.d(e,{a:e}),e},r.d=(t,e)=>{for(var n in e)r.o(e,n)&&!r.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},r.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r(796)})().default})); +//# sourceMappingURL=qr-code-styling.js.map \ No newline at end of file diff --git a/intranet/static/vendor/qrcode.min.js b/intranet/static/vendor/qrcode.min.js deleted file mode 100644 index 993e88f3966..00000000000 --- a/intranet/static/vendor/qrcode.min.js +++ /dev/null @@ -1 +0,0 @@ -var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j=0?p.get(q):0}}for(var r=0,m=0;mm;m++)for(var j=0;jm;m++)for(var j=0;j=0;)b^=f.G15<=0;)b^=f.G18<>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;cf;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=[''],h=0;d>h;h++){g.push("");for(var i=0;d>i;i++)g.push('');g.push("")}g.push("
"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}(); \ No newline at end of file diff --git a/intranet/templates/credits.html b/intranet/templates/credits.html index fda7e21f18a..9b8b35bd87f 100644 --- a/intranet/templates/credits.html +++ b/intranet/templates/credits.html @@ -29,6 +29,8 @@
  • Saloni Shah (2023)
  • Alan Zhu (2025)
  • Justin Lee (2025)
  • +
  • Aarush Chaurasia (2027)
  • +
  • Shreyas Jain (2027)
  • ...more to come...

  • @@ -43,6 +45,7 @@
  • Dr. Shane Torbert
  • Dr. Patrick White
  • Mr. Malcolm Eckel
  • +
  • Ms. JeanMarie Stewart
  • diff --git a/intranet/templates/dashboard/links.html b/intranet/templates/dashboard/links.html index 15502a9369a..dbf1b1d6c49 100644 --- a/intranet/templates/dashboard/links.html +++ b/intranet/templates/dashboard/links.html @@ -29,10 +29,8 @@ - Webmail
    - Schoology
    + Lost and Found
    SGA Study Guides
    - TJ Peer Tutoring
    Submit TJTV Announcement
    Submit Morning Announcement
    Library
    diff --git a/intranet/templates/eighth/location.html b/intranet/templates/eighth/location.html index 3b2f78a9f1d..6b39875b9c9 100644 --- a/intranet/templates/eighth/location.html +++ b/intranet/templates/eighth/location.html @@ -43,9 +43,6 @@
    Sponsor(s): {{ s.3 }}


    {% endfor %} - {% if real_user.is_student and attendance_open%} - Enter Attendance Code for Today's Blocks - {% endif %}
    Return to Dashboard diff --git a/intranet/templates/eighth/signup.html b/intranet/templates/eighth/signup.html index aaa8bad0bd5..45106b1c913 100644 --- a/intranet/templates/eighth/signup.html +++ b/intranet/templates/eighth/signup.html @@ -346,15 +346,18 @@

    <% } else { %> You are currently signed up for this activity. +
    + <% if (attendance_open) { %> + + <% } %> <% } %> <% } %> <%}%> <%}%>
    {% endverbatim %} - {% if real_user.is_student and attendance_open and not real_user.is_eighth_admin%} - Enter Attendance Code for Today's Blocks - {% endif %}
    @@ -553,4 +556,4 @@

    {{ block.date|date:"D, N j" }} - {% if block.locked %} + {% if block.attendance_open %} + + {% if block.rooms and not block.is_today and not block.current_signup_cancelled %} + + {% for r in block.rooms %} + {{ r.formatted_name }}{% if not forloop.last %}, {% endif %} + {% endfor %} + + {% endif %} + + {% if block.locked %}{% endif %} + Enter Code + + {% elif block.locked %} {% if block.current_signup %}