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
58665dd5
Commit
58665dd5
authored
Sep 19, 2017
by
christopher lee
Committed by
Christopher Lee
Oct 06, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add multilingual content for Subjects
parent
a4994b7d
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
243 additions
and
31 deletions
+243
-31
course_discovery/apps/api/filters.py
+5
-1
course_discovery/apps/api/serializers.py
+3
-0
course_discovery/apps/api/tests/test_serializers.py
+3
-3
course_discovery/apps/course_metadata/admin.py
+6
-3
course_discovery/apps/course_metadata/data_loaders/marketing_site.py
+16
-4
course_discovery/apps/course_metadata/data_loaders/tests/test_marketing_site.py
+25
-3
course_discovery/apps/course_metadata/migrations/0060_create_subjecttranslations_models.py
+47
-0
course_discovery/apps/course_metadata/migrations/0061_migrate_subjects_data.py
+51
-0
course_discovery/apps/course_metadata/models.py
+25
-3
course_discovery/apps/course_metadata/tests/factories.py
+2
-2
course_discovery/apps/course_metadata/tests/test_models.py
+31
-4
course_discovery/apps/course_metadata/tests/test_signals.py
+2
-2
course_discovery/conf/locale/en/LC_MESSAGES/django.mo
+0
-0
course_discovery/conf/locale/en/LC_MESSAGES/django.po
+5
-1
course_discovery/conf/locale/en/LC_MESSAGES/djangojs.mo
+0
-0
course_discovery/conf/locale/en/LC_MESSAGES/djangojs.po
+1
-1
course_discovery/conf/locale/eo/LC_MESSAGES/django.mo
+0
-0
course_discovery/conf/locale/eo/LC_MESSAGES/django.po
+5
-1
course_discovery/conf/locale/eo/LC_MESSAGES/djangojs.mo
+0
-0
course_discovery/conf/locale/eo/LC_MESSAGES/djangojs.po
+1
-1
course_discovery/settings/base.py
+15
-2
No files found.
course_discovery/apps/api/filters.py
View file @
58665dd5
...
@@ -181,7 +181,11 @@ class PersonFilter(filters.FilterSet):
...
@@ -181,7 +181,11 @@ class PersonFilter(filters.FilterSet):
class
SubjectFilter
(
filters
.
FilterSet
):
class
SubjectFilter
(
filters
.
FilterSet
):
language_code
=
filters
.
CharFilter
(
method
=
'_set_language'
)
def
_set_language
(
self
,
queryset
,
_
,
language_code
):
return
queryset
.
language
(
language_code
)
class
Meta
:
class
Meta
:
model
=
Subject
model
=
Subject
fields
=
(
'slug'
,
)
fields
=
(
'slug'
,
'language_code'
)
course_discovery/apps/api/serializers.py
View file @
58665dd5
...
@@ -163,6 +163,9 @@ class FAQSerializer(serializers.ModelSerializer):
...
@@ -163,6 +163,9 @@ class FAQSerializer(serializers.ModelSerializer):
class
SubjectSerializer
(
serializers
.
ModelSerializer
):
class
SubjectSerializer
(
serializers
.
ModelSerializer
):
"""Serializer for the ``Subject`` model."""
"""Serializer for the ``Subject`` model."""
name
=
serializers
.
CharField
(
source
=
'name_t'
)
subtitle
=
serializers
.
CharField
(
source
=
'subtitle_t'
)
description
=
serializers
.
CharField
(
source
=
'description_t'
)
@classmethod
@classmethod
def
prefetch_queryset
(
cls
):
def
prefetch_queryset
(
cls
):
...
...
course_discovery/apps/api/tests/test_serializers.py
View file @
58665dd5
...
@@ -949,11 +949,11 @@ class SubjectSerializerTests(TestCase):
...
@@ -949,11 +949,11 @@ class SubjectSerializerTests(TestCase):
serializer
=
SubjectSerializer
(
subject
)
serializer
=
SubjectSerializer
(
subject
)
expected
=
{
expected
=
{
'name'
:
subject
.
name
,
'name'
:
subject
.
name
_t
,
'description'
:
subject
.
description
,
'description'
:
subject
.
description
_t
,
'banner_image_url'
:
subject
.
banner_image_url
,
'banner_image_url'
:
subject
.
banner_image_url
,
'card_image_url'
:
subject
.
card_image_url
,
'card_image_url'
:
subject
.
card_image_url
,
'subtitle'
:
subject
.
subtitle
,
'subtitle'
:
subject
.
subtitle
_t
,
'slug'
:
subject
.
slug
,
'slug'
:
subject
.
slug
,
'uuid'
:
str
(
subject
.
uuid
),
'uuid'
:
str
(
subject
.
uuid
),
}
}
...
...
course_discovery/apps/course_metadata/admin.py
View file @
58665dd5
...
@@ -3,6 +3,7 @@ from django.http import HttpResponseRedirect
...
@@ -3,6 +3,7 @@ from django.http import HttpResponseRedirect
from
django.urls
import
reverse
from
django.urls
import
reverse
from
django.utils.safestring
import
mark_safe
from
django.utils.safestring
import
mark_safe
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
parler.admin
import
TranslatableAdmin
from
course_discovery.apps.course_metadata.exceptions
import
(
from
course_discovery.apps.course_metadata.exceptions
import
(
MarketingSiteAPIClientException
,
MarketingSitePublisherException
MarketingSiteAPIClientException
,
MarketingSitePublisherException
...
@@ -220,11 +221,13 @@ class OrganizationAdmin(admin.ModelAdmin):
...
@@ -220,11 +221,13 @@ class OrganizationAdmin(admin.ModelAdmin):
@admin.register
(
Subject
)
@admin.register
(
Subject
)
class
SubjectAdmin
(
admin
.
ModelAdmin
):
class
SubjectAdmin
(
TranslatableAdmin
):
list_display
=
(
'uuid'
,
'name'
,
'slug'
,)
# These fields are excluded here because they will be removed in favor of the translated fields.
exclude
=
(
'name'
,
'subtitle'
,
'description'
)
list_display
=
(
'uuid'
,
'name_t'
,
'slug'
,)
list_filter
=
(
'partner'
,)
list_filter
=
(
'partner'
,)
readonly_fields
=
(
'uuid'
,)
readonly_fields
=
(
'uuid'
,)
search_fields
=
(
'uuid'
,
'name'
,
'slug'
,)
search_fields
=
(
'uuid'
,
'name
_t
'
,
'slug'
,)
@admin.register
(
Person
)
@admin.register
(
Person
)
...
...
course_discovery/apps/course_metadata/data_loaders/marketing_site.py
View file @
58665dd5
...
@@ -143,15 +143,27 @@ class SubjectMarketingSiteDataLoader(AbstractMarketingSiteDataLoader):
...
@@ -143,15 +143,27 @@ class SubjectMarketingSiteDataLoader(AbstractMarketingSiteDataLoader):
slug
=
data
[
'field_subject_url_slug'
]
slug
=
data
[
'field_subject_url_slug'
]
defaults
=
{
defaults
=
{
'uuid'
:
data
[
'uuid'
],
'uuid'
:
data
[
'uuid'
],
'name'
:
data
[
'title'
],
'name
_t
'
:
data
[
'title'
],
'description'
:
self
.
clean_html
(
data
[
'body'
][
'value'
]),
'description
_t
'
:
self
.
clean_html
(
data
[
'body'
][
'value'
]),
'subtitle'
:
self
.
clean_html
(
data
[
'field_subject_subtitle'
][
'value'
]),
'subtitle
_t
'
:
self
.
clean_html
(
data
[
'field_subject_subtitle'
][
'value'
]),
'card_image_url'
:
self
.
_get_nested_url
(
data
.
get
(
'field_subject_card_image'
)),
'card_image_url'
:
self
.
_get_nested_url
(
data
.
get
(
'field_subject_card_image'
)),
# NOTE (CCB): This is not a typo. Yes, the banner image for subjects is in a field with xseries in the name.
# NOTE (CCB): This is not a typo. Yes, the banner image for subjects is in a field with xseries in the name.
'banner_image_url'
:
self
.
_get_nested_url
(
data
.
get
(
'field_xseries_banner_image'
))
'banner_image_url'
:
self
.
_get_nested_url
(
data
.
get
(
'field_xseries_banner_image'
))
}
}
subject
,
__
=
Subject
.
objects
.
update_or_create
(
slug
=
slug
,
partner
=
self
.
partner
,
defaults
=
defaults
)
# There is a bug with django-parler when using django's update_or_create() so we manually update or create.
try
:
subject
=
Subject
.
objects
.
get
(
slug
=
slug
,
partner
=
self
.
partner
)
for
key
,
value
in
defaults
.
items
():
setattr
(
subject
,
key
,
value
)
subject
.
save
()
except
Subject
.
DoesNotExist
:
new_values
=
{
'slug'
:
slug
,
'partner'
:
self
.
partner
}
new_values
.
update
(
defaults
)
subject
=
Subject
(
**
new_values
)
subject
.
save
()
logger
.
info
(
'Processed subject with slug [
%
s].'
,
slug
)
logger
.
info
(
'Processed subject with slug [
%
s].'
,
slug
)
return
subject
return
subject
...
...
course_discovery/apps/course_metadata/data_loaders/tests/test_marketing_site.py
View file @
58665dd5
...
@@ -146,9 +146,9 @@ class SubjectMarketingSiteDataLoaderTests(AbstractMarketingSiteDataLoaderTestMix
...
@@ -146,9 +146,9 @@ class SubjectMarketingSiteDataLoaderTests(AbstractMarketingSiteDataLoaderTestMix
subject
=
Subject
.
objects
.
get
(
slug
=
slug
,
partner
=
self
.
partner
)
subject
=
Subject
.
objects
.
get
(
slug
=
slug
,
partner
=
self
.
partner
)
expected_values
=
{
expected_values
=
{
'uuid'
:
UUID
(
data
[
'uuid'
]),
'uuid'
:
UUID
(
data
[
'uuid'
]),
'name'
:
data
[
'title'
],
'name
_t
'
:
data
[
'title'
],
'description'
:
self
.
loader
.
clean_html
(
data
[
'body'
][
'value'
]),
'description
_t
'
:
self
.
loader
.
clean_html
(
data
[
'body'
][
'value'
]),
'subtitle'
:
self
.
loader
.
clean_html
(
data
[
'field_subject_subtitle'
][
'value'
]),
'subtitle
_t
'
:
self
.
loader
.
clean_html
(
data
[
'field_subject_subtitle'
][
'value'
]),
'card_image_url'
:
data
[
'field_subject_card_image'
][
'url'
],
'card_image_url'
:
data
[
'field_subject_card_image'
][
'url'
],
'banner_image_url'
:
data
[
'field_xseries_banner_image'
][
'url'
],
'banner_image_url'
:
data
[
'field_xseries_banner_image'
][
'url'
],
}
}
...
@@ -166,6 +166,28 @@ class SubjectMarketingSiteDataLoaderTests(AbstractMarketingSiteDataLoaderTestMix
...
@@ -166,6 +166,28 @@ class SubjectMarketingSiteDataLoaderTests(AbstractMarketingSiteDataLoaderTestMix
for
datum
in
api_data
:
for
datum
in
api_data
:
self
.
assert_subject_loaded
(
datum
)
self
.
assert_subject_loaded
(
datum
)
@responses.activate
def
test_ingest2
(
self
):
self
.
mock_login_response
()
api_data
=
self
.
mock_api
()
for
data
in
api_data
:
subject_data
=
{
'uuid'
:
UUID
(
data
[
'uuid'
]),
'name_t'
:
data
[
'title'
],
'description_t'
:
self
.
loader
.
clean_html
(
data
[
'body'
][
'value'
]),
'subtitle_t'
:
self
.
loader
.
clean_html
(
data
[
'field_subject_subtitle'
][
'value'
]),
'card_image_url'
:
data
[
'field_subject_card_image'
][
'url'
],
'banner_image_url'
:
data
[
'field_xseries_banner_image'
][
'url'
],
}
slug
=
data
[
'field_subject_url_slug'
]
Subject
.
objects
.
create
(
slug
=
slug
,
partner
=
self
.
partner
,
**
subject_data
)
self
.
loader
.
ingest
()
for
datum
in
api_data
:
self
.
assert_subject_loaded
(
datum
)
class
SchoolMarketingSiteDataLoaderTests
(
AbstractMarketingSiteDataLoaderTestMixin
,
TestCase
):
class
SchoolMarketingSiteDataLoaderTests
(
AbstractMarketingSiteDataLoaderTestMixin
,
TestCase
):
loader_class
=
SchoolMarketingSiteDataLoader
loader_class
=
SchoolMarketingSiteDataLoader
...
...
course_discovery/apps/course_metadata/migrations/0060_create_subjecttranslations_models.py
0 → 100644
View file @
58665dd5
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-09-19 19:27
from
__future__
import
unicode_literals
import
django.db.models.deletion
import
django_extensions
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'course_metadata'
,
'0059_auto_20171002_1705'
),
]
operations
=
[
migrations
.
CreateModel
(
name
=
'SubjectTranslation'
,
fields
=
[
(
'id'
,
models
.
AutoField
(
auto_created
=
True
,
primary_key
=
True
,
serialize
=
False
,
verbose_name
=
'ID'
)),
(
'language_code'
,
models
.
CharField
(
db_index
=
True
,
max_length
=
15
,
verbose_name
=
'Language'
)),
(
'name_t'
,
models
.
CharField
(
max_length
=
255
)),
(
'subtitle_t'
,
models
.
CharField
(
blank
=
True
,
max_length
=
255
,
null
=
True
)),
(
'description_t'
,
models
.
TextField
(
blank
=
True
,
null
=
True
)),
(
'master'
,
models
.
ForeignKey
(
null
=
True
,
on_delete
=
django
.
db
.
models
.
deletion
.
CASCADE
,
related_name
=
'translations'
,
to
=
'course_metadata.Subject'
)),
],
options
=
{
'verbose_name'
:
'Subject model translations'
,
},
),
migrations
.
AlterField
(
model_name
=
'subject'
,
name
=
'slug'
,
field
=
django_extensions
.
db
.
fields
.
AutoSlugField
(
blank
=
True
,
editable
=
False
,
help_text
=
'Leave this field blank to have the value generated automatically.'
,
populate_from
=
'name_t'
),
),
migrations
.
AlterUniqueTogether
(
name
=
'subject'
,
unique_together
=
set
([(
'partner'
,
'slug'
),
(
'partner'
,
'uuid'
)]),
),
migrations
.
AlterUniqueTogether
(
name
=
'subjecttranslation'
,
unique_together
=
set
([(
'language_code'
,
'master'
)]),
),
]
course_discovery/apps/course_metadata/migrations/0061_migrate_subjects_data.py
0 → 100644
View file @
58665dd5
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-09-11 17:06
from
__future__
import
unicode_literals
import
logging
from
django.conf
import
settings
from
django.core.exceptions
import
ObjectDoesNotExist
from
django.db
import
migrations
logger
=
logging
.
getLogger
(
__name__
)
def
forwards_func
(
apps
,
schema_editor
):
Subject
=
apps
.
get_model
(
'course_metadata'
,
'Subject'
)
SubjectTranslation
=
apps
.
get_model
(
'course_metadata'
,
'SubjectTranslation'
)
for
subject
in
Subject
.
objects
.
all
():
SubjectTranslation
.
objects
.
create
(
master_id
=
subject
.
pk
,
language_code
=
settings
.
PARLER_DEFAULT_LANGUAGE_CODE
,
name_t
=
subject
.
name
,
subtitle_t
=
subject
.
subtitle
,
description_t
=
subject
.
description
)
def
backwards_func
(
apps
,
schema_editor
):
Subject
=
apps
.
get_model
(
'course_metadata'
,
'Subject'
)
SubjectTranslation
=
apps
.
get_model
(
'course_metadata'
,
'SubjectTranslation'
)
for
subject
in
Subject
.
objects
.
all
():
try
:
translation
=
SubjectTranslation
.
objects
.
filter
(
master_id
=
subject
.
pk
)
subject
.
name
=
translation
.
name_t
subject
.
subtitle
=
translation
.
subtitle_t
subject
.
description
=
translation
.
description_t
subject
.
save
()
# Note this only calls Model.save()
except
ObjectDoesNotExist
:
# nothing to migrate
logger
.
exception
(
'Migrating data from SubjectTranslation for master_id={} DoesNotExist'
.
format
(
subject
.
pk
))
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'course_metadata'
,
'0060_create_subjecttranslations_models'
),
]
operations
=
[
migrations
.
RunPython
(
forwards_func
,
backwards_func
),
]
course_discovery/apps/course_metadata/models.py
View file @
58665dd5
...
@@ -7,6 +7,7 @@ from uuid import uuid4
...
@@ -7,6 +7,7 @@ from uuid import uuid4
import
pytz
import
pytz
import
waffle
import
waffle
from
django.core.exceptions
import
ValidationError
from
django.db
import
models
,
transaction
from
django.db
import
models
,
transaction
from
django.db.models.query_utils
import
Q
from
django.db.models.query_utils
import
Q
from
django.utils.functional
import
cached_property
from
django.utils.functional
import
cached_property
...
@@ -15,6 +16,7 @@ from django.utils.translation import ugettext_lazy as _
...
@@ -15,6 +16,7 @@ from django.utils.translation import ugettext_lazy as _
from
django_extensions.db.fields
import
AutoSlugField
from
django_extensions.db.fields
import
AutoSlugField
from
django_extensions.db.models
import
TimeStampedModel
from
django_extensions.db.models
import
TimeStampedModel
from
haystack.query
import
SearchQuerySet
from
haystack.query
import
SearchQuerySet
from
parler.models
import
TranslatableModel
,
TranslatedFieldsModel
from
solo.models
import
SingletonModel
from
solo.models
import
SingletonModel
from
sortedm2m.fields
import
SortedManyToManyField
from
sortedm2m.fields
import
SortedManyToManyField
from
stdimage.models
import
StdImageField
from
stdimage.models
import
StdImageField
...
@@ -111,7 +113,7 @@ class LevelType(AbstractNamedModel):
...
@@ -111,7 +113,7 @@ class LevelType(AbstractNamedModel):
pass
pass
class
Subject
(
TimeStampedModel
):
class
Subject
(
T
ranslatableModel
,
T
imeStampedModel
):
""" Subject model. """
""" Subject model. """
uuid
=
models
.
UUIDField
(
blank
=
False
,
null
=
False
,
default
=
uuid4
,
editable
=
False
,
verbose_name
=
_
(
'UUID'
))
uuid
=
models
.
UUIDField
(
blank
=
False
,
null
=
False
,
default
=
uuid4
,
editable
=
False
,
verbose_name
=
_
(
'UUID'
))
name
=
models
.
CharField
(
max_length
=
255
,
blank
=
False
,
null
=
False
)
name
=
models
.
CharField
(
max_length
=
255
,
blank
=
False
,
null
=
False
)
...
@@ -119,7 +121,7 @@ class Subject(TimeStampedModel):
...
@@ -119,7 +121,7 @@ class Subject(TimeStampedModel):
description
=
models
.
TextField
(
blank
=
True
,
null
=
True
)
description
=
models
.
TextField
(
blank
=
True
,
null
=
True
)
banner_image_url
=
models
.
URLField
(
blank
=
True
,
null
=
True
)
banner_image_url
=
models
.
URLField
(
blank
=
True
,
null
=
True
)
card_image_url
=
models
.
URLField
(
blank
=
True
,
null
=
True
)
card_image_url
=
models
.
URLField
(
blank
=
True
,
null
=
True
)
slug
=
AutoSlugField
(
populate_from
=
'name'
,
editable
=
True
,
blank
=
True
,
slug
=
AutoSlugField
(
populate_from
=
'name
_t
'
,
editable
=
True
,
blank
=
True
,
help_text
=
_
(
'Leave this field blank to have the value generated automatically.'
))
help_text
=
_
(
'Leave this field blank to have the value generated automatically.'
))
partner
=
models
.
ForeignKey
(
Partner
)
partner
=
models
.
ForeignKey
(
Partner
)
...
@@ -128,11 +130,31 @@ class Subject(TimeStampedModel):
...
@@ -128,11 +130,31 @@ class Subject(TimeStampedModel):
class
Meta
:
class
Meta
:
unique_together
=
(
unique_together
=
(
(
'partner'
,
'name'
),
(
'partner'
,
'slug'
),
(
'partner'
,
'slug'
),
(
'partner'
,
'uuid'
),
(
'partner'
,
'uuid'
),
)
)
def
validate_unique
(
self
,
*
args
,
**
kwargs
):
super
(
Subject
,
self
)
.
validate_unique
(
*
args
,
**
kwargs
)
qs
=
Subject
.
objects
.
filter
(
partner
=
self
.
partner_id
)
if
qs
.
filter
(
translations__name_t
=
self
.
name_t
)
.
exists
():
raise
ValidationError
({
'name_t'
:
[
'Subject with this Name and Partner already exists'
,
]})
class
SubjectTranslation
(
TranslatedFieldsModel
):
master
=
models
.
ForeignKey
(
Subject
,
related_name
=
'translations'
,
null
=
True
)
name_t
=
models
.
CharField
(
max_length
=
255
,
blank
=
False
,
null
=
False
)
subtitle_t
=
models
.
CharField
(
max_length
=
255
,
blank
=
True
,
null
=
True
)
description_t
=
models
.
TextField
(
blank
=
True
,
null
=
True
)
def
__str__
(
self
):
return
self
.
name_t
class
Meta
:
unique_together
=
(
'language_code'
,
'master'
)
verbose_name
=
_
(
'Subject model translations'
)
class
Prerequisite
(
AbstractNamedModel
):
class
Prerequisite
(
AbstractNamedModel
):
""" Prerequisite model. """
""" Prerequisite model. """
...
...
course_discovery/apps/course_metadata/tests/factories.py
View file @
58665dd5
...
@@ -41,8 +41,8 @@ class SubjectFactory(factory.DjangoModelFactory):
...
@@ -41,8 +41,8 @@ class SubjectFactory(factory.DjangoModelFactory):
class
Meta
:
class
Meta
:
model
=
Subject
model
=
Subject
name
=
FuzzyText
()
name
_t
=
FuzzyText
()
# TODO Switch to 'name' when 'name_t' is renamed.
description
=
FuzzyText
()
description
_t
=
FuzzyText
()
banner_image_url
=
FuzzyURL
()
banner_image_url
=
FuzzyURL
()
card_image_url
=
FuzzyURL
()
card_image_url
=
FuzzyURL
()
partner
=
factory
.
SubFactory
(
PartnerFactory
)
partner
=
factory
.
SubFactory
(
PartnerFactory
)
...
...
course_discovery/apps/course_metadata/tests/test_models.py
View file @
58665dd5
...
@@ -6,11 +6,13 @@ import ddt
...
@@ -6,11 +6,13 @@ import ddt
import
mock
import
mock
from
dateutil.parser
import
parse
from
dateutil.parser
import
parse
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.exceptions
import
ValidationError
from
django.db
import
IntegrityError
from
django.db
import
IntegrityError
from
django.db.models.functions
import
Lower
from
django.db.models.functions
import
Lower
from
django.test
import
TestCase
from
django.test
import
TestCase
from
freezegun
import
freeze_time
from
freezegun
import
freeze_time
from
course_discovery.apps.api.tests.mixins
import
SiteMixin
from
course_discovery.apps.core.models
import
Currency
from
course_discovery.apps.core.models
import
Currency
from
course_discovery.apps.core.tests.helpers
import
make_image_file
from
course_discovery.apps.core.tests.helpers
import
make_image_file
from
course_discovery.apps.core.tests.mixins
import
ElasticsearchTestMixin
from
course_discovery.apps.core.tests.mixins
import
ElasticsearchTestMixin
...
@@ -18,8 +20,7 @@ from course_discovery.apps.core.utils import SearchQuerySetWrapper
...
@@ -18,8 +20,7 @@ from course_discovery.apps.core.utils import SearchQuerySetWrapper
from
course_discovery.apps.course_metadata.choices
import
CourseRunStatus
,
ProgramStatus
from
course_discovery.apps.course_metadata.choices
import
CourseRunStatus
,
ProgramStatus
from
course_discovery.apps.course_metadata.models
import
(
from
course_discovery.apps.course_metadata.models
import
(
FAQ
,
AbstractMediaModel
,
AbstractNamedModel
,
AbstractValueModel
,
CorporateEndorsement
,
Course
,
CourseRun
,
FAQ
,
AbstractMediaModel
,
AbstractNamedModel
,
AbstractValueModel
,
CorporateEndorsement
,
Course
,
CourseRun
,
Endorsement
,
Seat
,
SeatType
Endorsement
,
Seat
,
SeatType
,
Subject
)
)
from
course_discovery.apps.course_metadata.publishers
import
(
from
course_discovery.apps.course_metadata.publishers
import
(
CourseRunMarketingSitePublisher
,
ProgramMarketingSitePublisher
CourseRunMarketingSitePublisher
,
ProgramMarketingSitePublisher
)
)
...
@@ -487,8 +488,8 @@ class ProgramTests(TestCase):
...
@@ -487,8 +488,8 @@ class ProgramTests(TestCase):
return
factories
.
ProgramFactory
(
type
=
program_type
,
courses
=
[
course_run
.
course
])
return
factories
.
ProgramFactory
(
type
=
program_type
,
courses
=
[
course_run
.
course
])
def
assert_one_click_purchase_ineligible_program
(
def
assert_one_click_purchase_ineligible_program
(
self
,
end
=
None
,
enrollment_start
=
None
,
enrollment_end
=
None
,
seat_type
=
Seat
.
VERIFIED
,
self
,
end
=
None
,
enrollment_start
=
None
,
enrollment_end
=
None
,
seat_type
=
Seat
.
VERIFIED
,
upgrade_deadline
=
None
,
one_click_purchase_enabled
=
True
,
excluded_course_runs
=
None
,
program_type
=
None
upgrade_deadline
=
None
,
one_click_purchase_enabled
=
True
,
excluded_course_runs
=
None
,
program_type
=
None
):
):
course_run
=
factories
.
CourseRunFactory
(
course_run
=
factories
.
CourseRunFactory
(
end
=
end
,
enrollment_start
=
enrollment_start
,
enrollment_end
=
enrollment_end
end
=
end
,
enrollment_start
=
enrollment_start
,
enrollment_end
=
enrollment_end
...
@@ -1009,3 +1010,29 @@ class FAQTests(TestCase):
...
@@ -1009,3 +1010,29 @@ class FAQTests(TestCase):
question
=
'test question'
question
=
'test question'
faq
=
FAQ
.
objects
.
create
(
question
=
question
,
answer
=
'test'
)
faq
=
FAQ
.
objects
.
create
(
question
=
question
,
answer
=
'test'
)
self
.
assertEqual
(
str
(
faq
),
question
)
self
.
assertEqual
(
str
(
faq
),
question
)
class
SubjectTests
(
SiteMixin
,
TestCase
):
""" Tests of the Multilingual Subject model. """
def
test_partner_name_t_uniqueness
(
self
):
dummy_url
=
"http://www.example.com"
Subject
.
objects
.
create
(
name
=
"name1"
,
name_t
=
"aaa"
,
partner_id
=
self
.
partner
.
id
,
banner_image_url
=
dummy_url
,
card_image_url
=
dummy_url
)
invalid_subject
=
Subject
(
name
=
"name2"
,
name_t
=
"aaa"
,
partner_id
=
self
.
partner
.
id
,
banner_image_url
=
dummy_url
,
card_image_url
=
dummy_url
)
with
self
.
assertRaises
(
ValidationError
)
as
validation_error
:
invalid_subject
.
full_clean
()
self
.
assertEqual
(
str
(
validation_error
.
exception
),
"{'name_t': ['Subject with this Name and Partner already exists']}"
)
course_discovery/apps/course_metadata/tests/test_signals.py
View file @
58665dd5
...
@@ -3,7 +3,7 @@ import pytest
...
@@ -3,7 +3,7 @@ import pytest
from
django.apps
import
apps
from
django.apps
import
apps
from
factory
import
DjangoModelFactory
from
factory
import
DjangoModelFactory
from
course_discovery.apps.course_metadata.models
import
DataLoaderConfig
from
course_discovery.apps.course_metadata.models
import
DataLoaderConfig
,
SubjectTranslation
from
course_discovery.apps.course_metadata.tests
import
factories
from
course_discovery.apps.course_metadata.tests
import
factories
...
@@ -25,7 +25,7 @@ class TestCacheInvalidation:
...
@@ -25,7 +25,7 @@ class TestCacheInvalidation:
# connecting to. We want to test each of them.
# connecting to. We want to test each of them.
for
model
in
apps
.
get_app_config
(
'course_metadata'
)
.
get_models
():
for
model
in
apps
.
get_app_config
(
'course_metadata'
)
.
get_models
():
# Ignore models that aren't exposed by the API or are only used for testing.
# Ignore models that aren't exposed by the API or are only used for testing.
if
model
==
DataLoaderConfig
or
'abstract'
in
model
.
__name__
.
lower
():
if
model
in
[
DataLoaderConfig
,
SubjectTranslation
]
or
'abstract'
in
model
.
__name__
.
lower
():
continue
continue
factory
=
factory_map
.
get
(
model
)
factory
=
factory_map
.
get
(
model
)
...
...
course_discovery/conf/locale/en/LC_MESSAGES/django.mo
View file @
58665dd5
No preview for this file type
course_discovery/conf/locale/en/LC_MESSAGES/django.po
View file @
58665dd5
...
@@ -7,7 +7,7 @@ msgid ""
...
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-10-06 1
2:30+05
00\n"
"POT-Creation-Date: 2017-10-06 1
7:23+00
00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
...
@@ -286,6 +286,10 @@ msgid "Leave this field blank to have the value generated automatically."
...
@@ -286,6 +286,10 @@ msgid "Leave this field blank to have the value generated automatically."
msgstr ""
msgstr ""
#: apps/course_metadata/models.py
#: apps/course_metadata/models.py
msgid "Subject model translations"
msgstr ""
#: apps/course_metadata/models.py
msgid ""
msgid ""
"Logo to be displayed on certificates. If this logo is the same as "
"Logo to be displayed on certificates. If this logo is the same as "
"logo_image_url, copy and paste the same value to both fields."
"logo_image_url, copy and paste the same value to both fields."
...
...
course_discovery/conf/locale/en/LC_MESSAGES/djangojs.mo
View file @
58665dd5
No preview for this file type
course_discovery/conf/locale/en/LC_MESSAGES/djangojs.po
View file @
58665dd5
...
@@ -7,7 +7,7 @@ msgid ""
...
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-10-06 1
2:30+05
00\n"
"POT-Creation-Date: 2017-10-06 1
7:23+00
00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
...
...
course_discovery/conf/locale/eo/LC_MESSAGES/django.mo
View file @
58665dd5
No preview for this file type
course_discovery/conf/locale/eo/LC_MESSAGES/django.po
View file @
58665dd5
...
@@ -7,7 +7,7 @@ msgid ""
...
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-10-06 1
2:30+05
00\n"
"POT-Creation-Date: 2017-10-06 1
7:23+00
00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
...
@@ -339,6 +339,10 @@ msgstr ""
...
@@ -339,6 +339,10 @@ msgstr ""
"ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#"
"ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#: apps/course_metadata/models.py
#: apps/course_metadata/models.py
msgid "Subject model translations"
msgstr "Süßjéçt mödél tränslätïöns Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕ#"
#: apps/course_metadata/models.py
msgid ""
msgid ""
"Logo to be displayed on certificates. If this logo is the same as "
"Logo to be displayed on certificates. If this logo is the same as "
"logo_image_url, copy and paste the same value to both fields."
"logo_image_url, copy and paste the same value to both fields."
...
...
course_discovery/conf/locale/eo/LC_MESSAGES/djangojs.mo
View file @
58665dd5
No preview for this file type
course_discovery/conf/locale/eo/LC_MESSAGES/djangojs.po
View file @
58665dd5
...
@@ -7,7 +7,7 @@ msgid ""
...
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-10-06 1
2:30+05
00\n"
"POT-Creation-Date: 2017-10-06 1
7:23+00
00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
...
...
course_discovery/settings/base.py
View file @
58665dd5
...
@@ -55,6 +55,7 @@ THIRD_PARTY_APPS = [
...
@@ -55,6 +55,7 @@ THIRD_PARTY_APPS = [
'taggit_serializer'
,
'taggit_serializer'
,
'solo'
,
'solo'
,
'webpack_loader'
,
'webpack_loader'
,
'parler'
,
]
]
PROJECT_APPS
=
[
PROJECT_APPS
=
[
...
@@ -110,9 +111,21 @@ DATABASES = {
...
@@ -110,9 +111,21 @@ DATABASES = {
}
}
# Internationalization
# Internationalization
# https://docs.djangoproject.com/en/dev/topics/i18n/
# See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE
=
'en'
LANGUAGE_CODE
=
'en-us'
PARLER_DEFAULT_LANGUAGE_CODE
=
LANGUAGE_CODE
PARLER_LANGUAGES
=
{
1
:
(
{
'code'
:
LANGUAGE_CODE
,
},
),
'default'
:
{
'fallbacks'
:
[
PARLER_DEFAULT_LANGUAGE_CODE
],
'hide_untranslated'
:
False
,
}
}
TIME_ZONE
=
'UTC'
TIME_ZONE
=
'UTC'
...
...
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