Merge pull request #19771 from edx/dahlia/masters-track
Added master's track
This commit is contained in:
@@ -143,6 +143,7 @@ class CourseMode(models.Model):
|
||||
AUDIT = 'audit'
|
||||
NO_ID_PROFESSIONAL_MODE = 'no-id-professional'
|
||||
CREDIT_MODE = 'credit'
|
||||
MASTERS = 'masters'
|
||||
|
||||
DEFAULT_MODE = Mode(
|
||||
settings.COURSE_MODE_DEFAULTS['slug'],
|
||||
@@ -157,13 +158,13 @@ class CourseMode(models.Model):
|
||||
)
|
||||
DEFAULT_MODE_SLUG = settings.COURSE_MODE_DEFAULTS['slug']
|
||||
|
||||
ALL_MODES = [AUDIT, CREDIT_MODE, HONOR, NO_ID_PROFESSIONAL_MODE, PROFESSIONAL, VERIFIED, ]
|
||||
ALL_MODES = [AUDIT, CREDIT_MODE, HONOR, NO_ID_PROFESSIONAL_MODE, PROFESSIONAL, VERIFIED, MASTERS, ]
|
||||
|
||||
# Modes utilized for audit/free enrollments
|
||||
AUDIT_MODES = [AUDIT, HONOR]
|
||||
|
||||
# Modes that allow a student to pursue a verified certificate
|
||||
VERIFIED_MODES = [VERIFIED, PROFESSIONAL]
|
||||
VERIFIED_MODES = [VERIFIED, PROFESSIONAL, MASTERS]
|
||||
|
||||
# Modes that allow a student to pursue a non-verified certificate
|
||||
NON_VERIFIED_MODES = [HONOR, AUDIT, NO_ID_PROFESSIONAL_MODE]
|
||||
@@ -174,6 +175,9 @@ class CourseMode(models.Model):
|
||||
# Modes that are eligible to purchase credit
|
||||
CREDIT_ELIGIBLE_MODES = [VERIFIED, PROFESSIONAL, NO_ID_PROFESSIONAL_MODE]
|
||||
|
||||
# Modes for which certificates/programs may need to be updated
|
||||
CERTIFICATE_RELEVANT_MODES = CREDIT_MODES + CREDIT_ELIGIBLE_MODES + [MASTERS]
|
||||
|
||||
# Modes that are allowed to upsell
|
||||
UPSELL_TO_VERIFIED_MODES = [HONOR, AUDIT]
|
||||
|
||||
|
||||
@@ -12,9 +12,10 @@ class Command(BaseCommand):
|
||||
Enroll a user into a course
|
||||
"""
|
||||
help = """
|
||||
This enrolls a user into a given course with the default mode (e.g., 'honor', 'audit', etc).
|
||||
This enrolls a user into a given course
|
||||
|
||||
User email and course ID are required.
|
||||
Mode is optional. It defaults to the default mode (e.g., 'honor', 'audit', etc).
|
||||
|
||||
example:
|
||||
# Enroll a user test@example.com into the demo course
|
||||
@@ -35,7 +36,14 @@ class Command(BaseCommand):
|
||||
'-c', '--course',
|
||||
nargs=1,
|
||||
required=True,
|
||||
help='course ID to enroll the user in')
|
||||
help='course ID to enroll the user in'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-m', '--mode',
|
||||
required=False,
|
||||
default=None,
|
||||
help='course mode to enroll the user in'
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""
|
||||
@@ -43,10 +51,11 @@ class Command(BaseCommand):
|
||||
"""
|
||||
email = options['email'][0]
|
||||
course = options['course'][0]
|
||||
mode = options['mode']
|
||||
|
||||
user = User.objects.get(email=email)
|
||||
try:
|
||||
add_enrollment(user.username, course)
|
||||
add_enrollment(user.username, course, mode=mode)
|
||||
except CourseEnrollmentExistsError:
|
||||
# If the user is already enrolled in the course, do nothing.
|
||||
pass
|
||||
|
||||
25
lms/djangoapps/courseware/rules.py
Normal file
25
lms/djangoapps/courseware/rules.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
django-rules for courseware related features
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from student.models import CourseEnrollment
|
||||
|
||||
import rules
|
||||
|
||||
|
||||
@rules.predicate
|
||||
def is_verified_or_masters_track_exam(user, exam):
|
||||
"""
|
||||
Returns whether the user is in a verified or master's track
|
||||
"""
|
||||
course_id = CourseKey.from_string(exam['course_id'])
|
||||
mode, is_active = CourseEnrollment.enrollment_mode_for_user(user, course_id)
|
||||
return is_active and mode in ('verified', 'masters')
|
||||
|
||||
|
||||
# The edx_proctoring.api uses this permission to gate access to the
|
||||
# proctored experience
|
||||
can_take_proctored_exam = is_verified_or_masters_track_exam
|
||||
rules.set_perm('edx_proctoring.can_take_proctored_exam', is_verified_or_masters_track_exam)
|
||||
48
lms/djangoapps/courseware/tests/test_rules.py
Normal file
48
lms/djangoapps/courseware/tests/test_rules.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
Tests for permissions defined in courseware.rules
|
||||
"""
|
||||
import ddt
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from course_modes.tests.factories import CourseModeFactory
|
||||
|
||||
from opaque_keys.edx.locator import CourseLocator
|
||||
from student.models import CourseEnrollment
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class PermissionTests(TestCase):
|
||||
"""
|
||||
Tests for permissions defined in courseware.rules
|
||||
"""
|
||||
def setUp(self):
|
||||
super(PermissionTests, self).setUp()
|
||||
self.user = UserFactory()
|
||||
|
||||
self.course_id = CourseLocator('MITx', '000', 'Perm_course')
|
||||
CourseModeFactory(mode_slug='verified', course_id=self.course_id)
|
||||
CourseModeFactory(mode_slug='masters', course_id=self.course_id)
|
||||
|
||||
def tearDown(self):
|
||||
super(PermissionTests, self).tearDown()
|
||||
self.user.delete()
|
||||
|
||||
@ddt.data(
|
||||
(None, False),
|
||||
('audit', False),
|
||||
('verified', True),
|
||||
('masters', True),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_proctoring_perm(self, mode, should_have_perm):
|
||||
"""
|
||||
Test that the user has the edx_proctoring.can_take_proctored_exam permission
|
||||
"""
|
||||
if mode is not None:
|
||||
CourseEnrollment.enroll(self.user, self.course_id, mode=mode)
|
||||
else:
|
||||
CourseEnrollment.unenroll(self.user, self.course_id)
|
||||
has_perm = self.user.has_perm('edx_proctoring.can_take_proctored_exam', {'course_id': unicode(self.course_id)})
|
||||
assert has_perm == should_have_perm
|
||||
@@ -3384,6 +3384,12 @@ COURSE_ENROLLMENT_MODES = {
|
||||
"display_name": _("Honor"),
|
||||
"min_price": 0
|
||||
},
|
||||
"masters": {
|
||||
"id": 7,
|
||||
"slug": "masters",
|
||||
"display_name": _("Master's"),
|
||||
"min_price": 0
|
||||
},
|
||||
}
|
||||
|
||||
CONTENT_TYPE_GATE_GROUP_IDS = {
|
||||
|
||||
@@ -17,7 +17,7 @@ log = getLogger(__name__)
|
||||
|
||||
|
||||
# "interesting" here means "credentials will want to know about it"
|
||||
INTERESTING_MODES = CourseMode.CREDIT_ELIGIBLE_MODES + CourseMode.CREDIT_MODES
|
||||
INTERESTING_MODES = CourseMode.CERTIFICATE_RELEVANT_MODES
|
||||
INTERESTING_STATUSES = [
|
||||
CertificateStatuses.notpassing,
|
||||
CertificateStatuses.downloadable,
|
||||
|
||||
@@ -43,6 +43,8 @@ class TestCredentialsSignalsSendGrade(TestCase):
|
||||
[True, 'no-id-professional', 'downloadable'],
|
||||
[True, 'credit', 'downloadable'],
|
||||
[True, 'verified', 'notpassing'],
|
||||
[True, 'masters', 'downloadable'],
|
||||
[True, 'masters', 'notpassing'],
|
||||
[False, 'audit', 'downloadable'],
|
||||
[False, 'professional', 'generating'],
|
||||
[False, 'no-id-professional', 'generating'],
|
||||
|
||||
@@ -304,7 +304,7 @@ def award_course_certificate(self, username, course_run_key):
|
||||
username
|
||||
)
|
||||
return
|
||||
if certificate.mode in CourseMode.CREDIT_ELIGIBLE_MODES + CourseMode.CREDIT_MODES:
|
||||
if certificate.mode in CourseMode.CERTIFICATE_RELEVANT_MODES:
|
||||
try:
|
||||
course_overview = CourseOverview.get_from_id(course_key)
|
||||
except (CourseOverview.DoesNotExist, IOError):
|
||||
|
||||
Reference in New Issue
Block a user