From 5402010d16dfdd1ea1d09d5e478c9e4bd58c76c8 Mon Sep 17 00:00:00 2001 From: jhrr Date: Wed, 4 Dec 2024 14:59:19 +0000 Subject: [PATCH] Allow hook override of user profile avatar url in admin tags (#12689) Fixes #12661 --- CHANGELOG.txt | 1 + CONTRIBUTORS.md | 1 + .../customization/admin_templates.md | 16 +++++++++++++ docs/reference/hooks.md | 24 +++++++++++++++++++ docs/releases/6.4.md | 1 + .../admin/templatetags/wagtailadmin_tags.py | 11 +++++++++ wagtail/admin/tests/test_templatetags.py | 19 +++++++++++++++ wagtail/test/testapp/wagtail_hooks.py | 9 +++++++ 8 files changed, 82 insertions(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index ed1e5a524d..ef65d9f4f5 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -15,6 +15,7 @@ Changelog * Only allow selection of valid new parents within the copy Page view (Mauro Soche) * Add `on_serve_page` hook to modify the serving chain of pages (Krystian Magdziarz, Dawid Bugajewski) * Add support for `WAGTAIL_GRAVATAR_PROVIDER_URL` URLs with query string parameters (Ayaan Qadri, Guilhem Saurel) + * Add `get_avatar_url` hook to customise user avatars (jhrr) * Fix: Improve handling of translations for bulk page action confirmation messages (Matt Westcott) * Fix: Ensure custom rich text feature icons are correctly handled when provided as a list of SVG paths (Temidayo Azeez, Joel William, LB (Ben) Johnston) * Fix: Ensure manual edits to `StreamField` values do not throw an error (Stefan Hammer) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index e5bbc70f71..aa05b4dbc5 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -862,6 +862,7 @@ * Mauro Soche * Krystian Magdziarz * Guilhem Saurel +* jhrr ## Translators diff --git a/docs/advanced_topics/customization/admin_templates.md b/docs/advanced_topics/customization/admin_templates.md index 61edba517b..5731205de8 100644 --- a/docs/advanced_topics/customization/admin_templates.md +++ b/docs/advanced_topics/customization/admin_templates.md @@ -85,6 +85,22 @@ To replace the welcome message on the dashboard, create a template file `dashboa {% block branding_welcome %}Welcome to Frank's Site{% endblock %} ``` +(custom_user_profile_avatar)= + +## Custom user profile avatar + +To render a user avatar other than the one sourced from the `UserProfile` model or from [gravatar](https://gravatar.com/), you can use the [`get_avatar_url`](#get_avatar_url) hook and resolve the avatar's image url as you see fit. + +For example, you might have an avatar on a `Profile` model in your own application that is keyed to the `auth.User` model in the familiar pattern. In that case, you could register your hook as the in following example, and the Wagtail admin avatar will be replaced with your own `Profile` avatar accordingly. + +```python +@hooks.register('get_avatar_url') +def get_profile_avatar(user, size): + return user.profile.avatar +``` + +Additionally, you can use the default `size` parameter that is passed in to the hook if you need to attach it to a request or do any further processing on your image. + (custom_user_interface_fonts)= ## Custom user interface fonts diff --git a/docs/reference/hooks.md b/docs/reference/hooks.md index 741804bf15..b6df664208 100644 --- a/docs/reference/hooks.md +++ b/docs/reference/hooks.md @@ -102,6 +102,30 @@ depth: 1 --- ``` +## Appearance + +Hooks for modifying the display and appearance of basic CMS features and furniture. + +(get_avatar_url)= + +### `get_avatar_url` + +Specify a custom user avatar to be displayed in the Wagtail admin. The callable passed to this hook should accept a `user` object and a `size` parameter that can be used in any resize or thumbnail processing you might need to do. + +```python +from datetime import datetime + +@hooks.register('get_avatar_url') +def get_profile_avatar(user, size): + today = datetime.now() + is_christmas_day = today.month == 12 and today.day == 25 + + if is_christmas_day: + return '/static/images/santa.png' + + return None +``` + ## Admin modules Hooks for building new areas of the admin interface (alongside pages, images, documents, and so on). diff --git a/docs/releases/6.4.md b/docs/releases/6.4.md index a18ba4b40a..c6bed00a7a 100644 --- a/docs/releases/6.4.md +++ b/docs/releases/6.4.md @@ -24,6 +24,7 @@ depth: 1 * Only allow selection of valid new parents within the copy Page view (Mauro Soche) * Add [`on_serve_page`](on_serve_page) hook to modify the serving chain of pages (Krystian Magdziarz, Dawid Bugajewski) * Add support for [`WAGTAIL_GRAVATAR_PROVIDER_URL`](wagtail_gravatar_provider_url) URLs with query string parameters (Ayaan Qadri, Guilhem Saurel) + * Add [`get_avatar_url`](get_avatar_url) hook to customise user avatars (jhrr) ### Bug fixes diff --git a/wagtail/admin/templatetags/wagtailadmin_tags.py b/wagtail/admin/templatetags/wagtailadmin_tags.py index d3916641e1..e6474c519f 100644 --- a/wagtail/admin/templatetags/wagtailadmin_tags.py +++ b/wagtail/admin/templatetags/wagtailadmin_tags.py @@ -654,8 +654,19 @@ def avatar_url(user, size=50, gravatar_only=False): """ A template tag that receives a user and size and return the appropriate avatar url for that user. + + If the 'get_avatar_url' hook is defined, then that will intercept this + logic and point to whatever resource that function returns. In this way, + users can swap out the Wagtail UserProfile avatar for some other image or + field of their own choosing without needing to alter anything on the + existing models. + Example usage: {% avatar_url request.user 50 %} + """ + for hook_fn in hooks.get_hooks("get_avatar_url"): + if url := hook_fn(user, size): + return url if ( not gravatar_only diff --git a/wagtail/admin/tests/test_templatetags.py b/wagtail/admin/tests/test_templatetags.py index 4cf4f345c3..b521c5c4c5 100644 --- a/wagtail/admin/tests/test_templatetags.py +++ b/wagtail/admin/tests/test_templatetags.py @@ -1,4 +1,5 @@ import json +import os import unittest from datetime import datetime, timedelta from datetime import timezone as dt_timezone @@ -31,6 +32,24 @@ from wagtail.users.models import UserProfile from wagtail.utils.deprecation import RemovedInWagtail70Warning +class TestAvatarUrlInterceptTemplateTag(WagtailTestUtils, TestCase): + def setUp(self): + self.test_user = self.create_user( + username="testuser", + email="testuser@email.com", + password="password", + ) + + def test_get_avatar_url_undefined(self): + url = avatar_url(self.test_user) + self.assertIn("www.gravatar.com", url) + + @mock.patch.dict(os.environ, {"AVATAR_INTERCEPT": "True"}, clear=True) + def test_get_avatar_url_registered(self): + url = avatar_url(self.test_user) + self.assertEqual(url, "/some/avatar/fred.png") + + class TestAvatarTemplateTag(WagtailTestUtils, TestCase): def setUp(self): # Create a user diff --git a/wagtail/test/testapp/wagtail_hooks.py b/wagtail/test/testapp/wagtail_hooks.py index fb1d9bc6a7..7afe49a0f3 100644 --- a/wagtail/test/testapp/wagtail_hooks.py +++ b/wagtail/test/testapp/wagtail_hooks.py @@ -1,3 +1,5 @@ +import os + from django import forms from django.http import HttpResponse from django.utils.safestring import mark_safe @@ -432,3 +434,10 @@ def register_animated_advert_chooser_viewset(): @hooks.register("register_admin_viewset") def register_event_page_listing_viewset(): return event_page_listing_viewset + + +@hooks.register("get_avatar_url") +def register_avatar_intercept_url(user, size): + if os.environ.get("AVATAR_INTERCEPT"): + return "/some/avatar/fred.png" + return None