Commit 7ebb34b9 by Matt Tuchfarber Committed by Matt Tuchfarber

Adds an instructors field to the program

This allows the program creator to determine the order of the instructors on the program marketing page.
parent 7f7dbed3
...@@ -891,6 +891,7 @@ class ProgramSerializer(MinimalProgramSerializer): ...@@ -891,6 +891,7 @@ class ProgramSerializer(MinimalProgramSerializer):
) )
subjects = SubjectSerializer(many=True) subjects = SubjectSerializer(many=True)
staff = PersonSerializer(many=True) staff = PersonSerializer(many=True)
instructors = PersonSerializer(many=True)
applicable_seat_types = serializers.SerializerMethodField() applicable_seat_types = serializers.SerializerMethodField()
@classmethod @classmethod
...@@ -907,6 +908,7 @@ class ProgramSerializer(MinimalProgramSerializer): ...@@ -907,6 +908,7 @@ class ProgramSerializer(MinimalProgramSerializer):
'expected_learning_items', 'expected_learning_items',
'faq', 'faq',
'job_outlook_items', 'job_outlook_items',
'instructors',
# `type` is serialized by a third-party serializer. Providing this field name allows us to # `type` is serialized by a third-party serializer. Providing this field name allows us to
# prefetch `applicable_seat_types`, a m2m on `ProgramType`, through `type`, a foreign key to # prefetch `applicable_seat_types`, a m2m on `ProgramType`, through `type`, a foreign key to
# `ProgramType` on `Program`. # `ProgramType` on `Program`.
...@@ -930,7 +932,7 @@ class ProgramSerializer(MinimalProgramSerializer): ...@@ -930,7 +932,7 @@ class ProgramSerializer(MinimalProgramSerializer):
'min_hours_effort_per_week', 'max_hours_effort_per_week', 'video', 'expected_learning_items', 'min_hours_effort_per_week', 'max_hours_effort_per_week', 'video', 'expected_learning_items',
'faq', 'credit_backing_organizations', 'corporate_endorsements', 'job_outlook_items', 'faq', 'credit_backing_organizations', 'corporate_endorsements', 'job_outlook_items',
'individual_endorsements', 'languages', 'transcript_languages', 'subjects', 'price_ranges', 'individual_endorsements', 'languages', 'transcript_languages', 'subjects', 'price_ranges',
'staff', 'credit_redemption_overview', 'applicable_seat_types' 'staff', 'credit_redemption_overview', 'applicable_seat_types', 'instructors'
) )
......
...@@ -680,6 +680,7 @@ class ProgramSerializerTests(MinimalProgramSerializerTests): ...@@ -680,6 +680,7 @@ class ProgramSerializerTests(MinimalProgramSerializerTests):
program.individual_endorsements, many=True, context={'request': request} program.individual_endorsements, many=True, context={'request': request}
).data, ).data,
'staff': PersonSerializer(program.staff, many=True, context={'request': request}).data, 'staff': PersonSerializer(program.staff, many=True, context={'request': request}).data,
'instructors': PersonSerializer(program.instructors, many=True, context={'request': request}).data,
'job_outlook_items': [item.value for item in program.job_outlook_items.all()], 'job_outlook_items': [item.value for item in program.job_outlook_items.all()],
'languages': [serialize_language_to_code(l) for l in program.languages], 'languages': [serialize_language_to_code(l) for l in program.languages],
'weeks_to_complete': program.weeks_to_complete, 'weeks_to_complete': program.weeks_to_complete,
......
...@@ -59,6 +59,7 @@ class TestProgramViewSet(SerializationMixin): ...@@ -59,6 +59,7 @@ class TestProgramViewSet(SerializationMixin):
individual_endorsements=EndorsementFactory.create_batch(1), individual_endorsements=EndorsementFactory.create_batch(1),
expected_learning_items=ExpectedLearningItemFactory.create_batch(1), expected_learning_items=ExpectedLearningItemFactory.create_batch(1),
job_outlook_items=JobOutlookItemFactory.create_batch(1), job_outlook_items=JobOutlookItemFactory.create_batch(1),
instructors=PersonFactory.create_batch(1),
banner_image=make_image_file('test_banner.jpg'), banner_image=make_image_file('test_banner.jpg'),
video=VideoFactory(), video=VideoFactory(),
partner=self.partner partner=self.partner
...@@ -88,7 +89,7 @@ class TestProgramViewSet(SerializationMixin): ...@@ -88,7 +89,7 @@ class TestProgramViewSet(SerializationMixin):
def test_retrieve(self, django_assert_num_queries): def test_retrieve(self, django_assert_num_queries):
""" Verify the endpoint returns the details for a single program. """ """ Verify the endpoint returns the details for a single program. """
program = self.create_program() program = self.create_program()
with django_assert_num_queries(41): with django_assert_num_queries(47):
response = self.assert_retrieve_success(program) response = self.assert_retrieve_success(program)
# property does not have the right values while being indexed # property does not have the right values while being indexed
del program._course_run_weeks_to_complete del program._course_run_weeks_to_complete
...@@ -114,7 +115,7 @@ class TestProgramViewSet(SerializationMixin): ...@@ -114,7 +115,7 @@ class TestProgramViewSet(SerializationMixin):
partner=self.partner) partner=self.partner)
# property does not have the right values while being indexed # property does not have the right values while being indexed
del program._course_run_weeks_to_complete del program._course_run_weeks_to_complete
with django_assert_num_queries(30): with django_assert_num_queries(31):
response = self.assert_retrieve_success(program) response = self.assert_retrieve_success(program)
assert response.data == self.serialize_program(program) assert response.data == self.serialize_program(program)
assert course_list == list(program.courses.all()) # pylint: disable=no-member assert course_list == list(program.courses.all()) # pylint: disable=no-member
...@@ -123,7 +124,7 @@ class TestProgramViewSet(SerializationMixin): ...@@ -123,7 +124,7 @@ class TestProgramViewSet(SerializationMixin):
""" Verify the endpoint returns data for a program even if the program's courses have no course runs. """ """ Verify the endpoint returns data for a program even if the program's courses have no course runs. """
course = CourseFactory(partner=self.partner) course = CourseFactory(partner=self.partner)
program = ProgramFactory(courses=[course], partner=self.partner) program = ProgramFactory(courses=[course], partner=self.partner)
with django_assert_num_queries(23): with django_assert_num_queries(24):
response = self.assert_retrieve_success(program) response = self.assert_retrieve_success(program)
assert response.data == self.serialize_program(program) assert response.data == self.serialize_program(program)
......
...@@ -119,7 +119,7 @@ class ProgramAdmin(admin.ModelAdmin): ...@@ -119,7 +119,7 @@ class ProgramAdmin(admin.ModelAdmin):
raw_id_fields = ('video',) raw_id_fields = ('video',)
search_fields = ('uuid', 'title', 'marketing_slug') search_fields = ('uuid', 'title', 'marketing_slug')
filter_horizontal = ('job_outlook_items', 'expected_learning_items',) filter_horizontal = ('job_outlook_items', 'expected_learning_items', 'instructors')
# ordering the field display on admin page. # ordering the field display on admin page.
fields = ( fields = (
......
...@@ -6,7 +6,7 @@ from course_discovery.apps.core.models import Partner ...@@ -6,7 +6,7 @@ from course_discovery.apps.core.models import Partner
from course_discovery.apps.course_metadata.models import Program from course_discovery.apps.course_metadata.models import Program
from course_discovery.apps.course_metadata.tests.factories import ( from course_discovery.apps.course_metadata.tests.factories import (
CorporateEndorsementFactory, CourseFactory, CourseRunFactory, EndorsementFactory, ExpectedLearningItemFactory, CorporateEndorsementFactory, CourseFactory, CourseRunFactory, EndorsementFactory, ExpectedLearningItemFactory,
FAQFactory, JobOutlookItemFactory, OrganizationFactory, ProgramFactory FAQFactory, JobOutlookItemFactory, OrganizationFactory, PersonFactory, ProgramFactory
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -47,5 +47,6 @@ class Command(BaseCommand): ...@@ -47,5 +47,6 @@ class Command(BaseCommand):
individual_endorsements=[EndorsementFactory(endorser__partner=partner)], individual_endorsements=[EndorsementFactory(endorser__partner=partner)],
expected_learning_items=[ExpectedLearningItemFactory()], expected_learning_items=[ExpectedLearningItemFactory()],
faq=[FAQFactory()], faq=[FAQFactory()],
job_outlook_items=[JobOutlookItemFactory()] job_outlook_items=[JobOutlookItemFactory()],
instructors=[PersonFactory()],
) )
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-11-29 15:20
from __future__ import unicode_literals
import sortedm2m.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('course_metadata', '0072_courseentitlement_partner'),
]
operations = [
migrations.AddField(
model_name='program',
name='instructors',
field=sortedm2m.fields.SortedManyToManyField(blank=True, help_text='Used to organize the instructors on the program about page.', to='course_metadata.Person'),
),
]
...@@ -959,6 +959,11 @@ class Program(TimeStampedModel): ...@@ -959,6 +959,11 @@ class Program(TimeStampedModel):
video = models.ForeignKey(Video, default=None, null=True, blank=True) video = models.ForeignKey(Video, default=None, null=True, blank=True)
expected_learning_items = SortedManyToManyField(ExpectedLearningItem, blank=True) expected_learning_items = SortedManyToManyField(ExpectedLearningItem, blank=True)
faq = SortedManyToManyField(FAQ, blank=True) faq = SortedManyToManyField(FAQ, blank=True)
instructors = SortedManyToManyField(
Person,
blank=True,
help_text='Used to organize the instructors on the program about page.'
)
credit_backing_organizations = SortedManyToManyField( credit_backing_organizations = SortedManyToManyField(
Organization, blank=True, related_name='credit_backed_programs' Organization, blank=True, related_name='credit_backed_programs'
......
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
{{ subject.name }} {{ subject.name }}
{% endfor %} {% endfor %}
{% for instructor in object.instructors %} {% for instructor in object.instructors.all %}
{{ instructor.full_name }} {{ instructor.full_name }}
{% endfor %} {% endfor %}
......
...@@ -323,6 +323,11 @@ class ProgramFactory(factory.django.DjangoModelFactory): ...@@ -323,6 +323,11 @@ class ProgramFactory(factory.django.DjangoModelFactory):
if create: # pragma: no cover if create: # pragma: no cover
add_m2m_data(self.job_outlook_items, extracted) add_m2m_data(self.job_outlook_items, extracted)
@factory.post_generation
def instructors(self, create, extracted, **kwargs):
if create: # pragma: no cover
add_m2m_data(self.instructors, extracted)
class AbstractSocialNetworkModelFactory(factory.DjangoModelFactory): class AbstractSocialNetworkModelFactory(factory.DjangoModelFactory):
type = FuzzyChoice([name for name, __ in AbstractSocialNetworkModel.SOCIAL_NETWORK_CHOICES]) type = FuzzyChoice([name for name, __ in AbstractSocialNetworkModel.SOCIAL_NETWORK_CHOICES])
......
...@@ -294,6 +294,7 @@ class ProgramAdminFunctionalTests(SiteMixin, LiveServerTestCase): ...@@ -294,6 +294,7 @@ class ProgramAdminFunctionalTests(SiteMixin, LiveServerTestCase):
'field-excluded_course_runs', 'field-authoring_organizations', 'field-credit_backing_organizations', 'field-excluded_course_runs', 'field-authoring_organizations', 'field-credit_backing_organizations',
'field-one_click_purchase_enabled', 'field-hidden', 'field-corporate_endorsements', 'field-faq', 'field-one_click_purchase_enabled', 'field-hidden', 'field-corporate_endorsements', 'field-faq',
'field-individual_endorsements', 'field-job_outlook_items', 'field-expected_learning_items', 'field-individual_endorsements', 'field-job_outlook_items', 'field-expected_learning_items',
'field-instructors',
] ]
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
......
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