add pending role assignment model (#23506)

This commit is contained in:
Zachary Hancock
2020-03-27 14:29:09 -04:00
committed by GitHub
parent c4bd06210e
commit b9bafed817
6 changed files with 171 additions and 4 deletions

View File

@@ -5,7 +5,11 @@ from django.contrib import admin
from django.urls import reverse
from django.utils.html import format_html
from lms.djangoapps.program_enrollments.models import ProgramCourseEnrollment, ProgramEnrollment
from lms.djangoapps.program_enrollments.models import (
CourseAccessRoleAssignment,
ProgramCourseEnrollment,
ProgramEnrollment
)
class ProgramEnrollmentAdmin(admin.ModelAdmin):
@@ -67,7 +71,6 @@ def _pce_ce(pce):
enrollment=enrollment, active_string=active_string
)
_pce_pe_id.short_description = "Program Enrollment"
_pce_pe_user.short_description = "Pgm Enrollment: User"
_pce_pe_external_user_key.short_description = "Pgm Enrollment: Ext User Key"
@@ -102,5 +105,45 @@ class ProgramCourseEnrollmentAdmin(admin.ModelAdmin):
raw_id_fields = ('program_enrollment', 'course_enrollment')
def _pending_role_assignment_enrollment_id(pending_role_assignment):
"""
Generate a link to edit enrollment, with ID in link text.
"""
pce = pending_role_assignment.enrollment
if not pce:
return None
link_url = reverse(
"admin:program_enrollments_programcourseenrollment_change",
args=[pce.id],
)
link_text = "id={pce.id:05}".format(pce=pce)
return format_html("<a href={}>{}</a>", link_url, link_text)
def _pending_role_assignment_external_user_key(pending_role_assignment):
"""
Generate the external user key for a pending role assignment
"""
pce = pending_role_assignment.enrollment
return _pce_pe_external_user_key(pce)
_pending_role_assignment_enrollment_id.short_description = "Program Course Enrollment"
_pending_role_assignment_external_user_key.short_description = "Pgm Enrollment: Ext User Key"
class CourseAccessRoleAssignmentAdmin(admin.ModelAdmin):
"""
Admin tool for the CourseAccessRoleAssignment model
"""
list_display = (
'id',
'role',
_pending_role_assignment_enrollment_id,
_pending_role_assignment_external_user_key
)
list_filter = ('role',)
raw_id_fields = ('enrollment',)
admin.site.register(ProgramEnrollment, ProgramEnrollmentAdmin)
admin.site.register(ProgramCourseEnrollment, ProgramCourseEnrollmentAdmin)
admin.site.register(CourseAccessRoleAssignment, CourseAccessRoleAssignmentAdmin)

View File

