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

Use context processor for metadata in environment endpoint #4569

Merged
79 changes: 79 additions & 0 deletions hub/tests/test_i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
from django.test import TestCase

from hub.utils.i18n import I18nUtils
from kobo.static_lists import (
PROJECT_METADATA_DEFAULT_LABELS,
USER_METADATA_DEFAULT_LABELS,
)


class I18nTestCase(TestCase):
Expand All @@ -18,3 +22,78 @@ def test_welcome_message(self):
self.assertEqual(welcome_message_fr.raw, "Le message de bienvenue")
self.assertEqual(welcome_message.raw, "Global welcome message")
self.assertEqual(welcome_message_es.raw, welcome_message.raw)

def test_custom_label_translations(self):
def check_labels(field, default_labels, lang):
new_field = I18nUtils.set_custom_label(field, default_labels, lang)
assert new_field['name'] == field['name']
assert new_field['required'] == field['required']
assert new_field['label'] == field['label'][lang]

user_metadata_field = {
'name': 'Full name',
'required': False,
'label': {'default': 'Name', 'fr': 'Nom'},
}

project_metadata_field = {
'name': 'description',
'required': True,
'label': {'default': 'Description', 'fr': 'Details'},
}

check_labels(user_metadata_field, USER_METADATA_DEFAULT_LABELS, 'fr')
check_labels(
project_metadata_field, PROJECT_METADATA_DEFAULT_LABELS, 'fr'
)

def test_metadata_no_label_field(self):
def check_labels(field, default_labels, lang):
new_field = I18nUtils.set_custom_label(field, default_labels, lang)
assert new_field['name'] == field['name']
assert new_field['required'] == field['required']
assert new_field['label'] == default_labels[field['name']]

user_metadata_field = {
'name': 'full_name',
'required': False,
}

project_metadata_field = {
'name': 'description',
'required': True,
}

check_labels(user_metadata_field, USER_METADATA_DEFAULT_LABELS, 'fr')
check_labels(
project_metadata_field, PROJECT_METADATA_DEFAULT_LABELS, 'fr'
)

def test_custom_label_no_lang(self):
def check_labels(field, default_labels, lang):
new_field = I18nUtils.set_custom_label(field, default_labels, lang)
assert new_field['name'] == field['name']
assert new_field['required'] == field['required']
assert new_field['label'] == field['label']['default']

user_metadata_field = {
'name': 'full_name',
'required': False,
'label': {'default': 'Name'},
}

project_metadata_field = {
'name': 'description',
'required': True,
'label': {'default': 'Description'},
}
check_labels(
user_metadata_field,
USER_METADATA_DEFAULT_LABELS,
None
)
check_labels(
project_metadata_field,
PROJECT_METADATA_DEFAULT_LABELS,
None
)
42 changes: 36 additions & 6 deletions hub/utils/i18n.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# coding: utf-8
import copy
import json
import logging

Expand All @@ -12,7 +13,6 @@


class I18nUtils:

