Files
edx-platform/common/djangoapps/course_action_state/managers.py
Awais Qureshi 4ab536e58d BOM-2373
Run Pyupgrade on course-action-state.
2021-02-23 16:46:07 +05:00

155 lines
5.6 KiB
Python

"""
Model Managers for Course Actions
"""
import traceback
from django.db import models, transaction
class CourseActionStateManager(models.Manager):
"""
An abstract Model Manager class for Course Action State models.
This abstract class expects child classes to define the ACTION (string) field.
"""
class Meta:
"""Abstract manager class, with subclasses defining the ACTION (string) field."""
abstract = True
def find_all(self, exclude_args=None, **kwargs):
"""
Finds and returns all entries for this action and the given field names-and-values in kwargs.
The exclude_args dict allows excluding entries with the field names-and-values in exclude_args.
"""
return self.filter(action=self.ACTION, **kwargs).exclude(**(exclude_args or {}))
def find_first(self, exclude_args=None, **kwargs):
"""
Returns the first entry for the this action and the given fields in kwargs, if found.
The exclude_args dict allows excluding entries with the field names-and-values in exclude_args.
Raises ItemNotFoundError if more than 1 entry is found.
There may or may not be greater than one entry, depending on the usage pattern for this Action.
"""
objects = self.find_all(exclude_args=exclude_args, **kwargs)
if len(objects) == 0: # lint-amnesty, pylint: disable=no-else-raise
raise CourseActionStateItemNotFoundError(
"No entry found for action {action} with filter {filter}, excluding {exclude}".format(
action=self.ACTION,
filter=kwargs,
exclude=exclude_args,
))
else:
return objects[0]
def delete(self, entry_id):
"""
Deletes the entry with given id.
"""
self.filter(id=entry_id).delete()
class CourseActionUIStateManager(CourseActionStateManager):
"""
A Model Manager subclass of the CourseActionStateManager class that is aware of UI-related fields related
to state management, including "should_display" and "message".
"""
# add transaction protection to revert changes by get_or_create if an exception is raised before the final save.
@transaction.atomic
def update_state(
self, course_key, new_state, should_display=True, message="", user=None, allow_not_found=False, **kwargs
):
"""
Updates the state of the given course for this Action with the given data.
If allow_not_found is True, automatically creates an entry if it doesn't exist.
Raises CourseActionStateException if allow_not_found is False and an entry for the given course
for this Action doesn't exist.
"""
state_object, created = self.get_or_create(course_key=course_key, action=self.ACTION)
if created:
if allow_not_found:
state_object.created_user = user
else:
raise CourseActionStateItemNotFoundError(
"Cannot update non-existent entry for course_key {course_key} and action {action}".format(
action=self.ACTION,
course_key=course_key,
))
# some state changes may not be user-initiated so override the user field only when provided
if user:
state_object.updated_user = user
state_object.state = new_state
state_object.should_display = should_display
state_object.message = message
# update any additional fields in kwargs
if kwargs:
for key, value in kwargs.items():
setattr(state_object, key, value)
state_object.save()
return state_object
def update_should_display(self, entry_id, user, should_display):
"""
Updates the should_display field with the given value for the entry for the given id.
"""
return self.update(id=entry_id, updated_user=user, should_display=should_display)
class CourseRerunUIStateManager(CourseActionUIStateManager):
"""
A concrete model Manager for the Reruns Action.
"""
ACTION = "rerun"
class State:
"""
An Enum class for maintaining the list of possible states for Reruns.
"""
IN_PROGRESS = "in_progress"
FAILED = "failed"
SUCCEEDED = "succeeded"
def initiated(self, source_course_key, destination_course_key, user, display_name):
"""
To be called when a new rerun is initiated for the given course by the given user.
"""
self.update_state(
course_key=destination_course_key,
new_state=self.State.IN_PROGRESS,
user=user,
allow_not_found=True,
source_course_key=source_course_key,
display_name=display_name,
)
def succeeded(self, course_key):
"""
To be called when an existing rerun for the given course has successfully completed.
"""
self.update_state(
course_key=course_key,
new_state=self.State.SUCCEEDED,
)
def failed(self, course_key):
"""
To be called within an exception handler when an existing rerun for the given course has failed.
"""
self.update_state(
course_key=course_key,
new_state=self.State.FAILED,
message=traceback.format_exc()[-self.model.MAX_MESSAGE_LENGTH:], # truncate to fit
)
class CourseActionStateItemNotFoundError(Exception):
"""An exception class for errors specific to Course Action states."""
pass # lint-amnesty, pylint: disable=unnecessary-pass