Skip to content

Commit

Permalink
Feat: Upgrade regular signup form for nafath (#428)
Browse files Browse the repository at this point in the history
* feat!: code progress

* feat!: code progress

* feat!: code progress

* feat!: code progress account mfe related changes

* enhancements!: code progress

* enhancements!: code progress

* finalizing the nafath flow code

* feat: add term and conditions field

---------

Co-authored-by: Muhammad Faraz  Maqsood <[email protected]>
Co-authored-by: Muhammad Faraz  Maqsood <[email protected]>
  • Loading branch information
3 people authored Oct 24, 2023
1 parent 5b5aee3 commit daf18be
Show file tree
Hide file tree
Showing 17 changed files with 528 additions and 16 deletions.
10 changes: 9 additions & 1 deletion common/djangoapps/student/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
Registration,
UserAttribute,
UserProfile,
SocialLink,
email_exists_or_retired,
unique_id_for_user,
username_exists_or_retired
Expand Down Expand Up @@ -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,
Expand All @@ -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

Expand Down
58 changes: 58 additions & 0 deletions common/djangoapps/student/migrations/0045_auto_20231005_0906.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
Original file line number Diff line number Diff line change
@@ -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),
),
]
Original file line number Diff line number Diff line change
@@ -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),
),
]
70 changes: 58 additions & 12 deletions common/djangoapps/student/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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):
"""
Expand Down
11 changes: 10 additions & 1 deletion lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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",
]
)

Expand Down
22 changes: 22 additions & 0 deletions lms/templates/nafath_openedx_integration/base.html
Original file line number Diff line number Diff line change
@@ -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 %}

<!-- Their site uses old school block layout -->
{% block extra_js %}

<!-- Your package using 2SoD block layout -->
{% block javascript %}
<script src="{% static 'js/ninja.js' %}" type="text/javascript"></script>
{% endblock javascript %}

{% endblock extra_js %}
{% endcomment %}

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- {% extends 'ace_common/edx_ace/common/base_body.html' %} -->

{% load i18n %}
{% load static %}
{% block content %}
<p style="color: rgba(0,0,0,.75);">
{% 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 %}
<br />
</p>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -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 %}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ platform_name }}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!-- {% extends 'ace_common/edx_ace/common/base_head.html' %} -->
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{% load i18n %}
{% autoescape off %}
{% blocktrans %}SDAIA - Complete Nafath Authentication{% endblocktrans %}
{% endautoescape %}
10 changes: 10 additions & 0 deletions openedx/core/djangoapps/user_api/accounts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
21 changes: 20 additions & 1 deletion openedx/core/djangoapps/user_api/accounts/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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),
}
)

Expand Down Expand Up @@ -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 = ()
Expand Down
7 changes: 7 additions & 0 deletions openedx/core/djangoapps/user_authn/views/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.'))
Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit daf18be

Please sign in to comment.