Commit 1010c360 by Clinton Blackburn Committed by Clinton Blackburn

Added license field to Course Run model, API endpoint, and search index

The license is now ingested from the Course API on LMS and saved to the Course Run model. This value is in turn exposed on this service's course run and search API endpoints.

LEARNER-2791
parent 2b613ef4
...@@ -138,6 +138,7 @@ class CourseRunFilter(FilterSetMixin, filters.FilterSet): ...@@ -138,6 +138,7 @@ class CourseRunFilter(FilterSetMixin, filters.FilterSet):
active = filters.BooleanFilter(method='filter_active') active = filters.BooleanFilter(method='filter_active')
marketable = filters.BooleanFilter(method='filter_marketable') marketable = filters.BooleanFilter(method='filter_marketable')
keys = CharListFilter(name='key', lookup_expr='in') keys = CharListFilter(name='key', lookup_expr='in')
license = filters.CharFilter(name='license', lookup_expr='iexact')
@property @property
def qs(self): def qs(self):
...@@ -150,7 +151,7 @@ class CourseRunFilter(FilterSetMixin, filters.FilterSet): ...@@ -150,7 +151,7 @@ class CourseRunFilter(FilterSetMixin, filters.FilterSet):
class Meta: class Meta:
model = CourseRun model = CourseRun
fields = ['keys', 'hidden'] fields = ('keys', 'hidden', 'license',)
class ProgramFilter(FilterSetMixin, filters.FilterSet): class ProgramFilter(FilterSetMixin, filters.FilterSet):
......
...@@ -477,9 +477,9 @@ class CourseRunSerializer(MinimalCourseRunSerializer): ...@@ -477,9 +477,9 @@ class CourseRunSerializer(MinimalCourseRunSerializer):
class Meta(MinimalCourseRunSerializer.Meta): class Meta(MinimalCourseRunSerializer.Meta):
fields = MinimalCourseRunSerializer.Meta.fields + ( fields = MinimalCourseRunSerializer.Meta.fields + (
'course', 'full_description', 'announcement', 'video', 'seats', 'content_language', 'course', 'full_description', 'announcement', 'video', 'seats', 'content_language', 'license',
'transcript_languages', 'instructors', 'staff', 'min_effort', 'max_effort', 'weeks_to_complete', 'modified', 'transcript_languages', 'instructors', 'staff', 'min_effort', 'max_effort', 'weeks_to_complete', 'modified',
'level_type', 'availability', 'mobile_available', 'hidden', 'reporting_type', 'eligible_for_financial_aid' 'level_type', 'availability', 'mobile_available', 'hidden', 'reporting_type', 'eligible_for_financial_aid',
) )
def get_instructors(self, obj): # pylint: disable=unused-argument def get_instructors(self, obj): # pylint: disable=unused-argument
......
...@@ -274,6 +274,7 @@ class CourseRunSerializerTests(MinimalCourseRunSerializerTests): ...@@ -274,6 +274,7 @@ class CourseRunSerializerTests(MinimalCourseRunSerializerTests):
'availability': course_run.availability, 'availability': course_run.availability,
'reporting_type': course_run.reporting_type, 'reporting_type': course_run.reporting_type,
'status': course_run.status, 'status': course_run.status,
'license': course_run.license,
}) })
return expected return expected
......
...@@ -228,6 +228,14 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC ...@@ -228,6 +228,14 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC
url = reverse('api:v1:course_run-list') + '?active=1' url = reverse('api:v1:course_run-list') + '?active=1'
self.assert_list_results(url, expected) self.assert_list_results(url, expected)
def test_filter_by_license(self):
CourseRun.objects.all().delete()
course_runs_cc = CourseRunFactory.create_batch(3, course__partner=self.partner, license='cc-by-sa')
CourseRunFactory.create_batch(3, course__partner=self.partner, license='')
url = reverse('api:v1:course_run-list') + '?license=cc-by-sa'
self.assert_list_results(url, course_runs_cc)
def test_list_exclude_utm(self): def test_list_exclude_utm(self):
""" Verify the endpoint returns marketing URLs without UTM parameters. """ """ Verify the endpoint returns marketing URLs without UTM parameters. """
url = reverse('api:v1:course_run-list') + '?exclude_utm=1' url = reverse('api:v1:course_run-list') + '?exclude_utm=1'
......
...@@ -83,8 +83,10 @@ class CourseRunAdmin(admin.ModelAdmin): ...@@ -83,8 +83,10 @@ class CourseRunAdmin(admin.ModelAdmin):
'hidden', 'hidden',
('language', admin.RelatedOnlyFieldListFilter,), ('language', admin.RelatedOnlyFieldListFilter,),
'status', 'status',
'license',
) )
ordering = ('key',) ordering = ('key',)
raw_id_fields = ('course',)
readonly_fields = ('uuid',) readonly_fields = ('uuid',)
search_fields = ('uuid', 'key', 'title_override', 'course__title', 'slug',) search_fields = ('uuid', 'key', 'title_override', 'course__title', 'slug',)
save_error = False save_error = False
......
...@@ -196,6 +196,9 @@ class CoursesApiDataLoader(AbstractDataLoader): ...@@ -196,6 +196,9 @@ class CoursesApiDataLoader(AbstractDataLoader):
'hidden': body.get('hidden', False), 'hidden': body.get('hidden', False),
} }
# NOTE: The license field is non-nullable.
defaults['license'] = body.get('license') or ''
# When using a marketing site, only dates (excluding start) should come from the Course API. # When using a marketing site, only dates (excluding start) should come from the Course API.
if not self.partner.has_marketing_site: if not self.partner.has_marketing_site:
defaults.update({ defaults.update({
......
...@@ -40,6 +40,7 @@ COURSES_API_BODIES = [ ...@@ -40,6 +40,7 @@ COURSES_API_BODIES = [
'pacing': 'self', 'pacing': 'self',
'mobile_available': True, 'mobile_available': True,
'hidden': False, 'hidden': False,
'license': '',
}, },
{ {
'effort': None, 'effort': None,
...@@ -63,6 +64,7 @@ COURSES_API_BODIES = [ ...@@ -63,6 +64,7 @@ COURSES_API_BODIES = [
'pacing': 'instructor,', 'pacing': 'instructor,',
'mobile_available': False, 'mobile_available': False,
'hidden': False, 'hidden': False,
'license': 'all-rights-reserved',
}, },
{ {
# Add a second run of KyotoUx+000x (3T2016) to test merging data across # Add a second run of KyotoUx+000x (3T2016) to test merging data across
......
...@@ -186,6 +186,7 @@ class CoursesApiDataLoaderTests(ApiClientTestMixin, DataLoaderTestMixin, TestCas ...@@ -186,6 +186,7 @@ class CoursesApiDataLoaderTests(ApiClientTestMixin, DataLoaderTestMixin, TestCas
'short_description_override': None, 'short_description_override': None,
'video': None, 'video': None,
'hidden': body.get('hidden', False), 'hidden': body.get('hidden', False),
'license': body.get('license', ''),
} }
if not partner_has_marketing_site: if not partner_has_marketing_site:
......
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-10-06 20:48
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course_metadata', '0061_migrate_subjects_data'),
]
operations = [
migrations.AddField(
model_name='courserun',
name='license',
field=models.CharField(blank=True, db_index=True, max_length=255),
),
]
...@@ -450,6 +450,7 @@ class CourseRun(TimeStampedModel): ...@@ -450,6 +450,7 @@ class CourseRun(TimeStampedModel):
) )
reporting_type = models.CharField(max_length=255, choices=ReportingType.choices, default=ReportingType.mooc) reporting_type = models.CharField(max_length=255, choices=ReportingType.choices, default=ReportingType.mooc)
eligible_for_financial_aid = models.BooleanField(default=True) eligible_for_financial_aid = models.BooleanField(default=True)
license = models.CharField(max_length=255, blank=True, db_index=True)
tags = TaggableManager( tags = TaggableManager(
blank=True, blank=True,
......
...@@ -169,6 +169,7 @@ class CourseRunIndex(BaseCourseIndex, indexes.Indexable): ...@@ -169,6 +169,7 @@ class CourseRunIndex(BaseCourseIndex, indexes.Indexable):
subject_uuids = indexes.MultiValueField() subject_uuids = indexes.MultiValueField()
has_enrollable_paid_seats = indexes.BooleanField(null=False) has_enrollable_paid_seats = indexes.BooleanField(null=False)
paid_seat_enrollment_end = indexes.DateTimeField(null=True) paid_seat_enrollment_end = indexes.DateTimeField(null=True)
license = indexes.MultiValueField(model_attr='license', faceted=True)
def prepare_aggregation_key(self, obj): def prepare_aggregation_key(self, obj):
# Aggregate CourseRuns by Course key since that is how we plan to dedup CourseRuns on the marketing site. # Aggregate CourseRuns by Course key since that is how we plan to dedup CourseRuns on the marketing site.
......
...@@ -116,6 +116,7 @@ class CourseRunFactory(factory.DjangoModelFactory): ...@@ -116,6 +116,7 @@ class CourseRunFactory(factory.DjangoModelFactory):
slug = FuzzyText() slug = FuzzyText()
hidden = False hidden = False
weeks_to_complete = FuzzyInteger(1) weeks_to_complete = FuzzyInteger(1)
license = 'all-rights-reserved'
@factory.post_generation @factory.post_generation
def staff(self, create, extracted, **kwargs): def staff(self, create, extracted, **kwargs):
......
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