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
115 lines
5.0 KiB
Python
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()
|