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
75531b2f
Commit
75531b2f
authored
Jun 13, 2017
by
Marko Jevtic
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[LEARNER-437] Reflect discount on the Program About Page (WL)
parent
5a919f2c
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
98 additions
and
38 deletions
+98
-38
common/djangoapps/course_modes/tests/test_views.py
+1
-1
lms/djangoapps/commerce/models.py
+1
-0
lms/djangoapps/commerce/tests/test_utils.py
+3
-1
lms/djangoapps/commerce/utils.py
+2
-2
lms/djangoapps/courseware/tests/test_date_summary.py
+2
-6
lms/djangoapps/courseware/tests/test_views.py
+5
-6
lms/djangoapps/verify_student/tests/test_views.py
+3
-5
lms/static/sass/views/_program-marketing-page.scss
+20
-0
lms/templates/courseware/program_marketing.html
+31
-10
openedx/core/djangoapps/programs/tests/test_utils.py
+24
-6
openedx/core/djangoapps/programs/utils.py
+6
-1
No files found.
common/djangoapps/course_modes/tests/test_views.py
View file @
75531b2f
...
...
@@ -130,7 +130,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
# Configure whether we're upgrading or not
url
=
reverse
(
'course_modes_choose'
,
args
=
[
unicode
(
prof_course
.
id
)])
response
=
self
.
client
.
get
(
url
)
self
.
assertRedirects
(
response
,
'http://testserver/
test_basket
/?sku=TEST'
,
fetch_redirect_response
=
False
)
self
.
assertRedirects
(
response
,
'http://testserver/
basket/add
/?sku=TEST'
,
fetch_redirect_response
=
False
)
ecomm_test_utils
.
update_commerce_config
(
enabled
=
False
)
def
_generate_enterprise_learner_context
(
self
,
enable_audit_enrollment
=
False
):
...
...
lms/djangoapps/commerce/models.py
View file @
75531b2f
...
...
@@ -15,6 +15,7 @@ class CommerceConfiguration(ConfigurationModel):
API_NAME
=
'commerce'
CACHE_KEY
=
'commerce.api.data'
DEFAULT_RECEIPT_PAGE_URL
=
'/checkout/receipt/?order_number='
MULTIPLE_ITEMS_BASKET_PAGE_URL
=
'/basket/add/'
checkout_on_ecommerce_service
=
models
.
BooleanField
(
default
=
False
,
...
...
lms/djangoapps/commerce/tests/test_utils.py
View file @
75531b2f
...
...
@@ -97,7 +97,9 @@ class EcommerceServiceTests(TestCase):
def
test_get_checkout_page_url
(
self
,
skus
):
""" Verify the checkout page URL is properly constructed and returned. """
url
=
EcommerceService
()
.
get_checkout_page_url
(
*
skus
)
expected_url
=
'{root}/test_basket/?{skus}'
.
format
(
config
=
CommerceConfiguration
.
current
()
expected_url
=
'{root}{basket_url}?{skus}'
.
format
(
basket_url
=
config
.
MULTIPLE_ITEMS_BASKET_PAGE_URL
,
root
=
settings
.
ECOMMERCE_PUBLIC_URL_ROOT
,
skus
=
urlencode
({
'sku'
:
skus
},
doseq
=
True
),
)
...
...
lms/djangoapps/commerce/utils.py
View file @
75531b2f
...
...
@@ -87,9 +87,9 @@ class EcommerceService(object):
Absolute path to the ecommerce checkout page showing basket that contains specified products.
Example:
http://localhost:8002/basket/
single_item
/?sku=5H3HG5&sku=57FHHD
http://localhost:8002/basket/
add
/?sku=5H3HG5&sku=57FHHD
"""
return
'{checkout_page_path}?{skus}'
.
format
(
checkout_page_path
=
self
.
get_absolute_ecommerce_url
(
self
.
config
.
single_course_checkout_page
),
checkout_page_path
=
self
.
get_absolute_ecommerce_url
(
self
.
config
.
MULTIPLE_ITEMS_BASKET_PAGE_URL
),
skus
=
urlencode
({
'sku'
:
skus
},
doseq
=
True
),
)
lms/djangoapps/courseware/tests/test_date_summary.py
View file @
75531b2f
...
...
@@ -313,14 +313,10 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
def
test_ecommerce_checkout_redirect
(
self
):
"""Verify the block link redirects to ecommerce checkout if it's enabled."""
sku
=
'TESTSKU'
checkout_page
=
'/test_basket/'
CommerceConfiguration
.
objects
.
create
(
checkout_on_ecommerce_service
=
True
,
single_course_checkout_page
=
checkout_page
)
configuration
=
CommerceConfiguration
.
objects
.
create
(
checkout_on_ecommerce_service
=
True
)
self
.
setup_course_and_user
(
sku
=
sku
)
block
=
VerifiedUpgradeDeadlineDate
(
self
.
course
,
self
.
user
)
self
.
assertEqual
(
block
.
link
,
'{}?sku={}'
.
format
(
c
heckout_page
,
sku
))
self
.
assertEqual
(
block
.
link
,
'{}?sku={}'
.
format
(
c
onfiguration
.
MULTIPLE_ITEMS_BASKET_PAGE_URL
,
sku
))
## VerificationDeadlineDate
def
test_no_verification_deadline
(
self
):
...
...
lms/djangoapps/courseware/tests/test_views.py
View file @
75531b2f
...
...
@@ -469,12 +469,8 @@ class ViewsTestCase(ModuleStoreTestCase):
_id(bool): Tell the method to either expect an id in the href or not.
"""
checkout_page
=
'/test_basket/'
sku
=
'TEST123'
CommerceConfiguration
.
objects
.
create
(
checkout_on_ecommerce_service
=
True
,
single_course_checkout_page
=
checkout_page
)
configuration
=
CommerceConfiguration
.
objects
.
create
(
checkout_on_ecommerce_service
=
True
)
course
=
CourseFactory
.
create
()
CourseModeFactory
(
mode_slug
=
CourseMode
.
PROFESSIONAL
,
course_id
=
course
.
id
,
sku
=
sku
,
min_price
=
1
)
...
...
@@ -486,7 +482,10 @@ class ViewsTestCase(ModuleStoreTestCase):
# Construct the link according the following scenarios and verify its presence in the response:
# (1) shopping cart is enabled and the user is not logged in
# (2) shopping cart is enabled and the user is logged in
href
=
'<a href="{uri_stem}?sku={sku}" class="add-to-cart">'
.
format
(
uri_stem
=
checkout_page
,
sku
=
sku
)
href
=
'<a href="{uri_stem}?sku={sku}" class="add-to-cart">'
.
format
(
uri_stem
=
configuration
.
MULTIPLE_ITEMS_BASKET_PAGE_URL
,
sku
=
sku
,
)
# Generate the course about page content
response
=
self
.
client
.
get
(
reverse
(
'about_course'
,
args
=
[
unicode
(
course
.
id
)]))
...
...
lms/djangoapps/verify_student/tests/test_views.py
View file @
75531b2f
...
...
@@ -131,7 +131,6 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
)
def
test_start_flow_with_ecommerce
(
self
):
"""Verify user gets redirected to ecommerce checkout when ecommerce checkout is enabled."""
checkout_page
=
'/test_basket/'
sku
=
'TESTSKU'
# When passing a SKU ecommerce api gets called.
httpretty
.
register_uri
(
...
...
@@ -140,11 +139,10 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
body
=
json
.
dumps
([
'foo'
,
'bar'
]),
content_type
=
"application/json"
,
)
configuration
=
CommerceConfiguration
.
objects
.
create
(
checkout_on_ecommerce_service
=
True
)
checkout_page
=
configuration
.
MULTIPLE_ITEMS_BASKET_PAGE_URL
httpretty
.
register_uri
(
httpretty
.
GET
,
"{}{}"
.
format
(
TEST_PUBLIC_URL_ROOT
,
checkout_page
))
CommerceConfiguration
.
objects
.
create
(
checkout_on_ecommerce_service
=
True
,
single_course_checkout_page
=
checkout_page
)
course
=
self
.
_create_course
(
'verified'
,
sku
=
sku
)
self
.
_enroll
(
course
.
id
)
response
=
self
.
_get_page
(
'verify_student_start_flow'
,
course
.
id
,
expected_status_code
=
302
)
...
...
lms/static/sass/views/_program-marketing-page.scss
View file @
75531b2f
...
...
@@ -1085,6 +1085,26 @@
}
}
}
.description
{
display
:
block
;
float
:
left
;
}
.price
{
.green-highlight
{
font-weight
:
700
;
color
:
palette
(
success
,
text
);
}
.original-price
{
text-decoration
:
line-through
;
}
.savings
{
display
:
block
;
}
}
}
}
...
...
lms/templates/courseware/program_marketing.html
View file @
75531b2f
...
...
@@ -69,7 +69,7 @@ description_max_length = 250
<a
href=
"${buy_button_href}"
class=
"btn-brand btn-large hidden-sm btn-start"
>
${Text(_('Buy the {program_name} (${price} USD)')).format(
program_name=title,
price=
program['price_ranges'][0]['total']
price=
full_program_price
)}
</a>
% else:
...
...
@@ -99,7 +99,7 @@ description_max_length = 250
<div
class=
"col col-12 md-col-7 lg-col-8 left-col"
>
<ul
class=
"list-divided program-desc-tbl"
>
<li
class=
"item"
>
<span>
<span
class=
"description"
>
<span
class=
"fa fa-book fa-lg"
aria-hidden=
"true"
></span>
${_('Number of Courses')}
</span>
...
...
@@ -110,7 +110,7 @@ description_max_length = 250
</a>
</li>
<li
class=
"item"
>
<span>
<span
class=
"description"
>
<span
class=
"fa fa-clock-o fa-lg"
aria-hidden=
"true"
></span>
${_('Average Length')}
</span>
...
...
@@ -121,7 +121,7 @@ description_max_length = 250
</span>
</li>
<li
class=
"item"
>
<span>
<span
class=
"description"
>
<span
class=
"fa fa-tachometer fa-lg"
aria-hidden=
"true"
></span>
${_('Effort')}
</span>
...
...
@@ -133,15 +133,36 @@ description_max_length = 250
</span>
</li>
<li
class=
"item"
>
<span>
<span
class=
"description"
>
<span
class=
"fa fa-tag fa-lg"
aria-hidden=
"true"
></span>
${_('Price (USD)')}
</span>
<span>
${Text(_('${full_program_price} for entire program')).format(
full_program_price=full_program_price
)}
</span>
% if program.get('discount_data') and program['discount_data']['is_discounted']:
<span
class=
"price"
>
<span
role=
"group"
aria-label=
"${_('Original Price')}"
class=
"original-price"
>
${Text(_('${oldPrice}')).format(
oldPrice=full_program_price_format.format(program['discount_data']['total_incl_tax_excl_discounts'])
)}
</span>
<span
role=
"group"
aria-label=
"${_('Discounted Price')}"
class=
"discount green-highlight"
>
${Text(_('${newPrice}{htmlEnd} for entire program')).format(
newPrice=full_program_price,
htmlEnd=HTML('
</span>
')
)}
<span
class=
"savings green-highlight"
>
${Text(_('You save ${discount_value} {currency}')).format(
discount_value=full_program_price_format.format(program['discount_data']['discount_value']),
currency=program['discount_data']['currency']
)}
</span>
</span>
% else:
<span>
${Text(_('${full_program_price} for entire program')).format(
full_program_price=full_program_price
)}
</span>
% endif
</li>
</ul>
<div
id=
"accordion-group"
>
...
...
openedx/core/djangoapps/programs/tests/test_utils.py
View file @
75531b2f
...
...
@@ -872,6 +872,19 @@ class TestProgramMarketingDataExtender(ModuleStoreTestCase):
self
.
program
[
'applicable_seat_types'
]
=
[
seat
[
'type'
]]
return
seat
def
_update_discount_data
(
self
,
mock_discount_data
):
"""
Helper method that updates mocked discount data with
- a flag indicating whether the program price is discounted
- the amount of the discount (0 in case there's no discount)
"""
program_discounted_price
=
mock_discount_data
[
'total_incl_tax'
]
program_full_price
=
mock_discount_data
[
'total_incl_tax_excl_discounts'
]
mock_discount_data
.
update
({
'is_discounted'
:
program_discounted_price
<
program_full_price
,
'discount_value'
:
program_full_price
-
program_discounted_price
})
def
test_instructors
(
self
):
data
=
ProgramMarketingDataExtender
(
self
.
program
,
self
.
user
)
.
extend
()
...
...
@@ -887,10 +900,13 @@ class TestProgramMarketingDataExtender(ModuleStoreTestCase):
self
.
assertEqual
(
data
[
'avg_price_per_course'
],
program_full_price
/
self
.
number_of_courses
)
def
test_course_pricing_when_all_course_runs_have_no_seats
(
self
):
course
=
ModuleStoreCourseFactory
()
course
=
self
.
update_course
(
course
,
self
.
user
.
id
)
course_run
=
CourseRunFactory
(
key
=
unicode
(
course
.
id
),
seats
=
[])
program
=
ProgramFactory
(
courses
=
[
CourseFactory
(
course_runs
=
[
course_run
])])
# Create three seatless course runs and add them to the program
course_runs
=
[]
for
__
in
range
(
3
):
course
=
ModuleStoreCourseFactory
()
course
=
self
.
update_course
(
course
,
self
.
user
.
id
)
course_runs
.
append
(
CourseRunFactory
(
key
=
unicode
(
course
.
id
),
seats
=
[]))
program
=
ProgramFactory
(
courses
=
[
CourseFactory
(
course_runs
=
course_runs
)])
data
=
ProgramMarketingDataExtender
(
program
,
self
.
user
)
.
extend
()
...
...
@@ -988,7 +1004,7 @@ class TestProgramMarketingDataExtender(ModuleStoreTestCase):
self
.
_prepare_program_for_discounted_price_calculation_endpoint
()
mock_discount_data
=
{
'total_incl_tax_excl_discounts'
:
200.0
,
'currency'
:
"USD"
,
'currency'
:
'USD'
,
'total_incl_tax'
:
50.0
}
httpretty
.
register_uri
(
...
...
@@ -999,6 +1015,7 @@ class TestProgramMarketingDataExtender(ModuleStoreTestCase):
)
data
=
ProgramMarketingDataExtender
(
self
.
program
,
self
.
user
)
.
extend
()
self
.
_update_discount_data
(
mock_discount_data
)
self
.
assertEqual
(
data
[
'skus'
],
...
...
@@ -1015,7 +1032,7 @@ class TestProgramMarketingDataExtender(ModuleStoreTestCase):
self
.
_prepare_program_for_discounted_price_calculation_endpoint
()
mock_discount_data
=
{
'total_incl_tax_excl_discounts'
:
200.0
,
'currency'
:
"USD"
,
'currency'
:
'USD'
,
'total_incl_tax'
:
50.0
}
httpretty
.
register_uri
(
...
...
@@ -1026,6 +1043,7 @@ class TestProgramMarketingDataExtender(ModuleStoreTestCase):
)
data
=
ProgramMarketingDataExtender
(
self
.
program
,
AnonymousUserFactory
())
.
extend
()
self
.
_update_discount_data
(
mock_discount_data
)
self
.
assertEqual
(
data
[
'skus'
],
...
...
openedx/core/djangoapps/programs/utils.py
View file @
75531b2f
...
...
@@ -605,7 +605,7 @@ class ProgramMarketingDataExtender(ProgramDataExtender):
published_course_runs
=
filter
(
lambda
run
:
run
[
'status'
]
==
'published'
,
course
[
'course_runs'
])
if
len
(
published_course_runs
)
==
1
:
for
seat
in
published_course_runs
[
0
][
'seats'
]:
if
seat
[
'type'
]
in
applicable_seat_types
:
if
seat
[
'type'
]
in
applicable_seat_types
and
seat
[
'sku'
]
:
skus
.
append
(
seat
[
'sku'
])
else
:
# If a course in the program has more than 1 published course run
...
...
@@ -626,6 +626,11 @@ class ProgramMarketingDataExtender(ProgramDataExtender):
# Make an API call to calculate the discounted price
discount_data
=
api
.
baskets
.
calculate
.
get
(
sku
=
skus
)
program_discounted_price
=
discount_data
[
'total_incl_tax'
]
program_full_price
=
discount_data
[
'total_incl_tax_excl_discounts'
]
discount_data
[
'is_discounted'
]
=
program_discounted_price
<
program_full_price
discount_data
[
'discount_value'
]
=
program_full_price
-
program_discounted_price
self
.
data
.
update
({
'discount_data'
:
discount_data
,
'full_program_price'
:
discount_data
[
'total_incl_tax'
]
...
...
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