@staticmethod
def get_sitewide_message(slug="welcome_message", lang=None):
"""
Expand All @@ -33,12 +33,14 @@ def get_sitewide_message(slug="welcome_message", lang=None):
# - "<slug>"
# We order the results by the length of the slug to be sure
# localized version comes first.
sitewide_message = SitewideMessage.objects\
.filter(
Q(slug="{}_{}".format(slug, language)) |
Q(slug="{}".format(slug)))\
JacquelineMorrissette marked this conversation as resolved.
Show resolved Hide resolved
.order_by(Length("slug").desc())\
sitewide_message = (
SitewideMessage.objects.filter(
Q(slug="{}_{}".format(slug, language))
| Q(slug="{}".format(slug))
)
.order_by(Length("slug").desc())
JacquelineMorrissette marked this conversation as resolved.
Show resolved Hide resolved
.first()
)

if sitewide_message is not None:
return sitewide_message.body
Expand Down Expand Up @@ -82,3 +84,31 @@ def get_mfa_help_text(lang=None):
constance.config.SUPPORT_EMAIL,
)
return message

@staticmethod
def set_custom_label(
field: dict, default_label_dict: dict, lang: str = None,
):
JacquelineMorrissette marked this conversation as resolved.
Show resolved Hide resolved
"""
Return the translated label of the user metadata fields
"""
# Get default value if lang is not specified
language = lang if lang else get_language()

# This copy is to make unit tests work properly
field = copy.deepcopy(field)
JacquelineMorrissette marked this conversation as resolved.
Show resolved Hide resolved

# Check to see if label exists
try:
label = field['label']
try:
translation = label[language]
except KeyError:
# Use the default value if language is not available
translation = label['default']
except KeyError:
translation = default_label_dict[field['name']]

field['label'] = translation

return field
13 changes: 7 additions & 6 deletions kobo/apps/accounts/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.utils.translation import get_language
from django.utils.translation import gettext_lazy as t

from kobo.static_lists import COUNTRIES
from kobo.static_lists import COUNTRIES, USER_METADATA_DEFAULT_LABELS


# Only these fields can be controlled by constance.config.USER_METADATA_FIELDS
Expand All @@ -32,15 +32,15 @@ def __init__(self, *args, **kwargs):

class KoboSignupMixin(forms.Form):
full_name = forms.CharField(
label=t('Full name'),
label=USER_METADATA_DEFAULT_LABELS['full_name'],
JacquelineMorrissette marked this conversation as resolved.
Show resolved Hide resolved
required=False,
)
organization = forms.CharField(
label=t('Organization name'),
label=USER_METADATA_DEFAULT_LABELS['organization'],
JacquelineMorrissette marked this conversation as resolved.
Show resolved Hide resolved
required=False,
)
gender = forms.ChoiceField(
label=t('Gender'),
label=USER_METADATA_DEFAULT_LABELS['gender'],
required=False,
widget=forms.RadioSelect,
choices=(
Expand All @@ -50,13 +50,13 @@ class KoboSignupMixin(forms.Form):
),
)
sector = forms.ChoiceField(
label=t('Sector'),
label=USER_METADATA_DEFAULT_LABELS['sector'],
required=False,
# Don't set choices here; set them in the constructor so that changes
# made in the Django admin interface do not require a server restart
)
country = forms.ChoiceField(
label=t('Country'),
label=USER_METADATA_DEFAULT_LABELS['country'],
required=False,
choices=(('', ''),) + COUNTRIES,
)
Expand Down Expand Up @@ -99,6 +99,7 @@ def __init__(self, *args, **kwargs):
continue

field = self.fields[field_name]
print(desired_field, flush=True)
JacquelineMorrissette marked this conversation as resolved.
Show resolved Hide resolved
field.required = desired_field.get('required', False)

if 'label' in desired_field.keys():
Expand Down
22 changes: 22 additions & 0 deletions kobo/static_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,25 @@
'either, then you will need to request assistance by '
'contacting [##support email##](mailto:##support email##).'
)

PROJECT_METADATA_DEFAULT_LABELS = {
'sector': t('Sector'),
'country': t('Country'),
'operational_purpose': t('Operational purpose'),
'collects_ppi': t('Collects PPI'),
'description': t('Description'),
}

USER_METADATA_DEFAULT_LABELS = {
'full_name': t('Full name'),
'organization': t('Organization'),
'organization_website': t('Organization website'),
'sector': t('Sector'),
'gender': t('Gender'),
'bio': t('Bio'),
'city': t('City'),
'country': t('Country'),
'twitter': t('Twitter'),
'linkedin': t('LinkedIn'),
'instagram': t('Instagram'),
}
29 changes: 28 additions & 1 deletion kpi/context_processors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# coding: utf-8
import constance
import json
import markdown
from django.conf import settings
from django.urls import reverse
Expand Down Expand Up @@ -40,6 +41,32 @@ def get_mfa_enabled():
}


def custom_label_translations(
request, metadata_configs: dict, default_labels: dict
):
JacquelineMorrissette marked this conversation as resolved.
Show resolved Hide resolved
"""
Returns custom labels and translations for the signup form
from the USER_METADATA_FIELDS constance config.
"""

# Get User Metadata Fields
loaded_metadata_fields = json.loads(metadata_configs)
JacquelineMorrissette marked this conversation as resolved.
Show resolved Hide resolved

# Check if each user metadata has a label
for metadata_field in loaded_metadata_fields:
if 'label' in metadata_field.keys():
metadata_field = I18nUtils.set_custom_label(
metadata_field, default_labels, None
)
else:
# If label is not available, use the default set in the SignupForm
try:
metadata_field['label'] = default_labels[metadata_field['name']]
except KeyError:
continue
return loaded_metadata_fields


def django_settings(request):
return {"stripe_enabled": settings.STRIPE_ENABLED}

Expand All @@ -50,7 +77,6 @@ def sitewide_messages(request):
custom text in django templates
"""
if request.path_info == reverse('account_signup'):

