Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Upgrade regular signup form for nafath #428

Merged
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",
]
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),
),
]
69 changes: 57 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,56 @@ 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)


@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
15 changes: 15 additions & 0 deletions openedx/core/djangoapps/user_authn/views/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,21 @@ 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"

if is_registration_api_v1(request):
if 'confirm_email' in extra_fields:
del extra_fields['confirm_email']
Expand Down
Loading
Loading