@@ -2,6 +2,7 @@
Constants used throughout the program_enrollments app and exposed to other
in-process apps through api.py.
"""
from student.roles import CourseStaffRole
class ProgramEnrollmentStatuses(object):
@@ -113,3 +114,17 @@ class ProgramCourseOperationStatuses(
__OK__ = ProgramCourseEnrollmentStatuses.__ALL__
__ERRORS__ = (NOT_FOUND,) + _EnrollmentErrorStatuses.__ALL__
__ALL__ = __OK__ + __ERRORS__
class ProgramCourseEnrollmentRoles(object):
"""
Valid roles that can be assigned as part of a ProgramCourseEnrollment
"""
COURSE_STAFF = CourseStaffRole.ROLE
__ALL__ = (COURSE_STAFF,)
# Note: Any changes to this value will trigger a migration on
# CourseAccessRoleAssignment!
__MODEL_CHOICES__ = (
(role, role) for role in __ALL__
)

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-03-25 19:28
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import model_utils.fields
class Migration(migrations.Migration):
dependencies = [
('program_enrollments', '0009_update_course_enrollment_field_to_foreign_key'),
]
operations = [
migrations.CreateModel(
name='CourseAccessRoleAssignment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('role', models.CharField(choices=[('staff', 'staff')], max_length=64)),
('enrollment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='program_enrollments.ProgramCourseEnrollment')),
],
),
migrations.AlterUniqueTogether(
name='CourseAccessRoleAssignment',
unique_together=set([('role', 'enrollment')]),
),
]

View File

@@ -14,7 +14,7 @@ from simple_history.models import HistoricalRecords
from student.models import CourseEnrollment
from .constants import ProgramCourseEnrollmentStatuses, ProgramEnrollmentStatuses
from .constants import ProgramCourseEnrollmentRoles, ProgramCourseEnrollmentStatuses, ProgramEnrollmentStatuses
class ProgramEnrollment(TimeStampedModel):
@@ -149,3 +149,28 @@ class ProgramCourseEnrollment(TimeStampedModel):
" status={self.status!r}"
">"
).format(self=self)
class CourseAccessRoleAssignment(TimeStampedModel):
"""
This model represents a role that should be assigned to the eventual user of a pending enrollment.
.. no_pii:
"""
class Meta(object):
unique_together = ('role', 'enrollment')
role = models.CharField(max_length=64, choices=ProgramCourseEnrollmentRoles.__MODEL_CHOICES__)
enrollment = models.ForeignKey(ProgramCourseEnrollment, on_delete=models.CASCADE)
def __str__(self):
return '[CourseAccessRoleAssignment id={}]'.format(self.id)
def __repr__(self):
return (
"<CourseAccessRoleAssignment" # pylint: disable=missing-format-attribute
" id={self.id}"
" role={self.role!r}"
" enrollment={self.enrollment!r}"
">"
).format(self=self)

View File

@@ -45,3 +45,12 @@ class ProgramCourseEnrollmentFactory(DjangoModelFactory):
)
)
status = 'active'
class CourseAccessRoleAssignmentFactory(DjangoModelFactory):
""" A factory for the CourseAccessRoleAssignment model. """
class Meta(object):
model = models.CourseAccessRoleAssignment
enrollment = factory.SubFactory(ProgramCourseEnrollmentFactory)
role = 'staff'

View File

@@ -15,8 +15,13 @@ from course_modes.models import CourseMode
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
from student.tests.factories import CourseEnrollmentFactory, UserFactory
from ..constants import ProgramCourseEnrollmentRoles
from ..models import ProgramEnrollment
from .factories import ProgramCourseEnrollmentFactory, ProgramEnrollmentFactory
from .factories import (
CourseAccessRoleAssignmentFactory,
ProgramCourseEnrollmentFactory,
ProgramEnrollmentFactory
)
class ProgramEnrollmentModelTests(TestCase):
@@ -200,3 +205,41 @@ class ProgramCourseEnrollmentModelTests(TestCase):
course_enrollment=None,
status="active"
)
class CourseAccessRoleAssignmentTests(TestCase):
"""
Tests for the CourseAccessRoleAssignment model.
"""
def setUp(self):
super(CourseAccessRoleAssignmentTests, self).setUp()
self.program_course_enrollment = ProgramCourseEnrollmentFactory()
self.pending_role_assignment = CourseAccessRoleAssignmentFactory(
enrollment=self.program_course_enrollment,
role=ProgramCourseEnrollmentRoles.COURSE_STAFF,
)
def test_str_and_repr(self):
"""
Make sure str() and repr() work correctly on instances of this model.
"""
assert str(self.pending_role_assignment) == "[CourseAccessRoleAssignment id=1]"
# The record contains timestamp information, and a repeat of the ProgramCourseEnrollment repr()
# already tested above, let's just test the parts of the repr()
# that come before that.
assert (
"<CourseAccessRoleAssignment id=1"
" role='staff'"
" enrollment=<ProgramCourseEnrollment id=1"
) in repr(self.pending_role_assignment)
def test_unique(self):
"""
Multiple records with the same enrollment and role cannot be created
"""
with self.assertRaises(IntegrityError):
CourseAccessRoleAssignmentFactory(
enrollment=self.program_course_enrollment,
role=ProgramCourseEnrollmentRoles.COURSE_STAFF,
)