sitewide_message = I18nUtils.get_sitewide_message()
if sitewide_message is not None:
return {'welcome_message': sitewide_message}
Expand All @@ -63,6 +89,7 @@ class CombinedConfig:
An object that gets its attributes from both a dictionary (`extra_config`)
AND a django-constance LazyConfig object
"""

def __init__(self, constance_config, extra_config):
"""
constance_config: LazyConfig object
Expand Down
2 changes: 2 additions & 0 deletions kpi/deployment_backends/kc_access/shadow_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,8 @@ class Meta(ShadowModel.Meta):
# is using `LazyBooleanField` which is an integer behind the scene.
# We do not want to port this class to KPI only for one line of code.
is_mfa_active = models.PositiveSmallIntegerField(default=False)
password_date_changed = models.DateTimeField(null=True, blank=True)
validated_password = models.BooleanField(default=False)
JacquelineMorrissette marked this conversation as resolved.
Show resolved Hide resolved

@classmethod
def set_mfa_status(cls, user_id: int, is_active: bool):
Expand Down
33 changes: 30 additions & 3 deletions kpi/views/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@
from allauth.socialaccount.models import SocialApp

from hub.utils.i18n import I18nUtils
from kobo.static_lists import COUNTRIES
from kobo.static_lists import (
COUNTRIES,
PROJECT_METADATA_DEFAULT_LABELS,
USER_METADATA_DEFAULT_LABELS
)
from kobo.apps.hook.constants import SUBMISSION_PLACEHOLDER
from kobo.apps.accounts.mfa.models import MfaAvailableToUser
from kpi.context_processors import custom_label_translations
from kpi.utils.object_permission import get_database_user


Expand Down Expand Up @@ -55,8 +60,6 @@ def process_simple_configs(cls):
JSON_CONFIGS = [
'FREE_TIER_DISPLAY',
'FREE_TIER_THRESHOLDS',
'PROJECT_METADATA_FIELDS',
'USER_METADATA_FIELDS',
]

@classmethod
Expand Down Expand Up @@ -131,6 +134,28 @@ def process_mfa_configs(request):
data['mfa_code_length'] = settings.TRENCH_AUTH['CODE_LENGTH']
return data

@staticmethod
def process_project_metadata_configs(request):
data = {
'project_metadata_fields': custom_label_translations(
request,
constance.config.PROJECT_METADATA_FIELDS,
PROJECT_METADATA_DEFAULT_LABELS,
)
}
return data

@staticmethod
def process_user_metadata_configs(request):
data = {
'user_metadata_fields': custom_label_translations(
request,
constance.config.USER_METADATA_FIELDS,
USER_METADATA_DEFAULT_LABELS,
)
}
return data

@staticmethod
def process_other_configs(request):
data = {}
Expand Down Expand Up @@ -162,5 +187,7 @@ def get(self, request, *args, **kwargs):
data.update(self.process_json_configs())
data.update(self.process_choice_configs())
data.update(self.process_mfa_configs(request))
data.update(self.process_project_metadata_configs(request))
data.update(self.process_user_metadata_configs(request))
data.update(self.process_other_configs(request))
return Response(data)