Browse Source

Allow hook override of user profile avatar url in admin tags (#12689)

Fixes #12661
jhrr 3 months ago
parent
commit
5402010d16

+ 1 - 0
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)

+ 1 - 0
CONTRIBUTORS.md

@@ -862,6 +862,7 @@
 * Mauro Soche
 * Krystian Magdziarz
 * Guilhem Saurel
+* jhrr
 
 ## Translators
 

+ 16 - 0
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

+ 24 - 0
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).

+ 1 - 0
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
 

+ 11 - 0
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

+ 19 - 0
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

+ 9 - 0
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