Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
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
edx-platform
Commits
09405a75
Commit
09405a75
authored
Aug 05, 2015
by
Clinton Blackburn
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #9193 from edx/patch/2015-08-04
ECOM Patch
parents
69be9000
69e9ac1a
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
153 additions
and
55 deletions
+153
-55
lms/djangoapps/commerce/api/v1/models.py
+26
-4
lms/djangoapps/commerce/api/v1/serializers.py
+29
-1
lms/djangoapps/commerce/api/v1/tests/test_views.py
+93
-50
lms/djangoapps/commerce/api/v1/views.py
+5
-0
No files found.
lms/djangoapps/commerce/api/v1/models.py
View file @
09405a75
""" API v1 models. """
""" API v1 models. """
from
itertools
import
groupby
from
itertools
import
groupby
import
logging
import
logging
from
django.db
import
transaction
from
django.db
import
transaction
from
opaque_keys
import
InvalidKeyError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
course_modes.models
import
CourseMode
from
course_modes.models
import
CourseMode
from
verify_student.models
import
VerificationDeadline
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
@@ -17,11 +18,25 @@ class Course(object):
...
@@ -17,11 +18,25 @@ class Course(object):
modes
=
None
modes
=
None
_deleted_modes
=
None
_deleted_modes
=
None
def
__init__
(
self
,
id
,
modes
):
# pylint: disable=invalid-name,redefined-builtin
def
__init__
(
self
,
id
,
modes
,
verification_deadline
=
None
):
# pylint: disable=invalid-name,redefined-builtin
self
.
id
=
CourseKey
.
from_string
(
unicode
(
id
))
# pylint: disable=invalid-name
self
.
id
=
CourseKey
.
from_string
(
unicode
(
id
))
# pylint: disable=invalid-name
self
.
modes
=
list
(
modes
)
self
.
modes
=
list
(
modes
)
self
.
verification_deadline
=
verification_deadline
self
.
_deleted_modes
=
[]
self
.
_deleted_modes
=
[]
@property
def
name
(
self
):
""" Return course name. """
course_id
=
CourseKey
.
from_string
(
unicode
(
self
.
id
))
# pylint: disable=invalid-name
try
:
return
CourseOverview
.
get_from_id
(
course_id
)
.
display_name
except
CourseOverview
.
DoesNotExist
:
# NOTE (CCB): Ideally, the course modes table should only contain data for courses that exist in
# modulestore. If that is not the case, say for local development/testing, carry on without failure.
log
.
warning
(
'Failed to retrieve CourseOverview for [
%
s]. Using empty course name.'
,
course_id
)
return
None
def
get_mode_display_name
(
self
,
mode
):
def
get_mode_display_name
(
self
,
mode
):
""" Returns display name for the given mode. """
""" Returns display name for the given mode. """
slug
=
mode
.
mode_slug
.
strip
()
.
lower
()
slug
=
mode
.
mode_slug
.
strip
()
.
lower
()
...
@@ -42,6 +57,10 @@ class Course(object):
...
@@ -42,6 +57,10 @@ class Course(object):
@transaction.commit_on_success
@transaction.commit_on_success
def
save
(
self
,
*
args
,
**
kwargs
):
# pylint: disable=unused-argument
def
save
(
self
,
*
args
,
**
kwargs
):
# pylint: disable=unused-argument
""" Save the CourseMode objects to the database. """
""" Save the CourseMode objects to the database. """
# Update the verification deadline for the course (not the individual modes)
VerificationDeadline
.
set_deadline
(
self
.
id
,
self
.
verification_deadline
)
for
mode
in
self
.
modes
:
for
mode
in
self
.
modes
:
mode
.
course_id
=
self
.
id
mode
.
course_id
=
self
.
id
mode
.
mode_display_name
=
self
.
get_mode_display_name
(
mode
)
mode
.
mode_display_name
=
self
.
get_mode_display_name
(
mode
)
...
@@ -53,6 +72,8 @@ class Course(object):
...
@@ -53,6 +72,8 @@ class Course(object):
def
update
(
self
,
attrs
):
def
update
(
self
,
attrs
):
""" Update the model with external data (usually passed via API call). """
""" Update the model with external data (usually passed via API call). """
self
.
verification_deadline
=
attrs
.
get
(
'verification_deadline'
)
existing_modes
=
{
mode
.
mode_slug
:
mode
for
mode
in
self
.
modes
}
existing_modes
=
{
mode
.
mode_slug
:
mode
for
mode
in
self
.
modes
}
merged_modes
=
set
()
merged_modes
=
set
()
merged_mode_keys
=
set
()
merged_mode_keys
=
set
()
...
@@ -87,7 +108,8 @@ class Course(object):
...
@@ -87,7 +108,8 @@ class Course(object):
course_modes
=
CourseMode
.
objects
.
filter
(
course_id
=
course_id
)
course_modes
=
CourseMode
.
objects
.
filter
(
course_id
=
course_id
)
if
course_modes
:
if
course_modes
:
return
cls
(
unicode
(
course_id
),
list
(
course_modes
))
verification_deadline
=
VerificationDeadline
.
deadline_for_course
(
course_id
)
return
cls
(
course_id
,
list
(
course_modes
),
verification_deadline
=
verification_deadline
)
return
None
return
None
...
...
lms/djangoapps/commerce/api/v1/serializers.py
View file @
09405a75
""" API v1 serializers. """
""" API v1 serializers. """
from
datetime
import
datetime
import
pytz
from
rest_framework
import
serializers
from
rest_framework
import
serializers
from
commerce.api.v1.models
import
Course
from
commerce.api.v1.models
import
Course
...
@@ -25,11 +28,36 @@ class CourseModeSerializer(serializers.ModelSerializer):
...
@@ -25,11 +28,36 @@ class CourseModeSerializer(serializers.ModelSerializer):
class
CourseSerializer
(
serializers
.
Serializer
):
class
CourseSerializer
(
serializers
.
Serializer
):
""" Course serializer. """
""" Course serializer. """
id
=
serializers
.
CharField
()
# pylint: disable=invalid-name
id
=
serializers
.
CharField
()
# pylint: disable=invalid-name
name
=
serializers
.
CharField
(
read_only
=
True
)
verification_deadline
=
serializers
.
DateTimeField
(
blank
=
True
)
modes
=
CourseModeSerializer
(
many
=
True
,
allow_add_remove
=
True
)
modes
=
CourseModeSerializer
(
many
=
True
,
allow_add_remove
=
True
)
def
validate
(
self
,
attrs
):
""" Ensure the verification deadline occurs AFTER the course mode enrollment deadlines. """
verification_deadline
=
attrs
.
get
(
'verification_deadline'
,
None
)
if
verification_deadline
:
upgrade_deadline
=
None
# Find the earliest upgrade deadline
for
mode
in
attrs
[
'modes'
]:
expires
=
mode
.
expiration_datetime
if
expires
:
# If we don't already have an upgrade_deadline value, use datetime.max so that we can actually
# complete the comparison.
upgrade_deadline
=
min
(
expires
,
upgrade_deadline
or
datetime
.
max
.
replace
(
tzinfo
=
pytz
.
utc
))
# In cases where upgrade_deadline is None (e.g. the verified professional mode), allow a verification
# deadline to be set anyway.
if
upgrade_deadline
is
not
None
and
verification_deadline
<
upgrade_deadline
:
raise
serializers
.
ValidationError
(
'Verification deadline must be after the course mode upgrade deadlines.'
)
return
attrs
def
restore_object
(
self
,
attrs
,
instance
=
None
):
def
restore_object
(
self
,
attrs
,
instance
=
None
):
if
instance
is
None
:
if
instance
is
None
:
return
Course
(
attrs
[
'id'
],
attrs
[
'modes'
])
return
Course
(
attrs
[
'id'
],
attrs
[
'modes'
]
,
attrs
[
'verification_deadline'
]
)
instance
.
update
(
attrs
)
instance
.
update
(
attrs
)
return
instance
return
instance
lms/djangoapps/commerce/api/v1/tests/test_views.py
View file @
09405a75
...
@@ -8,12 +8,14 @@ from django.conf import settings
...
@@ -8,12 +8,14 @@ from django.conf import settings
from
django.contrib.auth.models
import
Permission
from
django.contrib.auth.models
import
Permission
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
django.test.utils
import
override_settings
from
django.test.utils
import
override_settings
import
pytz
from
rest_framework.utils.encoders
import
JSONEncoder
from
rest_framework.utils.encoders
import
JSONEncoder
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
course_modes.models
import
CourseMode
from
course_modes.models
import
CourseMode
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
from
verify_student.models
import
VerificationDeadline
PASSWORD
=
'test'
PASSWORD
=
'test'
JSON_CONTENT_TYPE
=
'application/json'
JSON_CONTENT_TYPE
=
'application/json'
...
@@ -31,20 +33,38 @@ class CourseApiViewTestMixin(object):
...
@@ -31,20 +33,38 @@ class CourseApiViewTestMixin(object):
self
.
course_mode
=
CourseMode
.
objects
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
u'verified'
,
min_price
=
100
,
self
.
course_mode
=
CourseMode
.
objects
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
u'verified'
,
min_price
=
100
,
currency
=
u'USD'
,
sku
=
u'ABC123'
)
currency
=
u'USD'
,
sku
=
u'ABC123'
)
@staticmethod
@classmethod
def
_serialize_course_mode
(
course_mode
):
def
_serialize_datetime
(
cls
,
dt
):
# pylint: disable=invalid-name
""" Serializes datetime values using Django REST Framework's encoder.
Use this to simplify equality assertions.
"""
if
dt
:
return
JSONEncoder
()
.
default
(
dt
)
return
None
@classmethod
def
_serialize_course_mode
(
cls
,
course_mode
):
""" Serialize a CourseMode to a dict. """
""" Serialize a CourseMode to a dict. """
# encode the datetime (if nonempty) using DRF's encoder, simplifying
# equality assertions.
expires
=
course_mode
.
expiration_datetime
if
expires
is
not
None
:
expires
=
JSONEncoder
()
.
default
(
expires
)
return
{
return
{
u'name'
:
course_mode
.
mode_slug
,
u'name'
:
course_mode
.
mode_slug
,
u'currency'
:
course_mode
.
currency
.
lower
(),
u'currency'
:
course_mode
.
currency
.
lower
(),
u'price'
:
course_mode
.
min_price
,
u'price'
:
course_mode
.
min_price
,
u'sku'
:
course_mode
.
sku
,
u'sku'
:
course_mode
.
sku
,
u'expires'
:
expires
,
u'expires'
:
cls
.
_serialize_datetime
(
course_mode
.
expiration_datetime
),
}
@classmethod
def
_serialize_course
(
cls
,
course
,
modes
=
None
,
verification_deadline
=
None
):
""" Serializes a course to a Python dict. """
modes
=
modes
or
[]
verification_deadline
=
verification_deadline
or
VerificationDeadline
.
deadline_for_course
(
course
.
id
)
return
{
u'id'
:
unicode
(
course
.
id
),
u'name'
:
unicode
(
course
.
display_name
),
u'verification_deadline'
:
cls
.
_serialize_datetime
(
verification_deadline
),
u'modes'
:
[
cls
.
_serialize_course_mode
(
mode
)
for
mode
in
modes
]
}
}
...
@@ -66,12 +86,7 @@ class CourseListViewTests(CourseApiViewTestMixin, ModuleStoreTestCase):
...
@@ -66,12 +86,7 @@ class CourseListViewTests(CourseApiViewTestMixin, ModuleStoreTestCase):
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
actual
=
json
.
loads
(
response
.
content
)
actual
=
json
.
loads
(
response
.
content
)
expected
=
[
expected
=
[
self
.
_serialize_course
(
self
.
course
,
[
self
.
course_mode
])]
{
u'id'
:
unicode
(
self
.
course
.
id
),
u'modes'
:
[
self
.
_serialize_course_mode
(
self
.
course_mode
)]
}
]
self
.
assertListEqual
(
actual
,
expected
)
self
.
assertListEqual
(
actual
,
expected
)
...
@@ -85,6 +100,9 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
...
@@ -85,6 +100,9 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
self
.
user
=
UserFactory
.
create
()
self
.
user
=
UserFactory
.
create
()
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
PASSWORD
)
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
PASSWORD
)
permission
=
Permission
.
objects
.
get
(
name
=
'Can change course mode'
)
self
.
user
.
user_permissions
.
add
(
permission
)
@ddt.data
(
'get'
,
'post'
,
'put'
)
@ddt.data
(
'get'
,
'post'
,
'put'
)
def
test_authentication_required
(
self
,
method
):
def
test_authentication_required
(
self
,
method
):
""" Verify only authenticated users can access the view. """
""" Verify only authenticated users can access the view. """
...
@@ -94,6 +112,7 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
...
@@ -94,6 +112,7 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
@ddt.data
(
'post'
,
'put'
)
@ddt.data
(
'post'
,
'put'
)
def
test_authorization_required
(
self
,
method
):
def
test_authorization_required
(
self
,
method
):
self
.
user
.
user_permissions
.
clear
()
""" Verify create/edit operations require appropriate permissions. """
""" Verify create/edit operations require appropriate permissions. """
response
=
getattr
(
self
.
client
,
method
)(
self
.
path
,
content_type
=
JSON_CONTENT_TYPE
)
response
=
getattr
(
self
.
client
,
method
)(
self
.
path
,
content_type
=
JSON_CONTENT_TYPE
)
self
.
assertEqual
(
response
.
status_code
,
403
)
self
.
assertEqual
(
response
.
status_code
,
403
)
...
@@ -104,10 +123,7 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
...
@@ -104,10 +123,7 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
actual
=
json
.
loads
(
response
.
content
)
actual
=
json
.
loads
(
response
.
content
)
expected
=
{
expected
=
self
.
_serialize_course
(
self
.
course
,
[
self
.
course_mode
])
u'id'
:
unicode
(
self
.
course
.
id
),
u'modes'
:
[
self
.
_serialize_course_mode
(
self
.
course_mode
)]
}
self
.
assertEqual
(
actual
,
expected
)
self
.
assertEqual
(
actual
,
expected
)
def
test_retrieve_invalid_course
(
self
):
def
test_retrieve_invalid_course
(
self
):
...
@@ -116,40 +132,75 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
...
@@ -116,40 +132,75 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
response
=
self
.
client
.
get
(
path
,
content_type
=
JSON_CONTENT_TYPE
)
response
=
self
.
client
.
get
(
path
,
content_type
=
JSON_CONTENT_TYPE
)
self
.
assertEqual
(
response
.
status_code
,
404
)
self
.
assertEqual
(
response
.
status_code
,
404
)
def
test_update
(
self
):
def
_get_update_response_and_expected_data
(
self
,
mode_expiration
,
verification_deadline
):
""" Verify the view supports updating a course. """
""" Returns expected data and response for course update. """
permission
=
Permission
.
objects
.
get
(
name
=
'Can change course mode'
)
self
.
user
.
user_permissions
.
add
(
permission
)
expiration_datetime
=
datetime
.
now
()
expected_course_mode
=
CourseMode
(
expected_course_mode
=
CourseMode
(
mode_slug
=
u'verified'
,
mode_slug
=
u'verified'
,
min_price
=
200
,
min_price
=
200
,
currency
=
u'USD'
,
currency
=
u'USD'
,
sku
=
u'ABC123'
,
sku
=
u'ABC123'
,
expiration_datetime
=
expiration_datetime
expiration_datetime
=
mode_expiration
)
)
expected
=
{
expected
=
self
.
_serialize_course
(
self
.
course
,
[
expected_course_mode
],
verification_deadline
)
u'id'
:
unicode
(
self
.
course
.
id
),
u'modes'
:
[
self
.
_serialize_course_mode
(
expected_course_mode
)]
# Sanity check: The API should return HTTP status 200 for updates
}
response
=
self
.
client
.
put
(
self
.
path
,
json
.
dumps
(
expected
),
content_type
=
JSON_CONTENT_TYPE
)
response
=
self
.
client
.
put
(
self
.
path
,
json
.
dumps
(
expected
),
content_type
=
JSON_CONTENT_TYPE
)
return
response
,
expected
def
test_update
(
self
):
""" Verify the view supports updating a course. """
# Sanity check: Ensure no verification deadline is set
self
.
assertIsNone
(
VerificationDeadline
.
deadline_for_course
(
self
.
course
.
id
))
# Generate the expected data
verification_deadline
=
datetime
(
year
=
2020
,
month
=
12
,
day
=
31
,
tzinfo
=
pytz
.
utc
)
expiration_datetime
=
datetime
.
now
(
pytz
.
utc
)
response
,
expected
=
self
.
_get_update_response_and_expected_data
(
expiration_datetime
,
verification_deadline
)
# Sanity check: The API should return HTTP status 200 for updates
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
# Verify the course and modes are returned as JSON
actual
=
json
.
loads
(
response
.
content
)
actual
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
actual
,
expected
)
self
.
assertEqual
(
actual
,
expected
)
# Verify the verification deadline is updated
self
.
assertEqual
(
VerificationDeadline
.
deadline_for_course
(
self
.
course
.
id
),
verification_deadline
)
def
test_update_invalid_dates
(
self
):
"""
Verify the API does not allow the verification deadline to be set before the course mode upgrade deadlines.
"""
expiration_datetime
=
datetime
.
now
(
pytz
.
utc
)
verification_deadline
=
datetime
(
year
=
1915
,
month
=
5
,
day
=
7
,
tzinfo
=
pytz
.
utc
)
response
,
__
=
self
.
_get_update_response_and_expected_data
(
expiration_datetime
,
verification_deadline
)
self
.
assertEqual
(
response
.
status_code
,
400
)
# Verify the error message is correct
actual
=
json
.
loads
(
response
.
content
)
expected
=
{
'non_field_errors'
:
[
'Verification deadline must be after the course mode upgrade deadlines.'
]
}
self
.
assertEqual
(
actual
,
expected
)
def
test_update_verification_deadline_without_expiring_modes
(
self
):
""" Verify verification deadline can be set if no course modes expire.
This accounts for the verified professional mode, which requires verification but should never expire.
"""
verification_deadline
=
datetime
(
year
=
1915
,
month
=
5
,
day
=
7
,
tzinfo
=
pytz
.
utc
)
response
,
__
=
self
.
_get_update_response_and_expected_data
(
None
,
verification_deadline
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
VerificationDeadline
.
deadline_for_course
(
self
.
course
.
id
),
verification_deadline
)
def
test_update_overwrite
(
self
):
def
test_update_overwrite
(
self
):
""" Verify that data submitted via PUT overwrites/deletes modes that are
""" Verify that data submitted via PUT overwrites/deletes modes that are
not included in the body of the request. """
not included in the body of the request. """
permission
=
Permission
.
objects
.
get
(
name
=
'Can change course mode'
)
self
.
user
.
user_permissions
.
add
(
permission
)
course_id
=
unicode
(
self
.
course
.
id
)
course_id
=
unicode
(
self
.
course
.
id
)
expected
=
{
expected_course_mode
=
CourseMode
(
mode_slug
=
u'credit'
,
min_price
=
500
,
currency
=
u'USD'
,
sku
=
u'ABC123'
)
u'id'
:
course_id
,
expected
=
self
.
_serialize_course
(
self
.
course
,
[
expected_course_mode
])
u'modes'
:
[
self
.
_serialize_course_mode
(
CourseMode
(
mode_slug
=
u'credit'
,
min_price
=
500
,
currency
=
u'USD'
,
sku
=
u'ABC123'
)),
]
}
path
=
reverse
(
'commerce_api:v1:courses:retrieve_update'
,
args
=
[
course_id
])
path
=
reverse
(
'commerce_api:v1:courses:retrieve_update'
,
args
=
[
course_id
])
response
=
self
.
client
.
put
(
path
,
json
.
dumps
(
expected
),
content_type
=
JSON_CONTENT_TYPE
)
response
=
self
.
client
.
put
(
path
,
json
.
dumps
(
expected
),
content_type
=
JSON_CONTENT_TYPE
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
...
@@ -167,9 +218,6 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
...
@@ -167,9 +218,6 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
def
test_update_professional_expiration
(
self
,
mode_slug
,
expiration_datetime
):
def
test_update_professional_expiration
(
self
,
mode_slug
,
expiration_datetime
):
""" Verify that pushing a mode with a professional certificate and an expiration datetime
""" Verify that pushing a mode with a professional certificate and an expiration datetime
will be rejected (this is not allowed). """
will be rejected (this is not allowed). """
permission
=
Permission
.
objects
.
get
(
name
=
'Can change course mode'
)
self
.
user
.
user_permissions
.
add
(
permission
)
mode
=
self
.
_serialize_course_mode
(
mode
=
self
.
_serialize_course_mode
(
CourseMode
(
CourseMode
(
mode_slug
=
mode_slug
,
mode_slug
=
mode_slug
,
...
@@ -190,19 +238,14 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
...
@@ -190,19 +238,14 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
def
assert_can_create_course
(
self
,
**
request_kwargs
):
def
assert_can_create_course
(
self
,
**
request_kwargs
):
""" Verify a course can be created by the view. """
""" Verify a course can be created by the view. """
course
=
CourseFactory
.
create
()
course
=
CourseFactory
.
create
()
course_id
=
unicode
(
course
.
id
)
expected_modes
=
[
CourseMode
(
mode_slug
=
u'verified'
,
min_price
=
150
,
currency
=
u'USD'
,
sku
=
u'ABC123'
),
expected
=
{
CourseMode
(
mode_slug
=
u'honor'
,
min_price
=
0
,
currency
=
u'USD'
,
sku
=
u'DEADBEEF'
)]
u'id'
:
course_id
,
expected
=
self
.
_serialize_course
(
course
,
expected_modes
)
u'modes'
:
[
path
=
reverse
(
'commerce_api:v1:courses:retrieve_update'
,
args
=
[
unicode
(
course
.
id
)])
self
.
_serialize_course_mode
(
CourseMode
(
mode_slug
=
u'verified'
,
min_price
=
150
,
currency
=
u'USD'
,
sku
=
u'ABC123'
)),
self
.
_serialize_course_mode
(
CourseMode
(
mode_slug
=
u'honor'
,
min_price
=
0
,
currency
=
u'USD'
,
sku
=
u'DEADBEEF'
)),
]
}
path
=
reverse
(
'commerce_api:v1:courses:retrieve_update'
,
args
=
[
course_id
])
response
=
self
.
client
.
put
(
path
,
json
.
dumps
(
expected
),
content_type
=
JSON_CONTENT_TYPE
,
**
request_kwargs
)
response
=
self
.
client
.
put
(
path
,
json
.
dumps
(
expected
),
content_type
=
JSON_CONTENT_TYPE
,
**
request_kwargs
)
self
.
assertEqual
(
response
.
status_code
,
201
)
self
.
assertEqual
(
response
.
status_code
,
201
)
actual
=
json
.
loads
(
response
.
content
)
actual
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
actual
,
expected
)
self
.
assertEqual
(
actual
,
expected
)
...
...
lms/djangoapps/commerce/api/v1/views.py
View file @
09405a75
...
@@ -41,3 +41,8 @@ class CourseRetrieveUpdateView(RetrieveUpdateAPIView):
...
@@ -41,3 +41,8 @@ class CourseRetrieveUpdateView(RetrieveUpdateAPIView):
return
course
return
course
raise
Http404
raise
Http404
def
pre_save
(
self
,
obj
):
# There is nothing to pre-save. The default behavior changes the Course.id attribute from
# a CourseKey to a string, which is not desired.
pass
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