Commit f733d1d8 by chrisndodge

Merge pull request #6234 from edx/afzaledx/WL-172-order-history

WL-172 Show a Order History list on the Student Dashboard for any PaidCo...
parents a07df1a6 872dbb87
......@@ -48,6 +48,7 @@ from edxmako.shortcuts import render_to_response, render_to_string
from mako.exceptions import TopLevelLookupException
from course_modes.models import CourseMode
from shoppingcart.api import order_history
from student.models import (
Registration, UserProfile, PendingNameChange,
PendingEmailChange, CourseEnrollment, unique_id_for_user,
......@@ -78,7 +79,6 @@ import external_auth.views
from bulk_email.models import Optout, CourseAuthorization
import shoppingcart
from shoppingcart.models import DonationConfiguration
from openedx.core.djangoapps.user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
......@@ -104,7 +104,7 @@ from student.helpers import (
check_verify_status_by_course
)
from xmodule.error_module import ErrorDescriptor
from shoppingcart.models import CourseRegistrationCode
from shoppingcart.models import DonationConfiguration, CourseRegistrationCode
from openedx.core.djangoapps.user_api.api import profile as profile_api
import analytics
......@@ -641,6 +641,9 @@ def dashboard(request):
# otherwise, use the default language
current_language = settings.LANGUAGE_DICT[settings.LANGUAGE_CODE]
# Populate the Order History for the side-bar.
order_history_list = order_history(user, course_org_filter=course_org_filter, org_filter_out_set=org_filter_out_set)
context = {
'enrollment_message': enrollment_message,
'course_enrollment_pairs': course_enrollment_pairs,
......@@ -670,6 +673,7 @@ def dashboard(request):
'platform_name': settings.PLATFORM_NAME,
'enrolled_courses_either_paid': enrolled_courses_either_paid,
'provider_states': [],
'order_history_list': order_history_list
}
if third_party_auth.is_enabled():
......
"""
API for for getting information about the user's shopping cart.
"""
from django.core.urlresolvers import reverse
from xmodule.modulestore.django import ModuleI18nService
from shoppingcart.models import OrderItem
def order_history(user, **kwargs):
"""
Returns the list of previously purchased orders for a user. Only the orders with
PaidCourseRegistration and CourseRegCodeItem are returned.
Params:
course_org_filter: Current Microsite's ORG.
org_filter_out_set: A list of all other Microsites' ORGs.
"""
course_org_filter = kwargs['course_org_filter'] if 'course_org_filter' in kwargs else None
org_filter_out_set = kwargs['org_filter_out_set'] if 'org_filter_out_set' in kwargs else []
order_history_list = []
purchased_order_items = OrderItem.objects.filter(user=user, status='purchased').select_subclasses().order_by('-fulfilled_time')
for order_item in purchased_order_items:
# Avoid repeated entries for the same order id.
if order_item.order.id not in [item['order_id'] for item in order_history_list]:
# If we are in a Microsite, then include the orders having courses attributed (by ORG) to that Microsite.
# Conversely, if we are not in a Microsite, then include the orders having courses
# not attributed (by ORG) to any Microsite.
order_item_course_id = getattr(order_item, 'course_id', None)
if order_item_course_id:
if (course_org_filter and course_org_filter == order_item_course_id.org) or \
(course_org_filter is None and order_item_course_id.org not in org_filter_out_set):
order_history_list.append({
'order_id': order_item.order.id,
'receipt_url': reverse('shoppingcart.views.show_receipt', kwargs={'ordernum': order_item.order.id}),
'order_date': ModuleI18nService().strftime(order_item.order.purchase_time, 'SHORT_DATE')
})
return order_history_list
"""
Tests for Microsite Dashboard with Shopping Cart History
"""
import mock
from django.conf import settings
from django.test.utils import override_settings
from django.core.urlresolvers import reverse
from mock import patch
from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase, mixed_store_config
)
from xmodule.modulestore.tests.factories import CourseFactory
from shoppingcart.models import (
Order, PaidCourseRegistration, CertificateItem, Donation
)
from student.tests.factories import UserFactory
from course_modes.models import CourseMode
# Since we don't need any XML course fixtures, use a modulestore configuration
# that disables the XML modulestore.
MODULESTORE_CONFIG = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {}, include_xml=False)
def fake_all_orgs(default=None): # pylint: disable=unused-argument
"""
create a fake list of all microsites
"""
return set(['fakeX', 'fooX'])
def fakex_microsite(name, default=None): # pylint: disable=unused-argument
"""
create a fake microsite site name
"""
return 'fakeX'
def non_microsite(name, default=None): # pylint: disable=unused-argument
"""
create a fake microsite site name
"""
return None
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_PAID_COURSE_REGISTRATION': True})
class TestOrderHistoryOnMicrositeDashboard(ModuleStoreTestCase):
"""
Test for microsite dashboard order history
"""
def setUp(self):
patcher = patch('student.models.tracker')
self.mock_tracker = patcher.start()
self.user = UserFactory.create()
self.user.set_password('password')
self.user.save()
self.addCleanup(patcher.stop)
# First Order with our (fakeX) microsite's course.
course1 = CourseFactory.create(org='fakeX', number='999', display_name='fakeX Course')
course1_key = course1.id
course1_mode = CourseMode(course_id=course1_key,
mode_slug="honor",
mode_display_name="honor cert",
min_price=20)
course1_mode.save()
cart = Order.get_cart_for_user(self.user)
PaidCourseRegistration.add_to_order(cart, course1_key)
cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
self.orderid_microsite = cart.id
# Second Order with another(fooX) microsite's course
course2 = CourseFactory.create(org='fooX', number='888', display_name='fooX Course')
course2_key = course2.id
course2_mode = CourseMode(course_id=course2.id,
mode_slug="honor",
mode_display_name="honor cert",
min_price=20)
course2_mode.save()
cart = Order.get_cart_for_user(self.user)
PaidCourseRegistration.add_to_order(cart, course2_key)
cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
self.orderid_other_microsite = cart.id
# Third Order with course not attributed to any microsite.
course3 = CourseFactory.create(org='otherorg', number='777', display_name='otherorg Course')
course3_key = course3.id
course3_mode = CourseMode(course_id=course3.id,
mode_slug="honor",
mode_display_name="honor cert",
min_price=20)
course3_mode.save()
cart = Order.get_cart_for_user(self.user)
PaidCourseRegistration.add_to_order(cart, course3_key)
cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
self.orderid_non_microsite = cart.id
# Fourth Order with course not attributed to any microsite but with a CertificateItem
course4 = CourseFactory.create(org='otherorg', number='888')
course4_key = course4.id
course4_mode = CourseMode(course_id=course4.id,
mode_slug="verified",
mode_display_name="verified cert",
min_price=20)
course4_mode.save()
cart = Order.get_cart_for_user(self.user)
CertificateItem.add_to_order(cart, course4_key, 20.0, 'verified')
cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
self.orderid_cert_non_microsite = cart.id
# Fifth Order with course not attributed to any microsite but with a Donation
course5 = CourseFactory.create(org='otherorg', number='999')
course5_key = course5.id
cart = Order.get_cart_for_user(self.user)
Donation.add_to_order(cart, 20.0, course5_key)
cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
self.orderid_donation = cart.id
# also add a donation not associated with a course to make sure the None case works OK
Donation.add_to_order(cart, 10.0, None)
cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
self.orderid_courseless_donation = cart.id
@mock.patch("microsite_configuration.microsite.get_value", fakex_microsite)
@mock.patch("microsite_configuration.microsite.get_all_orgs", fake_all_orgs)
def test_when_in_microsite_shows_orders_with_microsite_courses_only(self):
self.client.login(username=self.user.username, password="password")
response = self.client.get(reverse("dashboard"))
receipt_url_microsite_course = reverse('shoppingcart.views.show_receipt', kwargs={'ordernum': self.orderid_microsite})
receipt_url_microsite_course2 = reverse('shoppingcart.views.show_receipt', kwargs={'ordernum': self.orderid_other_microsite})
receipt_url_non_microsite = reverse('shoppingcart.views.show_receipt', kwargs={'ordernum': self.orderid_non_microsite})
receipt_url_cert_non_microsite = reverse('shoppingcart.views.show_receipt', kwargs={'ordernum': self.orderid_cert_non_microsite})
receipt_url_donation = reverse('shoppingcart.views.show_receipt', kwargs={'ordernum': self.orderid_donation})
self.assertIn(receipt_url_microsite_course, response.content)
self.assertNotIn(receipt_url_microsite_course2, response.content)
self.assertNotIn(receipt_url_non_microsite, response.content)
self.assertNotIn(receipt_url_cert_non_microsite, response.content)
self.assertNotIn(receipt_url_donation, response.content)
@mock.patch("microsite_configuration.microsite.get_value", non_microsite)
@mock.patch("microsite_configuration.microsite.get_all_orgs", fake_all_orgs)
def test_when_not_in_microsite_shows_orders_with_non_microsite_courses_only(self):
self.client.login(username=self.user.username, password="password")
response = self.client.get(reverse("dashboard"))
receipt_url_microsite_course = reverse('shoppingcart.views.show_receipt', kwargs={'ordernum': self.orderid_microsite})
receipt_url_microsite_course2 = reverse('shoppingcart.views.show_receipt', kwargs={'ordernum': self.orderid_other_microsite})
receipt_url_non_microsite = reverse('shoppingcart.views.show_receipt', kwargs={'ordernum': self.orderid_non_microsite})
receipt_url_cert_non_microsite = reverse('shoppingcart.views.show_receipt', kwargs={'ordernum': self.orderid_cert_non_microsite})
receipt_url_donation = reverse('shoppingcart.views.show_receipt', kwargs={'ordernum': self.orderid_donation})
receipt_url_courseless_donation = reverse('shoppingcart.views.show_receipt', kwargs={'ordernum': self.orderid_courseless_donation})
self.assertNotIn(receipt_url_microsite_course, response.content)
self.assertNotIn(receipt_url_microsite_course2, response.content)
self.assertIn(receipt_url_non_microsite, response.content)
self.assertIn(receipt_url_cert_non_microsite, response.content)
self.assertIn(receipt_url_donation, response.content)
self.assertIn(receipt_url_courseless_donation, response.content)
......@@ -1084,10 +1084,20 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
# check for the enrollment codes content
self.assertIn('Please send each professional one of these unique registration codes to enroll into the course.', resp.content)
# fetch the newly generated registration codes
course_registration_codes = CourseRegistrationCode.objects.filter(order=self.cart)
((template, context), _) = render_mock.call_args # pylint: disable=redefined-outer-name
self.assertEqual(template, 'shoppingcart/receipt.html')
self.assertEqual(context['order'], self.cart)
self.assertIn(reg_item, context['shoppingcart_items'][0])
# now check for all the registration codes in the receipt
# and all the codes should be unused at this point
self.assertIn(course_registration_codes[0].code, context['reg_code_info_list'][0]['code'])
self.assertIn(course_registration_codes[1].code, context['reg_code_info_list'][1]['code'])
self.assertFalse(context['reg_code_info_list'][0]['is_redeemed'])
self.assertFalse(context['reg_code_info_list'][1]['is_redeemed'])
self.assertIn(self.cart.purchase_time.strftime("%B %d, %Y"), resp.content)
self.assertIn(self.cart.company_name, resp.content)
self.assertIn(self.cart.company_contact_name, resp.content)
......@@ -1097,6 +1107,25 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self.assertIn('You have successfully purchased <b>{total_registration_codes} course registration codes'
.format(total_registration_codes=context['total_registration_codes']), resp.content)
# now redeem one of registration code from the previous order
redeem_url = reverse('register_code_redemption', args=[context['reg_code_info_list'][0]['code']])
#now activate the user by enrolling him/her to the course
response = self.client.post(redeem_url, **{'HTTP_HOST': 'localhost'})
self.assertEquals(response.status_code, 200)
self.assertTrue('View Course' in response.content)
# now view the receipt page again to see if any registration codes
# has been expired or not
resp = self.client.get(reverse('shoppingcart.views.show_receipt', args=[self.cart.id]))
self.assertEqual(resp.status_code, 200)
((template, context), _) = render_mock.call_args # pylint: disable=redefined-outer-name
self.assertEqual(template, 'shoppingcart/receipt.html')
# now check for all the registration codes in the receipt
# and one of code should be used at this point
self.assertTrue(context['reg_code_info_list'][0]['is_redeemed'])
self.assertFalse(context['reg_code_info_list'][1]['is_redeemed'])
@patch('shoppingcart.views.render_to_response', render_mock)
def test_show_receipt_success_with_upgrade(self):
......
......@@ -750,17 +750,27 @@ def _show_receipt_html(request, order):
request.session['attempting_upgrade'] = False
recipient_list = []
registration_codes = None
total_registration_codes = None
reg_code_info_list = []
recipient_list.append(getattr(order.user, 'email'))
if order_type == OrderTypes.BUSINESS:
registration_codes = CourseRegistrationCode.objects.filter(order=order)
total_registration_codes = registration_codes.count()
if order.company_contact_email:
recipient_list.append(order.company_contact_email)
if order.recipient_email:
recipient_list.append(order.recipient_email)
for __, course in shoppingcart_items:
course_registration_codes = CourseRegistrationCode.objects.filter(order=order, course_id=course.id)
total_registration_codes = course_registration_codes.count()
for course_registration_code in course_registration_codes:
reg_code_info_list.append({
'course_name': course.display_name,
'redemption_url': reverse('register_code_redemption', args=[course_registration_code.code]),
'code': course_registration_code.code,
'is_redeemed': RegistrationCodeRedemption.objects.filter(
registration_code=course_registration_code).exists(),
})
appended_recipient_emails = ", ".join(recipient_list)
context = {
......@@ -775,7 +785,7 @@ def _show_receipt_html(request, order):
'currency_symbol': settings.PAID_COURSE_REGISTRATION_CURRENCY[1],
'currency': settings.PAID_COURSE_REGISTRATION_CURRENCY[0],
'total_registration_codes': total_registration_codes,
'registration_codes': registration_codes,
'reg_code_info_list': reg_code_info_list,
'order_purchase_date': order.purchase_time.strftime("%B %d, %Y"),
}
# we want to have the ability to override the default receipt page when
......
......@@ -128,6 +128,13 @@
}
}
}
li.order-history {
span a {
font-size: 13px;
line-height: 20px;
}
}
}
.reverify-status-list {
......
......@@ -852,7 +852,7 @@
text-align: left;
}
&:last-child{
text-align: right;
text-align: center;
}
}
}
......@@ -865,15 +865,31 @@
padding: 15px 0;
text-align: center;
color: $dark-gray1;
width: 33.33333%;
width: 30%;
&:nth-child(2){width: 20%;}
&:nth-child(3){width: 40%;}
&:first-child{
text-align: left;
font-size: 18px;
text-transform: capitalize;
}
&:last-child{
text-align: right;
text-align: center;
span{
padding: 2px 10px;
font-size: 13px;
color: #fff;
display: inline-block;
border-radius: 3px;
min-width: 55px;
text-align: center;
&.red{
background: rgb(231, 92, 92);
}
&.green{
background: rgb(108, 204, 108);
}
}
}
}
}
......@@ -953,5 +969,17 @@
}
}
}
table.course-receipt{
tr{
td{
a{
&:before{content:" " attr(data-base-url) " ";display: inline-block;}
}
}
}
th:last-child{display: none;}
td:last-child{display: none;}
}
}
}
......@@ -146,6 +146,15 @@
</li>
% endif
% if len(order_history_list):
<li class="order-history">
<span class="title">${_("Order History")}</span>
% for order_history_item in order_history_list:
<span><a href="${order_history_item['receipt_url']}" target="_blank" class="edit-name">${order_history_item['order_date']}</a></span>
% endfor
</li>
% endif
% if external_auth_map is None or 'shib' not in external_auth_map.external_domain:
<li class="controls--account">
<span class="title"><a href="#password_reset_complete" rel="leanModal" id="pwd_reset_button">${_("Reset Password")}</a></span>
......
......@@ -52,17 +52,25 @@ from courseware.courses import course_image_url, get_course_about_section, get_c
<th>${_("Course Name")}</th>
<th>${_("Enrollment Code")}</th>
<th>${_("Enrollment Link")}</th>
<th>${_("Status")}</th>
</thead>
<tbody>
% for registration_code in registration_codes:
<% course = get_course_by_id(registration_code.course_id, depth=0) %>
% for reg_code_info in reg_code_info_list:
<tr>
<td>${_("{course_name}").format(course_name=course.display_name)}</td>
<td>${registration_code.code}</td>
<% redemption_url = reverse('register_code_redemption', args = [registration_code.code] ) %>
<% enrollment_url = '{redemption_url}'.format(redemption_url=redemption_url) %>
<td><a href="${redemption_url}">${enrollment_url}</a></td>
<td>${reg_code_info['course_name']}</td>
<td>${reg_code_info['code']}</td>
% if reg_code_info['is_redeemed']:
<td>${reg_code_info['redemption_url']}</td>
% else:
<td><a href="${reg_code_info['redemption_url']}" data-base-url="${site_name}">${reg_code_info['redemption_url']}</a></td>
% endif
<td>
% if reg_code_info['is_redeemed']:
<span class="red"></M>${_("Used")}</span>
% else:
<span class="green"></M>${_("Available")}</span>
% endif
</td>
</tr>
% endfor
</tbody>
......
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