Skip to content

Commit

Permalink
3649 all mitxonline courses should have a department (#2132)
Browse files Browse the repository at this point in the history
* Add department as requirement course program

* Format migration

* Update docs and cleanup.

* hold

* Update docs for tutor, fix bugs

* Repair tests
  • Loading branch information
collinpreston authored Mar 27, 2024
1 parent 57519ef commit 19ea70c
Show file tree
Hide file tree
Showing 14 changed files with 392 additions and 297 deletions.
234 changes: 162 additions & 72 deletions courses/management/commands/create_courseware.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
Creates a courseware object. This can be a program or a course (and optionally
a course run).
"""

from typing import Union
from django.db import models
from typing import List

from django.core.management import BaseCommand

from courses.models import Course, CourseRun, Department, Program
Expand All @@ -15,7 +20,108 @@ class Command(BaseCommand):

help = "Creates a courseware object (a program or course, with or without a courserun)."

def create_course_run(self, course, **kwargs):
def _check_if_courseware_object_readable_id_exists(
self, courseware_object: Union[Course, Program], courseware_id: str
):
"""
Queries courseware objects of the same type as the courseware_object
parameter to see if any exist with an ID matching the courseware_id
parameter. If any courseware objects exist in the query, an error
message is output and the command exits with a -1 status.
Args:
courseware_object (Union[Course, Program]): The type of courseware object being queried.
courseware_id (str): The courseware ID being used in the query.
"""
if courseware_object.objects.filter(readable_id=courseware_id).exists():
self.stderr.write(
self.style.ERROR(
f"{courseware_object.__name__} with ID {courseware_id} already exists."
)
)
exit(-1)

def _add_departments_to_courseware_object(
self, courseware_object: Union[Course, Program], departments: models.QuerySet
):
"""
Associates Departments with a Course or Program object.
Args:
courseware_object (Union[Course, Program]): Either a Course or Program object.
departments (models.QuerySet): A QuerySet of Department objects.
"""
if departments:
for dept in departments:
courseware_object.departments.add(dept.id)
courseware_object.save()
else:
self.stderr.write(
self.style.ERROR(
"There was an issue creating or adding departments to the courseware object."
)
)
exit(-1)

def _create_departments(self, departments: List[str]) -> models.QuerySet:
"""
Creates Department objects from a list of department names (strings).
Args:
departments (List[str]): List of department names.
Returns:
models.QuerySet: Query set containing all of the departments specified
in the list of department names.
"""
add_depts = Department.objects.filter(name__in=departments.split()).all()
for dept in departments.split():
found = len([db_dept for db_dept in add_depts if db_dept.name == dept]) > 0
if not found:
Department.objects.create(name=dept)

return Department.objects.filter(name__in=departments.split()).all()

def _department_must_be_defined_error(self):
"""
Outputs an error message indicating that departments must be
specified when creating a course or program object
and exits the command with a -1 status.
"""
self.stderr.write(
self.style.ERROR(
"Departments must be defined when creating a course or program."
)
)
exit(-1)

def _departments_do_not_exist_error(self):
"""
Outputs an error message indicating the specified departments
do no currently exist and exits the command with a -1 status.
"""
self.stderr.write(
self.style.ERROR("The departments specified do not currently exist.")
)
exit(-1)

def _successfully_created_courseware_object_message(
self, courseware_object: Union[Course, Program]
):
"""
Outputs a success message to the console in the format of
"Created <coureware_object_type> <courseware_object_id>: <courseware_object_title> (<courseware_object_readable_id>)"
Args:
courseware_object (Union[Course, Program]): The courseware object that the message is related to.
"""
self.stdout.write(
self.style.SUCCESS(
f"Created {courseware_object.__class__.__name__} {courseware_object.id}: {courseware_object.title} ({courseware_object.readable_id})."
)
)

def _create_course_run(self, course, **kwargs):
run_id = f"{course.readable_id}+{kwargs['create_run']}"

if CourseRun.objects.filter(
Expand All @@ -36,19 +142,23 @@ def create_course_run(self, course, **kwargs):
courseware_url_path=kwargs["run_url"],
live=kwargs["live"],
is_self_paced=kwargs["self_paced"],
start_date=parse_supplied_date(kwargs["start"])
if kwargs["start"]
else None,
start_date=(
parse_supplied_date(kwargs["start"]) if kwargs["start"] else None
),
end_date=parse_supplied_date(kwargs["end"]) if kwargs["end"] else None,
enrollment_start=parse_supplied_date(kwargs["enrollment_start"])
if kwargs["enrollment_start"]
else None,
enrollment_end=parse_supplied_date(kwargs["enrollment_end"])
if kwargs["enrollment_end"]
else None,
upgrade_deadline=parse_supplied_date(kwargs["upgrade"])
if kwargs["upgrade"]
else None,
enrollment_start=(
parse_supplied_date(kwargs["enrollment_start"])
if kwargs["enrollment_start"]
else None
),
enrollment_end=(
parse_supplied_date(kwargs["enrollment_end"])
if kwargs["enrollment_end"]
else None
),
upgrade_deadline=(
parse_supplied_date(kwargs["upgrade"]) if kwargs["upgrade"] else None
),
)

self.stdout.write(
Expand Down Expand Up @@ -172,7 +282,7 @@ def add_arguments(self, parser) -> None:
"-d",
"--dept",
"--department",
help="Add the courseware object to the specified department(s).",
help="Specify department(s) assigned to the courseware object.",
action="append",
dest="depts",
)
Expand All @@ -196,46 +306,30 @@ def handle(self, *args, **kwargs): # pylint: disable=unused-argument
)
exit(-1)

if kwargs["depts"] and len(kwargs["depts"]) > 0:
add_depts = Department.objects.filter(name__in=kwargs["depts"]).all()
if kwargs["type"] == "program":
if kwargs["depts"] and len(kwargs["depts"]) > 0:
add_depts = Department.objects.filter(name__in=kwargs["depts"]).all()
else:
self._department_must_be_defined_error()

if kwargs["create_depts"]:
for dept in kwargs["depts"]:
found = (
len([db_dept for db_dept in add_depts if db_dept.name == dept])
> 0
)

if not found:
new_dept = Department(name=dept)
new_dept.save()

add_depts = Department.objects.filter(name__in=kwargs["depts"]).all()
add_depts = self._create_departments(kwargs["depts"])
elif not add_depts:
self._departments_do_not_exist_error()

if kwargs["type"] == "program":
if Program.objects.filter(readable_id=kwargs["courseware_id"]).exists():
self.stderr.write(
self.style.ERROR(
f"Program with ID {kwargs['courseware_id']} already exists."
)
)
exit(-1)
self._check_if_courseware_object_readable_id_exists(
Program, kwargs["courseware_id"]
)

new_program = Program.objects.create(
readable_id=kwargs["courseware_id"],
title=kwargs["title"],
live=kwargs["live"],
)

if "add_depts" in locals():
new_program.departments.add(*add_depts)
new_program.save()
self._add_departments_to_courseware_object(new_program, add_depts)

self.stdout.write(
self.style.SUCCESS(
f"Created program {new_program.id}: {new_program.title} ({new_program.readable_id})."
)
)
self._successfully_created_courseware_object_message(new_program)

if kwargs["related"] is not None and len(kwargs["related"]) > 0:
for readable_id in kwargs["related"]:
Expand All @@ -261,44 +355,30 @@ def handle(self, *args, **kwargs): # pylint: disable=unused-argument
)
)
elif kwargs["type"] == "course":
if Course.objects.filter(readable_id=kwargs["courseware_id"]).exists():
self.stderr.write(
self.style.ERROR(
f"Course with ID {kwargs['courseware_id']} already exists."
)
)
exit(-1)

program = None

if "program" in kwargs and kwargs["program"] is not None:
try:
program = Program.objects.filter(pk=kwargs["program"]).first()
except:
program = Program.objects.filter(
readable_id=kwargs["program"]
).first()
self._check_if_courseware_object_readable_id_exists(
Course, kwargs["courseware_id"]
)

new_course = Course.objects.create(
title=kwargs["title"],
readable_id=kwargs["courseware_id"],
live=kwargs["live"],
)

if "add_depts" in locals():
new_course.departments.add(*add_depts)
new_course.save()
if kwargs["depts"] and len(kwargs["depts"]) > 0:
add_depts = Department.objects.filter(name__in=kwargs["depts"]).all()
else:
self._department_must_be_defined_error()

self.stdout.write(
self.style.SUCCESS(
f"Created course {new_course.id}: {new_course.title} ({new_course.readable_id})"
)
)
if kwargs["create_depts"]:
add_depts = self._create_departments(kwargs["depts"])
if "add_depts" not in locals() or not add_depts:
self._departments_do_not_exist_error()

if "create_run" in kwargs and kwargs["create_run"] is not None:
run_id = f"{new_course.readable_id}+{kwargs['create_run']}"
self._successfully_created_courseware_object_message(new_course)

self.create_course_run(new_course, **kwargs)
if "create_run" in kwargs and kwargs["create_run"] is not None:
self._create_course_run(new_course, **kwargs)

if kwargs["live"] or kwargs["force"]:
if kwargs["force"]:
Expand All @@ -310,6 +390,16 @@ def handle(self, *args, **kwargs): # pylint: disable=unused-argument

new_req = None

if "program" in kwargs and kwargs["program"] is not None:
try:
program = Program.objects.filter(pk=kwargs["program"]).first()
except:
program = Program.objects.filter(
readable_id=kwargs["program"]
).first()

self._add_departments_to_courseware_object(program, add_depts)

if program is not None and kwargs["required"]:
new_req = program.add_requirement(new_course)
elif program is not None and kwargs["elective"]:
Expand Down Expand Up @@ -344,6 +434,6 @@ def handle(self, *args, **kwargs): # pylint: disable=unused-argument

course = Course.objects.filter(readable_id=kwargs["courseware_id"]).get()

self.create_course_run(course, **kwargs)
self._create_course_run(course, **kwargs)
else:
self.stderr.write(self.style.ERROR(f"Not sure what {kwargs['type']} is."))
Loading

0 comments on commit 19ea70c

Please sign in to comment.