diff --git a/common/djangoapps/student/tests/test_microsite.py b/common/djangoapps/student/tests/test_microsite.py index b447ad3a13..d76aa9a72a 100644 --- a/common/djangoapps/student/tests/test_microsite.py +++ b/common/djangoapps/student/tests/test_microsite.py @@ -4,8 +4,24 @@ Test for User Creation from Micro-Sites from django.test import TestCase from student.models import UserSignupSource import mock +import json from django.core.urlresolvers import reverse +from django.contrib.auth.models import User +FAKE_MICROSITE = { + "SITE_NAME": "openedx.localhost", + "REGISTRATION_EXTRA_FIELDS": { + "address1": "required", + "city": "required", + "state": "required", + "country": "required", + "company": "required", + "title": "required" + }, + "extended_profile_fields": [ + "address1", "state", "company", "title" + ] +} def fake_site_name(name, default=None): # pylint: disable=W0613 """ @@ -14,8 +30,13 @@ def fake_site_name(name, default=None): # pylint: disable=W0613 if name == 'SITE_NAME': return 'openedx.localhost' else: - return None + return default +def fake_microsite_get_value(name, default=None): # pylint: disable=W0613 + """ + create a fake microsite site name + """ + return FAKE_MICROSITE.get(name, default) class TestMicrosite(TestCase): """Test for Account Creation from a white labeled Micro-Sites""" @@ -30,6 +51,14 @@ class TestMicrosite(TestCase): "honor_code": "true", "terms_of_service": "true", } + self.extended_params = dict(self.params.items() + { + "address1": "foo", + "city": "foo", + "state": "foo", + "country": "foo", + "company": "foo", + "title": "foo" + }.items()) @mock.patch("microsite_configuration.microsite.get_value", fake_site_name) def test_user_signup_source(self): @@ -49,3 +78,27 @@ class TestMicrosite(TestCase): response = self.client.post(self.url, self.params) self.assertEqual(response.status_code, 200) self.assertEqual(len(UserSignupSource.objects.filter(site='openedx.localhost')), 0) + + @mock.patch("microsite_configuration.microsite.get_value", fake_microsite_get_value) + def test_user_signup_missing_enhanced_profile(self): + """ + test to create a user form the microsite but don't provide any of the microsite specific + profile information + """ + response = self.client.post(self.url, self.params) + self.assertEqual(response.status_code, 400) + + @mock.patch("microsite_configuration.microsite.get_value", fake_microsite_get_value) + def test_user_signup_including_enhanced_profile(self): + """ + test to create a user form the microsite but don't provide any of the microsite specific + profile information + """ + response = self.client.post(self.url, self.extended_params) + self.assertEqual(response.status_code, 200) + user = User.objects.get(username=self.username) + meta = json.loads(user.profile.meta) + self.assertEqual(meta['address1'], 'foo') + self.assertEqual(meta['state'], 'foo') + self.assertEqual(meta['company'], 'foo') + self.assertEqual(meta['title'], 'foo') diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index c330ff3632..28cc714687 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -6,6 +6,7 @@ import logging import re import uuid import time +import json from collections import defaultdict from pytz import UTC @@ -1039,7 +1040,7 @@ def user_signup_handler(sender, **kwargs): # pylint: disable=W0613 log.info(u'user {} originated from a white labeled "Microsite"'.format(kwargs['instance'].id)) -def _do_create_account(post_vars): +def _do_create_account(post_vars, extended_profile=None): """ Given cleaned post variables, create the User and UserProfile objects, as well as the registration for this user. @@ -1089,6 +1090,10 @@ def _do_create_account(post_vars): profile.country = post_vars.get('country') profile.goals = post_vars.get('goals') + # add any extended profile information in the denormalized 'meta' field in the profile + if extended_profile: + profile.meta = json.dumps(extended_profile) + try: profile.year_of_birth = int(post_vars['year_of_birth']) except (ValueError, KeyError): @@ -1115,7 +1120,12 @@ def create_account(request, post_override=None): # pylint: disable-msg=too-many js = {'success': False} # pylint: disable-msg=invalid-name post_vars = post_override if post_override else request.POST - extra_fields = getattr(settings, 'REGISTRATION_EXTRA_FIELDS', {}) + + # allow for microsites to define their own set of required/optional/hidden fields + extra_fields = microsite.get_value( + 'REGISTRATION_EXTRA_FIELDS', + getattr(settings, 'REGISTRATION_EXTRA_FIELDS', {}) + ) if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH') and pipeline.running(request): post_vars = dict(post_vars.items()) @@ -1188,7 +1198,7 @@ def create_account(request, post_override=None): # pylint: disable-msg=too-many else: min_length = 2 - if len(post_vars[field_name]) < min_length: + if field_name not in post_vars or len(post_vars[field_name]) < min_length: error_str = { 'username': _('Username must be minimum of two characters long'), 'email': _('A properly formatted e-mail is required'), @@ -1204,7 +1214,12 @@ def create_account(request, post_override=None): # pylint: disable-msg=too-many 'city': _('A city is required'), 'country': _('A country is required') } - js['value'] = error_str[field_name] + + if field_name in error_str: + js['value'] = error_str[field_name] + else: + js['value'] = _('You are missing one or more required fields') + js['field'] = field_name return JsonResponse(js, status=400) @@ -1248,10 +1263,22 @@ def create_account(request, post_override=None): # pylint: disable-msg=too-many js['field'] = 'password' return JsonResponse(js, status=400) + # allow microsites to define 'extended profile fields' which are + # captured on user signup (for example via an overriden registration.html) + # and then stored in the UserProfile + extended_profile_fields = microsite.get_value('extended_profile_fields', []) + extended_profile = None + + for field in extended_profile_fields: + if field in post_vars: + if not extended_profile: + extended_profile = {} + extended_profile[field] = post_vars[field] + # Ok, looks like everything is legit. Create the account. try: with transaction.commit_on_success(): - ret = _do_create_account(post_vars) + ret = _do_create_account(post_vars, extended_profile) except AccountValidationError as e: return JsonResponse({'success': False, 'value': e.message, 'field': e.field}, status=400) diff --git a/common/lib/xmodule/xmodule/modulestore/mongo/draft.py b/common/lib/xmodule/xmodule/modulestore/mongo/draft.py index 3af17a3442..63f960da5b 100644 --- a/common/lib/xmodule/xmodule/modulestore/mongo/draft.py +++ b/common/lib/xmodule/xmodule/modulestore/mongo/draft.py @@ -632,7 +632,12 @@ class DraftModuleStore(MongoModuleStore): """ Depth first publishing from the given location """ - item = self.get_item(item_location) + try: + # handle child does not exist w/o killing publish + item = self.get_item(item_location) + except ItemNotFoundError: + log.warning('Cannot find: %s', item_location) + return # publish the children first if item.has_children: diff --git a/lms/djangoapps/shoppingcart/context_processor.py b/lms/djangoapps/shoppingcart/context_processor.py index 95beb106d3..540ab6a2f3 100644 --- a/lms/djangoapps/shoppingcart/context_processor.py +++ b/lms/djangoapps/shoppingcart/context_processor.py @@ -19,5 +19,8 @@ def user_has_cart_context_processor(request): request.user.is_authenticated() and # user is logged in and settings.FEATURES.get('ENABLE_PAID_COURSE_REGISTRATION') and # settings enable paid course reg and settings.FEATURES.get('ENABLE_SHOPPING_CART') and # settings enable shopping cart and - shoppingcart.models.Order.user_cart_has_items(request.user) # user's cart has items + shoppingcart.models.Order.user_cart_has_items( + request.user, + shoppingcart.models.PaidCourseRegistration + ) # user's cart has PaidCourseRegistrations )} diff --git a/lms/djangoapps/shoppingcart/models.py b/lms/djangoapps/shoppingcart/models.py index 406257fd01..6839300575 100644 --- a/lms/djangoapps/shoppingcart/models.py +++ b/lms/djangoapps/shoppingcart/models.py @@ -87,15 +87,17 @@ class Order(models.Model): return cart_order @classmethod - def user_cart_has_items(cls, user): + def user_cart_has_items(cls, user, item_type=None): """ Returns true if the user (anonymous user ok) has a cart with items in it. (Which means it should be displayed. + If a item_type is passed in, then we check to see if the cart has at least one of + those types of OrderItems """ if not user.is_authenticated(): return False cart = cls.get_cart_for_user(user) - return cart.has_items() + return cart.has_items(item_type) @property def total_cost(self): @@ -105,11 +107,19 @@ class Order(models.Model): """ return sum(i.line_cost for i in self.orderitem_set.filter(status=self.status)) # pylint: disable=E1101 - def has_items(self): + def has_items(self, item_type=None): """ Does the cart have any items in it? + If an item_type is passed in then we check to see if there are any items of that class type """ - return self.orderitem_set.exists() # pylint: disable=E1101 + if not item_type: + return self.orderitem_set.exists() # pylint: disable=E1101 + else: + items = self.orderitem_set.all().select_subclasses() + for item in items: + if isinstance(item, item_type): + return True + return False def clear(self): """ diff --git a/lms/djangoapps/shoppingcart/tests/test_models.py b/lms/djangoapps/shoppingcart/tests/test_models.py index 5f4a911071..cdaeda1826 100644 --- a/lms/djangoapps/shoppingcart/tests/test_models.py +++ b/lms/djangoapps/shoppingcart/tests/test_models.py @@ -52,6 +52,21 @@ class OrderTest(ModuleStoreTestCase): item = OrderItem(order=cart, user=self.user) item.save() self.assertTrue(Order.user_cart_has_items(self.user)) + self.assertFalse(Order.user_cart_has_items(self.user, CertificateItem)) + self.assertFalse(Order.user_cart_has_items(self.user, PaidCourseRegistration)) + + def test_user_cart_has_paid_course_registration_items(self): + cart = Order.get_cart_for_user(self.user) + item = PaidCourseRegistration(order=cart, user=self.user) + item.save() + self.assertTrue(Order.user_cart_has_items(self.user, PaidCourseRegistration)) + self.assertFalse(Order.user_cart_has_items(self.user, CertificateItem)) + + def test_user_cart_has_certificate_items(self): + cart = Order.get_cart_for_user(self.user) + CertificateItem.add_to_order(cart, self.course_key, self.cost, 'honor') + self.assertTrue(Order.user_cart_has_items(self.user, CertificateItem)) + self.assertFalse(Order.user_cart_has_items(self.user, PaidCourseRegistration)) def test_cart_clear(self): cart = Order.get_cart_for_user(user=self.user) diff --git a/lms/templates/registration/password_reset_email.html b/lms/templates/registration/password_reset_email.html index 568a140686..e7bbc6cf35 100644 --- a/lms/templates/registration/password_reset_email.html +++ b/lms/templates/registration/password_reset_email.html @@ -1,5 +1,5 @@ {% load i18n %}{% load url from future %}{% autoescape off %} -{% blocktrans with site_name=site_name %}You're receiving this e-mail because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %} +{% blocktrans %}You're receiving this e-mail because you requested a password reset for your user account at edx.org.{% endblocktrans %} {% trans "Please go to the following page and choose a new password:" %} {% block reset_link %} @@ -10,6 +10,6 @@ http{% if is_secure %}s{% endif %}://{{domain}}{% url 'student.views.password_re {% trans "Thanks for using our site!" %} -{% blocktrans with platform_name=platform_name %}The {{ platform_name }} Team{% endblocktrans %} +{% blocktrans %}The edX Team{% endblocktrans %} {% endautoescape %}