diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py
index a8d0d8c7bf9d..7451417fde8c 100644
--- a/common/djangoapps/course_modes/views.py
+++ b/common/djangoapps/course_modes/views.py
@@ -34,7 +34,7 @@
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
from openedx.features.ucsd_features.ecommerce.utils import is_user_eligible_for_discount
-from openedx.features.ucsd_features.ecommerce.EcommerceClient import EcommerceRestAPIClient
+from openedx.features.ucsd_features.ecommerce.ecommerce_client import EcommerceRestAPIClient
from openedx.features.ucsd_features.ecommerce.constants import IS_DISCOUNT_AVAILABLE_QUERY_PARAM
from student.models import CourseEnrollment
from util.db import outer_atomic
diff --git a/common/djangoapps/student/views/dashboard.py b/common/djangoapps/student/views/dashboard.py
index ef36de537878..f311b8b40d9f 100644
--- a/common/djangoapps/student/views/dashboard.py
+++ b/common/djangoapps/student/views/dashboard.py
@@ -619,7 +619,6 @@ def student_dashboard(request):
# Display activation message
activate_account_message = ''
- activation_email_support_link = 'http://edtech.ucsd.edu/uc-san-diego-online-help'
if not user.is_active:
activate_account_message = Text(_(
"Check your {email_start}{email}{email_end} inbox for an account activation link from {platform_name}. "
diff --git a/common/djangoapps/student/views/management.py b/common/djangoapps/student/views/management.py
index 0b7f16ecaf4c..8b15834ffea3 100644
--- a/common/djangoapps/student/views/management.py
+++ b/common/djangoapps/student/views/management.py
@@ -64,7 +64,7 @@
from openedx.core.djangoapps.user_api.models import UserRetirementRequest
from openedx.core.djangoapps.user_api.preferences import api as preferences_api
from openedx.features.ucsd_features.ecommerce.utils import is_user_eligible_for_discount
-from openedx.features.ucsd_features.ecommerce.EcommerceClient import EcommerceRestAPIClient
+from openedx.features.ucsd_features.ecommerce.ecommerce_client import EcommerceRestAPIClient
from openedx.features.ucsd_features.ecommerce.tasks import assign_course_voucher_to_user
from openedx.features.ucsd_features.ecommerce.constants import IS_DISCOUNT_AVAILABLE_QUERY_PARAM
from openedx.features.ucsd_features.utils import add_to_ga_events_cookie
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 7cbe95bf6048..4bdcf9b52e31 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -3492,160 +3492,4 @@ def _make_locale_paths(settings):
FEATURES['DISABLE_REFUND_FAILURE_NOTIFICATION'] = True
FEATURES['ENABLE_GEOGRAPHIC_DISCOUNTS'] = True
-COUNTRIES_ELIGIBLE_FOR_DISCOUNTS = {
- 'AF': 'Afghanistan',
- 'AL': 'Albania',
- 'DZ': 'Algeria',
- 'AO': 'Angola',
- 'AG': 'Antigua and Barbuda',
- 'AR': 'Argentina',
- 'AM': 'Armenia',
- 'AW': 'Aruba',
- 'AZ': 'Azerbaijan',
- 'BS': 'Bahamaz',
- 'BH': 'Bahrain',
- 'BD': 'Bangladesh',
- 'BB': 'Barbados',
- 'BY': 'Belarus',
- 'BZ': 'Belize',
- 'BJ': 'Benin',
- 'BT': 'Bhutan',
- 'BO': 'Bolivia',
- 'BA': 'Bosnia and Herzegovina',
- 'BW': 'Botswana',
- 'BR': 'Brazil',
- 'BN': 'Brunei Darussalam',
- 'BG': 'Bulgaria',
- 'BF': 'Burkina Faso',
- 'BI': 'Burundi',
- 'CV': 'Cabo Verde',
- 'KH': 'Cambodia',
- 'CM': 'Cameroon',
- 'CF': 'Central African Republic',
- 'TD': 'Chad',
- 'CL': 'Chile',
- 'CN': 'China',
- 'CO': 'Colombia',
- 'KM': 'Comoros',
- 'CD': 'Democratic Republic of the Congo',
- 'CD': 'Republic of Congo',
- 'CR': 'Costa Rica',
- 'CI': 'Côte d\'Ivoire',
- 'HR': 'Croatia',
- 'DJ': 'Djibouti',
- 'DM': 'Dominica',
- 'DO': 'Dominican Republic',
- 'EC': 'Ecuador',
- 'EG': 'Egypt',
- 'SV': 'El Salvador',
- 'GQ': 'Equatorial Guinea',
- 'ER': 'Eritrea',
- 'SZ': 'Eswatini',
- 'ET': 'Ethiopia',
- 'FJ': 'Fiji',
- 'GA': 'Gabon',
- 'GM': 'Gambia',
- 'GE': 'Georgia',
- 'GH': 'Ghana',
- 'GD': 'Grenada',
- 'GT': 'Guatemala',
- 'GN': 'Guinea',
- 'GW': 'Guinea-Bissau',
- 'GY': 'Guyana',
- 'HT': 'Haiti',
- 'HN': 'Honduras',
- 'HU': 'Hungary',
- 'IN': 'India',
- 'ID': 'Indonesia',
- 'IR': 'Iran',
- 'IQ': 'Iraq',
- 'JM': 'Jamaica',
- 'JO': 'Jordan',
- 'KZ': 'Kazakhstan',
- 'KE': 'Kenya',
- 'KI': 'Kiribati',
- 'RS': 'Kosovo',
- 'KW': 'Kuwait',
- 'KG': 'Kyrgyzstan',
- 'LA': 'Lao People\'s Democratic Republic',
- 'LB': 'Lebanon',
- 'LS': 'Lesotho',
- 'LR': 'Liberia',
- 'LY': 'Libya',
- 'MK': 'Macedonia',
- 'MG': 'Madagascar',
- 'MW': 'Malawi',
- 'MY': 'Malaysia',
- 'MV': 'Maldives',
- 'ML': 'Mali',
- 'MH': 'Marshall Islands',
- 'MR': 'Mauritania',
- 'MU': 'Mauritius',
- 'MX': 'Mexico',
- 'FM': 'Micronesia',
- 'MD': 'Moldova',
- 'MN': 'Mongolia',
- 'ME': 'Montenegro',
- 'MA': 'Morocco',
- 'MZ': 'Mozambique',
- 'MM': 'Myanmar',
- 'NA': 'Namibia',
- 'NR': 'Nauru',
- 'NP': 'Nepal',
- 'NI': 'Nicaragua',
- 'NE': 'Niger',
- 'NG': 'Nigeria',
- 'OM': 'Oman',
- 'PK': 'Pakistan',
- 'PW': 'Palau',
- 'PA': 'Panama',
- 'PG': 'Papua New Guinea',
- 'PY': 'Paraguay',
- 'PE': 'Peru',
- 'PH': 'Philippines',
- 'PL': 'Poland',
- 'QA': 'Qatar',
- 'RO': 'Romania',
- 'RU': 'Russian Federation',
- 'RW': 'Rwanda',
- 'WS': 'Samoa',
- 'ST': 'São Tomé and Príncipe',
- 'SA': 'Saudi Arabia',
- 'SN': 'Senegal',
- 'RS': 'Serbia',
- 'SC': 'Seychelles',
- 'SL': 'Sierra Leone',
- 'SB': 'Solomon Islands',
- 'SO': 'Somalia',
- 'ZA': 'South Africa',
- 'SS': 'South Sudan',
- 'LK': 'Sri Lanka',
- 'KN': 'Saint Kitts and Nevis',
- 'LC': 'Saint Lucia',
- 'VC': 'Saint Vincent and the Grenadines',
- 'SD': 'Sudan',
- 'SR': 'Suriname',
- 'SY': 'Syrian Arab Republic',
- 'TJ': 'Tajikistan',
- 'TZ': 'Tanzania',
- 'TH': 'Thailand',
- 'TL': 'Timor-Leste',
- 'TG': 'Togo',
- 'TO': 'Tonga',
- 'TT': 'Trinidad and Tobago',
- 'TN': 'Tunisia',
- 'TR': 'Turkey',
- 'TM': 'Turkmenistan',
- 'TV': 'Tuvalu',
- 'UG': 'Uganda',
- 'UA': 'Ukraine',
- 'AE': 'United Arab Emirates',
- 'UY': 'Uruguay',
- 'UZ': 'Uzbekistan',
- 'VU': 'Vanuatu',
- 'VE': 'Venezuela',
- 'VN': 'Viet nam',
- 'YE': 'Yemen',
- 'ZM': 'Zambia',
- 'ZW': 'Zimbabwe'
-}
+COUNTRIES_ELIGIBLE_FOR_DISCOUNTS = {}
diff --git a/lms/envs/test.py b/lms/envs/test.py
index 4127dff22749..8b5ea3705e9a 100644
--- a/lms/envs/test.py
+++ b/lms/envs/test.py
@@ -629,3 +629,4 @@
FEATURES['DISABLE_REFUND_FAILURE_NOTIFICATION'] = False
FEATURES['AUTOMATIC_PERMANENT_ACCOUNT_VERIFICATION'] = False
FEATURES['ENABLE_GEOGRAPHIC_DISCOUNTS'] = False
+COUNTRIES_ELIGIBLE_FOR_DISCOUNTS = {}
diff --git a/lms/static/js/student_account/components/StudentAccountDeletion.jsx b/lms/static/js/student_account/components/StudentAccountDeletion.jsx
index 216ff226f1ad..0f42869fc7d0 100644
--- a/lms/static/js/student_account/components/StudentAccountDeletion.jsx
+++ b/lms/static/js/student_account/components/StudentAccountDeletion.jsx
@@ -67,7 +67,7 @@ export class StudentAccountDeletion extends React.Component {
const changeAcctInfoText = StringUtils.interpolate(
gettext('{htmlStart}Want to change your email, name, or password instead?{htmlEnd}'),
{
- htmlStart: '',
+ htmlStart: '',
htmlEnd: '',
},
);
diff --git a/lms/static/js/student_account/views/RegisterView.js b/lms/static/js/student_account/views/RegisterView.js
index 9daa4720c710..db8063432cd5 100644
--- a/lms/static/js/student_account/views/RegisterView.js
+++ b/lms/static/js/student_account/views/RegisterView.js
@@ -110,18 +110,7 @@
field.errorMessages = this.escapeStrings(field.errorMessages);
}
- if (field.required) {
- requiredFields.push(field);
- } else {
- if (field.type !== 'hidden') {
- // For the purporse of displaying the optional field toggle,
- // the form should be considered to have optional fields
- // only if all of the optional fields are being rendering as
- // input elements that are visible on the page.
- this.hasOptionalFields = true;
- }
- optionalFields.push(field);
- }
+ requiredFields.push(field);
}
html = this.renderFields(requiredFields, 'required-fields');
diff --git a/openedx/features/ucsd_features/additional_registration_fields.py b/openedx/features/ucsd_features/additional_registration_fields.py
new file mode 100644
index 000000000000..74fcee098c9e
--- /dev/null
+++ b/openedx/features/ucsd_features/additional_registration_fields.py
@@ -0,0 +1,130 @@
+import csv
+from django.conf import settings
+from django.contrib import admin
+from django.db import models
+from django.forms import ModelForm
+from django.http import HttpResponse
+
+# Backwards compatible settings.AUTH_USER_MODEL
+USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
+
+
+# Models
+class AdditionalRegistrationFields(models.Model):
+ """
+ This model contains two extra fields that will be saved when a user registers.
+ The form that wraps this model is in the forms.py file.
+ """
+ user = models.OneToOneField(USER_MODEL, null=True)
+
+ gender_nb = models.CharField(blank=False, max_length=32, verbose_name=b'Gender', choices=[
+ (b'male', b'Male'),
+ (b'female', b'Female'),
+ (b'nonbinary', b'Non-Binary'),
+
+ (b'decline', b'Decline to State'),
+ ])
+
+ ethnicity = models.CharField(blank=False, max_length=128, verbose_name=b'Ethnicity', choices=[
+ (b'african-american-and-black', b'African American and Black'),
+ (b'american-indian-alaska-native', b'American Indian / Alaska Native'),
+ (b'asian', b'Asian'),
+ (b'hispanic-latinx', b'Hispanic / Latinx'),
+ (b'native-hawaiian-and-pacific-islander', b'Native Hawaiian and Pacific Islander'),
+ (b'southwest-asia-north-african', b'Southwest Asia / North African'),
+ (b'white', b'White'),
+
+ (b'decline', b'Decline to State'),
+ ])
+
+ age = models.CharField(blank=False, max_length=32, verbose_name=b'Age', choices=[
+ (b'13-17', b'13 - 17 years old'),
+ (b'18-22', b'18 - 22 years old'),
+ (b'23-29', b'23 - 29 years old'),
+ (b'30-49', b'30 - 49 years old'),
+ (b'50-plus', b'50+ years old'),
+
+ (b'decline', b'Decline to State'),
+ ])
+
+ education = models.CharField(blank=False, max_length=64, verbose_name=b'Highest level of education completed', choices=[
+ (b'graduate', b'Graduate'),
+ (b'undergraduate', b'Undergraduate'),
+ (b'up-to-high-school', b'Up to High School'),
+
+ (b'decline', b'Decline to State'),
+ ])
+
+ howheard = models.CharField(blank=False, max_length=64, verbose_name=b'How did you hear about UC San Diego Online', choices=[
+ (b'social-media', b'Social Media'),
+ (b'email', b'Email'),
+ (b'word-of-mouth', b'Word-of-Mouth'),
+ (b'print-advertisement', b'Print Advertisement'),
+ (b'other', b'Other'),
+ ])
+
+ class Meta:
+ verbose_name = "Additional Registration Information"
+ verbose_name_plural = "Additional Registration Information"
+
+
+# Form
+class AdditionalRegistrationFieldsForm(ModelForm):
+ """
+ The fields on this form are derived from the AdditionalRegistrationFields model in models.py.
+ """
+ def __init__(self, *args, **kwargs):
+ super(AdditionalRegistrationFieldsForm, self).__init__(*args, **kwargs)
+
+ class Meta(object):
+ model = AdditionalRegistrationFields
+ fields = ('gender_nb', 'ethnicity', 'age', 'education', 'howheard')
+
+
+# Admin Customization
+class ExportCsvMixin:
+ def export_as_csv(self, request, queryset):
+ meta = self.model._meta
+
+ response = HttpResponse(content_type='text/csv')
+ response['Content-Disposition'] = 'attachment; filename={}.csv'.format(meta)
+ writer = csv.writer(response)
+
+ field_map = {field.name: field for field in meta.fields}
+
+ header = []
+ for field in self.list_display:
+ if field in field_map:
+ header.append(field_map[field].verbose_name)
+ else:
+ header.append(field.title())
+
+ writer.writerow(header)
+ for obj in queryset:
+ row = []
+ for field in self.list_display:
+ if field in field_map:
+ row.append(getattr(obj, field))
+ else:
+ row.append(getattr(self, field)(obj))
+
+ writer.writerow(row)
+
+ return response
+
+ export_as_csv.short_description = "Export Selected"
+
+
+@admin.register(AdditionalRegistrationFields)
+class AdditionalRegistrationFieldsAdmin(admin.ModelAdmin, ExportCsvMixin):
+ list_display = ('user_id', 'username', 'email', 'gender_nb', 'ethnicity', 'age', 'education', 'howheard')
+ actions = ["export_as_csv"]
+
+ def user_id(self, x):
+ return x.user.id
+
+ def username(self, x):
+ return x.user.username
+
+ def email(self, x):
+ return x.user.email
diff --git a/openedx/features/ucsd_features/apps.py b/openedx/features/ucsd_features/apps.py
index 78213ffd662f..05cc5da8148d 100644
--- a/openedx/features/ucsd_features/apps.py
+++ b/openedx/features/ucsd_features/apps.py
@@ -7,3 +7,4 @@ class UcsdFeatures(AppConfig):
def ready(self):
super(UcsdFeatures, self).ready()
from .signals import *
+ from .additional_registration_fields import *
diff --git a/openedx/features/ucsd_features/ecommerce/constants.py b/openedx/features/ucsd_features/ecommerce/constants.py
index 9043d3363867..0ea7edb09707 100644
--- a/openedx/features/ucsd_features/ecommerce/constants.py
+++ b/openedx/features/ucsd_features/ecommerce/constants.py
@@ -1 +1,5 @@
+"""
+This file contains constants used in the "ecommerce" module of "UCSDFeatures" app
+"""
+
IS_DISCOUNT_AVAILABLE_QUERY_PARAM = 'is_discount_available'
diff --git a/openedx/features/ucsd_features/ecommerce/EcommerceClient.py b/openedx/features/ucsd_features/ecommerce/ecommerce_client.py
similarity index 58%
rename from openedx/features/ucsd_features/ecommerce/EcommerceClient.py
rename to openedx/features/ucsd_features/ecommerce/ecommerce_client.py
index e3346eea6733..5964b1026fc4 100644
--- a/openedx/features/ucsd_features/ecommerce/EcommerceClient.py
+++ b/openedx/features/ucsd_features/ecommerce/ecommerce_client.py
@@ -10,10 +10,34 @@
class EcommerceRestAPIClient:
+ """
+ Client that communicates with the Ecommerce service
+ """
+
def __init__(self, user, session=None):
+ """
+ Initialize the ecommerce client for the current user and session
+
+ Arguments:
+ user: User object
+ session: Current user's session
+ """
+
self.client = ecommerce_api_client(user, session)
def assign_voucher_to_user(self, user, course_key, course_sku=None):
+ """
+ Sends request to Ecommerce to assign a voucher to the user for the course.
+
+ Arguments:
+ user: User object
+ course_key (str): course_key of the course for which the voucher is to be assigned
+ course_sku (str): SKU of the course for which the voucher is to be assigned
+
+ Returns:
+ True: '' (Tuple): if voucher is successfully assigned
+ False: (Tuple): if voucher could not be assigned
+ """
try:
self.client.resource('/ucsd/api/v1/assign_voucher').post(
{
@@ -30,6 +54,16 @@ def assign_voucher_to_user(self, user, course_key, course_sku=None):
return False, ex.message
def check_coupon_availability_for_course(self, course_key):
+ """
+ Sends request to check if a coupon is available for the course
+
+ Arguments:
+ course_key (str): course_key of the course for which the coupon is to be checked for availability
+
+ Returns:
+ True: if coupon is available for the course
+ False: if coupon is not available for the course
+ """
try:
self.client.resource('/ucsd/api/v1/check_course_coupon').post(
{
diff --git a/openedx/features/ucsd_features/ecommerce/tasks.py b/openedx/features/ucsd_features/ecommerce/tasks.py
index 7766c8c76612..172bdc29c6df 100644
--- a/openedx/features/ucsd_features/ecommerce/tasks.py
+++ b/openedx/features/ucsd_features/ecommerce/tasks.py
@@ -1,8 +1,11 @@
+"""
+Tasks for the "ecommerce" module of "UCSDFeatures" app
+"""
from celery.task import task
from celery.utils.log import get_task_logger
from django.contrib.auth.models import User
-from openedx.features.ucsd_features.ecommerce.EcommerceClient import EcommerceRestAPIClient
+from openedx.features.ucsd_features.ecommerce.ecommerce_client import EcommerceRestAPIClient
logger = get_task_logger(__name__)
@@ -10,6 +13,15 @@
@task()
def assign_course_voucher_to_user(user_email, course_key, course_sku):
+ """
+ This task is responsible for sending the request to Ecommerce to
+ assign a voucher to the user for the course.
+
+ Arguments:
+ user_email (str): email of the user to whom the voucher is to be assigned
+ course_key (str): course_key of the course for which the voucher is to be assigned
+ course_sku (str): SKU of the course for which the voucher is to be assigned
+ """
try:
user = User.objects.get(email=user_email)
except User.DoesNotExist:
diff --git a/openedx/features/ucsd_features/ecommerce/tests/test_ecommerce_client.py b/openedx/features/ucsd_features/ecommerce/tests/test_ecommerce_client.py
index 45d86498837a..5f106a28c46d 100644
--- a/openedx/features/ucsd_features/ecommerce/tests/test_ecommerce_client.py
+++ b/openedx/features/ucsd_features/ecommerce/tests/test_ecommerce_client.py
@@ -10,7 +10,7 @@
from xmodule.modulestore.tests.factories import CourseFactory
from student.tests.factories import UserFactory
-from openedx.features.ucsd_features.ecommerce.EcommerceClient import EcommerceRestAPIClient
+from openedx.features.ucsd_features.ecommerce.ecommerce_client import EcommerceRestAPIClient
from openedx.features.ucsd_features.ecommerce.tests.utils import make_ecommerce_url
@@ -35,11 +35,11 @@ def test_assign_voucher_to_user_with_success_response(self):
content_type='application/json'
)
is_successfull, message = self.client.assign_voucher_to_user(self.user, course_key)
- self.assertEqual(is_successfull, True)
+ self.assertTrue(is_successfull)
self.assertEqual(message, '')
@httpretty.activate
- @patch('openedx.features.ucsd_features.ecommerce.EcommerceClient.logger.error', autospec=True)
+ @patch('openedx.features.ucsd_features.ecommerce.ecommerce_client.logger.exception', autospec=True)
def test_assign_voucher_to_user_with_failure_response(self, mocked_logger):
url = make_ecommerce_url('/ucsd/api/v1/assign_voucher/')
course_key = str(self.course.id)
@@ -54,5 +54,48 @@ def test_assign_voucher_to_user_with_failure_response(self, mocked_logger):
is_successfull, message = self.client.assign_voucher_to_user(self.user, course_key)
expected_message = 'Client Error 400: {}'.format(url)
- self.assertEqual(is_successfull, False)
+ expected_logged_exception = ('Got failure response from ecommerce while '
+ 'trying to assign a voucher to user.\n'
+ 'Details:{}'.format(expected_message))
+ self.assertFalse(is_successfull)
self.assertEqual(message, expected_message)
+ mocked_logger.assert_called_with(expected_logged_exception)
+
+ @httpretty.activate
+ def test_check_coupon_availability_for_course_with_success_response(self):
+ url = make_ecommerce_url('/ucsd/api/v1/check_course_coupon/')
+ course_key = str(self.course.id)
+
+ httpretty.register_uri(
+ httpretty.POST,
+ url,
+ status=200,
+ body='{}',
+ content_type='application/json'
+ )
+
+ is_successfull = self.client.check_coupon_availability_for_course(course_key)
+ self.assertTrue(is_successfull)
+
+ @httpretty.activate
+ @patch('openedx.features.ucsd_features.ecommerce.ecommerce_client.logger.exception', autospec=True)
+ def test_check_coupon_availability_for_course_with_failure_response(self, mocked_logger):
+ url = make_ecommerce_url('/ucsd/api/v1/check_course_coupon/')
+ course_key = str(self.course.id)
+
+ httpretty.register_uri(
+ httpretty.POST,
+ url,
+ status=400,
+ body='{}',
+ content_type='application/json'
+ )
+
+ expected_exception = 'Client Error 400: {}'.format(url)
+ expected_logged_exception = ('Got failure response from ecommerce while '
+ 'trying to check coupon availability for the course.\n'
+ 'Details:{}'.format(expected_exception))
+
+ is_successfull = self.client.check_coupon_availability_for_course(course_key)
+ self.assertFalse(is_successfull)
+ mocked_logger.assert_called_with(expected_logged_exception)
diff --git a/openedx/features/ucsd_features/ecommerce/tests/test_tasks.py b/openedx/features/ucsd_features/ecommerce/tests/test_tasks.py
index 57ab6ca27bd4..215ca6b0c895 100644
--- a/openedx/features/ucsd_features/ecommerce/tests/test_tasks.py
+++ b/openedx/features/ucsd_features/ecommerce/tests/test_tasks.py
@@ -9,6 +9,7 @@
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from student.tests.factories import UserFactory
+from course_modes.tests.factories import CourseModeFactory
from openedx.features.ucsd_features.ecommerce.tasks import assign_course_voucher_to_user
from openedx.features.ucsd_features.ecommerce.tests.utils import make_ecommerce_url
@@ -20,13 +21,15 @@ def setUp(self):
super(UCSDFeaturesEcommerceTasksTests, self).setUp()
self.course = CourseFactory.create()
self.user = UserFactory()
+ self.course_mode = CourseModeFactory.create(course_id=self.course.id, mode_slug='verified')
@patch('openedx.features.ucsd_features.ecommerce.tasks.logger.error', autospec=True)
def test_assign_course_voucher_to_user_when_no_user_exists(self, mocked_logger):
invalid_user_email = 'doesnotexsts@mail.com'
course_key = str(self.course.id)
- assign_course_voucher_to_user(invalid_user_email, course_key)
- mocked_logger.assert_called_once_with('User with email: doesnotexsts@mail.com not found. Cannot assign a voucher.')
+ assign_course_voucher_to_user.apply(args=(invalid_user_email, course_key, self.course_mode.sku)).get()
+ mocked_logger.assert_called_once_with('User with email: doesnotexsts@mail.com not found.'
+ ' Cannot assign a voucher.')
@httpretty.activate
@patch('openedx.features.ucsd_features.ecommerce.tasks.logger.info', autospec=True)
@@ -40,8 +43,11 @@ def test_assign_course_voucher_to_user_with_successful_assignment(self, mocked_l
content_type='application/json'
)
course_key = str(self.course.id)
- assign_course_voucher_to_user(self.user.email, course_key)
- expected_log_message = 'Successfully assigned a voucher to user {} for the course {}.'.format(self.user.username, course_key)
+ assign_course_voucher_to_user.apply(args=(self.user.email, course_key, self.course_mode.sku)).get()
+
+ expected_log_message = 'Successfully assigned a voucher to user {} for the course {}.'.format(
+ self.user.username, course_key
+ )
mocked_logger.assert_called_once_with(expected_log_message)
@httpretty.activate
@@ -56,7 +62,7 @@ def test_assign_course_voucher_to_user_with_failed_assignment(self, mocked_logge
content_type='application/json'
)
course_key = str(self.course.id)
- assign_course_voucher_to_user(self.user.email, course_key)
+ assign_course_voucher_to_user.apply(args=(self.user.email, course_key, self.course_mode.sku)).get()
expected_log_message = ('Failed to send request to assign a voucher to user'
' {} for the course {}.\nError message: '
'Client Error 400: {}'.format(
diff --git a/openedx/features/ucsd_features/ecommerce/tests/test_utils.py b/openedx/features/ucsd_features/ecommerce/tests/test_utils.py
index 4c1eec39b0e0..46b1969f044b 100644
--- a/openedx/features/ucsd_features/ecommerce/tests/test_utils.py
+++ b/openedx/features/ucsd_features/ecommerce/tests/test_utils.py
@@ -2,8 +2,10 @@
Tests for ecommerce utils
"""
import httpretty
+import mock
from django.conf import settings
+from django.test.utils import override_settings
from mock import patch
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
@@ -11,20 +13,102 @@
from student.tests.factories import UserFactory
from openedx.features.ucsd_features.ecommerce.utils import is_user_eligible_for_discount
+from openedx.features.ucsd_features.ecommerce.tests.utils import make_ecommerce_url
class UCSDFeaturesEcommerceUtilsTests(ModuleStoreTestCase):
- def setUpClass(self):
- super(UCSDFeaturesEcommerceUtilsTests, self).setUpClass()
- self.course = CourseFactory.create()
-
def setUp(self):
super(UCSDFeaturesEcommerceUtilsTests, self).setUp()
self.user = UserFactory()
+ self.course = CourseFactory.create()
def test_is_user_eligible_for_discount_with_disabled_feature(self):
- request = mock.MagicMock()
+ features = settings.FEATURES.copy()
+ features['ENABLE_GEOGRAPHIC_DISCOUNTS'] = False
+ with override_settings(FEATURES=features):
+ request = mock.MagicMock()
+ course_key = str(self.course.id)
+ return_value = is_user_eligible_for_discount(request, course_key)
+ self.assertFalse(return_value)
+
+ def test_is_user_eligible_for_discount_when_country_code_is_not_eligible(self):
+ features = settings.FEATURES.copy()
+ features['ENABLE_GEOGRAPHIC_DISCOUNTS'] = True
+ with override_settings(FEATURES=features):
+ eligible_countries = settings.COUNTRIES_ELIGIBLE_FOR_DISCOUNTS
+ eligible_countries = {
+ 'PK': 'Pakistan'
+ }
+ with override_settings(COUNTRIES_ELIGIBLE_FOR_DISCOUNTS=eligible_countries):
+ request = mock.MagicMock()
+ request.session = {
+ 'country_code': 'US'
+ }
+ request.user = self.user
+
+ course_key = str(self.course.id)
+ return_value = is_user_eligible_for_discount(request, course_key)
+ self.assertFalse(return_value)
+
+ @httpretty.activate
+ def test_is_user_eligible_for_discount_when_course_is_not_eligible(self):
+ url = make_ecommerce_url('/ucsd/api/v1/check_course_coupon/')
+ course_key = str(self.course.id)
+
+ httpretty.register_uri(
+ httpretty.POST,
+ url,
+ status=400,
+ body='{}',
+ content_type='application/json'
+ )
+
+ features = settings.FEATURES.copy()
+ features['ENABLE_GEOGRAPHIC_DISCOUNTS'] = True
+
+ with override_settings(FEATURES=features):
+ eligible_countries = settings.COUNTRIES_ELIGIBLE_FOR_DISCOUNTS
+ eligible_countries = {
+ 'US': 'United States of America'
+ }
+ with override_settings(COUNTRIES_ELIGIBLE_FOR_DISCOUNTS=eligible_countries):
+ request = mock.MagicMock()
+ request.session = {
+ 'country_code': 'US'
+ }
+ request.user = self.user
+
+ return_value = is_user_eligible_for_discount(request, course_key)
+ self.assertFalse(return_value)
+
+ @httpretty.activate
+ def test_is_user_eligible_for_discount_when_course_is_not_eligible(self):
+ url = make_ecommerce_url('/ucsd/api/v1/check_course_coupon/')
course_key = str(self.course.id)
- return_value = is_user_eligible_for_discount(request, course_key)
- self.assertFalse(return_value)
+
+ httpretty.register_uri(
+ httpretty.POST,
+ url,
+ status=200,
+ body='{}',
+ content_type='application/json'
+ )
+
+ features = settings.FEATURES.copy()
+ features['ENABLE_GEOGRAPHIC_DISCOUNTS'] = True
+
+ with override_settings(FEATURES=features):
+ eligible_countries = settings.COUNTRIES_ELIGIBLE_FOR_DISCOUNTS
+ eligible_countries = {
+ 'US': 'United States of America'
+ }
+ with override_settings(COUNTRIES_ELIGIBLE_FOR_DISCOUNTS=eligible_countries):
+ request = mock.MagicMock()
+ request.session = {
+ 'country_code': 'US'
+ }
+ request.user = self.user
+
+ return_value = is_user_eligible_for_discount(request, course_key)
+ self.assertTrue(return_value)
diff --git a/openedx/features/ucsd_features/ecommerce/utils.py b/openedx/features/ucsd_features/ecommerce/utils.py
index 5a72c6efd7fc..4075666c4f32 100644
--- a/openedx/features/ucsd_features/ecommerce/utils.py
+++ b/openedx/features/ucsd_features/ecommerce/utils.py
@@ -5,13 +5,26 @@
from django.conf import settings
-from openedx.features.ucsd_features.ecommerce.EcommerceClient import EcommerceRestAPIClient
+from openedx.features.ucsd_features.ecommerce.ecommerce_client import EcommerceRestAPIClient
logger = logging.getLogger(__name__)
def is_user_eligible_for_discount(request, course_key):
+ """
+ Checks if the user in the request is eligible to get a discount voucher for course with course_key.
+ A user is eligible for discount iff:
+ - ENABLE_GEOGRAPHIC_DISCOUNTS feature flag is set to True
+ - User's country_code is in the list of discount eligible countries (configured through
+ COUNTRIES_ELIGIBLE_FOR_DISCOUNTS setting)
+ - Course matching the course_key has a Discount Coupon created on the E-commerce side
+
+
+ Arguments:
+ request: Request object
+ course_key (str): course_key of the course for which the discount eligibility is to be checked
+ """
if not settings.FEATURES.get('ENABLE_GEOGRAPHIC_DISCOUNTS', False):
logger.info('Geographics discounts are not enabled hence skipping further processing.')
return False
diff --git a/openedx/features/ucsd_features/migrations/0001_initial.py b/openedx/features/ucsd_features/migrations/0001_initial.py
new file mode 100644
index 000000000000..1348a06498b3
--- /dev/null
+++ b/openedx/features/ucsd_features/migrations/0001_initial.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.29 on 2020-03-12 00:17
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='AdditionalRegistrationFields',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('gender_nb', models.CharField(choices=[(b'male', b'Male'), (b'female', b'Female'), (b'nonbinary', b'Non-Binary'), (b'decline', b'Decline to State')], max_length=32, verbose_name=b'Gender')),
+ ('ethnicity', models.CharField(choices=[(b'african-american-and-black', b'African American and Black'), (b'american-indian-alaska-native', b'American Indian / Alaska Native'), (b'asian', b'Asian'), (b'hispanic-latinx', b'Hispanic / Latinx'), (b'native-hawaiian-and-pacific-islander', b'Native Hawaiian and Pacific Islander'), (b'southwest-asia-north-african', b'Southwest Asia / North African'), (b'white', b'White'), (b'decline', b'Decline to State')], max_length=128, verbose_name=b'Ethnicity')),
+ ('age', models.CharField(choices=[(b'13-17', b'13 - 17 years old'), (b'18-22', b'18 - 22 years old'), (b'23-29', b'23 - 29 years old'), (b'30-49', b'30 - 49 years old'), (b'50-plus', b'50+ years old'), (b'decline', b'Decline to State')], max_length=32, verbose_name=b'Age')),
+ ('education', models.CharField(choices=[(b'graduate', b'Graduate'), (b'undergraduate', b'Undergraduate'), (b'up-to-high-school', b'Up to High School'), (b'decline', b'Decline to State')], max_length=64, verbose_name=b'Highest level of education completed')),
+ ('howheard', models.CharField(choices=[(b'social-media', b'Social Media'), (b'email', b'Email'), (b'word-of-mouth', b'Word-of-Mouth'), (b'print-advertisement', b'Print Advertisement'), (b'other', b'Other')], max_length=64, verbose_name=b'How did you hear about UC San Diego Online')),
+ ('user', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'verbose_name': 'Additional Registration Information',
+ 'verbose_name_plural': 'Additional Registration Information',
+ },
+ ),
+ ]
diff --git a/openedx/features/ucsd_features/migrations/__init__.py b/openedx/features/ucsd_features/migrations/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/openedx/features/ucsd_features/tests/test_utils.py b/openedx/features/ucsd_features/tests/test_utils.py
index 51a9b8581777..9f5c4453222a 100644
--- a/openedx/features/ucsd_features/tests/test_utils.py
+++ b/openedx/features/ucsd_features/tests/test_utils.py
@@ -21,7 +21,9 @@
@ddt.ddt
class UCSDFeaturesUtilsTests(ModuleStoreTestCase):
- """ Tests for the utils used by ucsd-specific customizations """
+ """
+ Tests for the utils used by ucsd-specific customizations
+ """
def setUp(self):
super(UCSDFeaturesUtilsTests, self).setUp()