diff --git a/common/djangoapps/student/helpers.py b/common/djangoapps/student/helpers.py index 35c6c1a7bb51..cfb047f1a947 100644 --- a/common/djangoapps/student/helpers.py +++ b/common/djangoapps/student/helpers.py @@ -30,6 +30,7 @@ Registration, UserAttribute, UserProfile, + SocialLink, email_exists_or_retired, unique_id_for_user, username_exists_or_retired @@ -740,7 +741,8 @@ def do_create_account(form, custom_form=None): profile_fields = [ "name", "level_of_education", "gender", "mailing_address", "city", "country", "goals", - "year_of_birth" + "year_of_birth", "national_id", "phone_number", "date_of_birth", "region", "address_line", + "english_language_level", "employment_status", "work_experience_level", "job_title", "terms_and_conditions" ] profile = UserProfile( user=user, @@ -754,6 +756,12 @@ def do_create_account(form, custom_form=None): except Exception: log.exception(f"UserProfile creation failed for user {user.id}.") raise + + try: + linkedin_social_link = SocialLink(user_profile=profile, platform="linkedin", social_link=form.cleaned_data.get("linkedin_account")) + linkedin_social_link.save() + except Exception: + log.exception(f"SocialLink creation failed for user {user.id}.") return user, profile, registration diff --git a/common/djangoapps/student/migrations/0045_auto_20231005_0906.py b/common/djangoapps/student/migrations/0045_auto_20231005_0906.py new file mode 100644 index 000000000000..a870e083e0b7 --- /dev/null +++ b/common/djangoapps/student/migrations/0045_auto_20231005_0906.py @@ -0,0 +1,58 @@ +# Generated by Django 3.2.20 on 2023-10-05 09:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('student', '0044_courseenrollmentcelebration_celebrate_weekly_goal'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='address_line', + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name='userprofile', + name='date_of_birth', + field=models.DateField(blank=True, default=None, null=True), + ), + migrations.AddField( + model_name='userprofile', + name='employment_status', + field=models.CharField(blank=True, choices=[('PU', 'Public industry'), ('PR', 'Private industry'), ('JS', 'Job seeker'), ('ST', 'Student')], max_length=3, null=True), + ), + migrations.AddField( + model_name='userprofile', + name='english_language_level', + field=models.CharField(blank=True, choices=[('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ('6', '6'), ('7', '7'), ('8', '8'), ('9', '9')], max_length=2, null=True), + ), + migrations.AddField( + model_name='userprofile', + name='job_title', + field=models.CharField(blank=True, max_length=63, null=True), + ), + migrations.AddField( + model_name='userprofile', + name='national_id', + field=models.CharField(blank=True, max_length=63, null=True), + ), + migrations.AddField( + model_name='userprofile', + name='region', + field=models.CharField(blank=True, choices=[('RD', 'Riyadh'), ('ER', 'Eastern'), ('AI', 'Asir'), ('JA', 'Jazan'), ('MN', 'Medina'), ('AS', 'Al-Qassim'), ('TU', 'Tabuk'), ('HI', "Ha'il"), ('NA', 'Najran'), ('AW', 'Al-Jawf'), ('AA', 'Al-Bahah'), ('NB', 'Northern Borders')], max_length=3, null=True), + ), + migrations.AddField( + model_name='userprofile', + name='work_experience_level', + field=models.CharField(blank=True, choices=[('JL', 'Junior level (0-2) years'), ('ML', 'Middle level (3-4) years'), ('SL', 'Senior level (5-10) years'), ('EL', 'Expert (+ 10 years)')], max_length=3, null=True), + ), + migrations.AlterField( + model_name='userprofile', + name='level_of_education', + field=models.CharField(blank=True, choices=[('MS', 'Middle School'), ('HS', 'High School'), ('DM', 'Diploma'), ('BS', 'Bachelor'), ('MR', 'Master'), ('PH', 'Ph.D.')], db_index=True, max_length=3, null=True), + ), + ] diff --git a/common/djangoapps/student/migrations/0046_alter_userprofile_english_language_level.py b/common/djangoapps/student/migrations/0046_alter_userprofile_english_language_level.py new file mode 100644 index 000000000000..9ce480102adc --- /dev/null +++ b/common/djangoapps/student/migrations/0046_alter_userprofile_english_language_level.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.20 on 2023-10-06 13:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('student', '0045_auto_20231005_0906'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='english_language_level', + field=models.CharField(blank=True, choices=[('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ('6', '6'), ('7', '7'), ('8', '8'), ('9', '9'), ('10', '10')], max_length=2, null=True), + ), + ] diff --git a/common/djangoapps/student/migrations/0047_userprofile_terms_and_conditions.py b/common/djangoapps/student/migrations/0047_userprofile_terms_and_conditions.py new file mode 100644 index 000000000000..b57074c545c1 --- /dev/null +++ b/common/djangoapps/student/migrations/0047_userprofile_terms_and_conditions.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.21 on 2023-10-23 06:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('student', '0046_alter_userprofile_english_language_level'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='terms_and_conditions', + field=models.BooleanField(default=True), + ), + ] diff --git a/common/djangoapps/student/models/user.py b/common/djangoapps/student/models/user.py index f29480e0ce02..bee8d4ce16b1 100644 --- a/common/djangoapps/student/models/user.py +++ b/common/djangoapps/student/models/user.py @@ -468,20 +468,15 @@ class Meta: # ('p_se', 'Doctorate in science or engineering'), # ('p_oth', 'Doctorate in another field'), LEVEL_OF_EDUCATION_CHOICES = ( - ('p', gettext_noop('Doctorate')), - ('m', gettext_noop("Master's or professional degree")), - ('b', gettext_noop("Bachelor's degree")), - ('a', gettext_noop("Associate degree")), - ('hs', gettext_noop("Secondary/high school")), - ('jhs', gettext_noop("Junior secondary/junior high/middle school")), - ('el', gettext_noop("Elementary/primary school")), - # Translators: 'None' refers to the student's level of education - ('none', gettext_noop("No formal education")), - # Translators: 'Other' refers to the student's level of education - ('other', gettext_noop("Other education")) + ('MS', 'Middle School'), + ('HS', 'High School'), + ('DM', 'Diploma'), + ('BS', 'Bachelor'), + ('MR', 'Master'), + ('PH', 'Ph.D.'), ) level_of_education = models.CharField( - blank=True, null=True, max_length=6, db_index=True, + blank=True, null=True, max_length=3, db_index=True, choices=LEVEL_OF_EDUCATION_CHOICES ) mailing_address = models.TextField(blank=True, null=True) @@ -551,6 +546,57 @@ class Meta: phone_regex = RegexValidator(regex=r'^\+?1?\d*$', message="Phone number can only contain numbers.") phone_number = models.CharField(validators=[phone_regex], blank=True, null=True, max_length=50) + # fields related to sdaia - nafath + REGION_CHOICES = ( + ('RD', 'Riyadh'), + ('ER', 'Eastern'), + ('AI', 'Asir'), + ('JA', 'Jazan'), + ('MN', 'Medina'), + ('AS', 'Al-Qassim'), + ('TU', 'Tabuk'), + ('HI', "Ha'il"), + ('NA', 'Najran'), + ('AW', 'Al-Jawf'), + ('AA', 'Al-Bahah'), + ('NB', 'Northern Borders'), + ) + EMPLOYMENT_STATUS_CHOICES = ( + ('PU', 'Public industry'), + ('PR', 'Private industry'), + ('JS', 'Job seeker'), + ('ST', 'Student'), + ) + WORK_EXPERIENCE_LEVEL_CHOICES = ( + ('JL', 'Junior level (0-2) years'), + ('ML', 'Middle level (3-4) years'), + ('SL', 'Senior level (5-10) years'), + ('EL', 'Expert (+ 10 years)'), + ) + ENGLISH_LANGUAGE_LEVEL_CHOICES = ( + ('0', '0'), + ('1', '1'), + ('2', '2'), + ('3', '3'), + ('4', '4'), + ('5', '5'), + ('6', '6'), + ('7', '7'), + ('8', '8'), + ('9', '9'), + ('10', '10'), + ) + national_id = models.CharField(blank=True, null=True, max_length=63) + date_of_birth = models.DateField(default=None, null=True, blank=True) + region = models.CharField(blank=True, null=True, max_length=3, choices=REGION_CHOICES) + address_line = models.TextField(blank=True, null=True) + english_language_level = models.CharField(blank=True, null=True, max_length=2, choices=ENGLISH_LANGUAGE_LEVEL_CHOICES) + employment_status = models.CharField(blank=True, null=True, max_length=3, choices=EMPLOYMENT_STATUS_CHOICES) + work_experience_level = models.CharField(blank=True, null=True, max_length=3, choices=WORK_EXPERIENCE_LEVEL_CHOICES) + job_title = models.CharField(blank=True, null=True, max_length=63) + terms_and_conditions = models.BooleanField(default=True) + + @property def has_profile_image(self): """ diff --git a/lms/envs/common.py b/lms/envs/common.py index c0182d47355a..e57774643e01 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -4141,11 +4141,17 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring 'bio', 'course_certificates', 'country', + 'city', + 'region', 'date_joined', 'language_proficiencies', - "level_of_education", + 'level_of_education', 'social_links', 'time_zone', + 'english_language_level', + 'employment_status', + 'work_experience_level', + 'job_title', # Not an actual field, but used to signal whether badges should be public. 'accomplishments_shared', @@ -4179,6 +4185,9 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring "phone_number", "activation_key", "pending_name_change", + "national_id", + "date_of_birth", + "address_line", ] ) diff --git a/lms/templates/nafath_openedx_integration/base.html b/lms/templates/nafath_openedx_integration/base.html new file mode 100644 index 000000000000..d13a13ed43ab --- /dev/null +++ b/lms/templates/nafath_openedx_integration/base.html @@ -0,0 +1,22 @@ + +{% comment %} +As the developer of this package, don't place anything here if you can help it +since this allows developers to have interoperability between your template +structure and their own. + +Example: Developer melding the 2SoD pattern to fit inside with another pattern:: + + {% extends "base.html" %} + {% load static %} + + + {% block extra_js %} + + + {% block javascript %} + + {% endblock javascript %} + + {% endblock extra_js %} +{% endcomment %} + diff --git a/lms/templates/nafath_openedx_integration/edx_ace/useractivationmessage/email/body.html b/lms/templates/nafath_openedx_integration/edx_ace/useractivationmessage/email/body.html new file mode 100644 index 000000000000..876d582afd7e --- /dev/null +++ b/lms/templates/nafath_openedx_integration/edx_ace/useractivationmessage/email/body.html @@ -0,0 +1,13 @@ + + +{% load i18n %} +{% load static %} +{% block content %} +

+ {% autoescape off %} + {# xss-lint: disable=django-blocktrans-missing-escape-filter #} + {% blocktrans %}This is one time OTP {{ activation_code }}. Kindly use this to complete nafath authentication with SDAIA.{% endblocktrans %} + {% endautoescape %} +
+

+{% endblock %} diff --git a/lms/templates/nafath_openedx_integration/edx_ace/useractivationmessage/email/body.txt b/lms/templates/nafath_openedx_integration/edx_ace/useractivationmessage/email/body.txt new file mode 100644 index 000000000000..01ac74599df1 --- /dev/null +++ b/lms/templates/nafath_openedx_integration/edx_ace/useractivationmessage/email/body.txt @@ -0,0 +1,2 @@ +{% load i18n %}{% autoescape off %}{% blocktrans %}This is one time OTP {{ activation_code }}. Kindly use this to complete nafath authentication with SDAIA.{% endblocktrans %} +{% endautoescape %} diff --git a/lms/templates/nafath_openedx_integration/edx_ace/useractivationmessage/email/from_name.txt b/lms/templates/nafath_openedx_integration/edx_ace/useractivationmessage/email/from_name.txt new file mode 100644 index 000000000000..dcbc23c00480 --- /dev/null +++ b/lms/templates/nafath_openedx_integration/edx_ace/useractivationmessage/email/from_name.txt @@ -0,0 +1 @@ +{{ platform_name }} diff --git a/lms/templates/nafath_openedx_integration/edx_ace/useractivationmessage/email/head.html b/lms/templates/nafath_openedx_integration/edx_ace/useractivationmessage/email/head.html new file mode 100644 index 000000000000..83d045ed4c69 --- /dev/null +++ b/lms/templates/nafath_openedx_integration/edx_ace/useractivationmessage/email/head.html @@ -0,0 +1 @@ + diff --git a/lms/templates/nafath_openedx_integration/edx_ace/useractivationmessage/email/subject.txt b/lms/templates/nafath_openedx_integration/edx_ace/useractivationmessage/email/subject.txt new file mode 100644 index 000000000000..80037dcd1a41 --- /dev/null +++ b/lms/templates/nafath_openedx_integration/edx_ace/useractivationmessage/email/subject.txt @@ -0,0 +1,4 @@ +{% load i18n %} +{% autoescape off %} +{% blocktrans %}SDAIA - Complete Nafath Authentication{% endblocktrans %} +{% endautoescape %} diff --git a/openedx/core/djangoapps/user_api/accounts/__init__.py b/openedx/core/djangoapps/user_api/accounts/__init__.py index 84daeccdb8f2..35e804b34121 100644 --- a/openedx/core/djangoapps/user_api/accounts/__init__.py +++ b/openedx/core/djangoapps/user_api/accounts/__init__.py @@ -22,6 +22,16 @@ EMAIL_MIN_LENGTH = 3 EMAIL_MAX_LENGTH = 254 # Limit per RFCs is 254 +# sdaia related constants +PHONE_NUMBER_MAX_LENGTH = 50 +NATIONAL_ID_MAX_LENGTH = 63 +LINKEDIN_ACCOUNT_MAX_LENGTH = 100 +REGION_MAX_LENGTH = 3 +ENGLISH_LANGUAGE_LEVEL_MAX_LENGTH = 2 +EMPLOYMENT_STATUS_MAX_LENGTH = 3 +WORK_EXPERIENCE_LEVEL_MAX_LENGTH = 3 +JOB_TITLE_MAX_LENGTH = 63 + ACCOUNT_VISIBILITY_PREF_KEY = 'account_privacy' # Indicates the user's preference that all users can view the shareable fields in their account information. diff --git a/openedx/core/djangoapps/user_api/accounts/serializers.py b/openedx/core/djangoapps/user_api/accounts/serializers.py index 75ecfa35bf2b..2aef5a32409d 100644 --- a/openedx/core/djangoapps/user_api/accounts/serializers.py +++ b/openedx/core/djangoapps/user_api/accounts/serializers.py @@ -171,6 +171,15 @@ def to_representation(self, user): # lint-amnesty, pylint: disable=arguments-di "phone_number": None, "pending_name_change": None, "verified_name": None, + "national_id":None, + "date_of_birth": None, + "city": None, + "region": None, + "address_line": None, + "english_language_level": None, + "employment_status": None, + "work_experience_level": None, + "job_title": None, } if user_profile: @@ -200,6 +209,15 @@ def to_representation(self, user): # lint-amnesty, pylint: disable=arguments-di ).data, "extended_profile": get_extended_profile(user_profile), "phone_number": user_profile.phone_number, + "national_id": AccountLegacyProfileSerializer.convert_empty_to_None(user_profile.national_id), + "date_of_birth": AccountLegacyProfileSerializer.convert_empty_to_None(user_profile.date_of_birth), + "city": AccountLegacyProfileSerializer.convert_empty_to_None(user_profile.city), + "region": AccountLegacyProfileSerializer.convert_empty_to_None(user_profile.region), + "address_line": AccountLegacyProfileSerializer.convert_empty_to_None(user_profile.address_line), + "english_language_level": AccountLegacyProfileSerializer.convert_empty_to_None(user_profile.english_language_level), + "employment_status": AccountLegacyProfileSerializer.convert_empty_to_None(user_profile.employment_status), + "work_experience_level": AccountLegacyProfileSerializer.convert_empty_to_None(user_profile.work_experience_level), + "job_title": AccountLegacyProfileSerializer.convert_empty_to_None(user_profile.job_title), } ) @@ -289,7 +307,8 @@ class Meta: fields = ( "name", "gender", "goals", "year_of_birth", "level_of_education", "country", "state", "social_links", "mailing_address", "bio", "profile_image", "requires_parental_consent", "language_proficiencies", - "phone_number", "city" + "phone_number", "city", "date_of_birth", "region", "city", "address_line", "english_language_level", + "employment_status", "work_experience_level", "job_title" ) # Currently no read-only field, but keep this so view code doesn't need to know. read_only_fields = () diff --git a/openedx/core/djangoapps/user_authn/views/login.py b/openedx/core/djangoapps/user_authn/views/login.py index 79cb78a2727c..472790859e4c 100644 --- a/openedx/core/djangoapps/user_authn/views/login.py +++ b/openedx/core/djangoapps/user_authn/views/login.py @@ -129,6 +129,8 @@ def _get_user_by_email_or_username(request, api_version): login_fields = ['email', 'password'] if is_api_v2: login_fields = ['email_or_username', 'password'] + if request.POST.get('is_nafath_user', False): + login_fields.remove('password') if any(f not in request.POST.keys() for f in login_fields): raise AuthFailedError(_('There was an error receiving your login information. Please email us.')) @@ -239,6 +241,11 @@ def _authenticate_first_party(request, unauthenticated_user, third_party_auth_re if not third_party_auth_requested: _check_user_auth_flow(request.site, unauthenticated_user) + if request.POST.get('is_nafath_user', False): + return authenticate( + username=username, + request=request + ) password = normalize_password(request.POST['password']) return authenticate( username=username, diff --git a/openedx/core/djangoapps/user_authn/views/register.py b/openedx/core/djangoapps/user_authn/views/register.py index c494f8e015c2..30e850ce57ce 100644 --- a/openedx/core/djangoapps/user_authn/views/register.py +++ b/openedx/core/djangoapps/user_authn/views/register.py @@ -164,6 +164,22 @@ def create_account_with_params(request, params): # pylint: disable=too-many-sta 'REGISTRATION_EXTRA_FIELDS', getattr(settings, 'REGISTRATION_EXTRA_FIELDS', {}) ) + extra_fields["phone_number"] = "required" + extra_fields["national_id"] = "optional" + extra_fields["linkedin_account"] = "optional" + extra_fields["date_of_birth"] = "required" + extra_fields["year_of_birth"] = "required" + extra_fields["gender"] = "required" + extra_fields["region"] = "required" + extra_fields["city"] = "required" + extra_fields["address_line"] = "optional" + extra_fields["level_of_education"] = "required" + extra_fields["english_language_level"] = "optional" + extra_fields["employment_status"] = "required" + extra_fields["work_experience_level"] = "required" + extra_fields["job_title"] = "required" + extra_fields["terms_and_conditions"] = "required" + if is_registration_api_v1(request): if 'confirm_email' in extra_fields: del extra_fields['confirm_email'] @@ -575,6 +591,7 @@ def post(self, request): ) data = request.POST.copy() + data["terms_and_conditions"] = True if data.get("terms_and_conditions")=="true" else False self._handle_terms_of_service(data) try: diff --git a/openedx/core/djangoapps/user_authn/views/registration_form.py b/openedx/core/djangoapps/user_authn/views/registration_form.py index e503265d1e68..42a4c481de4b 100644 --- a/openedx/core/djangoapps/user_authn/views/registration_form.py +++ b/openedx/core/djangoapps/user_authn/views/registration_form.py @@ -197,7 +197,10 @@ def __init__( "mailing_address": _("Your mailing address is required"), "goals": _("A description of your goals is required"), "city": _("A city is required"), - "country": _("A country is required") + "country": _("A country is required"), + "phone_number": _("Your phone number is required"), + "date_of_birth": _("Your date of birth is required"), + "gender": _("Your gender is required"), } for field_name, field_value in extra_fields.items(): if field_name not in self.fields: @@ -349,6 +352,16 @@ def __init__(self): "profession", "specialty", "marketing_emails_opt_in", + "phone_number", + "national_id", + "linkedin_account", + "date_of_birth", + "region", + "address_line", + "english_language_level", + "employment_status", + "work_experience_level", + "job_title", ] if settings.ENABLE_COPPA_COMPLIANCE and 'year_of_birth' in self.EXTRA_FIELDS: @@ -552,6 +565,252 @@ def _add_name_field(self, form_desc, required=True): required=required ) + def _add_phone_number_field(self, form_desc, required=True): + """Add a phone number field to a form description. + Arguments: + form_desc: A form description + Keyword Arguments: + required (bool): Whether this field is required; defaults to True + """ + # Translators: This label appears above a field on the registration form + # meant to hold the user's phone number. + phone_number_label = _("Phone Number") + + # Translators: These instructions appear on the registration form, immediately + # below a field meant to hold the user's phone number. + phone_number_instructions = _("This number will be used to contact you.") + + form_desc.add_field( + "phone_number", + label=phone_number_label, + instructions=phone_number_instructions, + restrictions={ + "max_length": accounts.PHONE_NUMBER_MAX_LENGTH, + }, + required=required + ) + + def _add_job_title_field(self, form_desc, required=True): + """Add a job title field to a form description. + Arguments: + form_desc: A form description + Keyword Arguments: + required (bool): Whether this field is required; defaults to True + """ + # Translators: This label appears above a field on the registration form + # meant to hold the user's job title. + job_title_label = _("Job Title") + + # Translators: These instructions appear on the registration form, immediately + # below a field meant to hold the user's job title. + job_title_instructions = _("This is the title of your current Job") + + form_desc.add_field( + "job_title", + label=job_title_label, + instructions=job_title_instructions, + restrictions={ + "max_length": accounts.JOB_TITLE_MAX_LENGTH, + }, + required=required + ) + + def _add_work_experience_level_field(self, form_desc, required=True): + """Add a work experience level field to a form description. + Arguments: + form_desc: A form description + Keyword Arguments: + required (bool): Whether this field is required; defaults to True + """ + # Translators: This label appears above a field on the registration form + # meant to hold the user's work experience level. + work_experience_level_label = _("Work Experience Level") + + # Translators: These instructions appear on the registration form, immediately + # below a field meant to hold the user's work experience level. + work_experience_level_instructions = _("This show you work experience level") + + form_desc.add_field( + "work_experience_level", + label=work_experience_level_label, + instructions=work_experience_level_instructions, + restrictions={ + "max_length": accounts.WORK_EXPERIENCE_LEVEL_MAX_LENGTH, + }, + required=required + ) + + def _add_employment_status_field(self, form_desc, required=True): + """Add a employment status field to a form description. + Arguments: + form_desc: A form description + Keyword Arguments: + required (bool): Whether this field is required; defaults to True + """ + # Translators: This label appears above a field on the registration form + # meant to hold the user's employment status. + employment_status_label = _("Employment Status") + + # Translators: These instructions appear on the registration form, immediately + # below a field meant to hold the user's employment status. + employment_status_instructions = _("This shows your employment status") + + form_desc.add_field( + "employment_status", + label=employment_status_label, + instructions=employment_status_instructions, + restrictions={ + "max_length": accounts.EMPLOYMENT_STATUS_MAX_LENGTH, + }, + required=required + ) + + def _add_national_id_field(self, form_desc, required=False): + """Add a national id field to a form description. + Arguments: + form_desc: A form description + Keyword Arguments: + required (bool): Whether this field is required; defaults to False + """ + # Translators: This label appears above a field on the registration form + # meant to hold the user's national id. + national_id_label = _("National Id") + + # Translators: These instructions appear on the registration form, immediately + # below a field meant to hold the user's national id. + national_id_instructions = _("This field is optional, add your national id here.") + + form_desc.add_field( + "national_id", + label=national_id_label, + instructions=national_id_instructions, + restrictions={ + "max_length": accounts.NATIONAL_ID_MAX_LENGTH, + }, + required=required + ) + + def _add_linkedin_account_field(self, form_desc, required=False): + """Add a linkedin account field to a form description. + Arguments: + form_desc: A form description + Keyword Arguments: + required (bool): Whether this field is required; defaults to False + """ + # Translators: This label appears above a field on the registration form + # meant to hold the user's linkedin account. + linkedin_account_label = _("LinkedIn Account") + + # Translators: These instructions appear on the registration form, immediately + # below a field meant to hold the user's linkedin account. + linkedin_account_instructions = _("This field is optional, add your linkedin account link here.") + + form_desc.add_field( + "linkedin_account", + label=linkedin_account_label, + instructions=linkedin_account_instructions, + restrictions={ + "max_length": accounts.LINKEDIN_ACCOUNT_MAX_LENGTH, + }, + required=required + ) + + def _add_english_language_level_field(self, form_desc, required=False): + """Add a english language level field to a form description. + Arguments: + form_desc: A form description + Keyword Arguments: + required (bool): Whether this field is required; defaults to False + """ + # Translators: This label appears above a field on the registration form + # meant to hold the user's english language level. + english_language_level_label = _("English Language Level") + + # Translators: These instructions appear on the registration form, immediately + # below a field meant to hold the user's english language level. + english_language_level_instructions = _("This field is optional, select your english language level link here.") + + form_desc.add_field( + "english_language_level", + label=english_language_level_label, + instructions=english_language_level_instructions, + restrictions={ + "max_length": accounts.ENGLISH_LANGUAGE_LEVEL_MAX_LENGTH, + }, + required=required + ) + + def _add_address_line_field(self, form_desc, required=False): + """Add a address line field to a form description. + Arguments: + form_desc: A form description + Keyword Arguments: + required (bool): Whether this field is required; defaults to False + """ + # Translators: This label appears above a field on the registration form + # meant to hold the user's address line. + address_line_label = _("Address Line") + + # Translators: These instructions appear on the registration form, immediately + # below a field meant to hold the user's address line. + address_line_instructions = _("This field is optional, add your address line here.") + + form_desc.add_field( + "address_line", + label=address_line_label, + instructions=address_line_instructions, + restrictions={}, + required=required + ) + + def _add_date_of_birth_field(self, form_desc, required=True): + """Add a date of birth field to a form description. + Arguments: + form_desc: A form description + Keyword Arguments: + required (datefield): Whether this field is required; defaults to True + """ + # Translators: This label appears above a field on the registration form + # meant to hold the user's date of birth. + date_of_birth_label = _("Date of birth") + + # Translators: These instructions appear on the registration form, immediately + # below a field meant to hold the user's date of birth. + date_of_birth_instructions = _("This field is required, add your date of birth here.") + + form_desc.add_field( + "date_of_birth", + label=date_of_birth_label, + instructions=date_of_birth_instructions, + restrictions={}, + required=required + ) + + def _add_region_field(self, form_desc, required=True): + """Add a region field to a form description. + Arguments: + form_desc: A form description + Keyword Arguments: + required (datefield): Whether this field is required; defaults to True + """ + # Translators: This label appears above a field on the registration form + # meant to hold the user's region. + region_label = _("Date of birth") + + # Translators: These instructions appear on the registration form, immediately + # below a field meant to hold the user's region. + region_instructions = _("This field is required, add your region here.") + + form_desc.add_field( + "region", + label=region_label, + instructions=region_instructions, + restrictions={ + "max_length": accounts.REGION_MAX_LENGTH, + }, + required=required + ) + def _add_username_field(self, form_desc, required=True): """Add a username field to a form description. Arguments: