diff --git a/lms/djangoapps/shoppingcart/models.py b/lms/djangoapps/shoppingcart/models.py index a35f4c720f..aa93509145 100644 --- a/lms/djangoapps/shoppingcart/models.py +++ b/lms/djangoapps/shoppingcart/models.py @@ -181,13 +181,16 @@ class Order(models.Model): return False @classmethod - def remove_cart_item_from_order(cls, item): + def remove_cart_item_from_order(cls, item, user): """ Removes the item from the cart if the item.order.status == 'cart'. + Also removes any code redemption associated with the order_item """ if item.order.status == 'cart': - log.info("Item {0} removed from the user cart".format(item.id)) + log.info("order item %s removed for user %s", str(item.id), user) item.delete() + # remove any redemption entry associated with the item + CouponRedemption.remove_code_redemption_from_item(item, user) @property def total_cost(self): @@ -1235,7 +1238,31 @@ class CouponRedemption(models.Model): coupon = models.ForeignKey(Coupon, db_index=True) @classmethod - def delete_coupon_redemption(cls, user, cart): + def remove_code_redemption_from_item(cls, item, user): + """ + If an item removed from shopping cart then we will remove + the corresponding redemption info of coupon code + """ + order_item_course_id = getattr(item, 'course_id') + try: + # Try to remove redemption information of coupon code, If exist. + coupon_redemption = cls.objects.get( + user=user, + coupon__course_id=order_item_course_id if order_item_course_id else CourseKeyField.Empty, + order=item.order_id + ) + coupon_redemption.delete() + log.info( + u'Coupon "%s" redemption entry removed for user "%s" for order item "%s"', + coupon_redemption.coupon.code, + user, + str(item.id), + ) + except CouponRedemption.DoesNotExist: + log.debug(u'Code redemption does not exist for order item id=%s.', str(item.id)) + + @classmethod + def remove_coupon_redemption_from_cart(cls, user, cart): """ This method delete coupon redemption """ diff --git a/lms/djangoapps/shoppingcart/tests/test_views.py b/lms/djangoapps/shoppingcart/tests/test_views.py index d79a18f69c..f6ae3de0f6 100644 --- a/lms/djangoapps/shoppingcart/tests/test_views.py +++ b/lms/djangoapps/shoppingcart/tests/test_views.py @@ -35,8 +35,8 @@ from shoppingcart.views import _can_download_report, _get_date_from_str from shoppingcart.models import ( Order, CertificateItem, PaidCourseRegistration, CourseRegCodeItem, Coupon, CourseRegistrationCode, RegistrationCodeRedemption, - DonationConfiguration -) + DonationConfiguration, + CouponRedemption) from student.tests.factories import UserFactory, AdminFactory, CourseModeFactory from courseware.tests.factories import InstructorFactory from student.models import CourseEnrollment @@ -674,11 +674,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): self.assertEqual(resp.status_code, 200) self.assertEquals(self.cart.orderitem_set.count(), 1) - info_log.assert_called_with( - 'order item %s removed for user %s', - str(cert_item.id), - self.user - ) + info_log.assert_called_with("order item %s removed for user %s", str(cert_item.id), self.user) @patch('shoppingcart.views.log.info') def test_remove_coupon_redemption_on_clear_cart(self, info_log): @@ -1402,6 +1398,8 @@ class ShoppingcartViewsClosedEnrollment(ModuleStoreTestCase): display_name='Testing Super Course', metadata={"invitation_only": False} ) + self.percentage_discount = 20.0 + self.coupon_code = 'asdsad' self.course_mode = CourseMode(course_id=self.testing_course.id, mode_slug="honor", mode_display_name="honor cert", @@ -1412,6 +1410,14 @@ class ShoppingcartViewsClosedEnrollment(ModuleStoreTestCase): self.tomorrow = self.now + timedelta(days=1) self.nextday = self.tomorrow + timedelta(days=1) + def add_coupon(self, course_key, is_active, code): + """ + add dummy coupon into models + """ + coupon = Coupon(code=code, description='testing code', course_id=course_key, + percentage_discount=self.percentage_discount, created_by=self.user, is_active=is_active) + coupon.save() + def login_user(self): """ Helper fn to login self.user @@ -1422,19 +1428,32 @@ class ShoppingcartViewsClosedEnrollment(ModuleStoreTestCase): def test_to_check_that_cart_item_enrollment_is_closed(self): self.login_user() reg_item1 = PaidCourseRegistration.add_to_order(self.cart, self.course_key) - PaidCourseRegistration.add_to_order(self.cart, self.testing_course.id) + expired_course_item = PaidCourseRegistration.add_to_order(self.cart, self.testing_course.id) # update the testing_course enrollment dates self.testing_course.enrollment_start = self.tomorrow self.testing_course.enrollment_end = self.nextday self.testing_course = self.update_course(self.testing_course, self.user.id) + # now add the same coupon code to the second course(testing_course) + self.add_coupon(self.testing_course.id, True, self.coupon_code) + resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code}) + self.assertEqual(resp.status_code, 200) + + coupon_redemption = CouponRedemption.objects.filter(coupon__course_id=getattr(expired_course_item, 'course_id'), + order=expired_course_item.order_id) + self.assertEqual(coupon_redemption.count(), 1) # testing_course enrollment is closed but the course is in the cart # so we delete that item from the cart and display the message in the cart + # coupon redemption entry should also be deleted when the item is expired. resp = self.client.get(reverse('shoppingcart.views.show_cart', args=[])) self.assertEqual(resp.status_code, 200) self.assertIn("{course_name} has been removed because the enrollment period has closed.".format(course_name=self.testing_course.display_name), resp.content) + # now the redemption entry should be deleted from the table. + coupon_redemption = CouponRedemption.objects.filter(coupon__course_id=getattr(expired_course_item, 'course_id'), + order=expired_course_item.order_id) + self.assertEqual(coupon_redemption.count(), 0) ((template, context), _tmp) = render_mock.call_args self.assertEqual(template, 'shoppingcart/shopping_cart.html') self.assertEqual(context['order'], self.cart) diff --git a/lms/djangoapps/shoppingcart/views.py b/lms/djangoapps/shoppingcart/views.py index 387ae6e1fb..729bd196bf 100644 --- a/lms/djangoapps/shoppingcart/views.py +++ b/lms/djangoapps/shoppingcart/views.py @@ -174,7 +174,7 @@ def show_cart(request): if is_any_course_expired: for expired_item in expired_cart_items: - Order.remove_cart_item_from_order(expired_item) + Order.remove_cart_item_from_order(expired_item, request.user) cart.update_order_type() appended_expired_course_names = ", ".join(expired_cart_item_names) @@ -232,42 +232,12 @@ def remove_item(request): else: item = items[0] if item.user == request.user: - order_item_course_id = getattr(item, 'course_id') - item.delete() - log.info( - u'order item %s removed for user %s', - item_id, - request.user, - ) - remove_code_redemption(order_item_course_id, item_id, item, request.user) + Order.remove_cart_item_from_order(item, request.user) item.order.update_order_type() return HttpResponse('OK') -def remove_code_redemption(order_item_course_id, item_id, item, user): - """ - If an item removed from shopping cart then we will remove - the corresponding redemption info of coupon code - """ - try: - # Try to remove redemption information of coupon code, If exist. - coupon_redemption = CouponRedemption.objects.get( - user=user, - coupon__course_id=order_item_course_id if order_item_course_id else CourseKeyField.Empty, - order=item.order_id - ) - coupon_redemption.delete() - log.info( - u'Coupon "%s" redemption entry removed for user "%s" for order item "%s"', - coupon_redemption.coupon.code, - user, - item_id, - ) - except CouponRedemption.DoesNotExist: - log.debug(u'Code redemption does not exist for order item id=%s.', item_id) - - @login_required @enforce_shopping_cart_enabled def reset_code_redemption(request): @@ -276,7 +246,7 @@ def reset_code_redemption(request): """ cart = Order.get_cart_for_user(request.user) cart.reset_cart_items_prices() - CouponRedemption.delete_coupon_redemption(request.user, cart) + CouponRedemption.remove_coupon_redemption_from_cart(request.user, cart) return HttpResponse('reset')