Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
course-discovery
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
course-discovery
Commits
ebb4292e
Commit
ebb4292e
authored
Aug 11, 2016
by
Simon Chen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ECOM-4419 Create the stdimage library integration
parent
8540060f
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
199 additions
and
9 deletions
+199
-9
.gitignore
+1
-0
course_discovery/apps/api/fields.py
+26
-0
course_discovery/apps/api/serializers.py
+4
-2
course_discovery/apps/api/tests/test_serializers.py
+16
-0
course_discovery/apps/core/tests/helpers.py
+22
-0
course_discovery/apps/core/tests/utils.py
+11
-0
course_discovery/apps/course_metadata/data_loaders/api.py
+21
-0
course_discovery/apps/course_metadata/data_loaders/tests/__init__.py
+1
-0
course_discovery/apps/course_metadata/data_loaders/tests/test_api.py
+41
-3
course_discovery/apps/course_metadata/migrations/0019_program_banner_image.py
+21
-0
course_discovery/apps/course_metadata/models.py
+12
-1
course_discovery/apps/course_metadata/tests/test_models.py
+14
-0
course_discovery/urls.py
+8
-3
requirements/base.txt
+1
-0
No files found.
.gitignore
View file @
ebb4292e
...
@@ -85,4 +85,5 @@ private.py
...
@@ -85,4 +85,5 @@ private.py
docs/_build/
docs/_build/
course_discovery/static/bower_components/
course_discovery/static/bower_components/
node_modules/
node_modules/
course_discovery/media/
docker/volumes/
docker/volumes/
course_discovery/apps/api/fields.py
0 → 100644
View file @
ebb4292e
from
rest_framework
import
serializers
class
StdImageSerializerField
(
serializers
.
Field
):
"""
Custom serializer field to render out proper JSON representation of the StdImage field on model
"""
def
to_representation
(
self
,
obj
):
serialized
=
{}
for
size_key
in
obj
.
field
.
variations
:
# Get different sizes specs from the model field
# Then get the file path from the available files
sized_file
=
getattr
(
obj
,
size_key
,
None
)
if
sized_file
:
path
=
sized_file
.
url
serialized_image
=
serialized
.
setdefault
(
size_key
,
{})
# In case MEDIA_URL does not include scheme+host, ensure that the URLs are absolute and not relative
serialized_image
[
'url'
]
=
self
.
context
[
'request'
]
.
build_absolute_uri
(
path
)
serialized_image
[
'width'
]
=
obj
.
field
.
variations
[
size_key
][
'width'
]
serialized_image
[
'height'
]
=
obj
.
field
.
variations
[
size_key
][
'height'
]
return
serialized
def
to_internal_value
(
self
,
obj
):
""" We do not need to save/edit this banner image through serializer yet """
pass
course_discovery/apps/api/serializers.py
View file @
ebb4292e
...
@@ -9,6 +9,7 @@ from rest_framework import serializers
...
@@ -9,6 +9,7 @@ from rest_framework import serializers
from
rest_framework.fields
import
DictField
from
rest_framework.fields
import
DictField
from
taggit_serializer.serializers
import
TagListSerializerField
,
TaggitSerializer
from
taggit_serializer.serializers
import
TagListSerializerField
,
TaggitSerializer
from
course_discovery.apps.api.fields
import
StdImageSerializerField
from
course_discovery.apps.catalogs.models
import
Catalog
from
course_discovery.apps.catalogs.models
import
Catalog
from
course_discovery.apps.course_metadata.models
import
(
from
course_discovery.apps.course_metadata.models
import
(
Course
,
CourseRun
,
Image
,
Organization
,
Person
,
Prerequisite
,
Seat
,
Subject
,
Video
,
Program
,
ProgramType
,
Course
,
CourseRun
,
Image
,
Organization
,
Person
,
Prerequisite
,
Seat
,
Subject
,
Video
,
Program
,
ProgramType
,
...
@@ -279,6 +280,7 @@ class ProgramSerializer(serializers.ModelSerializer):
...
@@ -279,6 +280,7 @@ class ProgramSerializer(serializers.ModelSerializer):
courses
=
serializers
.
SerializerMethodField
()
courses
=
serializers
.
SerializerMethodField
()
authoring_organizations
=
OrganizationSerializer
(
many
=
True
)
authoring_organizations
=
OrganizationSerializer
(
many
=
True
)
type
=
serializers
.
SlugRelatedField
(
slug_field
=
'name'
,
queryset
=
ProgramType
.
objects
.
all
())
type
=
serializers
.
SlugRelatedField
(
slug_field
=
'name'
,
queryset
=
ProgramType
.
objects
.
all
())
banner_image
=
StdImageSerializerField
()
def
get_courses
(
self
,
program
):
def
get_courses
(
self
,
program
):
course_serializer
=
ProgramCourseSerializer
(
course_serializer
=
ProgramCourseSerializer
(
...
@@ -294,8 +296,8 @@ class ProgramSerializer(serializers.ModelSerializer):
...
@@ -294,8 +296,8 @@ class ProgramSerializer(serializers.ModelSerializer):
class
Meta
:
class
Meta
:
model
=
Program
model
=
Program
fields
=
(
'uuid'
,
'title'
,
'subtitle'
,
'type'
,
'marketing_slug'
,
'marketing_url'
,
'card_image_url'
,
fields
=
(
'uuid'
,
'title'
,
'subtitle'
,
'type'
,
'marketing_slug'
,
'marketing_url'
,
'card_image_url'
,
'banner_image_url'
,
'authoring_organizations'
,
'courses'
,)
'banner_image
'
,
'banner_image
_url'
,
'authoring_organizations'
,
'courses'
,)
read_only_fields
=
(
'uuid'
,
'marketing_url'
,)
read_only_fields
=
(
'uuid'
,
'marketing_url'
,
'banner_image'
)
class
AffiliateWindowSerializer
(
serializers
.
ModelSerializer
):
class
AffiliateWindowSerializer
(
serializers
.
ModelSerializer
):
...
...
course_discovery/apps/api/tests/test_serializers.py
View file @
ebb4292e
...
@@ -16,6 +16,7 @@ from course_discovery.apps.api.serializers import (
...
@@ -16,6 +16,7 @@ from course_discovery.apps.api.serializers import (
from
course_discovery.apps.catalogs.tests.factories
import
CatalogFactory
from
course_discovery.apps.catalogs.tests.factories
import
CatalogFactory
from
course_discovery.apps.core.models
import
User
from
course_discovery.apps.core.models
import
User
from
course_discovery.apps.core.tests.factories
import
UserFactory
from
course_discovery.apps.core.tests.factories
import
UserFactory
from
course_discovery.apps.core.tests.helpers
import
make_image_file
from
course_discovery.apps.course_metadata.models
import
CourseRun
,
Program
from
course_discovery.apps.course_metadata.models
import
CourseRun
,
Program
from
course_discovery.apps.course_metadata.search_indexes
import
OrganizationsMixin
from
course_discovery.apps.course_metadata.search_indexes
import
OrganizationsMixin
from
course_discovery.apps.course_metadata.tests.factories
import
(
from
course_discovery.apps.course_metadata.tests.factories
import
(
...
@@ -251,7 +252,20 @@ class ProgramSerializerTests(TestCase):
...
@@ -251,7 +252,20 @@ class ProgramSerializerTests(TestCase):
org_list
=
OrganizationFactory
.
create_batch
(
1
)
org_list
=
OrganizationFactory
.
create_batch
(
1
)
course_list
=
CourseFactory
.
create_batch
(
3
)
course_list
=
CourseFactory
.
create_batch
(
3
)
program
=
ProgramFactory
(
authoring_organizations
=
org_list
,
courses
=
course_list
)
program
=
ProgramFactory
(
authoring_organizations
=
org_list
,
courses
=
course_list
)
program
.
banner_image
=
make_image_file
(
'test_banner.jpg'
)
program
.
save
()
serializer
=
ProgramSerializer
(
program
,
context
=
{
'request'
:
request
})
serializer
=
ProgramSerializer
(
program
,
context
=
{
'request'
:
request
})
expected_banner_image_urls
=
{
size_key
:
{
'url'
:
'{}{}'
.
format
(
'http://testserver'
,
getattr
(
program
.
banner_image
,
size_key
)
.
url
),
'width'
:
program
.
banner_image
.
field
.
variations
[
size_key
][
'width'
],
'height'
:
program
.
banner_image
.
field
.
variations
[
size_key
][
'height'
]
}
for
size_key
in
program
.
banner_image
.
field
.
variations
}
expected
=
{
expected
=
{
'uuid'
:
str
(
program
.
uuid
),
'uuid'
:
str
(
program
.
uuid
),
...
@@ -262,6 +276,7 @@ class ProgramSerializerTests(TestCase):
...
@@ -262,6 +276,7 @@ class ProgramSerializerTests(TestCase):
'marketing_url'
:
program
.
marketing_url
,
'marketing_url'
:
program
.
marketing_url
,
'card_image_url'
:
program
.
card_image_url
,
'card_image_url'
:
program
.
card_image_url
,
'banner_image_url'
:
program
.
banner_image_url
,
'banner_image_url'
:
program
.
banner_image_url
,
'banner_image'
:
expected_banner_image_urls
,
'authoring_organizations'
:
OrganizationSerializer
(
program
.
authoring_organizations
,
many
=
True
)
.
data
,
'authoring_organizations'
:
OrganizationSerializer
(
program
.
authoring_organizations
,
many
=
True
)
.
data
,
'courses'
:
ProgramCourseSerializer
(
'courses'
:
ProgramCourseSerializer
(
program
.
courses
,
program
.
courses
,
...
@@ -300,6 +315,7 @@ class ProgramSerializerTests(TestCase):
...
@@ -300,6 +315,7 @@ class ProgramSerializerTests(TestCase):
'marketing_slug'
:
program
.
marketing_slug
,
'marketing_slug'
:
program
.
marketing_slug
,
'marketing_url'
:
program
.
marketing_url
,
'marketing_url'
:
program
.
marketing_url
,
'card_image_url'
:
program
.
card_image_url
,
'card_image_url'
:
program
.
card_image_url
,
'banner_image'
:
{},
'banner_image_url'
:
program
.
banner_image_url
,
'banner_image_url'
:
program
.
banner_image_url
,
'authoring_organizations'
:
OrganizationSerializer
(
program
.
authoring_organizations
,
many
=
True
)
.
data
,
'authoring_organizations'
:
OrganizationSerializer
(
program
.
authoring_organizations
,
many
=
True
)
.
data
,
'courses'
:
ProgramCourseSerializer
(
'courses'
:
ProgramCourseSerializer
(
...
...
course_discovery/apps/core/tests/helpers.py
0 → 100644
View file @
ebb4292e
"""
Helper methods for testing the processing of image files.
"""
from
io
import
BytesIO
from
PIL
import
Image
from
django.core.files.uploadedfile
import
SimpleUploadedFile
def
make_image_stream
():
"""
Helper to generate values for program banner_image
"""
image
=
Image
.
new
(
'RGB'
,
(
1440
,
900
),
'green'
)
bio
=
BytesIO
()
image
.
save
(
bio
,
format
=
'JPEG'
)
return
bio
def
make_image_file
(
name
):
image_stream
=
make_image_stream
()
return
SimpleUploadedFile
(
name
,
image_stream
.
getvalue
(),
content_type
=
'image/jpeg'
)
course_discovery/apps/core/tests/utils.py
View file @
ebb4292e
...
@@ -5,6 +5,8 @@ from factory.fuzzy import (
...
@@ -5,6 +5,8 @@ from factory.fuzzy import (
BaseFuzzyAttribute
,
FuzzyText
,
FuzzyChoice
BaseFuzzyAttribute
,
FuzzyText
,
FuzzyChoice
)
)
from
course_discovery.apps.core.tests.helpers
import
make_image_stream
class
FuzzyDomain
(
BaseFuzzyAttribute
):
class
FuzzyDomain
(
BaseFuzzyAttribute
):
def
fuzz
(
self
):
def
fuzz
(
self
):
...
@@ -81,3 +83,12 @@ def mock_api_callback(url, data, results_key=True, pagination=False):
...
@@ -81,3 +83,12 @@ def mock_api_callback(url, data, results_key=True, pagination=False):
return
200
,
{},
json
.
dumps
(
body
)
return
200
,
{},
json
.
dumps
(
body
)
return
request_callback
return
request_callback
def
mock_jpeg_callback
():
def
request_callback
(
request
):
# pylint: disable=unused-argument
image_stream
=
make_image_stream
()
return
200
,
{},
image_stream
.
getvalue
()
return
request_callback
course_discovery/apps/course_metadata/data_loaders/api.py
View file @
ebb4292e
import
logging
import
logging
from
decimal
import
Decimal
from
decimal
import
Decimal
from
io
import
BytesIO
import
requests
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
django.core.files
import
File
from
course_discovery.apps.core.models
import
Currency
from
course_discovery.apps.core.models
import
Currency
from
course_discovery.apps.course_metadata.data_loaders
import
AbstractDataLoader
from
course_discovery.apps.course_metadata.data_loaders
import
AbstractDataLoader
...
@@ -299,6 +302,7 @@ class ProgramsApiDataLoader(AbstractDataLoader):
...
@@ -299,6 +302,7 @@ class ProgramsApiDataLoader(AbstractDataLoader):
program
,
__
=
Program
.
objects
.
update_or_create
(
uuid
=
uuid
,
defaults
=
defaults
)
program
,
__
=
Program
.
objects
.
update_or_create
(
uuid
=
uuid
,
defaults
=
defaults
)
self
.
_update_program_organizations
(
body
,
program
)
self
.
_update_program_organizations
(
body
,
program
)
self
.
_update_program_courses_and_runs
(
body
,
program
)
self
.
_update_program_courses_and_runs
(
body
,
program
)
self
.
_update_program_banner_image
(
body
,
program
)
program
.
save
()
program
.
save
()
except
Exception
:
# pylint: disable=broad-except
except
Exception
:
# pylint: disable=broad-except
logger
.
exception
(
'Failed to load program
%
s'
,
uuid
)
logger
.
exception
(
'Failed to load program
%
s'
,
uuid
)
...
@@ -335,3 +339,20 @@ class ProgramsApiDataLoader(AbstractDataLoader):
...
@@ -335,3 +339,20 @@ class ProgramsApiDataLoader(AbstractDataLoader):
image_key
=
'w{width}h{height}'
.
format
(
width
=
self
.
image_width
,
height
=
self
.
image_height
)
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
)
image_url
=
body
.
get
(
'banner_image_urls'
,
{})
.
get
(
image_key
)
return
image_url
return
image_url
def
_update_program_banner_image
(
self
,
body
,
program
):
image_url
=
self
.
_get_banner_image_url
(
body
)
if
not
image_url
:
logger
.
warning
(
'There are no banner image url for program
%
s'
,
program
.
title
)
return
r
=
requests
.
get
(
image_url
)
if
r
.
status_code
==
200
:
banner_downloaded
=
File
(
BytesIO
(
r
.
content
))
program
.
banner_image
.
save
(
'banner.jpg'
,
banner_downloaded
)
program
.
save
()
else
:
logger
.
exception
(
'Loading the banner image
%
s for program
%
s failed'
,
image_url
,
program
.
title
)
course_discovery/apps/course_metadata/data_loaders/tests/__init__.py
View file @
ebb4292e
JSON
=
'application/json'
JSON
=
'application/json'
JPEG
=
'image/jpeg'
course_discovery/apps/course_metadata/data_loaders/tests/test_api.py
View file @
ebb4292e
...
@@ -7,11 +7,11 @@ import responses
...
@@ -7,11 +7,11 @@ import responses
from
django.test
import
TestCase
from
django.test
import
TestCase
from
pytz
import
UTC
from
pytz
import
UTC
from
course_discovery.apps.core.tests.utils
import
mock_api_callback
from
course_discovery.apps.core.tests.utils
import
mock_api_callback
,
mock_jpeg_callback
from
course_discovery.apps.course_metadata.data_loaders.api
import
(
from
course_discovery.apps.course_metadata.data_loaders.api
import
(
OrganizationsApiDataLoader
,
CoursesApiDataLoader
,
EcommerceApiDataLoader
,
AbstractDataLoader
,
ProgramsApiDataLoader
OrganizationsApiDataLoader
,
CoursesApiDataLoader
,
EcommerceApiDataLoader
,
AbstractDataLoader
,
ProgramsApiDataLoader
)
)
from
course_discovery.apps.course_metadata.data_loaders.tests
import
JSON
from
course_discovery.apps.course_metadata.data_loaders.tests
import
JSON
,
JPEG
from
course_discovery.apps.course_metadata.data_loaders.tests.mixins
import
ApiClientTestMixin
,
DataLoaderTestMixin
from
course_discovery.apps.course_metadata.data_loaders.tests.mixins
import
ApiClientTestMixin
,
DataLoaderTestMixin
from
course_discovery.apps.course_metadata.models
import
(
from
course_discovery.apps.course_metadata.models
import
(
Course
,
CourseRun
,
Organization
,
Seat
,
Program
,
ProgramType
,
Course
,
CourseRun
,
Organization
,
Seat
,
Program
,
ProgramType
,
...
@@ -418,6 +418,22 @@ class ProgramsApiDataLoaderTests(ApiClientTestMixin, DataLoaderTestMixin, TestCa
...
@@ -418,6 +418,22 @@ class ProgramsApiDataLoaderTests(ApiClientTestMixin, DataLoaderTestMixin, TestCa
# Verify the additional course runs added in create_mock_courses_and_runs are excluded.
# Verify the additional course runs added in create_mock_courses_and_runs are excluded.
self
.
assertEqual
(
program
.
excluded_course_runs
.
count
(),
len
(
course_codes
))
self
.
assertEqual
(
program
.
excluded_course_runs
.
count
(),
len
(
course_codes
))
def
assert_program_banner_image_loaded
(
self
,
body
):
""" Assert a program corresponding to the specified data body has banner image loaded into DB """
program
=
Program
.
objects
.
get
(
uuid
=
AbstractDataLoader
.
clean_string
(
body
[
'uuid'
]),
partner
=
self
.
partner
)
banner_image_url
=
body
.
get
(
'banner_image_urls'
,
{})
.
get
(
'w1440h480'
)
if
banner_image_url
:
for
size_key
in
program
.
banner_image
.
field
.
variations
:
# Get different sizes specs from the model field
# Then get the file path from the available files
sized_image
=
getattr
(
program
.
banner_image
,
size_key
,
None
)
self
.
assertIsNotNone
(
sized_image
)
if
sized_image
:
path
=
getattr
(
program
.
banner_image
,
size_key
)
.
url
self
.
assertIsNotNone
(
path
)
self
.
assertIsNotNone
(
program
.
banner_image
.
field
.
variations
[
size_key
][
'width'
])
self
.
assertIsNotNone
(
program
.
banner_image
.
field
.
variations
[
size_key
][
'height'
])
@responses.activate
@responses.activate
def
test_ingest
(
self
):
def
test_ingest
(
self
):
""" Verify the method ingests data from the Organizations API. """
""" Verify the method ingests data from the Organizations API. """
...
@@ -427,7 +443,7 @@ class ProgramsApiDataLoaderTests(ApiClientTestMixin, DataLoaderTestMixin, TestCa
...
@@ -427,7 +443,7 @@ class ProgramsApiDataLoaderTests(ApiClientTestMixin, DataLoaderTestMixin, TestCa
self
.
loader
.
ingest
()
self
.
loader
.
ingest
()
# Verify the API was called with the correct authorization header
# Verify the API was called with the correct authorization header
self
.
assert_api_called
(
1
)
self
.
assert_api_called
(
2
)
# Verify the Programs were created correctly
# Verify the Programs were created correctly
self
.
assertEqual
(
Program
.
objects
.
count
(),
len
(
api_data
))
self
.
assertEqual
(
Program
.
objects
.
count
(),
len
(
api_data
))
...
@@ -452,3 +468,25 @@ class ProgramsApiDataLoaderTests(ApiClientTestMixin, DataLoaderTestMixin, TestCa
...
@@ -452,3 +468,25 @@ class ProgramsApiDataLoaderTests(ApiClientTestMixin, DataLoaderTestMixin, TestCa
self
.
assertEqual
(
Program
.
objects
.
count
(),
len
(
api_data
))
self
.
assertEqual
(
Program
.
objects
.
count
(),
len
(
api_data
))
self
.
assertEqual
(
Organization
.
objects
.
count
(),
0
)
self
.
assertEqual
(
Organization
.
objects
.
count
(),
0
)
@responses.activate
def
test_ingest_with_existing_banner_image
(
self
):
programs
=
self
.
mock_api
()
for
program_data
in
programs
:
banner_image_url
=
program_data
.
get
(
'banner_image_urls'
,
{})
.
get
(
'w1440h480'
)
if
banner_image_url
:
responses
.
add_callback
(
responses
.
GET
,
banner_image_url
,
callback
=
mock_jpeg_callback
(),
content_type
=
JPEG
)
self
.
loader
.
ingest
()
# Verify the API was called with the correct authorization header
self
.
assert_api_called
(
2
)
for
program
in
programs
:
self
.
assert_program_loaded
(
program
)
self
.
assert_program_banner_image_loaded
(
program
)
course_discovery/apps/course_metadata/migrations/0019_program_banner_image.py
0 → 100644
View file @
ebb4292e
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
import
stdimage.models
import
stdimage.utils
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'course_metadata'
,
'0018_auto_20160815_2252'
),
]
operations
=
[
migrations
.
AddField
(
model_name
=
'program'
,
name
=
'banner_image'
,
field
=
stdimage
.
models
.
StdImageField
(
upload_to
=
stdimage
.
utils
.
UploadToAutoSlugClassNameDir
(
'uuid'
,
path
=
'/media/programs/banner_images'
),
null
=
True
,
blank
=
True
),
),
]
course_discovery/apps/course_metadata/models.py
View file @
ebb4292e
...
@@ -14,6 +14,8 @@ from djchoices import DjangoChoices, ChoiceItem
...
@@ -14,6 +14,8 @@ from djchoices import DjangoChoices, ChoiceItem
from
haystack.query
import
SearchQuerySet
from
haystack.query
import
SearchQuerySet
from
simple_history.models
import
HistoricalRecords
from
simple_history.models
import
HistoricalRecords
from
sortedm2m.fields
import
SortedManyToManyField
from
sortedm2m.fields
import
SortedManyToManyField
from
stdimage.models
import
StdImageField
from
stdimage.utils
import
UploadToAutoSlugClassNameDir
from
taggit.managers
import
TaggableManager
from
taggit.managers
import
TaggableManager
from
course_discovery.apps.core.models
import
Currency
,
Partner
from
course_discovery.apps.core.models
import
Currency
,
Partner
...
@@ -584,7 +586,16 @@ class Program(TimeStampedModel):
...
@@ -584,7 +586,16 @@ class Program(TimeStampedModel):
min_hours_effort_per_week
=
models
.
PositiveSmallIntegerField
(
null
=
True
,
blank
=
True
)
min_hours_effort_per_week
=
models
.
PositiveSmallIntegerField
(
null
=
True
,
blank
=
True
)
max_hours_effort_per_week
=
models
.
PositiveSmallIntegerField
(
null
=
True
,
blank
=
True
)
max_hours_effort_per_week
=
models
.
PositiveSmallIntegerField
(
null
=
True
,
blank
=
True
)
authoring_organizations
=
SortedManyToManyField
(
Organization
,
blank
=
True
,
related_name
=
'authored_programs'
)
authoring_organizations
=
SortedManyToManyField
(
Organization
,
blank
=
True
,
related_name
=
'authored_programs'
)
banner_image
=
StdImageField
(
upload_to
=
UploadToAutoSlugClassNameDir
(
path
=
'/media/programs/banner_images'
,
populate_from
=
'uuid'
),
blank
=
True
,
null
=
True
,
variations
=
{
'large'
:
(
1440
,
480
),
'medium'
:
(
726
,
242
),
'small'
:
(
435
,
145
),
'x-small'
:
(
348
,
116
)}
)
banner_image_url
=
models
.
URLField
(
null
=
True
,
blank
=
True
,
help_text
=
_
(
'Image used atop detail pages'
))
banner_image_url
=
models
.
URLField
(
null
=
True
,
blank
=
True
,
help_text
=
_
(
'Image used atop detail pages'
))
card_image_url
=
models
.
URLField
(
null
=
True
,
blank
=
True
,
help_text
=
_
(
'Image used for discovery cards'
))
card_image_url
=
models
.
URLField
(
null
=
True
,
blank
=
True
,
help_text
=
_
(
'Image used for discovery cards'
))
video
=
models
.
ForeignKey
(
Video
,
default
=
None
,
null
=
True
,
blank
=
True
)
video
=
models
.
ForeignKey
(
Video
,
default
=
None
,
null
=
True
,
blank
=
True
)
...
...
course_discovery/apps/course_metadata/tests/test_models.py
View file @
ebb4292e
...
@@ -5,6 +5,7 @@ from decimal import Decimal
...
@@ -5,6 +5,7 @@ from decimal import Decimal
import
ddt
import
ddt
import
pytz
import
pytz
from
dateutil.parser
import
parse
from
dateutil.parser
import
parse
from
django.conf
import
settings
from
django.db
import
IntegrityError
from
django.db
import
IntegrityError
from
django.test
import
TestCase
from
django.test
import
TestCase
from
freezegun
import
freeze_time
from
freezegun
import
freeze_time
...
@@ -15,6 +16,7 @@ from course_discovery.apps.course_metadata.models import (
...
@@ -15,6 +16,7 @@ from course_discovery.apps.course_metadata.models import (
AbstractNamedModel
,
AbstractMediaModel
,
AbstractValueModel
,
CourseOrganization
,
Course
,
CourseRun
,
AbstractNamedModel
,
AbstractMediaModel
,
AbstractValueModel
,
CourseOrganization
,
Course
,
CourseRun
,
SeatType
)
SeatType
)
from
course_discovery.apps.course_metadata.tests
import
factories
from
course_discovery.apps.course_metadata.tests
import
factories
from
course_discovery.apps.core.tests.helpers
import
make_image_file
from
course_discovery.apps.ietf_language_tags.models
import
LanguageTag
from
course_discovery.apps.ietf_language_tags.models
import
LanguageTag
...
@@ -357,6 +359,18 @@ class ProgramTests(TestCase):
...
@@ -357,6 +359,18 @@ class ProgramTests(TestCase):
self
.
assertEqual
(
self
.
program
.
instructors
,
set
(
instructors
))
self
.
assertEqual
(
self
.
program
.
instructors
,
set
(
instructors
))
def
test_banner_image
(
self
):
self
.
program
.
banner_image
=
make_image_file
(
'test_banner.jpg'
)
self
.
program
.
save
()
image_url_prefix
=
'{}program/'
.
format
(
settings
.
MEDIA_URL
)
self
.
assertIn
(
image_url_prefix
,
self
.
program
.
banner_image
.
url
)
for
size_key
in
self
.
program
.
banner_image
.
field
.
variations
:
# Get different sizes specs from the model field
# Then get the file path from the available files
sized_file
=
getattr
(
self
.
program
.
banner_image
,
size_key
,
None
)
self
.
assertIsNotNone
(
sized_file
)
self
.
assertIn
(
image_url_prefix
,
sized_file
.
url
)
class
PersonSocialNetworkTests
(
TestCase
):
class
PersonSocialNetworkTests
(
TestCase
):
"""Tests of the PersonSocialNetwork model."""
"""Tests of the PersonSocialNetwork model."""
...
...
course_discovery/urls.py
View file @
ebb4292e
...
@@ -18,6 +18,7 @@ import os
...
@@ -18,6 +18,7 @@ import os
from
auth_backends.urls
import
auth_urlpatterns
from
auth_backends.urls
import
auth_urlpatterns
from
django.conf
import
settings
from
django.conf
import
settings
from
django.conf.urls
import
include
,
url
from
django.conf.urls
import
include
,
url
from
django.conf.urls.static
import
static
from
django.contrib
import
admin
from
django.contrib
import
admin
from
course_discovery.apps.core
import
views
as
core_views
from
course_discovery.apps.core
import
views
as
core_views
...
@@ -43,7 +44,11 @@ urlpatterns = auth_urlpatterns + [
...
@@ -43,7 +44,11 @@ urlpatterns = auth_urlpatterns + [
url
(
r'^comments/'
,
include
(
'django_comments.urls'
)),
url
(
r'^comments/'
,
include
(
'django_comments.urls'
)),
]
]
if
settings
.
DEBUG
and
os
.
environ
.
get
(
'ENABLE_DJANGO_TOOLBAR'
,
False
):
# pragma: no cover
if
settings
.
DEBUG
:
# pragma: no cover
import
debug_toolbar
# pylint: disable=wrong-import-order,wrong-import-position,import-error
# We need this url pattern to serve user uploaded assets according to
# https://docs.djangoproject.com/en/1.10/howto/static-files/#serving-files-uploaded-by-a-user-during-development
urlpatterns
+=
static
(
settings
.
MEDIA_URL
,
document_root
=
settings
.
MEDIA_ROOT
)
if
os
.
environ
.
get
(
'ENABLE_DJANGO_TOOLBAR'
,
False
):
import
debug_toolbar
# pylint: disable=wrong-import-order,wrong-import-position,import-error
urlpatterns
.
append
(
url
(
r'^__debug__/'
,
include
(
debug_toolbar
.
urls
)))
urlpatterns
.
append
(
url
(
r'^__debug__/'
,
include
(
debug_toolbar
.
urls
)))
requirements/base.txt
View file @
ebb4292e
...
@@ -12,6 +12,7 @@ django-haystack==2.5.0
...
@@ -12,6 +12,7 @@ django-haystack==2.5.0
django-libsass==0.7
django-libsass==0.7
django-simple-history==1.8.1
django-simple-history==1.8.1
django-sortedm2m==1.3.2
django-sortedm2m==1.3.2
django-stdimage==2.3.3
django-storages==1.5.0
django-storages==1.5.0
django-taggit==0.20.2
django-taggit==0.20.2
django-taggit-serializer==0.1.5
django-taggit-serializer==0.1.5
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment