user: allow customization of User.get_full_name() through templates (#72945)
gitea-wip/hobo/pipeline/pr-main This commit looks good
Details
gitea-wip/hobo/pipeline/pr-main This commit looks good
Details
This commit is contained in:
parent
65ff0ad9a6
commit
0c14103e4b
|
@ -288,6 +288,8 @@ if PROJECT_NAME != 'wcs' and 'authentic2' not in INSTALLED_APPS:
|
|||
'mellon.middleware.PassiveAuthenticationMiddleware',
|
||||
'hobo.provisionning.middleware.ProvisionningMiddleware',
|
||||
)
|
||||
if PROJECT_NAME != 'wcs':
|
||||
INSTALLED_APPS += ('hobo.user_name.apps.UserNameConfig',)
|
||||
|
||||
if 'authentic2' in INSTALLED_APPS:
|
||||
MIDDLEWARE = MIDDLEWARE + ('hobo.agent.authentic2.middleware.ProvisionningMiddleware',)
|
||||
|
|
|
@ -106,8 +106,9 @@ class RequestContextFilter(logging.Filter):
|
|||
if saml_identifier:
|
||||
record.user_uuid = saml_identifier.name_id
|
||||
record.user = record.user_uuid[:6]
|
||||
if hasattr(user, 'get_full_name') and user.get_full_name():
|
||||
record.user = record.user_display_name = user.get_full_name()
|
||||
if hasattr(user, 'original_get_full_name') and user.original_get_full_name():
|
||||
# record original full name, not templated version from user_name app
|
||||
record.user = record.user_display_name = user.original_get_full_name()
|
||||
if getattr(user, 'email', None):
|
||||
record.user = record.user_email = user.email
|
||||
if getattr(user, 'username', None):
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
import functools
|
||||
import logging
|
||||
|
||||
import django.db
|
||||
import django.template.exceptions
|
||||
from django.apps import AppConfig
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from . import utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_full_name(user):
|
||||
from hobo.agent.common.models import UserExtraAttributes
|
||||
|
||||
try:
|
||||
return utils.get_full_name_from_template(user)
|
||||
except (
|
||||
# There may have been an issue during provisionning
|
||||
UserExtraAttributes.DoesNotExist,
|
||||
# In case some templates are missing or invalid
|
||||
django.template.exceptions.TemplateDoesNotExist,
|
||||
django.template.exceptions.TemplateSyntaxError,
|
||||
) as e:
|
||||
logger.exception('hobo.user_name: cannot render user "%s" name', user)
|
||||
return user.original_get_full_name()
|
||||
|
||||
|
||||
def cached_extra_attributes(user):
|
||||
if 'authentic2' in settings.INSTALLED_APPS:
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
try:
|
||||
from authentic2.custom_user.models import User
|
||||
from authentic2.models import Attribute, AttributeValue
|
||||
except ImportError:
|
||||
return
|
||||
try:
|
||||
user_ct = ContentType.objects.get_for_model(User)
|
||||
except ContentType.DoesNotExist:
|
||||
return
|
||||
|
||||
extra_attributes = {}
|
||||
# we're in authentic, attributes are looked up differently
|
||||
for attribute in Attribute.objects.filter(disabled=False):
|
||||
atvs = AttributeValue.objects.filter(
|
||||
content_type=user_ct,
|
||||
object_id=user.id,
|
||||
attribute=attribute,
|
||||
)
|
||||
if not atvs:
|
||||
continue
|
||||
if not attribute.multiple:
|
||||
atv = atvs.first()
|
||||
extra_attributes[attribute.name] = atv.to_python()
|
||||
else:
|
||||
extra_attributes[attribute.name] = [atv.to_python() for atv in atvs]
|
||||
return extra_attributes
|
||||
|
||||
try:
|
||||
return user.extra_attributes.data
|
||||
except django.db.models.ObjectDoesNotExist:
|
||||
return {}
|
||||
|
||||
|
||||
class UserNameConfig(AppConfig):
|
||||
name = 'hobo.user_name'
|
||||
label = 'hobo_user_name'
|
||||
verbose_name = 'Hobo User Name'
|
||||
|
||||
def ready(self):
|
||||
"""
|
||||
We monkey-patch AUTH_USER_MODEL()
|
||||
to ensure consistency in the rendering of user name
|
||||
in the front-office, backoffice, emails, etc.
|
||||
"""
|
||||
logger.info('hobo.user_name: installing User.get_full_name customization…')
|
||||
User = get_user_model()
|
||||
|
||||
# to have a fallback when necessary if the new method crashes during render
|
||||
User.original_get_full_name = User.get_full_name
|
||||
# to replace the rendering everywhere in a consistent manner
|
||||
User.get_full_name = get_full_name
|
||||
# for easier access in templates
|
||||
User.cached_extra_attributes = functools.cached_property(cached_extra_attributes)
|
||||
User.cached_extra_attributes.__set_name__(User, 'cached_extra_attributes')
|
||||
# to avoid performance/recursion issues
|
||||
User.__str__ = User.original_get_full_name
|
||||
|
||||
if 'attributes' not in User.__dict__:
|
||||
User.attributes = functools.cached_property(cached_extra_attributes)
|
||||
User.attributes.__set_name__(User, 'attributes')
|
|
@ -0,0 +1,8 @@
|
|||
from django.template.loader import render_to_string
|
||||
|
||||
|
||||
def get_full_name_from_template(user):
|
||||
template_name = 'includes/user-info-user-name.html'
|
||||
context = {}
|
||||
context['user'] = user
|
||||
return render_to_string(template_name, context)
|
|
@ -1,11 +1,16 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
import hobo.test_utils
|
||||
|
||||
SETTINGS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
TESTS_DIR = os.path.join(os.path.dirname(SETTINGS_DIR), 'tests')
|
||||
sys.path.append(TESTS_DIR)
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
BROKER_URL = 'memory://'
|
||||
|
||||
INSTALLED_APPS += ('hobo.agent.common',)
|
||||
INSTALLED_APPS += ('hobo.agent.common', 'hobo.user_name.apps.UserNameConfig')
|
||||
|
||||
ALLOWED_HOSTS.append('localhost')
|
||||
|
||||
|
@ -32,6 +37,7 @@ DATABASES = {
|
|||
}
|
||||
}
|
||||
|
||||
TEMPLATES[0]['DIRS'].append('tests/templates') # pylint: disable=undefined-variable
|
||||
TEMPLATES[0]['OPTIONS'].setdefault('builtins', []).append('hobo.templatetags.hobo')
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{{ user.first_name }} {{ user.cached_extra_attributes.foo }}
|
|
@ -0,0 +1,37 @@
|
|||
import pytest
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from hobo.agent.common.models import UserExtraAttributes
|
||||
from hobo.user_name.utils import get_full_name_from_template
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def user(db):
|
||||
u = User.objects.create(
|
||||
first_name='Jane',
|
||||
last_name='Doe',
|
||||
)
|
||||
UserExtraAttributes.objects.create(user=u, data={'foo': 'bar'})
|
||||
return u
|
||||
|
||||
|
||||
def test_user_original_get_full_name(user):
|
||||
assert user.original_get_full_name() == 'Jane Doe'
|
||||
|
||||
|
||||
def test_user_cached_extra_attributes(user):
|
||||
assert user.cached_extra_attributes == {'foo': 'bar'}
|
||||
|
||||
|
||||
def test_user_cached_extra_attributes_missing_fallbacks_to_empty_dict(user):
|
||||
user.extra_attributes.delete()
|
||||
user.refresh_from_db()
|
||||
assert user.cached_extra_attributes == {}
|
||||
|
||||
|
||||
def test_user_get_full_name_from_template(user):
|
||||
assert get_full_name_from_template(user) == 'Jane bar'
|
||||
|
||||
|
||||
def test_user_get_full_name(user):
|
||||
assert user.get_full_name() == 'Jane bar'
|
|
@ -0,0 +1 @@
|
|||
{{ user.first_name }} {{ user.cached_extra_attributes.nicknames.0 }} {{ user.cached_extra_attributes.nicknames.2 }}
|
|
@ -0,0 +1,92 @@
|
|||
import pytest
|
||||
from authentic2.models import Attribute, AttributeValue
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import override_settings
|
||||
from tenant_schemas.utils import tenant_context
|
||||
|
||||
from hobo.agent.common.models import UserExtraAttributes
|
||||
from hobo.user_name.utils import get_full_name_from_template
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
TEMPLATES_DIR_CONFIG_INDIVIDUAL = ['tests/templates']
|
||||
TEMPLATES_DIR_CONFIG_MULTIPLE = ['tests_authentic/custom_templates']
|
||||
|
||||
|
||||
def test_get_full_name_from_template_utils_from_multiple_attrs(db, tenant, settings):
|
||||
with tenant_context(tenant):
|
||||
user = User.objects.create(
|
||||
first_name='Jane',
|
||||
last_name='Doe',
|
||||
)
|
||||
Attribute.objects.create(
|
||||
name='foo',
|
||||
label='Foo',
|
||||
kind='string',
|
||||
required=False,
|
||||
multiple=False,
|
||||
user_visible=True,
|
||||
user_editable=True,
|
||||
)
|
||||
Attribute.objects.create(
|
||||
name='nicknames',
|
||||
label='Nicknames',
|
||||
kind='string',
|
||||
required=False,
|
||||
multiple=True,
|
||||
user_visible=True,
|
||||
user_editable=True,
|
||||
)
|
||||
user.attributes.nicknames = ['Milly', 'Molly', 'Minnie']
|
||||
user.attributes.foo = 'bar'
|
||||
user.save()
|
||||
|
||||
templates_conf_individual = settings.TEMPLATES.copy()
|
||||
templates_conf_individual[0]['DIRS'] = TEMPLATES_DIR_CONFIG_INDIVIDUAL
|
||||
with override_settings(TEMPLATES=templates_conf_individual):
|
||||
assert get_full_name_from_template(user) == 'Jane bar'
|
||||
|
||||
templates_conf_multiple = settings.TEMPLATES.copy()
|
||||
templates_conf_multiple[0]['DIRS'] = TEMPLATES_DIR_CONFIG_MULTIPLE
|
||||
with override_settings(TEMPLATES=templates_conf_multiple):
|
||||
assert get_full_name_from_template(user) == 'Jane Milly Minnie'
|
||||
|
||||
|
||||
def test_get_full_name_from_template_accessor_from_multiple_attrs(db, tenant, settings):
|
||||
with tenant_context(tenant):
|
||||
user = User.objects.create(
|
||||
first_name='Jane',
|
||||
last_name='Doe',
|
||||
)
|
||||
Attribute.objects.create(
|
||||
name='foo',
|
||||
label='Foo',
|
||||
kind='string',
|
||||
required=False,
|
||||
multiple=False,
|
||||
user_visible=True,
|
||||
user_editable=True,
|
||||
)
|
||||
Attribute.objects.create(
|
||||
name='nicknames',
|
||||
label='Nicknames',
|
||||
kind='string',
|
||||
required=False,
|
||||
multiple=True,
|
||||
user_visible=True,
|
||||
user_editable=True,
|
||||
)
|
||||
user.attributes.nicknames = ['Milly', 'Molly', 'Minnie']
|
||||
user.attributes.foo = 'bar'
|
||||
user.save()
|
||||
|
||||
templates_conf_individual = settings.TEMPLATES.copy()
|
||||
templates_conf_individual[0]['DIRS'] = TEMPLATES_DIR_CONFIG_INDIVIDUAL
|
||||
with override_settings(TEMPLATES=templates_conf_individual):
|
||||
assert user.get_full_name() == 'Jane bar'
|
||||
|
||||
templates_conf_multiple = settings.TEMPLATES.copy()
|
||||
templates_conf_multiple[0]['DIRS'] = TEMPLATES_DIR_CONFIG_MULTIPLE
|
||||
with override_settings(TEMPLATES=templates_conf_multiple):
|
||||
assert user.get_full_name() == 'Jane Milly Minnie'
|
Loading…
Reference in New Issue