Source code for intranet.apps.dataimport.management.commands.update_administrators

#!/usr/bin/env python3

import csv
import sys
from typing import Any

from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand, CommandError, CommandParser
from django.db import transaction

from intranet.utils.date import get_senior_graduation_year

from ....users.models import User


[docs] class Command(BaseCommand): help = "Update subschool administrator information, inheriting from each student's counselor" def add_arguments(self, parser: CommandParser) -> None: parser.add_argument( "filename", type=str, nargs="?", default=None, help="Path to SIS import CSV with a Student ID column, and optionally an Administrator column", ) parser.add_argument("--all", action="store_true", dest="all", default=False, help="Update every student who has not graduated") parser.add_argument( "--graduation-years", dest="graduation_years", nargs="+", type=int, default=None, metavar="YEAR", help="Update only students graduating in these years (e.g. --graduation-years 2027 2028)", ) parser.add_argument("--run", action="store_true", dest="run", default=False, help="Actually modifies the DB") def handle(self, *args: str, **kwargs: Any) -> None: filename = kwargs["filename"] all_students = kwargs["all"] graduation_years = kwargs["graduation_years"] if sum(map(bool, (filename, all_students, graduation_years))) != 1: raise CommandError("Pass exactly one of: a CSV filename, --all, or --graduation-years") to_run = kwargs["run"] if not to_run: sys.stdout.write( """This script is running in pretend mode. Pass --run to actually run this script. Please MAKE SURE you have updated info before running this script. Actually running is a destructive operation. """ ) # Collect the students to work through, paired with any administrator named for them targets = [] if filename: try: with open(filename, encoding="utf-8") as f: data = list(csv.DictReader(f)) except OSError as ex: raise CommandError(str(ex)) from ex if data and "Student ID" not in data[0]: raise CommandError(f"{filename} has no 'Student ID' column") for row in data: sid = row["Student ID"].strip() user = get_user_model().objects.user_with_student_id(sid) if user is None: sys.stdout.write(f"There is no Ion account found for SID {sid}\n") continue targets.append((user, (row.get("Administrator") or "").strip())) else: # Every one of these runs through the counselor chain, so pull it in up front students = get_user_model().objects.filter(user_type="student").select_related("counselor__administrator") if graduation_years: students = students.filter(graduation_year__in=graduation_years) else: students = students.filter(graduation_year__gte=get_senior_graduation_year()) sys.stdout.write(f"Working through {students.count()} student(s).\n") targets = [(user, "") for user in students] # One transaction, so a failure part way through does not split students across administrators with transaction.atomic(): for user, listed_name in targets: administrator = self.resolve_administrator(user, listed_name) if administrator is None: continue if administrator != user.administrator: sys.stdout.write(f"Switching administrator for {user.username} from {user.administrator} to {administrator}\n") if to_run: user.administrator = administrator user.save(update_fields=["administrator"])
[docs] def resolve_administrator(self, user: User, listed_name: str) -> User | None: """Returns the administrator to assign ``user``, or ``None`` if it cannot be determined.""" # Kept out of handle() because the try/except around the lookup, plus the counselor # fallback, pushes handle() past the complexity limit when inlined. # We assume every administrator has a unique last name name = listed_name.split(",")[0].strip() if name: try: return get_user_model().objects.get(user_type="teacher", last_name=name) except get_user_model().DoesNotExist: sys.stdout.write(f"There is no teacher account found with last name {name}\n") return None except get_user_model().MultipleObjectsReturned: sys.stdout.write(f"There are multiple teacher accounts with last name {name}\n") return None # Nothing named explicitly, so inherit from the counselor if user.counselor is None: sys.stdout.write(f"{user.username} has no administrator listed and no counselor to inherit one from\n") return None if user.counselor.administrator is None: sys.stdout.write(f"Counselor {user.counselor} has no administrator set, so {user.username} cannot inherit one\n") return None return user.counselor.administrator