From 0b2840ee6f63bf15b37f25ffffd1b02ab3cbd39a Mon Sep 17 00:00:00 2001 From: Renzo Lucioni Date: Mon, 8 Sep 2014 14:07:01 -0400 Subject: [PATCH 1/5] Skip audit enrollment if user is in the experimental branch --- .../course_modes/tests/test_views.py | 28 +++++++++++++++++++ common/djangoapps/course_modes/views.py | 4 ++- common/templates/course_modes/choose.html | 3 +- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/common/djangoapps/course_modes/tests/test_views.py b/common/djangoapps/course_modes/tests/test_views.py index 07bbeb7218..72320ebca8 100644 --- a/common/djangoapps/course_modes/tests/test_views.py +++ b/common/djangoapps/course_modes/tests/test_views.py @@ -12,6 +12,7 @@ from xmodule.modulestore.tests.django_utils import ( from xmodule.modulestore.tests.factories import CourseFactory from course_modes.tests.factories import CourseModeFactory from student.tests.factories import CourseEnrollmentFactory, UserFactory +from student.models import CourseEnrollment # Since we don't need any XML course fixtures, use a modulestore configuration @@ -221,3 +222,30 @@ class CourseModeViewTest(ModuleStoreTestCase): actual_amount = self.client.session['donation_for_course'][unicode(self.course.id)] expected_amount = decimal.Decimal(self.POST_PARAMS_FOR_COURSE_MODE['verified']['contribution']) self.assertEqual(actual_amount, expected_amount) + + def test_enrollment_skipped_if_autoreg(self): + # TODO (ECOM-16): Remove once we complete the auto-reg AB test. + session = self.client.session + session['auto_register'] = True + session.save() + + # Create the course modes + for mode in ('audit', 'honor', 'verified'): + CourseModeFactory(mode_slug=mode, course_id=self.course.id) + + # Now enroll in the course + CourseEnrollmentFactory( + user=self.user, + is_active=True, + mode="honor", + course_id=unicode(self.course.id), + ) + + # Choose the mode (POST request) + choose_track_url = reverse('course_modes_choose', args=[unicode(self.course.id)]) + self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE['audit']) + + # Verify that enrollment mode is still honor + mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) + self.assertEqual(mode, "honor") + self.assertEqual(is_active, True) diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index 38525e48cf..249aff9568 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -141,7 +141,9 @@ class ChooseModeView(View): return HttpResponseBadRequest(_("Enrollment mode not supported")) if requested_mode in ("audit", "honor"): - CourseEnrollment.enroll(user, course_key, requested_mode) + # TODO (ECOM-16): Skip enrollment if we're in the experimental branch + if not request.session.get('auto_register', False): + CourseEnrollment.enroll(user, course_key, requested_mode) return redirect('dashboard') mode_info = allowed_modes[requested_mode] diff --git a/common/templates/course_modes/choose.html b/common/templates/course_modes/choose.html index 26fe66da77..0d4c4993f6 100644 --- a/common/templates/course_modes/choose.html +++ b/common/templates/course_modes/choose.html @@ -275,7 +275,8 @@ $(document).ready(function() { % endif %if not upgrade: - +## TODO (ECOM-16): For the duration of the experiment, audit track enrollment is skipped +## to keep the user on the honor track % if "audit" in modes: ${_("or")} From 12d5c9b281245a1cf7fab462deaa1b09489548b0 Mon Sep 17 00:00:00 2001 From: Renzo Lucioni Date: Mon, 8 Sep 2014 17:33:02 -0400 Subject: [PATCH 2/5] Do not call upper on a profile country code of None --- common/djangoapps/embargo/middleware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/djangoapps/embargo/middleware.py b/common/djangoapps/embargo/middleware.py index e7c199cc6e..99c911c320 100644 --- a/common/djangoapps/embargo/middleware.py +++ b/common/djangoapps/embargo/middleware.py @@ -166,7 +166,7 @@ class EmbargoMiddleware(object): profile_country = cache.get(cache_key) if profile_country is None: profile = getattr(user, 'profile', None) - if profile is not None and profile.country is not None: + if profile is not None and profile.country.code is not None: profile_country = profile.country.code.upper() else: profile_country = "" From 92a745b638de28ad5246f0d18d69d0c9689f97e9 Mon Sep 17 00:00:00 2001 From: Usman Khalid <2200617@gmail.com> Date: Mon, 8 Sep 2014 19:45:52 +0500 Subject: [PATCH 3/5] CodeMirror should use monospace fonts. TNL-197 --- cms/static/sass/elements/_forms.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/cms/static/sass/elements/_forms.scss b/cms/static/sass/elements/_forms.scss index d0152bdd40..9ee9ee3919 100644 --- a/cms/static/sass/elements/_forms.scss +++ b/cms/static/sass/elements/_forms.scss @@ -442,6 +442,7 @@ code { .CodeMirror { @extend %t-copy-sub1; background: #fff; + font-family: $f-monospace; } .text-editor { From 6d914e2b0a5f5a6605780dffaba964791c2546e2 Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Tue, 2 Sep 2014 09:57:08 -0400 Subject: [PATCH 4/5] AWS settings for XBlocks to have filesystem-like storage Upgrades to latest pyfs to support auth not through environment variables --- cms/envs/aws.py | 10 ++++++++++ lms/envs/aws.py | 10 ++++++++++ requirements/edx/base.txt | 2 +- requirements/edx/edx-private.txt | 2 +- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/cms/envs/aws.py b/cms/envs/aws.py index 1027612035..e7e32043d8 100644 --- a/cms/envs/aws.py +++ b/cms/envs/aws.py @@ -213,6 +213,16 @@ if FEATURES.get('AUTH_USE_CAS'): with open(CONFIG_ROOT / CONFIG_PREFIX + "auth.json") as auth_file: AUTH_TOKENS = json.load(auth_file) +############### XBlock filesystem field config ########## +if 'XBLOCK-FS-STORAGE-BUCKET' in ENV_TOKENS: + DJFS = { + 'type' : 's3fs', + 'bucket' : ENV_TOKENS.get('XBLOCK-FS-STORAGE-BUCKET', None), + 'prefix' : ENV_TOKENS.get('XBLOCK-FS-STORAGE-PREFIX', '/xblock-storage/') + } + DJFS['aws_access_key_id'] = AUTH_TOKENS.get('AWS_ACCESS_KEY_ID', None) + DJFS['aws_secret_access_key'] = AUTH_TOKENS.get('AWS_SECRET_ACCESS_KEY', None) + EMAIL_HOST_USER = AUTH_TOKENS.get('EMAIL_HOST_USER', EMAIL_HOST_USER) EMAIL_HOST_PASSWORD = AUTH_TOKENS.get('EMAIL_HOST_PASSWORD', EMAIL_HOST_PASSWORD) diff --git a/lms/envs/aws.py b/lms/envs/aws.py index 9deb623aa6..eb4f24ce75 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -298,6 +298,16 @@ VIDEO_CDN_URL = ENV_TOKENS.get('VIDEO_CDN_URL', {}) with open(CONFIG_ROOT / CONFIG_PREFIX + "auth.json") as auth_file: AUTH_TOKENS = json.load(auth_file) +############### XBlock filesystem field config ########## +if 'XBLOCK-FS-STORAGE-BUCKET' in ENV_TOKENS: + DJFS = { + 'type' : 's3fs', + 'bucket' : ENV_TOKENS.get('XBLOCK-FS-STORAGE-BUCKET', None), + 'prefix' : ENV_TOKENS.get('XBLOCK-FS-STORAGE-PREFIX', '/xblock-storage/') + } + DJFS['aws_access_key_id'] = AUTH_TOKENS.get('AWS_ACCESS_KEY_ID', None) + DJFS['aws_secret_access_key'] = AUTH_TOKENS.get('AWS_SECRET_ACCESS_KEY', None) + ############### Module Store Items ########## HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS = ENV_TOKENS.get('HOSTNAME_MODULESTORE_DEFAULT_MAPPINGS', {}) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index ee6dcba847..67e53509b7 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -139,4 +139,4 @@ git+https://github.com/mitocw/django-cas.git # edX packages edx-submissions==0.0.7 --e git+https://github.com/pmitros/django-pyfs.git@514607d78535fd80bfd23184cd292ee5799b500d#egg=djpyfs +-e git+https://github.com/pmitros/django-pyfs.git@36f64c0f1f458ce7b9ecb2347667b202d13a8317#egg=djpyfs diff --git a/requirements/edx/edx-private.txt b/requirements/edx/edx-private.txt index 9688af89e0..004ad91808 100644 --- a/requirements/edx/edx-private.txt +++ b/requirements/edx/edx-private.txt @@ -12,7 +12,7 @@ -e git+https://github.com/pmitros/DoneXBlock.git@1ce0ac14d9f3df3083b951262ec82e84b58d16d1#egg=done-xblock -e git+https://github.com/pmitros/AudioXBlock.git@1fbf19cc21613aead62799469e1593adb037fdd9#egg=audio-xblock -e git+https://github.com/pmitros/AnimationXBlock.git@d2b551bb8f49a138088e10298576102164145b87#egg=animation-xblock --e git+https://github.com/pmitros/ProfileXBlock.git@1ede6341c96c7d4a8e5942e7085e859de762f128#egg=profile-xblock +-e git+https://github.com/pmitros/ProfileXBlock.git@4aeaa24aa2bc7d9cb2d2bb60d6f05def3b856be0#egg=profile-xblock # This XBlock shows a list of recommendations. # It is an R&D prototype, intended for roll-out one location in one course. From 5759a3fd29b4248a8e615a9f78f2c1e16f9a394f Mon Sep 17 00:00:00 2001 From: Will Daly Date: Wed, 10 Sep 2014 10:47:38 -0400 Subject: [PATCH 5/5] Fix a unicode error in CyberSource2 --- .../shoppingcart/processors/CyberSource2.py | 2 +- .../processors/tests/test_CyberSource2.py | 21 +++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lms/djangoapps/shoppingcart/processors/CyberSource2.py b/lms/djangoapps/shoppingcart/processors/CyberSource2.py index 2e2734ac4d..dffe5c1ed2 100644 --- a/lms/djangoapps/shoppingcart/processors/CyberSource2.py +++ b/lms/djangoapps/shoppingcart/processors/CyberSource2.py @@ -101,7 +101,7 @@ def processor_hash(value): """ secret_key = get_processor_config().get('SECRET_KEY', '') - hash_obj = hmac.new(secret_key, value, sha256) + hash_obj = hmac.new(secret_key.encode('utf-8'), value.encode('utf-8'), sha256) return binascii.b2a_base64(hash_obj.digest())[:-1] # last character is a '\n', which we don't want diff --git a/lms/djangoapps/shoppingcart/processors/tests/test_CyberSource2.py b/lms/djangoapps/shoppingcart/processors/tests/test_CyberSource2.py index 9178d51807..a1c9b619c5 100644 --- a/lms/djangoapps/shoppingcart/processors/tests/test_CyberSource2.py +++ b/lms/djangoapps/shoppingcart/processors/tests/test_CyberSource2.py @@ -205,9 +205,21 @@ class CyberSource2Test(TestCase): self.assertFalse(result['success']) self.assertIn(u"did not return a required parameter", result['error_html']) + @patch.object(OrderItem, 'purchased_callback') + def test_sign_then_verify_unicode(self, purchased_callback): + params = self._signed_callback_params( + self.order.id, self.COST, self.COST, + first_name=u'\u2699' + ) + + # Verify that this executes without a unicode error + result = process_postpay_callback(params) + self.assertTrue(result['success']) + def _signed_callback_params( self, order_id, order_amount, paid_amount, - accepted=True, signature=None, card_number='xxxxxxxxxxxx1111' + accepted=True, signature=None, card_number='xxxxxxxxxxxx1111', + first_name='John' ): """ Construct parameters that could be returned from CyberSource @@ -227,6 +239,7 @@ class CyberSource2Test(TestCase): accepted (bool): Whether the payment was accepted or rejected. signature (string): If provided, use this value instead of calculating the signature. card_numer (string): If provided, use this value instead of the default credit card number. + first_name (string): If provided, the first name of the user. Returns: dict @@ -306,7 +319,7 @@ class CyberSource2Test(TestCase): "req_transaction_uuid": "ddd9935b82dd403f9aa4ba6ecf021b1f", "auth_trans_ref_no": "85080648RYI23S6I", "req_bill_to_surname": "Doe", - "req_bill_to_forename": "John", + "req_bill_to_forename": first_name, "req_bill_to_email": "john@example.com", "req_override_custom_receipt_page": "http://localhost:8000/shoppingcart/postpay_callback/", "req_access_key": "abcd12345", @@ -335,8 +348,8 @@ class CyberSource2Test(TestCase): """ return processor_hash( ",".join([ - "{0}={1}".format(signed_field, params[signed_field]) + u"{0}={1}".format(signed_field, params[signed_field]) for signed_field - in params['signed_field_names'].split(",") + in params['signed_field_names'].split(u",") ]) )