Commit 0716037d by Afzal Wali

Added Logo Image to the ProgramType

Created a new endpoing for ProgramType as a list-only viewset.
parent fbac0672
......@@ -689,6 +689,19 @@ class ProgramSerializer(MinimalProgramSerializer):
)
class ProgramTypeSerializer(serializers.ModelSerializer):
"""
Serializer for the Program Types
"""
logo_image = StdImageSerializerField()
class Meta:
model = ProgramType
fields = (
'name', 'logo_image',
)
class AffiliateWindowSerializer(serializers.ModelSerializer):
""" Serializer for Affiliate Window product feeds. """
......
......@@ -17,7 +17,7 @@ from course_discovery.apps.api.serializers import (
CourseRunWithProgramsSerializer, CourseWithProgramsSerializer, CorporateEndorsementSerializer,
FAQSerializer, EndorsementSerializer, PositionSerializer, FlattenedCourseRunWithCourseSerializer,
MinimalCourseSerializer, MinimalOrganizationSerializer, MinimalCourseRunSerializer, MinimalProgramSerializer,
CourseSerializer, TypeaheadCourseRunSearchSerializer, TypeaheadProgramSearchSerializer
CourseSerializer, TypeaheadCourseRunSearchSerializer, TypeaheadProgramSearchSerializer, ProgramTypeSerializer
)
from course_discovery.apps.catalogs.tests.factories import CatalogFactory
from course_discovery.apps.core.models import User
......@@ -28,7 +28,7 @@ from course_discovery.apps.course_metadata.models import CourseRun, Program
from course_discovery.apps.course_metadata.tests.factories import (
CourseFactory, CourseRunFactory, SubjectFactory, PrerequisiteFactory, ImageFactory, VideoFactory,
OrganizationFactory, PersonFactory, SeatFactory, ProgramFactory, CorporateEndorsementFactory, EndorsementFactory,
JobOutlookItemFactory, ExpectedLearningItemFactory, PositionFactory
JobOutlookItemFactory, ExpectedLearningItemFactory, PositionFactory, ProgramTypeFactory
)
from course_discovery.apps.ietf_language_tags.models import LanguageTag
......@@ -764,6 +764,26 @@ class ProgramSerializerTests(MinimalProgramSerializerTests):
self.assertDictEqual(serializer.data, expected)
class ProgramTypeSerializerTests(TestCase):
serializer_class = ProgramTypeSerializer
def get_expected_data(self, program_type, request):
image_field = StdImageSerializerField()
image_field._context = {'request': request} # pylint: disable=protected-access
return {
'name': program_type.name,
'logo_image': image_field.to_representation(program_type.logo_image),
}
def test_data(self):
request = make_request()
program_type = ProgramTypeFactory()
serializer = self.serializer_class(program_type, context={'request': request})
expected = self.get_expected_data(program_type, request)
self.assertDictEqual(serializer.data, expected)
class ContainedCourseRunsSerializerTests(TestCase):
def test_data(self):
instance = {
......
......@@ -9,7 +9,7 @@ from rest_framework.test import APIRequestFactory
from course_discovery.apps.api.serializers import (
CatalogSerializer, CourseWithProgramsSerializer, CourseSerializerExcludingClosedRuns,
CourseRunWithProgramsSerializer, MinimalProgramSerializer, ProgramSerializer,
FlattenedCourseRunWithCourseSerializer, OrganizationSerializer
FlattenedCourseRunWithCourseSerializer, OrganizationSerializer, ProgramTypeSerializer
)
......@@ -50,6 +50,9 @@ class SerializationMixin(object):
extra_context
)
def serialize_program_type(self, program_type, many=False, format=None, extra_context=None):
return self._serialize_object(ProgramTypeSerializer, program_type, many, format, extra_context)
def serialize_catalog_course(self, course, many=False, format=None, extra_context=None):
return self._serialize_object(CourseSerializerExcludingClosedRuns, course, many, format, extra_context)
......
......@@ -8,10 +8,11 @@ from course_discovery.apps.api.v1.views.programs import ProgramViewSet
from course_discovery.apps.core.tests.factories import USER_PASSWORD, UserFactory
from course_discovery.apps.core.tests.helpers import make_image_file
from course_discovery.apps.course_metadata.choices import ProgramStatus
from course_discovery.apps.course_metadata.models import Program
from course_discovery.apps.course_metadata.models import Program, ProgramType
from course_discovery.apps.course_metadata.tests.factories import (
CourseFactory, CourseRunFactory, VideoFactory, OrganizationFactory, PersonFactory, ProgramFactory,
CorporateEndorsementFactory, EndorsementFactory, JobOutlookItemFactory, ExpectedLearningItemFactory
CorporateEndorsementFactory, EndorsementFactory, JobOutlookItemFactory, ExpectedLearningItemFactory,
ProgramTypeFactory
)
......@@ -160,3 +161,49 @@ class ProgramViewSetTests(SerializationMixin, APITestCase):
def test_minimal_serializer_use(self):
""" Verify that the list view uses the minimal serializer. """
self.assertEqual(ProgramViewSet(action='list').get_serializer_class(), MinimalProgramSerializer)
class ProgramTypeViewSetTests(SerializationMixin, APITestCase):
list_path = reverse('api:v1:program_type-list')
def setUp(self):
super(ProgramTypeViewSetTests, self).setUp()
self.user = UserFactory(is_staff=True, is_superuser=True)
self.client.login(username=self.user.username, password=USER_PASSWORD)
def test_authentication(self):
""" Verify the endpoint requires the user to be authenticated. """
response = self.client.get(self.list_path)
self.assertEqual(response.status_code, 200)
self.client.logout()
response = self.client.get(self.list_path)
self.assertEqual(response.status_code, 403)
def assert_list_results(self, url, expected, expected_query_count, extra_context=None):
"""
Asserts the results serialized/returned at the URL matches those that are expected.
Args:
url (str): URL from which data should be retrieved
expected (list[ProgramType]): Expected program_types
Notes:
The API usually returns items in reverse order of creation (e.g. newest first). You may need to reverse
the values of `expected` if you encounter issues. This method will NOT do that reversal for you.
Returns:
None
"""
with self.assertNumQueries(expected_query_count):
response = self.client.get(url)
self.assertEqual(
response.data['results'],
self.serialize_program_type(expected, many=True, extra_context=extra_context)
)
def test_list(self):
""" Verify the endpoint returns a list of all program_types. """
ProgramTypeFactory.create_batch(3)
expected = ProgramType.objects.all()
self.assert_list_results(self.list_path, expected, 4)
......@@ -8,7 +8,7 @@ from course_discovery.apps.api.v1.views.catalogs import CatalogViewSet
from course_discovery.apps.api.v1.views.course_runs import CourseRunViewSet
from course_discovery.apps.api.v1.views.courses import CourseViewSet
from course_discovery.apps.api.v1.views.organizations import OrganizationViewSet
from course_discovery.apps.api.v1.views.programs import ProgramViewSet
from course_discovery.apps.api.v1.views.programs import ProgramViewSet, ProgramTypeListViewSet
partners_router = routers.SimpleRouter()
partners_router.register(r'affiliate_window/catalogs', AffiliateWindowViewSet, base_name='affiliate_window')
......@@ -24,6 +24,7 @@ router.register(r'courses', CourseViewSet, base_name='course')
router.register(r'course_runs', CourseRunViewSet, base_name='course_run')
router.register(r'organizations', OrganizationViewSet, base_name='organization')
router.register(r'programs', ProgramViewSet, base_name='program')
router.register(r'program_types', ProgramTypeListViewSet, base_name='program_type')
router.register(r'search/all', search_views.AggregateSearchViewSet, base_name='search-all')
router.register(r'search/courses', search_views.CourseSearchViewSet, base_name='search-courses')
router.register(r'search/course_runs', search_views.CourseRunSearchViewSet, base_name='search-course_runs')
......
from rest_framework import viewsets
from rest_framework import mixins, viewsets
from rest_framework.filters import DjangoFilterBackend
from rest_framework.permissions import IsAuthenticated
from course_discovery.apps.api import filters, serializers
from course_discovery.apps.api.v1.views import get_query_param
from course_discovery.apps.course_metadata.models import ProgramType
# pylint: disable=no-member
......@@ -66,3 +67,11 @@ class ProgramViewSet(viewsets.ReadOnlyModelViewSet):
multiple: false
"""
return super(ProgramViewSet, self).list(request, *args, **kwargs)
class ProgramTypeListViewSet(mixins.ListModelMixin,
viewsets.GenericViewSet):
""" ProgramType resource. """
serializer_class = serializers.ProgramTypeSerializer
permission_classes = (IsAuthenticated,)
queryset = ProgramType.objects.all()
# -*- coding: utf-8 -*-
# Generated by Django 1.9.11 on 2016-12-13 10:57
from __future__ import unicode_literals
from django.db import migrations
import stdimage.models
import stdimage.utils
class Migration(migrations.Migration):
dependencies = [
('course_metadata', '0038_seat_sku'),
]
operations = [
migrations.AddField(
model_name='programtype',
name='logo_image',
field=stdimage.models.StdImageField(blank=True, help_text='Please provide an image file with transparent background', null=True, upload_to=stdimage.utils.UploadToAutoSlug('name', path='media/program_types/logo_images')),
),
]
......@@ -17,6 +17,7 @@ from haystack import connections
from haystack.query import SearchQuerySet
from sortedm2m.fields import SortedManyToManyField
from stdimage.models import StdImageField
from stdimage.utils import UploadToAutoSlug
from taggit.managers import TaggableManager
from course_discovery.apps.core.models import Currency, Partner
......@@ -580,6 +581,18 @@ class ProgramType(TimeStampedModel):
'associated courses, but enrolled in other seat types, will NOT have their completion '
'of the course counted toward the completion of the program.'),
)
logo_image = StdImageField(
upload_to=UploadToAutoSlug(populate_from='name', path='media/program_types/logo_images'),
blank=True,
null=True,
variations={
'large': (256, 256),
'medium': (128, 128),
'small': (64, 64),
'x-small': (32, 32),
},
help_text=_('Please provide an image file with transparent background'),
)
def __str__(self):
return self.name
......
......@@ -189,6 +189,7 @@ class ProgramTypeFactory(factory.django.DjangoModelFactory):
model = ProgramType
name = FuzzyText()
logo_image = FuzzyText(prefix='https://example.com/program/logo')
@factory.post_generation
def applicable_seat_types(self, create, extracted, **kwargs):
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-12-08 15:03+0500\n"
"POT-Creation-Date: 2016-12-14 15:06+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
#: apps/api/filters.py
#, python-brace-format
......@@ -362,6 +362,10 @@ msgid ""
msgstr ""
#: apps/course_metadata/models.py
msgid "Please provide an image file with transparent background"
msgstr ""
#: apps/course_metadata/models.py
msgid "The user-facing display title for this Program."
msgstr ""
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-12-08 15:03+0500\n"
"POT-Creation-Date: 2016-12-14 15:06+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
#: static/js/catalogs-change-form.js
msgid "Preview"
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-12-08 15:03+0500\n"
"POT-Creation-Date: 2016-12-14 15:06+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: apps/api/filters.py
......@@ -449,6 +449,12 @@ msgstr ""
" єѕѕє ¢ιłłυм ∂σłσяє є#"
#: apps/course_metadata/models.py
msgid "Please provide an image file with transparent background"
msgstr ""
"Pléäsé prövïdé än ïmägé fïlé wïth tränspärént ßäçkgröünd Ⱡ'σяєм ιρѕυм ∂σłσя "
"ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#: apps/course_metadata/models.py
msgid "The user-facing display title for this Program."
msgstr ""
"Thé üsér-fäçïng dïspläý tïtlé för thïs Prögräm. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт,"
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-12-08 15:03+0500\n"
"POT-Creation-Date: 2016-12-14 15:06+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: static/js/catalogs-change-form.js
......
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