Commit dcf8ed8f by Clinton Blackburn Committed by GitHub

Added image to Program model (#171)

- Image field added to Program model
- Search index now includes the image_url field for the Program model
- Field exposed via API
- Image URL now ingested by data loader

ECOM-5005
parent 8e407444
......@@ -45,7 +45,7 @@ COURSE_RUN_SEARCH_FIELDS = (
)
PROGRAM_SEARCH_FIELDS = (
'uuid', 'title', 'subtitle', 'category', 'marketing_url', 'organizations', 'content_type', 'text',
'uuid', 'title', 'subtitle', 'category', 'marketing_url', 'organizations', 'content_type', 'image_url', 'text',
)
......@@ -257,7 +257,7 @@ class ContainedCoursesSerializer(serializers.Serializer):
class ProgramSerializer(serializers.ModelSerializer):
class Meta:
model = Program
fields = ('uuid', 'title', 'subtitle', 'category', 'marketing_slug', 'marketing_url',)
fields = ('uuid', 'title', 'subtitle', 'category', 'marketing_slug', 'marketing_url', 'image_url',)
read_only_fields = ('uuid', 'marketing_url',)
......
......@@ -179,6 +179,7 @@ class ProgramSerializerTests(TestCase):
'category': program.category,
'marketing_slug': program.marketing_slug,
'marketing_url': program.marketing_url,
'image_url': program.image_url,
}
self.assertDictEqual(serializer.data, expected)
......@@ -398,5 +399,6 @@ class ProgramSearchSerializerTests(TestCase):
'marketing_url': program.marketing_url,
'organizations': [OrganizationsMixin.format_organization(organization)],
'content_type': 'program_{category}'.format(category=program.category),
'image_url': program.image_url,
}
self.assertDictEqual(serializer.data, expected)
......@@ -491,6 +491,8 @@ class EcommerceApiDataLoader(AbstractDataLoader):
class ProgramsApiDataLoader(AbstractDataLoader):
""" Loads programs from the Programs API. """
image_width = 435
image_height = 145
def ingest(self):
client = self.api_client
......@@ -523,6 +525,7 @@ class ProgramsApiDataLoader(AbstractDataLoader):
'category': body['category'],
'status': body['status'],
'marketing_slug': body['marketing_slug'],
'image': self._get_image(body),
}
program, __ = Program.objects.update_or_create(uuid=body['uuid'], defaults=defaults)
......@@ -535,3 +538,17 @@ class ProgramsApiDataLoader(AbstractDataLoader):
program.organizations.clear()
program.organizations.add(*organizations)
def _get_image(self, body):
image = None
image_key = 'w{width}h{height}'.format(width=self.image_width, height=self.image_height)
image_url = body.get('banner_image_urls', {}).get(image_key)
if image_url:
defaults = {
'width': self.image_width,
'height': self.image_height,
}
image, __ = Image.objects.update_or_create(src=image_url, defaults=defaults)
return image
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course_metadata', '0007_auto_20160720_1749'),
]
operations = [
migrations.AddField(
model_name='program',
name='image',
field=models.ForeignKey(to='course_metadata.Image', blank=True, null=True, default=None),
),
]
......@@ -474,8 +474,13 @@ class Program(TimeStampedModel):
max_length=255
)
image = models.ForeignKey(Image, default=None, null=True, blank=True)
organizations = models.ManyToManyField(Organization, blank=True)
def __str__(self):
return self.title
@property
def marketing_url(self):
if self.marketing_slug:
......@@ -484,8 +489,12 @@ class Program(TimeStampedModel):
return None
def __str__(self):
return self.title
@property
def image_url(self):
if self.image:
return self.image.src
return None
class PersonSocialNetwork(AbstractSocialNetworkModel):
......
......@@ -113,6 +113,7 @@ class ProgramIndex(OrganizationsMixin, BaseIndex, indexes.Indexable):
category = indexes.CharField(model_attr='category', faceted=True)
marketing_url = indexes.CharField(model_attr='marketing_url', null=True)
organizations = indexes.MultiValueField(faceted=True)
image_url = indexes.CharField(model_attr='image_url', null=True)
def prepare_content_type(self, obj):
return 'program_{category}'.format(category=obj.category)
......@@ -149,6 +149,7 @@ class ProgramFactory(factory.django.DjangoModelFactory):
category = 'xseries'
status = 'unpublished'
marketing_slug = factory.Sequence(lambda n: 'test-slug-{}'.format(n)) # pylint: disable=unnecessary-lambda
image = factory.SubFactory(ImageFactory)
class AbstractSocialNetworkModelFactory(factory.DjangoModelFactory):
......
......@@ -1105,7 +1105,13 @@ class ProgramsApiDataLoaderTests(DataLoaderTestMixin, TestCase):
'display_name': 'Delft University of Technology',
'key': 'DelftX'
}
]
],
'banner_image_urls': {
'w1440h480': 'https://example.com/delft-water__1440x480.jpg',
'w348h116': 'https://example.com/delft-water__348x116.jpg',
'w726h242': 'https://example.com/delft-water__726x242.jpg',
'w435h145': 'https://example.com/delft-water__435x145.jpg'
}
},
{
'uuid': 'b043f467-5e80-4225-93d2-248a93a8556a',
......@@ -1120,7 +1126,8 @@ class ProgramsApiDataLoaderTests(DataLoaderTestMixin, TestCase):
'display_name': 'Massachusetts Institute of Technology',
'key': 'MITx'
}
]
],
'banner_image_urls': {},
},
]
......@@ -1167,6 +1174,12 @@ class ProgramsApiDataLoaderTests(DataLoaderTestMixin, TestCase):
self.assertEqual(keys, [org.key for org in expected_organizations])
self.assertListEqual(list(program.organizations.all()), expected_organizations)
image_url = body.get('banner_image_urls', {}).get('w435h145')
if image_url:
image = Image.objects.get(src=image_url, width=self.loader_class.image_width,
height=self.loader_class.image_height)
self.assertEqual(program.image, image)
@responses.activate
def test_ingest(self):
""" Verify the method ingests data from the Organizations API. """
......
......@@ -242,22 +242,32 @@ class AbstractValueModelTests(TestCase):
class ProgramTests(TestCase):
"""Tests of the Program model."""
def setUp(self):
super(ProgramTests, self).setUp()
self.program = factories.ProgramFactory()
def test_str(self):
"""Verify that a program is properly converted to a str."""
program = factories.ProgramFactory()
self.assertEqual(str(program), program.title)
self.assertEqual(str(self.program), self.program.title)
def test_marketing_url(self):
""" Verify the property creates a complete marketing URL. """
program = factories.ProgramFactory()
expected = '{root}/{category}/{slug}'.format(root=settings.MARKETING_URL_ROOT.strip('/'),
category=program.category, slug=program.marketing_slug)
self.assertEqual(program.marketing_url, expected)
category=self.program.category, slug=self.program.marketing_slug)
self.assertEqual(self.program.marketing_url, expected)
def test_marketing_url_without_slug(self):
""" Verify the property returns None if the Program has no marketing_slug set. """
program = factories.ProgramFactory(marketing_slug='')
self.assertIsNone(program.marketing_url)
self.program.marketing_slug = ''
self.assertIsNone(self.program.marketing_url)
def test_image_url(self):
""" Verify the property returns the associated image's URL. """
self.assertEqual(self.program.image_url, self.program.image.src)
self.program.image = None
self.assertIsNone(self.program.image)
self.assertIsNone(self.program.image_url)
class PersonSocialNetworkTests(TestCase):
......
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