Skip to content

Commit bb2c74e

Browse files
Added the ability to add custom fields using extended_profile_fields and EDNX_CUSTOM_REGISTRATION_FIELDS
1 parent 640122f commit bb2c74e

File tree

2 files changed

+131
-10
lines changed

2 files changed

+131
-10
lines changed

openedx/core/djangoapps/user_api/accounts/settings_views.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from django.conf import settings
99
from django.contrib import messages
1010
from django.contrib.auth.decorators import login_required
11+
from django.core.exceptions import ImproperlyConfigured
1112
from django.shortcuts import redirect
1213
from django.urls import reverse
1314
from django.utils.translation import ugettext as _
@@ -214,6 +215,36 @@ def get_user_orders(user):
214215
return user_orders
215216

216217

218+
def _get_custom_context(field_labels_map):
219+
"""eduNEXT: Used to retrieve labels and options of custom fields defined in
220+
EDNX_CUSTOM_REGISTRATION_FIELDS.
221+
222+
Returns:
223+
A tuple with custom labels and options.
224+
"""
225+
field_labels = {}
226+
field_options = {}
227+
custom_fields = getattr(settings, "EDNX_CUSTOM_REGISTRATION_FIELDS", [])
228+
229+
for field in custom_fields:
230+
field_name = field.get("name")
231+
232+
if not field_name: # Required to identify the field.
233+
msg = "Custom fields must have a `name` defined in their configuration."
234+
raise ImproperlyConfigured(msg)
235+
236+
field_label = field.get("label")
237+
if field_name not in field_labels_map and field_label:
238+
field_labels[field_name] = _(field_label) # pylint: disable=translation-of-non-string
239+
240+
options = field.get("options")
241+
242+
if options:
243+
field_options[field_name] = options
244+
245+
return field_labels, field_options
246+
247+
217248
def _get_extended_profile_fields():
218249
"""Retrieve the extended profile fields from site configuration to be shown on the
219250
Account Settings page
@@ -246,12 +277,15 @@ def _get_extended_profile_fields():
246277
"specialty": _(u"Specialty")
247278
}
248279

280+
custom_labels, custom_options = _get_custom_context(field_labels_map)
281+
field_labels_map.update(custom_labels)
282+
249283
extended_profile_field_names = configuration_helpers.get_value('extended_profile_fields', [])
250284
for field_to_exclude in fields_already_showing:
251285
if field_to_exclude in extended_profile_field_names:
252286
extended_profile_field_names.remove(field_to_exclude)
253287

254-
extended_profile_field_options = configuration_helpers.get_value('EXTRA_FIELD_OPTIONS', [])
288+
extended_profile_field_options = configuration_helpers.get_value('EXTRA_FIELD_OPTIONS', custom_options)
255289
extended_profile_field_option_tuples = {}
256290
for field in extended_profile_field_options.keys():
257291
field_options = extended_profile_field_options[field]

openedx/core/djangoapps/user_authn/views/registration_form.py

Lines changed: 96 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ class RegistrationFormFactory(object):
296296

297297
DEFAULT_FIELDS = ["email", "name", "username", "password"]
298298

299-
EXTRA_FIELDS = [
299+
EXTRA_FIELDS_BASE = [
300300
"confirm_email",
301301
"first_name",
302302
"last_name",
@@ -344,7 +344,7 @@ def __init__(self):
344344
self.field_handlers = {}
345345
valid_fields = self.DEFAULT_FIELDS + self.EXTRA_FIELDS
346346
for field_name in valid_fields:
347-
handler = getattr(self, "_add_{field_name}_field".format(field_name=field_name))
347+
handler = getattr(self, "_add_{field_name}_field".format(field_name=field_name), self._add_custom_field)
348348
self.field_handlers[field_name] = handler
349349

350350
field_order = configuration_helpers.get_value('REGISTRATION_FIELD_ORDER')
@@ -358,6 +358,20 @@ def __init__(self):
358358

359359
self.field_order = field_order
360360

361+
@property
362+
def EXTRA_FIELDS(self):
363+
"""eduNEXT: Property that returns extra fields list plus extended profile fields. This
364+
property allows us to add custom fields to the registration form using extended_profile_fields.
365+
"""
366+
extended_profile_fields = getattr(settings, 'extended_profile_fields', [])
367+
368+
# In case there's duplicates
369+
if set(self.EXTRA_FIELDS_BASE) != set(extended_profile_fields):
370+
difference = set(extended_profile_fields).difference(set(self.EXTRA_FIELDS_BASE))
371+
return self.EXTRA_FIELDS_BASE + list(difference)
372+
373+
return self.EXTRA_FIELDS_BASE + extended_profile_fields
374+
361375
def get_registration_form(self, request):
362376
"""Return a description of the registration form.
363377
This decouples clients from the API definition:
@@ -425,9 +439,12 @@ def get_registration_form(self, request):
425439
if field_name in self.DEFAULT_FIELDS:
426440
self.field_handlers[field_name](form_desc, required=True)
427441
elif self._is_field_visible(field_name):
428-
self.field_handlers[field_name](
442+
field_handler = self.field_handlers[field_name]
443+
extra_field = {"field_name": field_name} if field_handler.__name__ == "_add_custom_field" else {}
444+
field_handler(
429445
form_desc,
430-
required=self._is_field_required(field_name)
446+
required=self._is_field_required(field_name),
447+
**extra_field
431448
)
432449

433450
return form_desc
@@ -625,6 +642,77 @@ def _add_year_of_birth_field(self, form_desc, required=True):
625642
required=required
626643
)
627644

