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
41f24b58
Commit
41f24b58
authored
Aug 31, 2016
by
Simon Chen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ECOM-5392 publish program data to marketing site to create or edit the about page
parent
3e846e51
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
637 additions
and
9 deletions
+637
-9
course_discovery/apps/course_metadata/admin.py
+21
-5
course_discovery/apps/course_metadata/models.py
+25
-1
course_discovery/apps/course_metadata/publishers.py
+161
-0
course_discovery/apps/course_metadata/tests/__init__.py
+18
-0
course_discovery/apps/course_metadata/tests/mixins.py
+123
-0
course_discovery/apps/course_metadata/tests/test_models.py
+43
-3
course_discovery/apps/course_metadata/tests/test_publishers.py
+218
-0
course_discovery/apps/edx_catalog_extensions/migrations/0003_create_publish_to_marketing_site_flag.py
+28
-0
No files found.
course_discovery/apps/course_metadata/admin.py
View file @
41f24b58
from
django.contrib
import
admin
from
django.contrib
import
admin
,
messages
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
django.http
import
HttpResponseRedirect
from
django.http
import
HttpResponseRedirect
from
simple_history.admin
import
SimpleHistoryAdmin
from
simple_history.admin
import
SimpleHistoryAdmin
from
course_discovery.apps.course_metadata.forms
import
ProgramAdminForm
from
course_discovery.apps.course_metadata.forms
import
ProgramAdminForm
from
course_discovery.apps.course_metadata.models
import
*
# pylint: disable=wildcard-import
from
course_discovery.apps.course_metadata.models
import
*
# pylint: disable=wildcard-import
from
course_discovery.apps.course_metadata.publishers
import
ProgramPublisherException
class
SeatInline
(
admin
.
TabularInline
):
class
SeatInline
(
admin
.
TabularInline
):
...
@@ -84,6 +85,7 @@ class ProgramAdmin(admin.ModelAdmin):
...
@@ -84,6 +85,7 @@ class ProgramAdmin(admin.ModelAdmin):
'credit_backing_organizations'
'credit_backing_organizations'
)
)
fields
+=
filter_horizontal
fields
+=
filter_horizontal
save_error
=
None
def
custom_course_runs_display
(
self
,
obj
):
def
custom_course_runs_display
(
self
,
obj
):
return
", "
.
join
([
str
(
run
)
for
run
in
obj
.
course_runs
])
return
", "
.
join
([
str
(
run
)
for
run
in
obj
.
course_runs
])
...
@@ -91,13 +93,27 @@ class ProgramAdmin(admin.ModelAdmin):
...
@@ -91,13 +93,27 @@ class ProgramAdmin(admin.ModelAdmin):
custom_course_runs_display
.
short_description
=
"Included course runs"
custom_course_runs_display
.
short_description
=
"Included course runs"
def
response_add
(
self
,
request
,
obj
,
post_url_continue
=
None
):
def
response_add
(
self
,
request
,
obj
,
post_url_continue
=
None
):
return
HttpResponseRedirect
(
reverse
(
'admin_metadata:update_course_runs'
,
kwargs
=
{
'pk'
:
obj
.
pk
}))
if
self
.
save_error
:
return
self
.
response_post_save_add
(
request
,
obj
)
else
:
return
HttpResponseRedirect
(
reverse
(
'admin_metadata:update_course_runs'
,
kwargs
=
{
'pk'
:
obj
.
pk
}))
def
response_change
(
self
,
request
,
obj
):
def
response_change
(
self
,
request
,
obj
):
if
'_continue'
in
request
.
POST
or
'_save'
in
request
.
POST
:
if
self
.
save_error
:
return
HttpResponseRedirect
(
reverse
(
'admin_metadata:update_course_runs'
,
kwargs
=
{
'pk'
:
obj
.
pk
})
)
return
self
.
response_post_save_change
(
request
,
obj
)
else
:
else
:
return
HttpResponseRedirect
(
reverse
(
'admin:course_metadata_program_add'
))
if
any
(
status
in
request
.
POST
for
status
in
[
'_continue'
,
'_save'
]):
return
HttpResponseRedirect
(
reverse
(
'admin_metadata:update_course_runs'
,
kwargs
=
{
'pk'
:
obj
.
pk
}))
else
:
return
HttpResponseRedirect
(
reverse
(
'admin:course_metadata_program_add'
))
def
save_model
(
self
,
request
,
obj
,
form
,
change
):
try
:
obj
.
save
()
self
.
save_error
=
False
except
ProgramPublisherException
as
ex
:
messages
.
add_message
(
request
,
messages
.
ERROR
,
ex
.
message
)
self
.
save_error
=
True
@admin.register
(
ProgramType
)
@admin.register
(
ProgramType
)
...
...
course_discovery/apps/course_metadata/models.py
View file @
41f24b58
...
@@ -5,7 +5,7 @@ from urllib.parse import urljoin
...
@@ -5,7 +5,7 @@ from urllib.parse import urljoin
from
uuid
import
uuid4
from
uuid
import
uuid4
import
pytz
import
pytz
from
django.db
import
models
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.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
django_extensions.db.fields
import
AutoSlugField
from
django_extensions.db.fields
import
AutoSlugField
...
@@ -16,8 +16,10 @@ from simple_history.models import HistoricalRecords
...
@@ -16,8 +16,10 @@ from simple_history.models import HistoricalRecords
from
sortedm2m.fields
import
SortedManyToManyField
from
sortedm2m.fields
import
SortedManyToManyField
from
stdimage.models
import
StdImageField
from
stdimage.models
import
StdImageField
from
taggit.managers
import
TaggableManager
from
taggit.managers
import
TaggableManager
import
waffle
from
course_discovery.apps.core.models
import
Currency
,
Partner
from
course_discovery.apps.core.models
import
Currency
,
Partner
from
course_discovery.apps.course_metadata.publishers
import
MarketingSitePublisher
from
course_discovery.apps.course_metadata.query
import
CourseQuerySet
,
CourseRunQuerySet
,
ProgramQuerySet
from
course_discovery.apps.course_metadata.query
import
CourseQuerySet
,
CourseRunQuerySet
,
ProgramQuerySet
from
course_discovery.apps.course_metadata.utils
import
UploadToFieldNamePath
from
course_discovery.apps.course_metadata.utils
import
UploadToFieldNamePath
from
course_discovery.apps.course_metadata.utils
import
clean_query
from
course_discovery.apps.course_metadata.utils
import
clean_query
...
@@ -701,6 +703,28 @@ class Program(TimeStampedModel):
...
@@ -701,6 +703,28 @@ class Program(TimeStampedModel):
staff
=
itertools
.
chain
.
from_iterable
(
staff
)
staff
=
itertools
.
chain
.
from_iterable
(
staff
)
return
set
(
staff
)
return
set
(
staff
)
@property
def
is_active
(
self
):
return
self
.
status
==
self
.
Status
.
Active
def
save
(
self
,
*
args
,
**
kwargs
):
if
waffle
.
switch_is_active
(
'publish_program_to_marketing_site'
)
and
\
self
.
partner
.
has_marketing_site
:
# Before save, get from database the existing data if exists
existing_program
=
None
if
self
.
id
:
existing_program
=
Program
.
objects
.
get
(
id
=
self
.
id
)
# Pass existing data to the publisher so it can decide whether we should publish
publisher
=
MarketingSitePublisher
(
existing_program
)
with
transaction
.
atomic
():
super
(
Program
,
self
)
.
save
(
*
args
,
**
kwargs
)
# Once save complete, we need to update the marketing site
# So the marketing page for this program is automatically updated
publisher
.
publish_program
(
self
)
else
:
super
(
Program
,
self
)
.
save
(
*
args
,
**
kwargs
)
class
PersonSocialNetwork
(
AbstractSocialNetworkModel
):
class
PersonSocialNetwork
(
AbstractSocialNetworkModel
):
""" Person Social Network model. """
""" Person Social Network model. """
...
...
course_discovery/apps/course_metadata/publishers.py
0 → 100644
View file @
41f24b58
import
json
import
requests
from
django.utils.functional
import
cached_property
class
ProgramPublisherException
(
Exception
):
def
__init__
(
self
,
message
):
super
(
ProgramPublisherException
,
self
)
.
__init__
(
message
)
suffix
=
'The program data has not been saved. Please check your marketing site configuration'
self
.
message
=
'{exception_msg} {suffix}'
.
format
(
exception_msg
=
message
,
suffix
=
suffix
)
class
MarketingSiteAPIClient
(
object
):
"""
The marketing site API client we can use to communicate with the marketing site
"""
username
=
None
password
=
None
api_url
=
None
def
__init__
(
self
,
marketing_site_api_username
,
marketing_site_api_password
,
api_url
):
if
not
(
marketing_site_api_username
and
marketing_site_api_password
):
raise
ProgramPublisherException
(
'Marketing Site API credentials are not properly configured!'
)
self
.
username
=
marketing_site_api_username
self
.
password
=
marketing_site_api_password
self
.
api_url
=
api_url
.
strip
(
'/'
)
@cached_property
def
init_session
(
self
):
# Login to set session cookies
session
=
requests
.
Session
()
login_url
=
'{root}/user'
.
format
(
root
=
self
.
api_url
)
login_data
=
{
'name'
:
self
.
username
,
'pass'
:
self
.
password
,
'form_id'
:
'user_login'
,
'op'
:
'Log in'
,
}
response
=
session
.
post
(
login_url
,
data
=
login_data
)
expected_url
=
'{root}/users/{username}'
.
format
(
root
=
self
.
api_url
,
username
=
self
.
username
)
if
not
(
response
.
status_code
==
200
and
response
.
url
==
expected_url
):
raise
ProgramPublisherException
(
'Marketing Site Login failed!'
)
return
session
@cached_property
def
api_session
(
self
):
self
.
init_session
.
headers
.
update
(
self
.
headers
)
return
self
.
init_session
@cached_property
def
csrf_token
(
self
):
token_url
=
'{root}/restws/session/token'
.
format
(
root
=
self
.
api_url
)
response
=
self
.
init_session
.
get
(
token_url
)
if
not
response
.
status_code
==
200
:
raise
ProgramPublisherException
(
'Failed to retrieve Marketing Site CSRF token!'
)
token
=
response
.
content
.
decode
(
'utf8'
)
return
token
@cached_property
def
user_id
(
self
):
# Get a user ID
user_url
=
'{root}/user.json?name={username}'
.
format
(
root
=
self
.
api_url
,
username
=
self
.
username
)
response
=
self
.
init_session
.
get
(
user_url
)
if
not
response
.
status_code
==
200
:
raise
ProgramPublisherException
(
'Failed to retrieve Marketing site user details!'
)
user_id
=
response
.
json
()[
'list'
][
0
][
'uid'
]
return
user_id
@cached_property
def
headers
(
self
):
return
{
'Content-Type'
:
'application/json'
,
'X-CSRF-Token'
:
self
.
csrf_token
,
}
class
MarketingSitePublisher
(
object
):
"""
This is the publisher that would publish the object data to marketing site
"""
data_before
=
None
def
__init__
(
self
,
program_before
=
None
):
if
program_before
:
self
.
data_before
=
{
'type'
:
program_before
.
type
,
'status'
:
program_before
.
status
,
'title'
:
program_before
.
title
,
}
def
_get_node_data
(
self
,
program
,
user_id
):
return
{
'type'
:
str
(
program
.
type
)
.
lower
(),
'title'
:
program
.
title
,
'field_uuid'
:
str
(
program
.
uuid
),
'uuid'
:
str
(
program
.
uuid
),
'author'
:
{
'id'
:
user_id
,
},
'status'
:
1
if
program
.
is_active
else
0
}
def
_get_node_id
(
self
,
api_client
,
uuid
):
node_url
=
'{root}/node.json?field_uuid={uuid}'
.
format
(
root
=
api_client
.
api_url
,
uuid
=
uuid
)
response
=
api_client
.
api_session
.
get
(
node_url
)
if
response
.
status_code
==
200
:
found
=
response
.
json
()
if
found
:
list_item
=
found
.
get
(
'list'
)
if
list_item
:
return
list_item
[
0
][
'nid'
]
def
_edit_node
(
self
,
api_client
,
nid
,
node_data
):
if
node_data
.
get
(
'uuid'
):
# Drupal do not allow us to update the UUID field on node update
del
node_data
[
'uuid'
]
node_url
=
'{root}/node.json/{nid}'
.
format
(
root
=
api_client
.
api_url
,
nid
=
nid
)
response
=
api_client
.
api_session
.
put
(
node_url
,
data
=
json
.
dumps
(
node_data
))
if
response
.
status_code
!=
200
:
raise
ProgramPublisherException
(
"Marketing site page edit failed!"
)
def
_create_node
(
self
,
api_client
,
node_data
):
node_url
=
'{root}/node.json'
.
format
(
root
=
api_client
.
api_url
)
response
=
api_client
.
api_session
.
post
(
node_url
,
data
=
json
.
dumps
(
node_data
))
if
response
.
status_code
==
201
:
return
response
.
json
()
else
:
raise
ProgramPublisherException
(
"Marketing site page creation failed!"
)
def
publish_program
(
self
,
program
):
if
not
program
.
partner
.
has_marketing_site
:
return
if
not
(
program
.
partner
.
marketing_site_api_username
and
program
.
partner
.
marketing_site_api_password
):
msg
=
'Marketing Site API credentials are not properly configured for Partner [{partner}]!'
.
format
(
partner
=
program
.
partner
.
short_code
)
raise
ProgramPublisherException
(
msg
)
if
self
.
data_before
and
\
self
.
data_before
.
get
(
'title'
)
==
program
.
title
and
\
self
.
data_before
.
get
(
'status'
)
==
program
.
status
and
\
self
.
data_before
.
get
(
'type'
)
==
program
.
type
:
# We don't need to publish to marketing site because
# nothing we care about has changed. This would save at least 4 network calls
return
api_client
=
MarketingSiteAPIClient
(
program
.
partner
.
marketing_site_api_username
,
program
.
partner
.
marketing_site_api_password
,
program
.
partner
.
marketing_site_url_root
)
node_data
=
self
.
_get_node_data
(
program
,
api_client
.
user_id
)
nid
=
self
.
_get_node_id
(
api_client
,
program
.
uuid
)
if
nid
:
# We would like to edit the existing node
self
.
_edit_node
(
api_client
,
nid
,
node_data
)
else
:
# We should create a new node
self
.
_create_node
(
api_client
,
node_data
)
course_discovery/apps/course_metadata/tests/__init__.py
View file @
41f24b58
from
waffle.models
import
Switch
def
toggle_switch
(
name
,
active
):
"""
Activate or deactivate a feature switch.
The switch is created if it does not exist.
Arguments:
name (str): name of the switch to be toggled
active (bool): boolean indicating if the switch should be activated or deactivated
Returns:
Switch: Waffle Switch
"""
switch
,
__
=
Switch
.
objects
.
get_or_create
(
name
=
name
,
defaults
=
{
'active'
:
active
})
switch
.
active
=
active
switch
.
save
()
return
switch
course_discovery/apps/course_metadata/tests/mixins.py
0 → 100644
View file @
41f24b58
import
json
from
factory.fuzzy
import
FuzzyText
,
FuzzyInteger
import
responses
from
course_discovery.apps.core.tests.utils
import
FuzzyUrlRoot
class
MarketingSiteAPIClientTestMixin
(
object
):
"""
The mixing to help mock the responses for marketing site API Client
"""
def
setUp
(
self
):
super
(
MarketingSiteAPIClientTestMixin
,
self
)
.
setUp
()
self
.
username
=
FuzzyText
()
.
fuzz
()
self
.
password
=
FuzzyText
()
.
fuzz
()
self
.
api_root
=
FuzzyUrlRoot
()
.
fuzz
()
self
.
csrf_token
=
FuzzyText
()
.
fuzz
()
self
.
user_id
=
FuzzyInteger
(
1
)
.
fuzz
()
def
mock_login_response
(
self
,
status
):
""" Mock the response of the marketing site login """
response_url
=
'{root}/users/{username}'
.
format
(
root
=
self
.
api_root
,
username
=
self
.
username
)
def
request_callback
(
request
):
# pylint: disable=unused-argument
headers
=
{
'location'
:
response_url
}
return
(
302
,
headers
,
None
)
responses
.
add_callback
(
responses
.
POST
,
'{root}/user'
.
format
(
root
=
self
.
api_root
),
callback
=
request_callback
,
content_type
=
'text/html'
,
)
responses
.
add
(
responses
.
GET
,
response_url
,
body
=
''
,
content_type
=
'text/html'
,
status
=
status
)
def
mock_csrf_token_response
(
self
,
status
):
responses
.
add
(
responses
.
GET
,
'{root}/restws/session/token'
.
format
(
root
=
self
.
api_root
),
body
=
self
.
csrf_token
,
content_type
=
'text/html'
,
status
=
status
)
def
mock_user_id_response
(
self
,
status
):
data
=
{
'list'
:
[{
'uid'
:
self
.
user_id
}]
}
responses
.
add
(
responses
.
GET
,
'{root}/user.json?name={username}'
.
format
(
root
=
self
.
api_root
,
username
=
self
.
username
),
body
=
json
.
dumps
(
data
),
content_type
=
'application/json'
,
status
=
status
,
match_querystring
=
True
)
class
MarketingSitePublisherTestMixin
(
MarketingSiteAPIClientTestMixin
):
"""
The mixing to help mock the responses for marketing site publisher
"""
def
setUp
(
self
):
super
(
MarketingSitePublisherTestMixin
,
self
)
.
setUp
()
self
.
nid
=
FuzzyText
()
.
fuzz
()
def
mock_api_client
(
self
,
status
):
self
.
mock_login_response
(
status
)
self
.
mock_csrf_token_response
(
status
)
self
.
mock_user_id_response
(
status
)
def
mock_node_retrieval
(
self
,
program_uuid
,
exists
=
True
):
data
=
{}
status
=
404
if
exists
:
data
=
{
'list'
:
[{
'nid'
:
self
.
nid
}]
}
status
=
200
responses
.
add
(
responses
.
GET
,
'{root}/node.json?field_uuid={uuid}'
.
format
(
root
=
self
.
api_root
,
uuid
=
str
(
program_uuid
)),
body
=
json
.
dumps
(
data
),
content_type
=
'application/json'
,
status
=
status
,
match_querystring
=
True
)
def
mock_node_edit
(
self
,
status
):
responses
.
add
(
responses
.
PUT
,
'{root}/node.json/{nid}'
.
format
(
root
=
self
.
api_root
,
nid
=
self
.
nid
),
body
=
json
.
dumps
({}),
content_type
=
'application/json'
,
status
=
status
)
def
mock_node_create
(
self
,
response_data
,
status
):
responses
.
add
(
responses
.
POST
,
'{root}/node.json'
.
format
(
root
=
self
.
api_root
),
body
=
json
.
dumps
(
response_data
),
content_type
=
'application/json'
,
status
=
status
)
course_discovery/apps/course_metadata/tests/test_models.py
View file @
41f24b58
...
@@ -6,7 +6,9 @@ from dateutil.parser import parse
...
@@ -6,7 +6,9 @@ from dateutil.parser import parse
from
django.conf
import
settings
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
factory.fuzzy
import
FuzzyText
from
freezegun
import
freeze_time
from
freezegun
import
freeze_time
import
responses
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
...
@@ -14,10 +16,11 @@ from course_discovery.apps.core.utils import SearchQuerySetWrapper
...
@@ -14,10 +16,11 @@ from course_discovery.apps.core.utils import SearchQuerySetWrapper
from
course_discovery.apps.course_metadata.models
import
(
from
course_discovery.apps.course_metadata.models
import
(
AbstractMediaModel
,
AbstractNamedModel
,
AbstractValueModel
,
AbstractMediaModel
,
AbstractNamedModel
,
AbstractValueModel
,
CorporateEndorsement
,
Course
,
CourseRun
,
Endorsement
,
CorporateEndorsement
,
Course
,
CourseRun
,
Endorsement
,
FAQ
,
SeatType
FAQ
,
SeatType
,
Program
)
)
from
course_discovery.apps.course_metadata.tests
import
factories
from
course_discovery.apps.course_metadata.tests
import
factories
,
toggle_switch
from
course_discovery.apps.course_metadata.tests.factories
import
CourseRunFactory
,
ImageFactory
from
course_discovery.apps.course_metadata.tests.factories
import
CourseRunFactory
,
ImageFactory
from
course_discovery.apps.course_metadata.tests.mixins
import
MarketingSitePublisherTestMixin
from
course_discovery.apps.ietf_language_tags.models
import
LanguageTag
from
course_discovery.apps.ietf_language_tags.models
import
LanguageTag
...
@@ -251,7 +254,8 @@ class AbstractValueModelTests(TestCase):
...
@@ -251,7 +254,8 @@ class AbstractValueModelTests(TestCase):
self
.
assertEqual
(
str
(
instance
),
value
)
self
.
assertEqual
(
str
(
instance
),
value
)
class
ProgramTests
(
TestCase
):
@ddt.ddt
class
ProgramTests
(
MarketingSitePublisherTestMixin
,
TestCase
):
"""Tests of the Program model."""
"""Tests of the Program model."""
def
setUp
(
self
):
def
setUp
(
self
):
...
@@ -369,6 +373,42 @@ class ProgramTests(TestCase):
...
@@ -369,6 +373,42 @@ class ProgramTests(TestCase):
program
=
self
.
create_program_with_seats
()
program
=
self
.
create_program_with_seats
()
self
.
assertEqual
(
program
.
seat_types
,
set
([
'credit'
,
'verified'
]))
self
.
assertEqual
(
program
.
seat_types
,
set
([
'credit'
,
'verified'
]))
@ddt.data
(
Program
.
Status
.
choices
)
def
test_is_active
(
self
,
status
):
self
.
program
.
status
=
status
self
.
assertEqual
(
self
.
program
.
is_active
,
status
==
Program
.
Status
.
Active
)
@responses.activate
def
test_save_without_publish
(
self
):
self
.
program
.
title
=
FuzzyText
()
.
fuzz
()
self
.
program
.
save
()
self
.
assertEqual
(
len
(
responses
.
calls
),
0
)
@responses.activate
def
test_save_and_publish_success
(
self
):
self
.
program
.
partner
.
marketing_site_url_root
=
self
.
api_root
self
.
program
.
partner
.
marketing_site_api_username
=
self
.
username
self
.
program
.
partner
.
marketing_site_api_password
=
self
.
password
self
.
program
.
save
()
self
.
mock_api_client
(
200
)
self
.
mock_node_retrieval
(
self
.
program
.
uuid
)
self
.
mock_node_edit
(
200
)
toggle_switch
(
'publish_program_to_marketing_site'
,
True
)
self
.
program
.
title
=
FuzzyText
()
.
fuzz
()
self
.
program
.
save
()
self
.
assertEqual
(
len
(
responses
.
calls
),
6
)
toggle_switch
(
'publish_program_to_marketing_site'
,
False
)
@responses.activate
def
test_save_and_no_marketing_site
(
self
):
self
.
program
.
partner
.
marketing_site_url_root
=
None
self
.
program
.
save
()
toggle_switch
(
'publish_program_to_marketing_site'
,
True
)
self
.
program
.
title
=
FuzzyText
()
.
fuzz
()
self
.
program
.
save
()
self
.
assertEqual
(
len
(
responses
.
calls
),
0
)
toggle_switch
(
'publish_program_to_marketing_site'
,
False
)
class
PersonSocialNetworkTests
(
TestCase
):
class
PersonSocialNetworkTests
(
TestCase
):
"""Tests of the PersonSocialNetwork model."""
"""Tests of the PersonSocialNetwork model."""
...
...
course_discovery/apps/course_metadata/tests/test_publishers.py
0 → 100644
View file @
41f24b58
from
django.test
import
TestCase
import
responses
from
course_discovery.apps.course_metadata.publishers
import
(
MarketingSiteAPIClient
,
MarketingSitePublisher
,
ProgramPublisherException
,
)
from
course_discovery.apps.course_metadata.tests.factories
import
ProgramFactory
from
course_discovery.apps.course_metadata.tests.mixins
import
(
MarketingSiteAPIClientTestMixin
,
MarketingSitePublisherTestMixin
,
)
from
course_discovery.apps.course_metadata.models
import
Program
class
MarketingSiteAPIClientTests
(
MarketingSiteAPIClientTestMixin
,
TestCase
):
"""
Unit test cases for MarketinSiteAPIClient
"""
def
setUp
(
self
):
super
(
MarketingSiteAPIClientTests
,
self
)
.
setUp
()
self
.
api_client
=
MarketingSiteAPIClient
(
self
.
username
,
self
.
password
,
self
.
api_root
)
@responses.activate
def
test_init_session
(
self
):
self
.
mock_login_response
(
200
)
session
=
self
.
api_client
.
init_session
self
.
assertEqual
(
len
(
responses
.
calls
),
2
)
self
.
assertIsNotNone
(
session
)
@responses.activate
def
test_init_session_failed
(
self
):
self
.
mock_login_response
(
500
)
with
self
.
assertRaises
(
ProgramPublisherException
):
self
.
api_client
.
init_session
# pylint: disable=pointless-statement
@responses.activate
def
test_csrf_token
(
self
):
self
.
mock_login_response
(
200
)
self
.
mock_csrf_token_response
(
200
)
csrf_token
=
self
.
api_client
.
csrf_token
self
.
assertEqual
(
len
(
responses
.
calls
),
3
)
self
.
assertEqual
(
self
.
csrf_token
,
csrf_token
)
@responses.activate
def
test_csrf_token_failed
(
self
):
self
.
mock_login_response
(
200
)
self
.
mock_csrf_token_response
(
500
)
with
self
.
assertRaises
(
ProgramPublisherException
):
self
.
api_client
.
csrf_token
# pylint: disable=pointless-statement
@responses.activate
def
test_user_id
(
self
):
self
.
mock_login_response
(
200
)
self
.
mock_user_id_response
(
200
)
user_id
=
self
.
api_client
.
user_id
self
.
assertEqual
(
len
(
responses
.
calls
),
3
)
self
.
assertEqual
(
self
.
user_id
,
user_id
)
@responses.activate
def
test_user_id_failed
(
self
):
self
.
mock_login_response
(
200
)
self
.
mock_user_id_response
(
500
)
with
self
.
assertRaises
(
ProgramPublisherException
):
self
.
api_client
.
user_id
# pylint: disable=pointless-statement
@responses.activate
def
test_api_session
(
self
):
self
.
mock_login_response
(
200
)
self
.
mock_csrf_token_response
(
200
)
api_session
=
self
.
api_client
.
api_session
self
.
assertEqual
(
len
(
responses
.
calls
),
3
)
self
.
assertIsNotNone
(
api_session
)
self
.
assertEqual
(
api_session
.
headers
.
get
(
'Content-Type'
),
'application/json'
)
self
.
assertEqual
(
api_session
.
headers
.
get
(
'X-CSRF-Token'
),
self
.
csrf_token
)
@responses.activate
def
test_api_session_failed
(
self
):
self
.
mock_login_response
(
500
)
self
.
mock_csrf_token_response
(
500
)
with
self
.
assertRaises
(
ProgramPublisherException
):
self
.
api_client
.
api_session
# pylint: disable=pointless-statement
class
MarketingSitePublisherTests
(
MarketingSitePublisherTestMixin
,
TestCase
):
"""
Unit test cases for the MarketingSitePublisher
"""
def
setUp
(
self
):
super
(
MarketingSitePublisherTests
,
self
)
.
setUp
()
self
.
program
=
ProgramFactory
()
self
.
program
.
partner
.
marketing_site_url_root
=
self
.
api_root
self
.
program
.
partner
.
marketing_site_api_username
=
self
.
username
self
.
program
.
partner
.
marketing_site_api_password
=
self
.
password
self
.
program
.
save
()
# pylint: disable=no-member
self
.
api_client
=
MarketingSiteAPIClient
(
self
.
username
,
self
.
password
,
self
.
api_root
)
def
test_get_node_data
(
self
):
publisher
=
MarketingSitePublisher
()
publish_data
=
publisher
.
_get_node_data
(
self
.
program
,
self
.
user_id
)
# pylint: disable=protected-access
expected
=
{
'type'
:
str
(
self
.
program
.
type
)
.
lower
(),
'title'
:
self
.
program
.
title
,
'field_uuid'
:
str
(
self
.
program
.
uuid
),
'uuid'
:
str
(
self
.
program
.
uuid
),
'author'
:
{
'id'
:
self
.
user_id
,
},
'status'
:
1
if
self
.
program
.
status
==
Program
.
Status
.
Active
else
0
}
self
.
assertDictEqual
(
publish_data
,
expected
)
@responses.activate
def
test_get_node_id
(
self
):
self
.
mock_api_client
(
200
)
self
.
mock_node_retrieval
(
self
.
program
.
uuid
)
publisher
=
MarketingSitePublisher
()
node_id
=
publisher
.
_get_node_id
(
self
.
api_client
,
self
.
program
.
uuid
)
# pylint: disable=protected-access
self
.
assertEqual
(
len
(
responses
.
calls
),
4
)
self
.
assertEqual
(
node_id
,
self
.
nid
)
@responses.activate
def
test_get_non_existent_node_id
(
self
):
self
.
mock_api_client
(
200
)
self
.
mock_node_retrieval
(
self
.
program
.
uuid
,
exists
=
False
)
publisher
=
MarketingSitePublisher
()
node_id
=
publisher
.
_get_node_id
(
self
.
api_client
,
self
.
program
.
uuid
)
# pylint: disable=protected-access
self
.
assertIsNone
(
node_id
)
@responses.activate
def
test_edit_node
(
self
):
self
.
mock_api_client
(
200
)
self
.
mock_node_edit
(
200
)
publisher
=
MarketingSitePublisher
()
publish_data
=
publisher
.
_get_node_data
(
self
.
program
,
self
.
user_id
)
# pylint: disable=protected-access
publisher
.
_edit_node
(
self
.
api_client
,
self
.
nid
,
publish_data
)
# pylint: disable=protected-access
self
.
assertEqual
(
len
(
responses
.
calls
),
4
)
@responses.activate
def
test_edit_node_failed
(
self
):
self
.
mock_api_client
(
200
)
self
.
mock_node_edit
(
500
)
publisher
=
MarketingSitePublisher
()
publish_data
=
publisher
.
_get_node_data
(
self
.
program
,
self
.
user_id
)
# pylint: disable=protected-access
with
self
.
assertRaises
(
ProgramPublisherException
):
publisher
.
_edit_node
(
self
.
api_client
,
self
.
nid
,
publish_data
)
# pylint: disable=protected-access
@responses.activate
def
test_create_node
(
self
):
self
.
mock_api_client
(
200
)
expected
=
{
'list'
:
[{
'nid'
:
self
.
nid
}]
}
self
.
mock_node_create
(
expected
,
201
)
publisher
=
MarketingSitePublisher
()
publish_data
=
publisher
.
_get_node_data
(
self
.
program
,
self
.
user_id
)
# pylint: disable=protected-access
data
=
publisher
.
_create_node
(
self
.
api_client
,
publish_data
)
# pylint: disable=protected-access
self
.
assertEqual
(
data
,
expected
)
@responses.activate
def
test_create_node_failed
(
self
):
self
.
mock_api_client
(
200
)
self
.
mock_node_create
({},
500
)
publisher
=
MarketingSitePublisher
()
publish_data
=
publisher
.
_get_node_data
(
self
.
program
,
self
.
user_id
)
# pylint: disable=protected-access
with
self
.
assertRaises
(
ProgramPublisherException
):
publisher
.
_create_node
(
self
.
api_client
,
publish_data
)
# pylint: disable=protected-access
@responses.activate
def
test_publish_program_create
(
self
):
self
.
mock_api_client
(
200
)
expected
=
{
'list'
:
[{
'nid'
:
self
.
nid
}]
}
self
.
mock_node_retrieval
(
self
.
program
.
uuid
,
exists
=
False
)
self
.
mock_node_create
(
expected
,
201
)
publisher
=
MarketingSitePublisher
()
publisher
.
publish_program
(
self
.
program
)
self
.
assertEqual
(
len
(
responses
.
calls
),
6
)
@responses.activate
def
test_publish_program_edit
(
self
):
self
.
mock_api_client
(
200
)
self
.
mock_node_retrieval
(
self
.
program
.
uuid
)
self
.
mock_node_edit
(
200
)
publisher
=
MarketingSitePublisher
()
publisher
.
publish_program
(
self
.
program
)
self
.
assertEqual
(
len
(
responses
.
calls
),
6
)
@responses.activate
def
test_publish_modified_program
(
self
):
self
.
mock_api_client
(
200
)
self
.
mock_node_retrieval
(
self
.
program
.
uuid
)
self
.
mock_node_edit
(
200
)
program_before
=
ProgramFactory
()
publisher
=
MarketingSitePublisher
(
program_before
)
publisher
.
publish_program
(
self
.
program
)
self
.
assertEqual
(
len
(
responses
.
calls
),
6
)
@responses.activate
def
test_publish_unmodified_program
(
self
):
self
.
mock_api_client
(
200
)
publisher
=
MarketingSitePublisher
(
self
.
program
)
publisher
.
publish_program
(
self
.
program
)
self
.
assertEqual
(
len
(
responses
.
calls
),
0
)
course_discovery/apps/edx_catalog_extensions/migrations/0003_create_publish_to_marketing_site_flag.py
0 → 100644
View file @
41f24b58
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
def
create_switch
(
apps
,
schema_editor
):
"""Create and activate the publish_program_to_marketing_site switch if it does not already exist."""
Switch
=
apps
.
get_model
(
'waffle'
,
'Switch'
)
Switch
.
objects
.
get_or_create
(
name
=
'publish_program_to_marketing_site'
,
defaults
=
{
'active'
:
False
})
def
delete_switch
(
apps
,
schema_editor
):
"""Delete the publish_program_to_marketing_site switch."""
Switch
=
apps
.
get_model
(
'waffle'
,
'Switch'
)
Switch
.
objects
.
filter
(
name
=
'publish_program_to_marketing_site'
)
.
delete
()
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'edx_catalog_extensions'
,
'0002_convert_program_category_to_type'
),
(
'waffle'
,
'0001_initial'
),
]
operations
=
[
migrations
.
RunPython
(
create_switch
,
reverse_code
=
delete_switch
),
]
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