Files
edx-platform/cms/djangoapps/course_creators/models.py
Farhaan Bukhsh 48ad7effb1 feat: grant course/library creation rights by organization (#26616)
Current State (before this commit):

  Studio, as of today doesn't have a way to restrict a user to
  create a course in a particular organization. What Studio
  provides right now is a CourseCreator permission which gives
  an Admin the power to grant a user the permission to create
  a course.

  For example: If the Admin has given a user Spiderman the
  permission to create courses, Spiderman can now create courses
  in any organization i.e Marvel as well as DC.
  There is no way to restrict Spiderman from creating courses
  under DC.

Purpose of this commit:

  The changes done here gives Admin the ability to restrict a
  user on an Organization level from creating courses via the
  Course Creators section of the Studio Django administration
  panel.

  For example: Now, the Admin can give the user Spiderman the
  privilege of creating courses only under Marvel organization.
  The moment Spiderman tries to create a course under some
  other organization(i.e DC), Studio will show an error message.

  This change is available to all Studio instances that
  enable the FEATURES['ENABLE_CREATOR_GROUP'] flag.
  Regardless of the flag, it will not affect any instances that choose
  not to use it.

BB-3622
2021-09-10 12:43:26 -04:00

115 lines
5.0 KiB
Python

"""
Table for storing information about whether or not Studio users have course creation privileges.
"""
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
from django.db import models
from django.db.models.signals import post_init, post_save
from django.dispatch import Signal, receiver
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from organizations.models import Organization
# A signal that will be sent when users should be added or removed from the creator group
update_creator_state = Signal(providing_args=["caller", "user", "state", "organizations"])
# A signal that will be sent when admin should be notified of a pending user request
send_admin_notification = Signal(providing_args=["user"])
# A signal that will be sent when user should be notified of change in course creator privileges
send_user_notification = Signal(providing_args=["user", "state"])
class CourseCreator(models.Model):
"""
Creates the database table model.
.. no_pii:
"""
UNREQUESTED = 'unrequested'
PENDING = 'pending'
GRANTED = 'granted'
DENIED = 'denied'
# Second value is the "human-readable" version.
STATES = (
(UNREQUESTED, _('unrequested')),
(PENDING, _('pending')),
(GRANTED, _('granted')),
(DENIED, _('denied')),
)
user = models.OneToOneField(User, help_text=_("Studio user"), on_delete=models.CASCADE)
state_changed = models.DateTimeField('state last updated', auto_now_add=True,
help_text=_("The date when state was last updated"))
state = models.CharField(max_length=24, blank=False, choices=STATES, default=UNREQUESTED,
help_text=_("Current course creator state"))
note = models.CharField(max_length=512, blank=True, help_text=_("Optional notes about this user (for example, "
"why course creation access was denied)"))
organizations = models.ManyToManyField(Organization, blank=True,
help_text=_("Organizations under which the user is allowed "
"to create courses."))
all_organizations = models.BooleanField(default=True,
help_text=_("Grant the user the permission to create courses "
"in ALL organizations"))
def __str__(self):
return f"{self.user} | {self.state} [{self.state_changed}]"
@receiver(post_init, sender=CourseCreator)
def post_init_callback(sender, **kwargs): # lint-amnesty, pylint: disable=unused-argument
"""
Extend to store previous state.
"""
instance = kwargs['instance']
instance.orig_state = instance.state
instance.orig_all_organizations = instance.all_organizations
@receiver(post_save, sender=CourseCreator)
def post_save_callback(sender, **kwargs):
"""
Extend to update state_changed time and fire event to update course creator group, if appropriate.
"""
instance = kwargs['instance']
# We only wish to modify the state_changed time if the state has been modified. We don't wish to
# modify it for changes to the notes field.
# We need to keep track of all_organization switch. If this switch is changed we are going to remove the
# Course Creator group.
if instance.state != instance.orig_state or instance.all_organizations != instance.orig_all_organizations:
granted_state_change = instance.state == CourseCreator.GRANTED or instance.orig_state == CourseCreator.GRANTED
# If either old or new state is 'granted', we must manipulate the course creator
# group maintained by authz. That requires staff permissions (stored admin).
if granted_state_change:
assert hasattr(instance, 'admin'), 'Must have stored staff user to change course creator group'
update_creator_state.send(
sender=sender,
caller=instance.admin,
user=instance.user,
state=instance.state,
all_organizations=instance.all_organizations
)
# If user has been denied access, granted access, or previously granted access has been
# revoked, send a notification message to the user.
if instance.state == CourseCreator.DENIED or granted_state_change:
send_user_notification.send(
sender=sender,
user=instance.user,
state=instance.state
)
# If the user has gone into the 'pending' state, send a notification to interested admin.
if instance.state == CourseCreator.PENDING:
send_admin_notification.send(
sender=sender,
user=instance.user
)
instance.state_changed = timezone.now()
instance.orig_state = instance.state
instance.orig_all_organizations = instance.all_organizations
instance.save()