645+
def _get_custom_field_dict(self, field_name):
646+
"""Given a field name searches for its definition dictionary.
647+
Arguments:
648+
field_name (str): the name of the field to search for.
649+
"""
650+
custom_fields = getattr(settings, "EDNX_CUSTOM_REGISTRATION_FIELDS", [])
651+
for field in custom_fields:
652+
if field.get("name") == field_name:
653+
return field
654+
return {}
655+
656+
def _get_custom_html_override(self, text_field, html_piece=None):
657+
"""Overrides field with html piece.
658+
Arguments:
659+
text_field: field to override. It must have the following format:
660+
"Here {} goes the HTML piece." In `{}` will be inserted the HTML piece.
661+
662+
Keyword Arguments:
663+
html_piece: string containing HTML components to be inserted.
664+
"""
665+
if html_piece:
666+
html_piece = HTML(html_piece) if isinstance(html_piece, six.string_types) else ""
667+
return Text(_(text_field)).format(html_piece) # pylint: disable=translation-of-non-string
668+
return text_field
669+
670+
def _add_custom_field(self, form_desc, required=True, **kwargs):
671+
"""Adds custom fields to a form description.
672+
Arguments:
673+
form_desc: A form description
674+
675+
Keyword Arguments:
676+
required (bool): Whether this field is required; defaults to False
677+
field_name (str): Name used to get field information when creating it.
678+
"""
679+
field_name = kwargs.pop("field_name")
680+
custom_field_dict = self._get_custom_field_dict(field_name)
681+
if not custom_field_dict:
682+
msg = "Field with name {} must have a definition dictionary.".format(field_name)
683+
raise ImproperlyConfigured(msg)
684+
685+
# Check to convert options:
686+
field_options = custom_field_dict.get("options")
687+
if field_options:
688+
field_options = [(six.text_type(option.lower()), option) for option in field_options]
689+
690+
# Set default option if applies:
691+
default_option = custom_field_dict.get("default")
692+
if default_option:
693+
form_desc.override_field_properties(
694+
field_name,
695+
default=default_option
696+
)
697+
698+
field_type = custom_field_dict.get("type")
699+
700+
form_desc.add_field(
701+
field_name,
702+
label=self._get_custom_html_override(
703+
custom_field_dict.get("label"),
704+
custom_field_dict.get("html_override"),
705+
),
706+
field_type=field_type,
707+
options=field_options,
708+
instructions=custom_field_dict.get("instructions"),
709+
placeholder=custom_field_dict.get("placeholder"),
710+
restrictions=custom_field_dict.get("restrictions"),
711+
include_default_option=bool(default_option) or field_type == "select",
712+
required=required,
713+
error_messages=custom_field_dict.get("errorMessages")
714+
)
715+
628716
def _add_field_with_configurable_select_options(self, field_name, field_label, form_desc, required=False):
629717
"""Add a field to a form description.
630718
If select options are given for this field, it will be a select type
@@ -637,11 +725,10 @@ def _add_field_with_configurable_select_options(self, field_name, field_label, f
637725
638726
Keyword Arguments:
639727
required (bool): Whether this field is required; defaults to False
640-
641728
"""
642-
643-
extra_field_options = configuration_helpers.get_value('EXTRA_FIELD_OPTIONS')
644-
if extra_field_options is None or extra_field_options.get(field_name) is None:
729+
custom_options = self._get_custom_field_dict(field_name).get("options")
730+
extra_field_options = configuration_helpers.get_value('EXTRA_FIELD_OPTIONS', {})
731+
if extra_field_options.get(field_name) is None and custom_options is None:
645732
field_type = "text"
646733
include_default_option = False
647734
options = None
@@ -650,7 +737,7 @@ def _add_field_with_configurable_select_options(self, field_name, field_label, f
650737
else:
651738
field_type = "select"
652739
include_default_option = True
653-
field_options = extra_field_options.get(field_name)
740+
field_options = extra_field_options.get(field_name, custom_options)
654741
options = [(six.text_type(option.lower()), option) for option in field_options]
655742
error_msg = ''
656743
error_msg = getattr(accounts, u'REQUIRED_FIELD_{}_SELECT_MSG'.format(field_name.upper()))

0 commit comments

Comments
 (0)