Commit 9cecf8c1 by Matt Drayer

Merge pull request #718 from edx/vkaracic/basket-quantity

Basket quantity
parents e61b0c23 312db705
...@@ -48,3 +48,5 @@ if ENABLE_OAUTH2_TESTS and not all([LMS_URL_ROOT, LMS_USERNAME, LMS_PASSWORD]): ...@@ -48,3 +48,5 @@ if ENABLE_OAUTH2_TESTS and not all([LMS_URL_ROOT, LMS_USERNAME, LMS_PASSWORD]):
raise RuntimeError('LMS settings are required to run OAuth2 tests.') raise RuntimeError('LMS settings are required to run OAuth2 tests.')
ENABLE_COUPON_ADMIN_TESTS = str2bool(os.environ.get('ENABLE_COUPON_ADMIN_TESTS', False)) ENABLE_COUPON_ADMIN_TESTS = str2bool(os.environ.get('ENABLE_COUPON_ADMIN_TESTS', False))
BULK_PURCHASE_SKU = os.environ.get('BULK_PURCHASE_SKU')
import re
from acceptance_tests.config import BULK_PURCHASE_SKU
from acceptance_tests.pages.ecommerce import EcommerceAppPage
class BasketPage(EcommerceAppPage):
path = 'basket'
def is_browser_on_page(self):
return self.browser.title.startswith('Basket')
class BasketAddProductPage(EcommerceAppPage):
path = 'basket/single-item/?sku={}'.format(BULK_PURCHASE_SKU)
def _quantity_selector(self):
return "input[name='form-0-quantity']"
def is_browser_on_page(self):
return self.browser.title.startswith('Basket')
def get_product_subtotal(self):
price = self.q(css="div.price").first.text[0]
return float(re.sub(r'[^0-9.]', '', price))
def get_product_quantity(self):
quantity = int(self.q(css="#id_form-0-quantity").first.attrs('value')[0])
return quantity
def update_product_quantity(self, quantity=1):
self.q(css=self._quantity_selector()).fill(quantity)
self.q(css="div.checkout-quantity button.btn").click()
...@@ -22,13 +22,6 @@ def _get_coupon_name(is_discount): ...@@ -22,13 +22,6 @@ def _get_coupon_name(is_discount):
return prefix + suffix return prefix + suffix
class BasketPage(EcommerceAppPage):
path = 'basket'
def is_browser_on_page(self):
return self.browser.title.startswith('Basket')
class CouponsCreatePage(EcommerceAppPage): class CouponsCreatePage(EcommerceAppPage):
path = 'coupons/new' path = 'coupons/new'
......
from unittest import skipUnless
from bok_choy.web_app_test import WebAppTest
from acceptance_tests.config import BULK_PURCHASE_SKU, LMS_EMAIL, LMS_PASSWORD
from acceptance_tests.mixins import (
EcommerceApiMixin,
EnrollmentApiMixin,
LogistrationMixin,
PaymentMixin
)
from acceptance_tests.pages.basket import BasketPage, BasketAddProductPage
@skipUnless(BULK_PURCHASE_SKU, 'Bulk Purchase SKU not provided, skipping Bulk Purchase tests.')
class BulkPurchaseTests(EcommerceApiMixin, EnrollmentApiMixin, LogistrationMixin, PaymentMixin, WebAppTest):
def setUp(self):
""" Instantiate the page objects. """
super(BulkPurchaseTests, self).setUp()
self.basket_page = BasketPage(self.browser)
self.basket_add_product_page = BasketAddProductPage(self.browser)
def test_bulk_purchase(self):
"""
Verifies that the basket behaves correctly for a "bulk purchase" product
"""
quantity = 5
self.login_with_lms(LMS_EMAIL, LMS_PASSWORD)
self.basket_add_product_page.visit()
self.assertTrue(self.basket_add_product_page.is_browser_on_page())
initial_product_subtotal = self.basket_add_product_page.get_product_subtotal()
self.basket_add_product_page.update_product_quantity(quantity)
self.assertTrue(self.basket_add_product_page.get_product_quantity(), quantity)
expected_product_subtotal = initial_product_subtotal * quantity
updated_product_subtotal = self.basket_add_product_page.get_product_subtotal()
self.assertEqual(updated_product_subtotal, expected_product_subtotal)
...@@ -7,9 +7,9 @@ from acceptance_tests.config import VERIFIED_COURSE_ID, ENABLE_CYBERSOURCE_TESTS ...@@ -7,9 +7,9 @@ from acceptance_tests.config import VERIFIED_COURSE_ID, ENABLE_CYBERSOURCE_TESTS
from acceptance_tests.mixins import (CouponMixin, EcommerceApiMixin, EnrollmentApiMixin, from acceptance_tests.mixins import (CouponMixin, EcommerceApiMixin, EnrollmentApiMixin,
LogistrationMixin, UnenrollmentMixin, PaymentMixin) LogistrationMixin, UnenrollmentMixin, PaymentMixin)
from acceptance_tests.constants import CYBERSOURCE_DATA1, CYBERSOURCE_DATA2 from acceptance_tests.constants import CYBERSOURCE_DATA1, CYBERSOURCE_DATA2
from acceptance_tests.pages import (BasketPage, CouponsCreatePage, from acceptance_tests.pages import (CouponsCreatePage, CouponsDetailsPage, CouponsListPage,
CouponsDetailsPage, CouponsListPage,
DashboardHomePage, RedeemVoucherPage) DashboardHomePage, RedeemVoucherPage)
from acceptance_tests.pages.basket import BasketPage
@ddt.ddt @ddt.ddt
......
...@@ -8,6 +8,10 @@ COURSE_ID_REGEX = r'[^/+]+(/|\+)[^/+]+(/|\+)[^/]+' ...@@ -8,6 +8,10 @@ COURSE_ID_REGEX = r'[^/+]+(/|\+)[^/+]+(/|\+)[^/]+'
COURSE_ID_PATTERN = r'(?P<course_id>{})'.format(COURSE_ID_REGEX) COURSE_ID_PATTERN = r'(?P<course_id>{})'.format(COURSE_ID_REGEX)
# Seat constants
SEAT_PRODUCT_CLASS_NAME = "Seat"
# Enrollment Code constants # Enrollment Code constants
ENROLLMENT_CODE_PRODUCT_CLASS_NAME = 'Enrollment Code' ENROLLMENT_CODE_PRODUCT_CLASS_NAME = 'Enrollment Code'
ENROLLMENT_CODE_SWITCH = 'create_enrollment_codes' ENROLLMENT_CODE_SWITCH = 'create_enrollment_codes'
......
...@@ -250,15 +250,15 @@ class CouponOfferViewTests(CourseCatalogTestMixin, LmsApiMockMixin, TestCase): ...@@ -250,15 +250,15 @@ class CouponOfferViewTests(CourseCatalogTestMixin, LmsApiMockMixin, TestCase):
self.assertEqual(response.context['new_price'], new_price) self.assertEqual(response.context['new_price'], new_price)
class CouponRedeemViewTests(CouponMixin, CourseCatalogTestMixin, TestCase): class CouponRedeemViewTests(CouponMixin, CourseCatalogTestMixin, LmsApiMockMixin, TestCase):
redeem_url = reverse('coupons:redeem') redeem_url = reverse('coupons:redeem')
def setUp(self): def setUp(self):
super(CouponRedeemViewTests, self).setUp() super(CouponRedeemViewTests, self).setUp()
self.user = self.create_user() self.user = self.create_user()
self.client.login(username=self.user.username, password=self.password) self.client.login(username=self.user.username, password=self.password)
course = CourseFactory() self.course = CourseFactory()
self.seat = course.create_or_update_seat('verified', True, 50, self.partner) self.seat = self.course.create_or_update_seat('verified', True, 50, self.partner)
self.catalog = Catalog.objects.create(partner=self.partner) self.catalog = Catalog.objects.create(partner=self.partner)
self.catalog.stock_records.add(StockRecord.objects.get(product=self.seat)) self.catalog.stock_records.add(StockRecord.objects.get(product=self.seat))
...@@ -302,8 +302,10 @@ class CouponRedeemViewTests(CouponMixin, CourseCatalogTestMixin, TestCase): ...@@ -302,8 +302,10 @@ class CouponRedeemViewTests(CouponMixin, CourseCatalogTestMixin, TestCase):
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.context['error'], _('The voucher is not applicable to your current basket.')) self.assertEqual(response.context['error'], _('The voucher is not applicable to your current basket.'))
@httpretty.activate
def test_basket_redirect_discount_code(self): def test_basket_redirect_discount_code(self):
""" Verify the view redirects to the basket single-item view when a discount code is provided. """ """ Verify the view redirects to the basket single-item view when a discount code is provided. """
self.mock_course_api_response(course=self.course)
self.create_coupon(catalog=self.catalog, code=COUPON_CODE, benefit_value=5) self.create_coupon(catalog=self.catalog, code=COUPON_CODE, benefit_value=5)
expected_url = self.get_full_url(path=reverse('basket:summary')) expected_url = self.get_full_url(path=reverse('basket:summary'))
self.assert_redemption_page_redirects(expected_url) self.assert_redemption_page_redirects(expected_url)
......
...@@ -7,6 +7,7 @@ from django.conf import settings ...@@ -7,6 +7,7 @@ from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.test import override_settings from django.test import override_settings
from django.utils.translation import ugettext_lazy as _
import httpretty import httpretty
from oscar.core.loading import get_class, get_model from oscar.core.loading import get_class, get_model
from oscar.test import newfactories as factories from oscar.test import newfactories as factories
...@@ -15,6 +16,7 @@ from requests.exceptions import ConnectionError, Timeout ...@@ -15,6 +16,7 @@ from requests.exceptions import ConnectionError, Timeout
from slumber.exceptions import SlumberBaseException from slumber.exceptions import SlumberBaseException
from testfixtures import LogCapture from testfixtures import LogCapture
from ecommerce.core.constants import ENROLLMENT_CODE_PRODUCT_CLASS_NAME, ENROLLMENT_CODE_SWITCH
from ecommerce.core.models import SiteConfiguration from ecommerce.core.models import SiteConfiguration
from ecommerce.core.tests import toggle_switch from ecommerce.core.tests import toggle_switch
from ecommerce.core.url_utils import get_lms_url from ecommerce.core.url_utils import get_lms_url
...@@ -31,6 +33,7 @@ Applicator = get_class('offer.utils', 'Applicator') ...@@ -31,6 +33,7 @@ Applicator = get_class('offer.utils', 'Applicator')
Basket = get_model('basket', 'Basket') Basket = get_model('basket', 'Basket')
Benefit = get_model('offer', 'Benefit') Benefit = get_model('offer', 'Benefit')
Catalog = get_model('catalogue', 'Catalog') Catalog = get_model('catalogue', 'Catalog')
Product = get_model('catalogue', 'Product')
Selector = get_class('partner.strategy', 'Selector') Selector = get_class('partner.strategy', 'Selector')
StockRecord = get_model('partner', 'StockRecord') StockRecord = get_model('partner', 'StockRecord')
...@@ -46,9 +49,9 @@ class BasketSingleItemViewTests(CouponMixin, CourseCatalogTestMixin, LmsApiMockM ...@@ -46,9 +49,9 @@ class BasketSingleItemViewTests(CouponMixin, CourseCatalogTestMixin, LmsApiMockM
self.user = self.create_user() self.user = self.create_user()
self.client.login(username=self.user.username, password=self.password) self.client.login(username=self.user.username, password=self.password)
course = CourseFactory() self.course = CourseFactory()
course.create_or_update_seat('verified', True, 50, self.partner) self.course.create_or_update_seat('verified', True, 50, self.partner)
product = course.create_or_update_seat('verified', False, 0, self.partner) product = self.course.create_or_update_seat('verified', False, 0, self.partner)
self.stock_record = StockRecordFactory(product=product, partner=self.partner) self.stock_record = StockRecordFactory(product=product, partner=self.partner)
self.catalog = Catalog.objects.create(partner=self.partner) self.catalog = Catalog.objects.create(partner=self.partner)
self.catalog.stock_records.add(self.stock_record) self.catalog.stock_records.add(self.stock_record)
...@@ -97,7 +100,7 @@ class BasketSingleItemViewTests(CouponMixin, CourseCatalogTestMixin, LmsApiMockM ...@@ -97,7 +100,7 @@ class BasketSingleItemViewTests(CouponMixin, CourseCatalogTestMixin, LmsApiMockM
self.create_coupon(catalog=self.catalog, code=COUPON_CODE, benefit_value=5) self.create_coupon(catalog=self.catalog, code=COUPON_CODE, benefit_value=5)
self.mock_footer_api_response() self.mock_footer_api_response()
self.mock_course_api_response() self.mock_course_api_response(course=self.course)
url = '{path}?sku={sku}&code={code}'.format(path=self.path, sku=self.stock_record.partner_sku, url = '{path}?sku={sku}&code={code}'.format(path=self.path, sku=self.stock_record.partner_sku,
code=COUPON_CODE) code=COUPON_CODE)
response = self.client.get(url) response = self.client.get(url)
...@@ -141,7 +144,7 @@ class BasketSummaryViewTests(CourseCatalogTestMixin, LmsApiMockMixin, TestCase): ...@@ -141,7 +144,7 @@ class BasketSummaryViewTests(CourseCatalogTestMixin, LmsApiMockMixin, TestCase):
def mock_course_api_error(self, error): def mock_course_api_error(self, error):
def callback(request, uri, headers): # pylint: disable=unused-argument def callback(request, uri, headers): # pylint: disable=unused-argument
raise error raise error
course_url = get_lms_url('api/courses/v1/courses/{}/'.format(self.course)) course_url = get_lms_url('api/courses/v1/courses/{}/'.format(self.course.id))
httpretty.register_uri(httpretty.GET, course_url, body=callback, content_type='application/json') httpretty.register_uri(httpretty.GET, course_url, body=callback, content_type='application/json')
def create_basket_and_add_product(self, product): def create_basket_and_add_product(self, product):
...@@ -179,6 +182,20 @@ class BasketSummaryViewTests(CourseCatalogTestMixin, LmsApiMockMixin, TestCase): ...@@ -179,6 +182,20 @@ class BasketSummaryViewTests(CourseCatalogTestMixin, LmsApiMockMixin, TestCase):
) )
) )
def test_enrollment_code_seat_type(self):
"""Verify the correct seat type attribute is retrieved."""
course = CourseFactory()
toggle_switch(ENROLLMENT_CODE_SWITCH, True)
course.create_or_update_seat('verified', False, 10, self.partner)
enrollment_code = Product.objects.get(product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME)
self.create_basket_and_add_product(enrollment_code)
self.mock_course_api_response(course)
self.mock_footer_api_response()
response = self.client.get(self.path)
self.assertEqual(response.status_code, 200)
line_data = response.context['formset_lines_data'][0][1]
self.assertEqual(line_data['seat_type'], _(enrollment_code.attr.seat_type.capitalize()))
@ddt.data( @ddt.data(
(Benefit.PERCENTAGE, 100), (Benefit.PERCENTAGE, 100),
(Benefit.PERCENTAGE, 50), (Benefit.PERCENTAGE, 50),
...@@ -199,26 +216,24 @@ class BasketSummaryViewTests(CourseCatalogTestMixin, LmsApiMockMixin, TestCase): ...@@ -199,26 +216,24 @@ class BasketSummaryViewTests(CourseCatalogTestMixin, LmsApiMockMixin, TestCase):
response = self.client.get(self.path) response = self.client.get(self.path)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context['lines']), 1) self.assertEqual(len(response.context['formset_lines_data']), 1)
self.assertEqual(response.context['lines'][0].benefit_value, format_benefit_value(benefit)) line_data = response.context['formset_lines_data'][0][1]
self.assertEqual(response.context['lines'][0].has_discount, True) self.assertEqual(line_data['benefit_value'], format_benefit_value(benefit))
self.assertEqual(line_data['seat_type'], _(seat.attr.certificate_type.capitalize()))
self.assertEqual(line_data['course_name'], self.course.name)
self.assertFalse(line_data['enrollment_code'])
self.assertEqual(response.context['payment_processors'][0].NAME, DummyProcessor.NAME) self.assertEqual(response.context['payment_processors'][0].NAME, DummyProcessor.NAME)
self.assertEqual(json.loads(response.context['footer']), {'footer': 'edX Footer'}) self.assertEqual(json.loads(response.context['footer']), {'footer': 'edX Footer'})
def test_no_basket_response(self): def test_no_basket_response(self):
""" Verify there are no lines in the context for a non-existing basket. """ """ Verify there are no form and line data in the context for a non-existing basket. """
response = self.client.get(self.path) response = self.client.get(self.path)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['lines'], []) self.assertEqual(response.context['formset_lines_data'], [])
def test_line_no_cert_type(self):
seat = self.create_seat(course=self.course, cert_type='TEST', seat_price=100)
self.create_basket_and_add_product(seat)
response = self.client.get(self.path)
self.assertIsNone(response.context['lines'][0].certificate_type)
def test_line_item_discount_data(self): def test_line_item_discount_data(self):
""" Verify that line item has correct discount data. """ """ Verify that line item has correct discount data. """
self.mock_course_api_response(self.course)
seat = self.create_seat(self.course) seat = self.create_seat(self.course)
basket = self.create_basket_and_add_product(seat) basket = self.create_basket_and_add_product(seat)
self.create_and_apply_benefit_to_basket(basket, seat, Benefit.PERCENTAGE, 50) self.create_and_apply_benefit_to_basket(basket, seat, Benefit.PERCENTAGE, 50)
...@@ -228,11 +243,9 @@ class BasketSummaryViewTests(CourseCatalogTestMixin, LmsApiMockMixin, TestCase): ...@@ -228,11 +243,9 @@ class BasketSummaryViewTests(CourseCatalogTestMixin, LmsApiMockMixin, TestCase):
basket.add_product(seat_without_benefit, 1) basket.add_product(seat_without_benefit, 1)
response = self.client.get(self.path) response = self.client.get(self.path)
self.assertEqual(response.context['lines'][0].benefit_value, '50%') lines = response.context['formset_lines_data']
self.assertEqual(response.context['lines'][0].has_discount, True) self.assertEqual(lines[0][1]['benefit_value'], '50%')
self.assertEqual(lines[1][1]['benefit_value'], None)
self.assertEqual(response.context['lines'][1].benefit_value, None)
self.assertEqual(response.context['lines'][1].has_discount, False)
def test_cached_course(self): def test_cached_course(self):
""" Verify that the course info is cached. """ """ Verify that the course info is cached. """
......
...@@ -9,10 +9,12 @@ from django.core.cache import cache ...@@ -9,10 +9,12 @@ from django.core.cache import cache
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from requests.exceptions import ConnectionError, Timeout from requests.exceptions import ConnectionError, Timeout
from opaque_keys.edx.keys import CourseKey
from oscar.apps.basket.views import * # pylint: disable=wildcard-import, unused-wildcard-import from oscar.apps.basket.views import * # pylint: disable=wildcard-import, unused-wildcard-import
from edx_rest_api_client.client import EdxRestApiClient from edx_rest_api_client.client import EdxRestApiClient
from slumber.exceptions import SlumberBaseException from slumber.exceptions import SlumberBaseException
from ecommerce.core.constants import ENROLLMENT_CODE_PRODUCT_CLASS_NAME, SEAT_PRODUCT_CLASS_NAME
from ecommerce.core.url_utils import get_lms_url from ecommerce.core.url_utils import get_lms_url
from ecommerce.coupons.views import get_voucher_from_code from ecommerce.coupons.views import get_voucher_from_code
from ecommerce.extensions.analytics.utils import prepare_analytics_data from ecommerce.extensions.analytics.utils import prepare_analytics_data
...@@ -62,42 +64,62 @@ class BasketSummaryView(BasketView): ...@@ -62,42 +64,62 @@ class BasketSummaryView(BasketView):
""" """
Display basket contents and checkout/payment options. Display basket contents and checkout/payment options.
""" """
def _determine_seat_type(self, product):
"""
Return the seat type based on the product class
"""
seat_type = None
if product.get_product_class().name == SEAT_PRODUCT_CLASS_NAME:
seat_type = get_certificate_type_display_value(product.attr.certificate_type)
elif product.get_product_class().name == ENROLLMENT_CODE_PRODUCT_CLASS_NAME:
seat_type = get_certificate_type_display_value(product.attr.seat_type)
return seat_type
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(BasketSummaryView, self).get_context_data(**kwargs) context = super(BasketSummaryView, self).get_context_data(**kwargs)
formset = context.get('formset', [])
lines = context.get('line_list', []) lines = context.get('line_list', [])
lines_data = []
api = EdxRestApiClient(get_lms_url('api/courses/v1/')) api = EdxRestApiClient(get_lms_url('api/courses/v1/'))
for line in lines: for line in lines:
course_id = line.product.course_id course_key = CourseKey.from_string(line.product.attr.course_key)
cache_key = 'courses_api_detail_{}'.format(course_key)
# Get each course type so we can display to the user at checkout.
try:
line.certificate_type = get_certificate_type_display_value(line.product.attr.certificate_type)
except ValueError:
line.certificate_type = None
cache_key = 'courses_api_detail_{}'.format(course_id)
cache_hash = hashlib.md5(cache_key).hexdigest() cache_hash = hashlib.md5(cache_key).hexdigest()
course_name = None
image_url = None
short_description = None
try: try:
course = cache.get(cache_hash) course = cache.get(cache_hash)
if not course: if not course:
course = api.courses(course_id).get() course = api.courses(course_key).get()
course['image_url'] = get_lms_url(course['media']['course_image']['uri'])
cache.set(cache_hash, course, settings.COURSES_API_CACHE_TIMEOUT) cache.set(cache_hash, course, settings.COURSES_API_CACHE_TIMEOUT)
line.course = course image_url = get_lms_url(course['media']['course_image']['uri'])
short_description = course['short_description']
course_name = course['name']
except (ConnectionError, SlumberBaseException, Timeout): except (ConnectionError, SlumberBaseException, Timeout):
logger.exception('Failed to retrieve data from Course API for course [%s].', course_id) logger.exception('Failed to retrieve data from Course API for course [%s].', course_key)
if line.has_discount: if line.has_discount:
benefit = self.request.basket.applied_offers().values()[0].benefit benefit = self.request.basket.applied_offers().values()[0].benefit
line.benefit_value = format_benefit_value(benefit) benefit_value = format_benefit_value(benefit)
else: else:
line.benefit_value = None benefit_value = None
lines_data.append({
'seat_type': self._determine_seat_type(line.product),
'course_name': course_name,
'course_key': course_key,
'image_url': image_url,
'course_short_description': short_description,
'benefit_value': benefit_value,
'enrollment_code': line.product.get_product_class().name == ENROLLMENT_CODE_PRODUCT_CLASS_NAME,
})
context.update({ context.update({
'analytics_data': prepare_analytics_data( 'analytics_data': prepare_analytics_data(
self.request.user, self.request.user,
self.request.site.siteconfiguration.segment_key, self.request.site.siteconfiguration.segment_key,
course_id unicode(course_key)
), ),
}) })
...@@ -106,6 +128,6 @@ class BasketSummaryView(BasketView): ...@@ -106,6 +128,6 @@ class BasketSummaryView(BasketView):
'payment_processors': self.request.site.siteconfiguration.get_payment_processors(), 'payment_processors': self.request.site.siteconfiguration.get_payment_processors(),
'homepage_url': get_lms_url(''), 'homepage_url': get_lms_url(''),
'footer': get_lms_footer(), 'footer': get_lms_footer(),
'lines': lines, 'formset_lines_data': zip(formset, lines_data),
}) })
return context return context
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
.container { .container {
padding: 60px 20px 30px 20px; padding: 60px 20px 30px 20px;
.alertinner p:last-child {
display: none;
}
.basket-items, .basket-items,
.total, .total,
.verification-note, .verification-note,
...@@ -42,6 +46,20 @@ ...@@ -42,6 +46,20 @@
@include text-align(right); @include text-align(right);
} }
.checkout-quantity {
.quantity {
display: inline-block;
width: 40%;
padding: 3px;
}
button {
display: inline-block;
vertical-align:top;
margin-left: 5px;
width: 50%;
}
}
.course_image img { .course_image img {
margin: 0 auto; margin: 0 auto;
......
...@@ -13,10 +13,11 @@ ...@@ -13,10 +13,11 @@
{% csrf_token %} {% csrf_token %}
{{ formset.management_form }} {{ formset.management_form }}
{% for form, line_data in formset_lines_data %}
{% with line=form.instance product=form.instance.product %}
{% purchase_info_for_line request line as session %}
<div class="basket-items"> <div class="basket-items">
{% for line in lines %} {% if line_data.seat_type %}
{% purchase_info_for_line request line as session %}
{% if line.certificate_type %}
<p class="certificate_type"> <p class="certificate_type">
{% trans "Earn a valuable certificate to showcase the skills you learn in" %} {% trans "Earn a valuable certificate to showcase the skills you learn in" %}
</p> </p>
...@@ -24,17 +25,27 @@ ...@@ -24,17 +25,27 @@
<div class="row"> <div class="row">
<div class="col-sm-2 course_image"> <div class="col-sm-2 course_image">
{{ form.id }} {{ form.id }}
<img class="thumbnail" src="{{ line.course.image_url }}" alt="{{ line.course.name }}"/> <img class="thumbnail" src="{{ line_data.image_url }}" alt="{{ line_data.course_name }}"/>
</div> </div>
<div class="col-sm-5"> <div class="col-sm-5">
<p class="course_name">{{ line.course.name }} - {{ line.course.org }} ({{ line.course.number }})</p> <p class="course_name">{{ line_data.course_name }} - {{ line_data.course_key.org }} ({{ line_data.course_key.run }})</p>
<p class="course_description">{{ line.course.short_description }}</p> <p class="course_description">{{ line_data.course_short_description }}</p>
</div> </div>
<div class="col-sm-5 course_prices"> {% if line_data.enrollment_code %}
<div class="col-sm-2">
<div class="checkout-quantity">
<div class="input-group {% if form.errors %}error{% endif %}">
{% render_field form.quantity class+="quantity" %}
<button class="btn btn-default" type="submit" data-loading-text="{% trans 'Updating...' %}">{% trans "Update" %}</button>
</div>
</div>
</div>
{% endif %}
<div class="col-sm-{% if line_data.enrollment_code %}3{% else %}5{% endif %} course_prices">
{% if line.has_discount %} {% if line.has_discount %}
<div class="discount"> <div class="discount">
<div class="benefit"> <div class="benefit">
{% blocktrans with benefit_value=line.benefit_value %} {% blocktrans with benefit_value=line_data.benefit_value %}
{{benefit_value}} off {{benefit_value}} off
{% endblocktrans %} {% endblocktrans %}
</div> </div>
...@@ -48,6 +59,7 @@ ...@@ -48,6 +59,7 @@
</div> </div>
</div> </div>
</div> </div>
{% endwith %}
{% endfor %} {% endfor %}
</div> </div>
</form> </form>
...@@ -55,6 +67,7 @@ ...@@ -55,6 +67,7 @@
<div class="total"> <div class="total">
<div class="row"> <div class="row">
{% if not line_data.enrollment_code %}
{% block vouchers %} {% block vouchers %}
{% if basket.contains_a_voucher %} {% if basket.contains_a_voucher %}
<div class="vouchers col-sm-6"> <div class="vouchers col-sm-6">
...@@ -96,6 +109,7 @@ ...@@ -96,6 +109,7 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endblock vouchers %} {% endblock vouchers %}
{% endif %}
{% block baskettotals %} {% block baskettotals %}
{% include 'basket/partials/basket_totals.html' with editable=1 %} {% include 'basket/partials/basket_totals.html' with editable=1 %}
...@@ -115,23 +129,23 @@ ...@@ -115,23 +129,23 @@
{% trans "Place Order" %} {% trans "Place Order" %}
</a> </a>
{% else %} {% else %}
{% for processor in payment_processors %} {% for processor in payment_processors %}
<button data-track-type="click" <button data-track-type="click"
data-track-event="edx.bi.ecommerce.basket.payment_selected" data-track-event="edx.bi.ecommerce.basket.payment_selected"
data-track-category="checkout" data-track-category="checkout"
data-processor-name="{{ processor.NAME }}" data-processor-name="{{ processor.NAME }}"
data-course-id="{{ course.id }}" data-course-id="{{ course.id }}"
class="btn payment-button" class="btn payment-button"
value="{{ processor.NAME|lower }}" value="{{ processor.NAME|lower }}"
id="{{ processor.NAME|lower }}"> id="{{ processor.NAME|lower }}">
{% if processor.NAME == 'cybersource' %} {% if processor.NAME == 'cybersource' %}
{% trans "Checkout" %} {% trans "Checkout" %}
{% elif processor.NAME == 'paypal' %} {% elif processor.NAME == 'paypal' %}
{# Translators: Do NOT translate the name PayPal. #} {# Translators: Do NOT translate the name PayPal. #}
{% trans "Checkout with PayPal" %} {% trans "Checkout with PayPal" %}
{% endif %} {% endif %}
</button> </button>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
</div> </div>
</div> </div>
......
...@@ -284,6 +284,7 @@ class LmsApiMockMixin(object): ...@@ -284,6 +284,7 @@ class LmsApiMockMixin(object):
def mock_course_api_response(self, course=None): def mock_course_api_response(self, course=None):
""" Helper function to register an API endpoint for the course information. """ """ Helper function to register an API endpoint for the course information. """
course_info = { course_info = {
'short_description': 'Test description',
'media': { 'media': {
'course_image': { 'course_image': {
'uri': '/asset-v1:test+test+test+type@asset+block@images_course_image.jpg' 'uri': '/asset-v1:test+test+test+type@asset+block@images_course_image.jpg'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment