diff --git a/lms/djangoapps/branding/api.py b/lms/djangoapps/branding/api.py index 119f3aa06b..b040774678 100644 --- a/lms/djangoapps/branding/api.py +++ b/lms/djangoapps/branding/api.py @@ -277,7 +277,7 @@ def _absolute_url(is_secure, url_path): def _absolute_url_staticfile(is_secure, name): - """Construct an absolute URL back to a static resource on the site. + """Construct an absolute URL to a static resource on the site. Arguments: is_secure (bool): If true, use HTTPS as the protocol. @@ -288,4 +288,13 @@ def _absolute_url_staticfile(is_secure, name): """ url_path = staticfiles_storage.url(name) + + # In production, the static files URL will be an absolute + # URL pointing to a CDN. If this happens, we can just + # return the URL. + if urlparse.urlparse(url_path).netloc: + return url_path + + # For local development, the returned URL will be relative, + # so we need to make it absolute. return _absolute_url(is_secure, url_path) diff --git a/lms/djangoapps/branding/tests/test_views.py b/lms/djangoapps/branding/tests/test_views.py index b11ab62518..41c7db22cb 100644 --- a/lms/djangoapps/branding/tests/test_views.py +++ b/lms/djangoapps/branding/tests/test_views.py @@ -102,6 +102,26 @@ class TestFooter(TestCase): # Copyright self.assertIn("copyright", json_data) + def test_absolute_urls_with_cdn(self): + self._set_feature_flag(True) + + # Ordinarily, we'd use `override_settings()` to override STATIC_URL, + # which is what the staticfiles storage backend is using to construct the URL. + # Unfortunately, other parts of the system are caching this value on module + # load, which can cause other tests to fail. To ensure that this change + # doesn't affect other tests, we patch the `url()` method directly instead. + cdn_url = "http://cdn.example.com/static/image.png" + with mock.patch('branding.api.staticfiles_storage.url', return_value=cdn_url): + resp = self._get_footer() + + self.assertEqual(resp.status_code, 200) + json_data = json.loads(resp.content) + + self.assertEqual(json_data["logo_image"], cdn_url) + + for link in json_data["mobile_links"]: + self.assertEqual(link["url"], cdn_url) + @ddt.data( ("en", "registered trademarks"), ("eo", u"régïstéréd trädémärks"), # Dummy language string diff --git a/lms/djangoapps/shoppingcart/models.py b/lms/djangoapps/shoppingcart/models.py index b462d268c7..8bf707890d 100644 --- a/lms/djangoapps/shoppingcart/models.py +++ b/lms/djangoapps/shoppingcart/models.py @@ -1185,10 +1185,15 @@ class RegistrationCodeRedemption(models.Model): Returns RegistrationCodeRedemption object if registration code has been used during the course enrollment else Returns None. """ - try: - return cls.objects.get(course_enrollment=course_enrollment) - except RegistrationCodeRedemption.DoesNotExist: - return None + # theoretically there could be more than one (e.g. someone self-unenrolls + # then re-enrolls with a different regcode) + reg_codes = cls.objects.filter(course_enrollment=course_enrollment).order_by('-redeemed_at') + if reg_codes: + # return the first one. In all normal use cases of registration codes + # the user will only have one + return reg_codes[0] + + return None @classmethod def is_registration_code_redeemed(cls, course_reg_code): diff --git a/lms/djangoapps/shoppingcart/tests/test_models.py b/lms/djangoapps/shoppingcart/tests/test_models.py index b3581e0832..447e9bf29f 100644 --- a/lms/djangoapps/shoppingcart/tests/test_models.py +++ b/lms/djangoapps/shoppingcart/tests/test_models.py @@ -27,7 +27,8 @@ from shoppingcart.models import ( Order, OrderItem, CertificateItem, InvalidCartItem, CourseRegistrationCode, PaidCourseRegistration, CourseRegCodeItem, Donation, OrderItemSubclassPK, - Invoice, CourseRegistrationCodeInvoiceItem, InvoiceTransaction, InvoiceHistory + Invoice, CourseRegistrationCodeInvoiceItem, InvoiceTransaction, InvoiceHistory, + RegistrationCodeRedemption ) from student.tests.factories import UserFactory from student.models import CourseEnrollment @@ -470,6 +471,60 @@ class PaidCourseRegistrationTest(ModuleStoreTestCase): # check that the registration codes are generated against the order self.assertEqual(len(CourseRegistrationCode.objects.filter(order=self.cart)), item.qty) + def test_regcode_redemptions(self): + """ + Asserts the data model around RegistrationCodeRedemption + """ + self.cart.order_type = 'business' + self.cart.save() + CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2) + self.cart.purchase() + + reg_code = CourseRegistrationCode.objects.filter(order=self.cart)[0] + + enrollment = CourseEnrollment.enroll(self.user, self.course_key) + + redemption = RegistrationCodeRedemption( + registration_code=reg_code, + redeemed_by=self.user, + course_enrollment=enrollment + ) + redemption.save() + + test_redemption = RegistrationCodeRedemption.registration_code_used_for_enrollment(enrollment) + + self.assertEqual(test_redemption.id, redemption.id) # pylint: disable=no-member + + def test_regcode_multi_redemptions(self): + """ + Asserts the data model around RegistrationCodeRedemption and + what happens when we do multiple redemptions by same user + """ + self.cart.order_type = 'business' + self.cart.save() + CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2) + self.cart.purchase() + + reg_codes = CourseRegistrationCode.objects.filter(order=self.cart) + + self.assertEqual(len(reg_codes), 2) + + enrollment = CourseEnrollment.enroll(self.user, self.course_key) + + ids = [] + for reg_code in reg_codes: + redemption = RegistrationCodeRedemption( + registration_code=reg_code, + redeemed_by=self.user, + course_enrollment=enrollment + ) + redemption.save() + ids.append(redemption.id) # pylint: disable=no-member + + test_redemption = RegistrationCodeRedemption.registration_code_used_for_enrollment(enrollment) + + self.assertIn(test_redemption.id, ids) # pylint: disable=no-member + def test_add_with_default_mode(self): """ Tests add_to_cart where the mode specified in the argument is NOT in the database diff --git a/lms/djangoapps/verify_student/admin.py b/lms/djangoapps/verify_student/admin.py index 73d878a786..241bcea17a 100644 --- a/lms/djangoapps/verify_student/admin.py +++ b/lms/djangoapps/verify_student/admin.py @@ -14,6 +14,7 @@ class SoftwareSecurePhotoVerificationAdmin(admin.ModelAdmin): """ list_display = ('id', 'user', 'status', 'receipt_id', 'submitted_at', 'updated_at') exclude = ('window',) # TODO: Remove after deleting this field from the model. + raw_id_fields = ('user', 'reviewing_user') search_fields = ( 'receipt_id', ) @@ -26,6 +27,7 @@ class VerificationStatusAdmin(admin.ModelAdmin): list_display = ('timestamp', 'user', 'status', 'checkpoint') readonly_fields = () search_fields = ('checkpoint__checkpoint_location', 'user__username') + raw_id_fields = ('user',) def get_readonly_fields(self, request, obj=None): """When editing an existing record, all fields should be read-only. @@ -47,6 +49,7 @@ class VerificationStatusAdmin(admin.ModelAdmin): class SkippedReverificationAdmin(admin.ModelAdmin): """Admin for the SkippedReverification table. """ list_display = ('created_at', 'user', 'course_id', 'checkpoint') + raw_id_fields = ('user',) readonly_fields = ('user', 'course_id') search_fields = ('user__username', 'course_id', 'checkpoint__checkpoint_location')