Commit 58fea44f by Bill DeRusha

HACK Course Modes - Add course mode configs for capability based mode definition

parent 75945b8c
......@@ -14,7 +14,7 @@ from opaque_keys import InvalidKeyError
from util.date_utils import get_time_display
from xmodule.modulestore.django import modulestore
from course_modes.models import CourseMode, CourseModeExpirationConfig
from course_modes.models import CourseMode, CourseModeConfig, CourseModeExpirationConfig
# Technically, we shouldn't be doing this, since verify_student is defined
# in LMS, and course_modes is defined in common.
......@@ -185,7 +185,8 @@ class CourseModeAdmin(admin.ModelAdmin):
'_expiration_datetime',
'verification_deadline',
'sku',
'bulk_sku'
'bulk_sku',
'course_mode_config',
)
search_fields = ('course_id',)
......@@ -197,7 +198,8 @@ class CourseModeAdmin(admin.ModelAdmin):
'min_price',
'expiration_datetime_custom',
'sku',
'bulk_sku'
'bulk_sku',
'course_mode_config',
)
def expiration_datetime_custom(self, obj):
......@@ -216,5 +218,13 @@ class CourseModeExpirationConfigAdmin(admin.ModelAdmin):
class Meta(object):
model = CourseModeExpirationConfig
class CourseModeConfigAdmin(admin.ModelAdmin):
"""Admin interface for the course mode auto expiration configuration. """
class Meta(object):
model = CourseModeConfig
admin.site.register(CourseMode, CourseModeAdmin)
admin.site.register(CourseModeExpirationConfig, CourseModeExpirationConfigAdmin)
admin.site.register(CourseModeConfig, CourseModeConfigAdmin)
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
import model_utils.fields
class Migration(migrations.Migration):
dependencies = [
('course_modes', '0007_coursemode_bulk_sku'),
]
operations = [
migrations.CreateModel(
name='CourseModeConfig',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
('name', models.CharField(max_length=100)),
('display_name', models.CharField(max_length=255)),
('certificate', models.BooleanField(default=False, verbose_name='Certificate')),
('id_verification', models.BooleanField(default=False, verbose_name='ID Verification')),
('credit_eligible', models.BooleanField(default=False, verbose_name='Credit Eligible')),
('cohort', models.BooleanField(default=False, verbose_name='Cohort')),
('upsell_course_mode', models.ForeignKey(default=None, blank=True, to='course_modes.CourseModeConfig', null=True, verbose_name='Upsell Course Mode Config')),
],
),
migrations.AddField(
model_name='coursemode',
name='created',
field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False),
),
migrations.AddField(
model_name='coursemode',
name='modified',
field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False),
),
migrations.AddField(
model_name='coursemode',
name='course_mode_config',
field=models.ForeignKey(default=None, blank=True, to='course_modes.CourseModeConfig', null=True, verbose_name='Course Mode Config'),
),
]
......@@ -10,6 +10,7 @@ from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from model_utils.models import TimeStampedModel
from xmodule_django.models import CourseKeyField
Mode = namedtuple('Mode',
......@@ -26,7 +27,7 @@ Mode = namedtuple('Mode',
])
class CourseMode(models.Model):
class CourseMode(TimeStampedModel):
"""
We would like to offer a course in a variety of modes.
......@@ -109,12 +110,20 @@ class CourseMode(models.Model):
)
)
course_mode_config = models.ForeignKey(
'CourseModeConfig',
null=True,
blank=True,
default=None,
verbose_name=_("Course Mode Config")
)
HONOR = 'honor'
PROFESSIONAL = 'professional'
VERIFIED = "verified"
AUDIT = "audit"
NO_ID_PROFESSIONAL_MODE = "no-id-professional"
CREDIT_MODE = "credit"
VERIFIED = 'verified'
AUDIT = 'audit'
NO_ID_PROFESSIONAL_MODE = 'no-id-professional'
CREDIT_MODE = 'credit'
DEFAULT_MODE = Mode(AUDIT, _('Audit'), 0, '', 'usd', None, None, None, None)
DEFAULT_MODE_SLUG = AUDIT
......@@ -125,6 +134,8 @@ class CourseMode(models.Model):
# Modes that allow a student to pursue a non-verified certificate
NON_VERIFIED_MODES = [HONOR, AUDIT, NO_ID_PROFESSIONAL_MODE]
CERTIFICATE_GRANTING_MODES = [VERIFIED, PROFESSIONAL, NO_ID_PROFESSIONAL_MODE]
# Modes that allow a student to earn credit with a university partner
CREDIT_MODES = [CREDIT_MODE]
......@@ -167,7 +178,11 @@ class CourseMode(models.Model):
NOTE (CCB): This is a silly hack needed because all of the class methods use tuples
with a property named slug instead of mode_slug.
"""
return self.mode_slug
course_mode_config = self.course_mode_config
if course_mode_config:
return course_mode_config.name
else:
return self.mode_slug
@property
def expiration_datetime(self):
......@@ -182,6 +197,50 @@ class CourseMode(models.Model):
self.expiration_datetime_is_explicit = True
self._expiration_datetime = new_datetime
@property
def has_id_verification(self):
"""
Returns whether or not the course_mode allows id verification.
"""
course_mode_config = self.course_mode_config
if course_mode_config:
return course_mode_config.id_verification
else:
return self.mode_slug in self.VERIFIED_MODES
@property
def has_certificate(self):
"""
Returns whether or not the course_mode grants a certificate.
"""
course_mode_config = self.course_mode_config
if course_mode_config:
return course_mode_config.certificate
else:
return self.mode_slug in self.CERTIFICATE_GRANTING_MODES
@property
def has_cohorting(self):
"""
Returns whether or not the course_mode allows cohorting by mode.
"""
course_mode_config = self.course_mode_config
if course_mode_config:
return course_mode_config.cohort
else:
return self.mode_slug == self.VERIFIED
@property
def is_credit_eligible(self):
"""
Returns whether or not the course_mode is credit eligible.
"""
course_mode_config = self.course_mode_config
if course_mode_config:
return course_mode_config.credit_eligible
else:
return self.mode_slug in self.CREDIT_MODES
@classmethod
def all_modes_for_courses(cls, course_id_list):
"""Find all modes for a list of course IDs, including expired modes.
......@@ -700,3 +759,36 @@ class CourseModeExpirationConfig(ConfigurationModel):
def __unicode__(self):
""" Returns the unicode date of the verification window. """
return unicode(self.verification_window)
class CourseModeConfig(TimeStampedModel):
"""
Configure course mode type capabilites.
"""
class Meta(object):
app_label = "course_modes"
name = models.CharField(max_length=100)
display_name = models.CharField(max_length=255)
certificate = models.BooleanField(default=False, verbose_name=_("Certificate"))
id_verification = models.BooleanField(default=False, verbose_name=_("ID Verification"))
upsell_course_mode = models.ForeignKey(
'self',
null=True,
blank=True,
default=None,
# Translators: this label indicates the course_mode config which this course_mode config can progress to:
verbose_name=_("Upsell Course Mode Config"),
)
credit_eligible = models.BooleanField(default=False, verbose_name=_("Credit Eligible"))
cohort = models.BooleanField(default=False, verbose_name=_("Cohort"))
def __unicode__(self):
""" Returns the unicode date of the verification window. """
return unicode(self.name)
# -*- coding: utf-8 -*-
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 = [
('course_modes', '0008_auto_20160617_1422'),
('student', '0006_logoutviewconfiguration'),
]
operations = [
migrations.AddField(
model_name='courseenrollment',
name='course_mode',
field=models.ForeignKey(verbose_name='Course Mode', blank=True, to='course_modes.CourseMode', null=True),
),
migrations.AddField(
model_name='courseenrollment',
name='modified',
field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False),
),
migrations.AddField(
model_name='historicalcourseenrollment',
name='course_mode',
field=models.ForeignKey(related_name='+', on_delete=django.db.models.deletion.DO_NOTHING, db_constraint=False, blank=True, to='course_modes.CourseMode', null=True),
),
migrations.AddField(
model_name='historicalcourseenrollment',
name='modified',
field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False),
),
migrations.AlterField(
model_name='courseenrollment',
name='created',
field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False),
),
migrations.AlterField(
model_name='historicalcourseenrollment',
name='created',
field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False),
),
]
......@@ -951,7 +951,7 @@ class CourseEnrollmentManager(models.Manager):
)
class CourseEnrollment(models.Model):
class CourseEnrollment(TimeStampedModel):
"""
Represents a Student's Enrollment record for a single Course. You should
generally not manipulate CourseEnrollment objects directly, but use the
......@@ -967,7 +967,6 @@ class CourseEnrollment(models.Model):
user = models.ForeignKey(User)
course_id = CourseKeyField(max_length=255, db_index=True)
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
# If is_active is False, then the student is not considered to be enrolled
# in the course (is_enrolled() will return False)
......@@ -977,6 +976,13 @@ class CourseEnrollment(models.Model):
# list of possible values.
mode = models.CharField(default=CourseMode.DEFAULT_MODE_SLUG, max_length=100)
course_mode = models.ForeignKey(
CourseMode,
null=True,
blank=True,
verbose_name=_("Course Mode")
)
objects = CourseEnrollmentManager()
# Maintain a history of requirement status updates for auditing purposes
......
......@@ -6,7 +6,7 @@ from django.db import transaction
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from course_modes.models import CourseMode
from course_modes.models import CourseMode, CourseModeConfig
from lms.djangoapps.verify_student.models import VerificationDeadline
log = logging.getLogger(__name__)
......@@ -80,7 +80,9 @@ class Course(object):
for posted_mode in attrs.get('modes', []):
merged_mode = existing_modes.get(posted_mode.mode_slug, CourseMode())
course_mode_config = CourseModeConfig.objects.filter(name=posted_mode.mode_slug).first()
merged_mode.course_mode_config = course_mode_config
merged_mode.course_id = self.id
merged_mode.mode_slug = posted_mode.mode_slug
merged_mode.mode_display_name = posted_mode.mode_slug
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment