diff --git a/common/djangoapps/student/helpers.py b/common/djangoapps/student/helpers.py index 35c6c1a7bb51..fbb81bb684f2 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,7 @@ 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", "gender", ] profile = UserProfile( user=user, @@ -754,6 +755,13 @@ def do_create_account(form, custom_form=None): except Exception: log.exception(f"UserProfile creation failed for user {user.id}.") raise + + try: + social_link_fields = ["platform", "social_link"] + 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_20230927_0625.py b/common/djangoapps/student/migrations/0045_auto_20230927_0625.py new file mode 100644 index 000000000000..dd653966f8b4 --- /dev/null +++ b/common/djangoapps/student/migrations/0045_auto_20230927_0625.py @@ -0,0 +1,63 @@ +# Generated by Django 3.2.20 on 2023-09-27 06:25 + +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.CharField(blank=True, max_length=255, 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='employement_status', + field=models.CharField(blank=True, max_length=63, null=True), + ), + migrations.AddField( + model_name='userprofile', + name='english_language_level', + field=models.CharField(blank=True, max_length=3, 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, max_length=63, null=True), + ), + migrations.AddField( + model_name='userprofile', + name='signup_form_options', + field=models.JSONField(default={'employement_status': ['Public industry', 'Private industry', 'Job seeker', 'Student'], 'english_language_level': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'region': ['Riyadh', 'Eastern', 'Asir', 'Jazan', 'Medina', 'Al-Qassim', 'Tabuk', "Ha'il", 'Najran', 'Al-Jawf', 'Al-Bahah', 'Northern Borders'], 'type_of_degree': ['Middle School', 'High School', 'Diploma', 'Bachelor', 'Master', 'Ph.D.'], 'work_experience_level': ['Junior level (0-2) years', 'Middle level (3-4) years', 'Senior level (5-10) years', 'Expert (+ 10 years)']}), + ), + migrations.AddField( + model_name='userprofile', + name='type_of_degree', + field=models.CharField(blank=True, max_length=63, null=True), + ), + migrations.AddField( + model_name='userprofile', + name='work_experience_level', + field=models.CharField(blank=True, max_length=63, null=True), + ), + ] diff --git a/common/djangoapps/student/models/user.py b/common/djangoapps/student/models/user.py index f29480e0ce02..3859e814e080 100644 --- a/common/djangoapps/student/models/user.py +++ b/common/djangoapps/student/models/user.py @@ -398,6 +398,46 @@ class UserStanding(models.Model): standing_last_changed_at = models.DateTimeField(auto_now=True) +def default_options(): + return { + "region": [ + "Riyadh", + "Eastern", + "Asir", + "Jazan", + "Medina", + "Al-Qassim", + "Tabuk", + "Ha'il", + "Najran", + "Al-Jawf", + "Al-Bahah", + "Northern Borders", + ], + "type_of_degree": [ + "Middle School", + "High School", + "Diploma", + "Bachelor", + "Master", + "Ph.D.", + ], + "english_language_level": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + "employement_status": [ + "Public industry", + "Private industry", + "Job seeker", + "Student", + ], + "work_experience_level": [ + "Junior level (0-2) years", + "Middle level (3-4) years", + "Senior level (5-10) years", + "Expert (+ 10 years)", + ], + } + + class UserProfile(models.Model): """This is where we store all the user demographic fields. We have a separate table for this rather than extending the built-in Django auth_user. @@ -551,6 +591,19 @@ 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 + 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=63) + address_line = models.CharField(blank=True, null=True, max_length=255) + type_of_degree = models.CharField(blank=True, null=True, max_length=63) + english_language_level = models.CharField(blank=True, null=True, max_length=3) + employement_status = models.CharField(blank=True, null=True, max_length=63) + work_experience_level = models.CharField(blank=True, null=True, max_length=63) + job_title = models.CharField(blank=True, null=True, max_length=63) + signup_form_options = models.JSONField(default=default_options()) + + @property def has_profile_image(self): """ diff --git a/openedx/core/djangoapps/user_api/accounts/__init__.py b/openedx/core/djangoapps/user_api/accounts/__init__.py index 84daeccdb8f2..e321a3967bb2 100644 --- a/openedx/core/djangoapps/user_api/accounts/__init__.py +++ b/openedx/core/djangoapps/user_api/accounts/__init__.py @@ -22,6 +22,11 @@ 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 + 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_authn/views/register.py b/openedx/core/djangoapps/user_authn/views/register.py index c494f8e015c2..51e77539a698 100644 --- a/openedx/core/djangoapps/user_authn/views/register.py +++ b/openedx/core/djangoapps/user_authn/views/register.py @@ -164,6 +164,12 @@ 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["gender"] = "required" + if is_registration_api_v1(request): if 'confirm_email' in extra_fields: del extra_fields['confirm_email'] diff --git a/openedx/core/djangoapps/user_authn/views/registration_form.py b/openedx/core/djangoapps/user_authn/views/registration_form.py index e503265d1e68..5816c524ec87 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,11 @@ def __init__(self): "profession", "specialty", "marketing_emails_opt_in", + "phone_number", + "national_id", + "linkedin_account", + "date_of_birth", + "gender", ] if settings.ENABLE_COPPA_COMPLIANCE and 'year_of_birth' in self.EXTRA_FIELDS: @@ -552,6 +560,126 @@ 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_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_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_gender_field(self, form_desc, required=True): + """Add a gender 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 gender. + gender_label = _("Date of birth") + + # Translators: These instructions appear on the registration form, immediately + # below a field meant to hold the user's gender. + gender_instructions = _("This field is required, add your gender here.") + + form_desc.add_field( + "gender", + label=gender_label, + instructions=gender_instructions, + restrictions={}, + required=required + ) def _add_username_field(self, form_desc, required=True): """Add a username field to a form description. Arguments: