Source code for intranet.apps.eighth.views.admin.attendance

import csv
import logging
from datetime import MAXYEAR, MINYEAR, date, datetime, timedelta

from cacheops import invalidate_obj
from django import http
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.db.models import Count, Q
from django.shortcuts import redirect, render
from django.utils import timezone

from .....utils.helpers import is_entirely_digit
from ....auth.decorators import eighth_admin_required
from ...models import EighthActivity, EighthBlock, EighthRoom, EighthScheduledActivity, EighthSignup
from ...utils import get_start_date

logger = logging.getLogger(__name__)


[docs]@eighth_admin_required def delinquent_students_view(request): lower_absence_limit = request.GET.get("lower", "") upper_absence_limit = request.GET.get("upper", "") include_freshmen = request.GET.get("freshmen", "off") == "on" include_sophomores = request.GET.get("sophomores", "off") == "on" include_juniors = request.GET.get("juniors", "off") == "on" include_seniors = request.GET.get("seniors", "off") == "on" if not request.META["QUERY_STRING"]: include_freshmen = True include_sophomores = True include_juniors = True include_seniors = True start_date = request.GET.get("start", "") end_date = request.GET.get("end", "") if lower_absence_limit == "" or not is_entirely_digit(lower_absence_limit): lower_absence_limit = "1" lower_absence_limit_filter = 1 else: lower_absence_limit_filter = lower_absence_limit if upper_absence_limit == "" or not is_entirely_digit(upper_absence_limit): upper_absence_limit = "100" upper_absence_limit_filter = 100 else: upper_absence_limit_filter = upper_absence_limit try: start_date = datetime.strptime(start_date, "%Y-%m-%d") start_date_filter = start_date except ValueError: start_date = "" start_date_filter = date(MINYEAR, 1, 1) try: end_date = datetime.strptime(end_date, "%Y-%m-%d") end_date_filter = end_date except ValueError: end_date = "" end_date_filter = date(MAXYEAR, 12, 31) context = { "lower_absence_limit": lower_absence_limit, "upper_absence_limit": upper_absence_limit, "include_freshmen": include_freshmen, "include_sophomores": include_sophomores, "include_juniors": include_juniors, "include_seniors": include_seniors, "start_date": start_date, "end_date": end_date, } query_params = ["lower", "upper", "freshmen", "sophomores", "juniors", "seniors", "start", "end"] if set(request.GET.keys()).intersection(set(query_params)): # attendance MUST have been taken on the activity for the absence to be valid delinquents = [] if int(upper_absence_limit_filter) > 0 and int(lower_absence_limit_filter) > 0: delinquents = ( EighthSignup.objects.filter( was_absent=True, scheduled_activity__attendance_taken=True, scheduled_activity__block__date__gte=start_date_filter, scheduled_activity__block__date__lte=end_date_filter, ) .values("user") .annotate(absences=Count("user")) .filter(absences__gte=lower_absence_limit_filter, absences__lte=upper_absence_limit_filter) # Order with most absences at top .order_by("user") .values("user", "absences") ) user_ids = [d["user"] for d in delinquents] delinquent_users = get_user_model().objects.filter(id__in=user_ids).order_by("id") for index, user in enumerate(delinquent_users): delinquents[index]["user"] = user delinquents = list(delinquents) def filter_by_grade(delinquent): grade = delinquent["user"].grade.number include = False if include_freshmen: include |= grade == 9 if include_sophomores: include |= grade == 10 if include_juniors: include |= grade == 11 if include_seniors: include |= grade == 12 return include delinquents = list(filter(filter_by_grade, delinquents)) delinquents = sorted(delinquents, key=lambda x: (-1 * x["absences"], x["user"].last_name)) else: delinquents = None context["delinquents"] = delinquents if request.resolver_match.url_name == "eighth_admin_view_delinquent_students": context["admin_page_title"] = "Delinquent Students" return render(request, "eighth/admin/delinquent_students.html", context) else: response = http.HttpResponse(content_type="text/csv") response["Content-Disposition"] = 'attachment; filename="delinquent_students.csv"' writer = csv.writer(response) writer.writerow( ["Start Date", "End Date", "Absences", "Last Name", "First Name", "Student ID", "Grade", "Counselor", "TJ Email", "Personal Email"] ) for delinquent in delinquents: row = [] row.append(str(start_date).split(" ", 1)[0]) row.append(str(end_date).split(" ", 1)[0]) row.append(delinquent["absences"]) row.append(delinquent["user"].last_name) row.append(delinquent["user"].first_name) row.append(delinquent["user"].student_id) row.append(delinquent["user"].grade.number) counselor = delinquent["user"].counselor row.append(counselor.last_name if counselor else "") row.append(delinquent["user"].tj_email) row.append(delinquent["user"].non_tj_email or "") writer.writerow(row) return response
[docs]@eighth_admin_required def no_signups_roster(request, block_id): try: block = EighthBlock.objects.get(id=block_id) except EighthBlock.DoesNotExist as e: raise http.Http404 from e unsigned = block.get_unsigned_students() unsigned = sorted(unsigned, key=lambda u: (u.last_name, u.first_name)) user_signups_hidden = block.get_hidden_signups() if request.resolver_match.url_name == "eighth_admin_no_signups_roster": context = {"eighthblock": block, "users": unsigned, "user_signups_hidden": user_signups_hidden, "admin_page_title": "No Signups Roster"} return render(request, "eighth/admin/no_signups_roster.html", context) else: response = http.HttpResponse(content_type="text/csv") response["Content-Disposition"] = 'attachment; filename="no_signups_roster.csv"' writer = csv.writer(response) writer.writerow(["Block ID", "Block Date", "Last Name", "First Name", "Student ID", "Grade", "Counselor", "TJ Email", "Personal Email"]) for user in unsigned: row = [] row.append(str(block.id)) row.append(str(block)) row.append(user.last_name) row.append(user.first_name) row.append(user.student_id) row.append(user.grade.number) counselor = user.counselor row.append(counselor.last_name if counselor else "") row.append(user.tj_email) row.append(user.non_tj_email or "") writer.writerow(row) return response
[docs]@eighth_admin_required def after_deadline_signup_view(request): start_date = request.GET.get("start", "") end_date = request.GET.get("end", "") try: start_date = timezone.make_aware(datetime.strptime(start_date, "%Y-%m-%d")) except ValueError: start_date = get_start_date(request) try: end_date = timezone.make_aware(datetime.strptime(end_date, "%Y-%m-%d")) except ValueError: end_date = start_date + timedelta(days=7) signups = EighthSignup.objects.filter(after_deadline=True, time__gte=start_date, time__lte=end_date).order_by("-time") context = {"admin_page_title": "After Deadline Signups", "signups": signups, "start_date": start_date, "end_date": end_date} if request.resolver_match.url_name == "eighth_admin_download_after_deadline_signups_csv": response = http.HttpResponse(content_type="text/csv") response["Content-Disposition"] = 'attachment; filename="after_deadline_signups.csv"' writer = csv.writer(response) writer.writerow( [ "Start Date", "End Date", "Time", "Block", "Last Name", "First Name", "Student ID", "Grade", "From Activity", "From Sponsor", "To Activity ID", "To Activity", "To Sponsor", ] ) for signup in signups: row = [] row.append(datetime.strftime(start_date, "%m/%d/%Y")) row.append(datetime.strftime(end_date, "%m/%d/%Y")) row.append(signup.time) row.append(signup.scheduled_activity.block) row.append(signup.user.last_name) row.append(signup.user.first_name) row.append(signup.user.student_id) row.append(signup.user.grade.number) row.append(signup.previous_activity_name) row.append(signup.previous_activity_sponsors) row.append(signup.scheduled_activity.activity.id) row.append(signup.scheduled_activity.activity.name_with_flags) row.append(" ,".join([str(sponsor) for sponsor in signup.scheduled_activity.get_true_sponsors()])) writer.writerow(row) return response return render(request, "eighth/admin/after_deadline_signups.html", context)
[docs]@eighth_admin_required def activities_without_attendance_view(request): blocks = EighthBlock.objects.filter(date__gte=get_start_date(request)) block_id = request.GET.get("block", None) block = None if block_id is not None: try: block = EighthBlock.objects.get(id=block_id) except (EighthBlock.DoesNotExist, ValueError): pass context = {"blocks": blocks, "chosen_block": block} if block is not None: start_date = get_start_date(request) scheduled_activities = block.eighthscheduledactivity_set.filter(block__date__gte=start_date, attendance_taken=False).order_by( "-activity__special", "activity__name" ) # float special to top context["scheduled_activities"] = scheduled_activities if request.POST.get("take_attendance_zero", False) is not False: zero_students = scheduled_activities.filter(members=None) signups = EighthSignup.objects.filter(scheduled_activity__in=zero_students) logger.debug(zero_students) if signups.count() == 0: zero_students.update(attendance_taken=True) messages.success(request, f"Took attendance for {zero_students.count()} empty activities.") else: messages.error(request, f"Apparently there were actually {signups.count()} signups. Maybe one is no longer empty?") return redirect(f"/eighth/admin/attendance/no_attendance?block={block.id}") if request.POST.get("take_attendance_cancelled", False) is not False: cancelled = scheduled_activities.filter(cancelled=True) signups = EighthSignup.objects.filter(scheduled_activity__in=cancelled) logger.debug(cancelled) logger.debug(signups) signups.update(was_absent=True) cancelled.update(attendance_taken=True) messages.success(request, f"Took attendance for {cancelled.count()} cancelled activities. {signups.count()} students marked absent.") return redirect(f"/eighth/admin/attendance/no_attendance?block={block.id}") context["admin_page_title"] = "Activities That Haven't Taken Attendance" return render(request, "eighth/admin/activities_without_attendance.html", context)
[docs]@eighth_admin_required def migrate_outstanding_passes_view(request): if request.method == "POST": try: block_id = request.POST.get("block", None) block = EighthBlock.objects.get(id=block_id) except EighthBlock.DoesNotExist as e: raise http.Http404 from e activity, _ = EighthActivity.objects.get_or_create(name="Z - 8th Period Pass Not Received", deleted=False) activity.restricted = True activity.sticky = True activity.administrative = True if not activity.description: activity.description = "Pass received from the 8th period office was not turned in." activity.save() invalidate_obj(activity) pass_not_received, _ = EighthScheduledActivity.objects.get_or_create(block=block, activity=activity) EighthSignup.objects.filter(scheduled_activity__block=block, after_deadline=True, pass_accepted=False).update( scheduled_activity=pass_not_received ) messages.success(request, "Successfully migrated outstanding passes.") return redirect("eighth_admin_dashboard") else: blocks = EighthBlock.objects.filter(date__gte=get_start_date(request)) context = {"admin_page_title": "Migrate Outstanding Passes", "blocks": blocks} return render(request, "eighth/admin/migrate_outstanding_passes.html", context)
[docs]@eighth_admin_required def out_of_building_schedules_view(request, block_id=None): blocks = EighthBlock.objects.filter(date__gte=get_start_date(request)) if block_id is None: block_id = request.GET.get("block", None) block = None if block_id is not None: try: block = EighthBlock.objects.get(id=block_id) except (EighthBlock.DoesNotExist, ValueError): pass context = {"blocks": blocks, "chosen_block": block} if block is not None: rooms = EighthRoom.objects.filter(name__icontains="out of building") if rooms: rooms_filter = Q(scheduled_activity__rooms__in=rooms) | ( Q(scheduled_activity__rooms=None) & Q(scheduled_activity__activity__rooms__in=rooms) ) signups = ( EighthSignup.objects.filter(scheduled_activity__block=block).filter(rooms_filter).distinct().order_by("scheduled_activity__activity") ) context["signups"] = signups if request.resolver_match.url_name == "eighth_admin_export_out_of_building_schedules": context["admin_page_title"] = "Export Out of Building Schedules" return render(request, "eighth/admin/out_of_building_schedules.html", context) else: response = http.HttpResponse(content_type="text/csv") block_date_str = datetime.strftime(block.date, "%m_%d_%Y") filename = f'"out_of_building_schedules_{block_date_str}.csv"' response["Content-Disposition"] = "attachment; filename=" + filename writer = csv.writer(response) writer.writerow(["Last Name", "First Name", "Student ID", "Date", "Block", "Activity ID", "Activity Name"]) for signup in signups: row = [] row.append(signup.user.last_name) row.append(signup.user.first_name) row.append(signup.user.student_id) row.append(signup.scheduled_activity.block.date) row.append(signup.scheduled_activity.block.block_letter) row.append(signup.scheduled_activity.activity.id) row.append(signup.scheduled_activity.title_with_flags) writer.writerow(row) return response
[docs]@eighth_admin_required def clear_absence_view(request, signup_id): if request.method == "POST": try: signup = EighthSignup.objects.get(id=signup_id) except (EighthSignup.DoesNotExist, ValueError) as e: raise http.Http404 from e signup.was_absent = False signup.save() invalidate_obj(signup) if "next" in request.GET: return redirect(request.GET["next"]) return redirect("eighth_admin_dashboard") else: return http.HttpResponseNotAllowed(["POST"], "HTTP 405: METHOD NOT ALLOWED")
[docs]@eighth_admin_required def open_passes_view(request): passes = EighthSignup.objects.filter(after_deadline=True, pass_accepted=False) if request.method == "POST": pass_ids = list(request.POST.keys()) csrf = "csrfmiddlewaretoken" if csrf in pass_ids: pass_ids.remove(csrf) accepted = 0 rejected = 0 for signup_id in pass_ids: signup = EighthSignup.objects.get(id=signup_id) status = request.POST.get(signup_id) if status == "accept": signup.accept_pass() accepted += 1 elif status == "reject": signup.reject_pass() rejected += 1 invalidate_obj(signup) messages.success(request, f"Accepted {accepted} and rejected {rejected} passes.") if request.resolver_match.url_name == "eighth_admin_view_open_passes_csv": response = http.HttpResponse(content_type="text/csv") filename = '"open_passes.csv"' response["Content-Disposition"] = "attachment; filename=" + filename writer = csv.writer(response) writer.writerow(["Block", "Activity", "Student", "Grade", "Absences", "Time (Last Modified)", "Time (Created)"]) for p in passes: row = [] row.append(p.scheduled_activity.block) row.append(p.scheduled_activity.activity) row.append("{}, {} {}".format(p.user.last_name, p.user.first_name, p.user.nickname if p.user.nickname else "")) row.append(int(p.user.grade)) row.append(p.user.absence_count()) row.append(p.last_modified_time) row.append(p.created_time) writer.writerow(row) return response context = {"admin_page_title": "Open Passes", "passes": passes} return render(request, "eighth/admin/open_passes.html", context)