Commit 381b146d by Clinton Blackburn

Updated CourseRun model

- Added status column
- Using django-choices for pacing type

ECOM-5410
parent a8e017dc
......@@ -53,7 +53,8 @@ class CourseRunAdmin(admin.ModelAdmin):
list_display = ('uuid', 'key', 'title',)
list_filter = (
'course__partner',
('language', admin.RelatedOnlyFieldListFilter,)
('language', admin.RelatedOnlyFieldListFilter,),
'status',
)
ordering = ('key',)
readonly_fields = ('uuid',)
......@@ -65,7 +66,7 @@ class ProgramAdmin(admin.ModelAdmin):
form = ProgramAdminForm
inlines = [FaqsInline, IndividualEndorsementInline, CorporateEndorsementsInline]
list_display = ('id', 'uuid', 'title', 'category', 'type', 'partner', 'status',)
list_filter = ('partner', 'type',)
list_filter = ('partner', 'type', 'status',)
ordering = ('uuid', 'title', 'status')
readonly_fields = ('uuid', 'custom_course_runs_display', 'excluded_course_runs',)
search_fields = ('uuid', 'title', 'marketing_slug')
......
......@@ -149,9 +149,9 @@ class CoursesApiDataLoader(AbstractDataLoader):
pacing = pacing.lower()
if pacing == 'instructor':
return CourseRun.INSTRUCTOR_PACED
return CourseRun.Pacing.Instructor
elif pacing == 'self':
return CourseRun.SELF_PACED
return CourseRun.Pacing.Self
else:
return None
......
......@@ -214,10 +214,10 @@ class CoursesApiDataLoaderTests(ApiClientTestMixin, DataLoaderTestMixin, TestCas
('', None),
('foo', None),
(None, None),
('instructor', CourseRun.INSTRUCTOR_PACED),
('Instructor', CourseRun.INSTRUCTOR_PACED),
('self', CourseRun.SELF_PACED),
('Self', CourseRun.SELF_PACED),
('instructor', CourseRun.Pacing.Instructor),
('Instructor', CourseRun.Pacing.Instructor),
('self', CourseRun.Pacing.Self),
('Self', CourseRun.Pacing.Self),
)
def test_get_pacing_type(self, pacing, expected_pacing_type):
""" Verify the method returns a pacing type corresponding to the API response's pacing field. """
......
......@@ -48,7 +48,7 @@ class ProgramAdminForm(forms.ModelForm):
def clean(self):
status = self.cleaned_data.get('status')
banner_image = self.cleaned_data.get('banner_image')
if status == Program.ProgramStatus.Active and not banner_image:
if status == Program.Status.Active and not banner_image:
raise ValidationError(_('Status cannot be change to active without banner image.'))
return self.cleaned_data
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import djchoices.choices
class Migration(migrations.Migration):
dependencies = [
('course_metadata', '0023_auto_20160826_1912'),
]
operations = [
migrations.AddField(
model_name='courserun',
name='status',
field=models.CharField(db_index=True, validators=[djchoices.choices.ChoicesValidator({'unpublished': 'Unpublished', 'published': 'Published'})], choices=[('published', 'Published'), ('unpublished', 'Unpublished')], max_length=255, default='unpublished'),
preserve_default=False,
),
migrations.AddField(
model_name='historicalcourserun',
name='status',
field=models.CharField(db_index=True, validators=[djchoices.choices.ChoicesValidator({'unpublished': 'Unpublished', 'published': 'Published'})], choices=[('published', 'Published'), ('unpublished', 'Unpublished')], max_length=255, default='unpublished'),
preserve_default=False,
),
migrations.AlterField(
model_name='courserun',
name='pacing_type',
field=models.CharField(choices=[('instructor_paced', 'Instructor-paced'), ('self_paced', 'Self-paced')], null=True, db_index=True, validators=[djchoices.choices.ChoicesValidator({'instructor_paced': 'Instructor-paced', 'self_paced': 'Self-paced'})], blank=True, max_length=255),
),
migrations.AlterField(
model_name='historicalcourserun',
name='pacing_type',
field=models.CharField(choices=[('instructor_paced', 'Instructor-paced'), ('self_paced', 'Self-paced')], null=True, db_index=True, validators=[djchoices.choices.ChoicesValidator({'instructor_paced': 'Instructor-paced', 'self_paced': 'Self-paced'})], blank=True, max_length=255),
),
migrations.AlterField(
model_name='program',
name='status',
field=models.CharField(db_index=True, help_text='The lifecycle status of this Program.', choices=[('unpublished', 'Unpublished'), ('active', 'Active'), ('retired', 'Retired'), ('deleted', 'Deleted')], max_length=24, validators=[djchoices.choices.ChoicesValidator({'unpublished': 'Unpublished', 'active': 'Active', 'deleted': 'Deleted', 'retired': 'Retired'})]),
),
]
......@@ -308,21 +308,23 @@ class Course(TimeStampedModel):
class CourseRun(TimeStampedModel):
""" CourseRun model. """
SELF_PACED = 'self_paced'
INSTRUCTOR_PACED = 'instructor_paced'
PACING_CHOICES = (
# Translators: Self-paced refers to course runs that operate on the student's schedule.
(SELF_PACED, _('Self-paced')),
class Status(DjangoChoices):
Published = ChoiceItem('published', _('Published'))
Unpublished = ChoiceItem('unpublished', _('Unpublished'))
class Pacing(DjangoChoices):
# Translators: Instructor-paced refers to course runs that operate on a schedule set by the instructor,
# similar to a normal university course.
(INSTRUCTOR_PACED, _('Instructor-paced')),
)
Instructor = ChoiceItem('instructor_paced', _('Instructor-paced'))
# Translators: Self-paced refers to course runs that operate on the student's schedule.
Self = ChoiceItem('self_paced', _('Self-paced'))
uuid = models.UUIDField(default=uuid4, editable=False, verbose_name=_('UUID'))
course = models.ForeignKey(Course, related_name='course_runs')
key = models.CharField(max_length=255, unique=True)
status = models.CharField(max_length=255, null=False, blank=False, db_index=True, choices=Status.choices,
validators=[Status.validator])
title_override = models.CharField(
max_length=255, default=None, null=True, blank=True,
help_text=_(
......@@ -351,7 +353,8 @@ class CourseRun(TimeStampedModel):
help_text=_('Estimated maximum number of hours per week needed to complete a course run.'))
language = models.ForeignKey(LanguageTag, null=True, blank=True)
transcript_languages = models.ManyToManyField(LanguageTag, blank=True, related_name='transcript_courses')
pacing_type = models.CharField(max_length=255, choices=PACING_CHOICES, db_index=True, null=True, blank=True)
pacing_type = models.CharField(max_length=255, db_index=True, null=True, blank=True, choices=Pacing.choices,
validators=[Pacing.validator])
syllabus = models.ForeignKey(SyllabusItem, default=None, null=True, blank=True)
card_image_url = models.URLField(null=True, blank=True)
video = models.ForeignKey(Video, default=None, null=True, blank=True)
......@@ -565,7 +568,7 @@ class ProgramType(TimeStampedModel):
class Program(TimeStampedModel):
class ProgramStatus(DjangoChoices):
class Status(DjangoChoices):
Unpublished = ChoiceItem('unpublished', _('Unpublished'))
Active = ChoiceItem('active', _('Active'))
Retired = ChoiceItem('retired', _('Retired'))
......@@ -580,8 +583,8 @@ class Program(TimeStampedModel):
category = models.CharField(help_text=_('The category / type of Program.'), max_length=32)
type = models.ForeignKey(ProgramType, null=True, blank=True)
status = models.CharField(
help_text=_('The lifecycle status of this Program.'), max_length=24, null=False, blank=False,
choices=ProgramStatus.choices, validators=[ProgramStatus.validator]
help_text=_('The lifecycle status of this Program.'), max_length=24, null=False, blank=False, db_index=True,
choices=Status.choices, validators=[Status.validator]
)
marketing_slug = models.CharField(
help_text=_('Slug used to generate links to the marketing site'), blank=True, max_length=255, db_index=True)
......
......@@ -106,6 +106,7 @@ class CourseFactory(factory.DjangoModelFactory):
class CourseRunFactory(factory.DjangoModelFactory):
status = CourseRun.Status.Published
uuid = factory.LazyFunction(uuid4)
key = FuzzyText(prefix='course-run-id/', suffix='/fake')
course = factory.SubFactory(CourseFactory)
......@@ -122,7 +123,7 @@ class CourseRunFactory(factory.DjangoModelFactory):
video = factory.SubFactory(VideoFactory)
min_effort = FuzzyInteger(1, 10)
max_effort = FuzzyInteger(10, 20)
pacing_type = FuzzyChoice([name for name, __ in CourseRun.PACING_CHOICES])
pacing_type = FuzzyChoice([name for name, __ in CourseRun.Pacing.choices])
slug = FuzzyText()
@factory.post_generation
......@@ -239,7 +240,7 @@ class ProgramFactory(factory.django.DjangoModelFactory):
uuid = factory.LazyFunction(uuid4)
subtitle = 'test-subtitle'
type = factory.SubFactory(ProgramTypeFactory)
status = Program.ProgramStatus.Unpublished
status = Program.Status.Unpublished
marketing_slug = factory.Sequence(lambda n: 'test-slug-{}'.format(n)) # pylint: disable=unnecessary-lambda
banner_image_url = FuzzyText(prefix='https://example.com/program/banner')
card_image_url = FuzzyText(prefix='https://example.com/program/card')
......
......@@ -109,7 +109,7 @@ class AdminTests(TestCase):
def test_program_without_image_and_active_status(self):
""" Verify that new program cannot be added without `image` and active status together."""
data = self._post_data(Program.ProgramStatus.Active)
data = self._post_data(Program.Status.Active)
form = ProgramAdminForm(data, {'banner_image': ''})
self.assertFalse(form.is_valid())
self.assertEqual(form.errors['__all__'], ['Status cannot be change to active without banner image.'])
......@@ -117,9 +117,9 @@ class AdminTests(TestCase):
form.save()
@ddt.data(
Program.ProgramStatus.Deleted,
Program.ProgramStatus.Retired,
Program.ProgramStatus.Unpublished
Program.Status.Deleted,
Program.Status.Retired,
Program.Status.Unpublished
)
def test_program_without_image_and_non_active_status(self, status):
""" Verify that new program can be added without `image` and non-active
......@@ -129,10 +129,10 @@ class AdminTests(TestCase):
self.valid_post_form(data, {'banner_image': ''})
@ddt.data(
Program.ProgramStatus.Deleted,
Program.ProgramStatus.Retired,
Program.ProgramStatus.Unpublished,
Program.ProgramStatus.Active
Program.Status.Deleted,
Program.Status.Retired,
Program.Status.Unpublished,
Program.Status.Active
)
def test_program_with_image(self, status):
""" Verify that new program can be added with `image` and any status."""
......@@ -157,7 +157,7 @@ class AdminTests(TestCase):
def test_new_program_without_courses(self):
""" Verify that new program can be added without `courses`."""
data = self._post_data(Program.ProgramStatus.Unpublished)
data = self._post_data(Program.Status.Unpublished)
data['courses'] = []
form = ProgramAdminForm(data)
self.assertTrue(form.is_valid())
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import djchoices.choices
class Migration(migrations.Migration):
dependencies = [
('publisher', '0004_auto_20160810_0854'),
]
operations = [
migrations.AlterField(
model_name='courserun',
name='pacing_type',
field=models.CharField(max_length=255, null=True, blank=True, choices=[('instructor_paced', 'Instructor-paced'), ('self_paced', 'Self-paced')], db_index=True, validators=[djchoices.choices.ChoicesValidator({'self_paced': 'Self-paced', 'instructor_paced': 'Instructor-paced'})]),
),
migrations.AlterField(
model_name='historicalcourserun',
name='pacing_type',
field=models.CharField(max_length=255, null=True, blank=True, choices=[('instructor_paced', 'Instructor-paced'), ('self_paced', 'Self-paced')], db_index=True, validators=[djchoices.choices.ChoicesValidator({'self_paced': 'Self-paced', 'instructor_paced': 'Instructor-paced'})]),
),
]
......@@ -138,7 +138,8 @@ class CourseRun(TimeStampedModel, ChangedByMixin):
enrollment_end = models.DateTimeField(null=True, blank=True)
certificate_generation = models.DateTimeField(null=True, blank=True)
pacing_type = models.CharField(
max_length=255, choices=CourseMetadataCourseRun.PACING_CHOICES, db_index=True, null=True, blank=True
max_length=255, db_index=True, null=True, blank=True, choices=CourseMetadataCourseRun.Pacing.choices,
validators=[CourseMetadataCourseRun.Pacing.validator]
)
staff = SortedManyToManyField(Person, blank=True, related_name='publisher_course_runs_staffed')
min_effort = models.PositiveSmallIntegerField(
......
......@@ -47,7 +47,7 @@ class CourseRunFactory(factory.DjangoModelFactory):
min_effort = FuzzyInteger(1, 10)
max_effort = FuzzyInteger(10, 20)
language = factory.Iterator(LanguageTag.objects.all())
pacing_type = FuzzyChoice([name for name, __ in CourseMetadataCourseRun.PACING_CHOICES])
pacing_type = FuzzyChoice([name for name, __ in CourseMetadataCourseRun.Pacing.choices])
length = FuzzyInteger(1, 10)
seo_review = "test-seo-review"
keywords = "Test1, Test2, Test3"
......
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