We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Form media are not included in account edition with settings panels
### Issue Summary

I added some fields including a `RichTextField` to a custom `User` model. Then I added a `UserSettingsPanel(BaseSettingsPanel)` in a new tab, registered via `register_account_settings_panel`.

All worked but I was unable to get the Draftail editor.

The bug is very close to #4466. But I am unsure what is the way to properly fix it, due to panels. Moreover it seems that `form` is an undefined value in `wagtail/admin/templates/wagtailadmin/account/account.html`

### Steps to Reproduce

1. Start a new project with `wagtail start myproject`
2. Custom the User model and account forms this way: https://forge.cliss21.org/cliss21/services_pour_tous/commit/4ab71c92b999fd3dc2872d491f952404c2a935f8
3. Go to `/admin/account/`
3. I locally overcame it this way: https://forge.cliss21.org/cliss21/services_pour_tous/commit/669bf290b0b58a6bc532cd17c2523d3b23afe554

I have confirmed that this issue can be reproduced as described on a fresh Wagtail project.

PS: a link to https://docs.wagtail.io/en/stable/reference/hooks.html#register-account-settings-panel could be done from https://docs.wagtail.io/en/stable/advanced_topics/customisation/custom_user_models.html.

### Technical details

```
$ python --version
Python 3.9.2
$ pip show django | grep Version
Version: 3.2.4
$ pip show wagtail | grep Version:
Version: 2.13.2
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `wagtail/admin/views/account.py`
Content:
```
1 from collections import OrderedDict
2 
3 from django.conf import settings
4 from django.contrib import messages
5 from django.contrib.auth import update_session_auth_hash
6 from django.contrib.auth import views as auth_views
7 from django.db import transaction
8 from django.http import Http404
9 from django.shortcuts import redirect
10 from django.template.loader import render_to_string
11 from django.template.response import TemplateResponse
12 from django.urls import reverse, reverse_lazy
13 from django.utils.translation import gettext as _
14 from django.utils.translation import gettext_lazy, override
15 from django.views.decorators.debug import sensitive_post_parameters
16 
17 from wagtail.admin.forms.account import (
18     AvatarPreferencesForm, LocalePreferencesForm, NameEmailForm, NotificationPreferencesForm)
19 from wagtail.admin.forms.auth import LoginForm, PasswordChangeForm, PasswordResetForm
20 from wagtail.admin.localization import get_available_admin_languages, get_available_admin_time_zones
21 from wagtail.core import hooks
22 from wagtail.core.models import UserPagePermissionsProxy
23 from wagtail.users.models import UserProfile
24 from wagtail.utils.loading import get_custom_form
25 
26 
27 def get_user_login_form():
28     form_setting = 'WAGTAILADMIN_USER_LOGIN_FORM'
29     if hasattr(settings, form_setting):
30         return get_custom_form(form_setting)
31     else:
32         return LoginForm
33 
34 
35 # Helper functions to check password management settings to enable/disable views as appropriate.
36 # These are functions rather than class-level constants so that they can be overridden in tests
37 # by override_settings
38 
39 def password_management_enabled():
40     return getattr(settings, 'WAGTAIL_PASSWORD_MANAGEMENT_ENABLED', True)
41 
42 
43 def email_management_enabled():
44     return getattr(settings, 'WAGTAIL_EMAIL_MANAGEMENT_ENABLED', True)
45 
46 
47 def password_reset_enabled():
48     return getattr(settings, 'WAGTAIL_PASSWORD_RESET_ENABLED', password_management_enabled())
49 
50 
51 # Tabs
52 
53 
54 class SettingsTab:
55     def __init__(self, name, title, order=0):
56         self.name = name
57         self.title = title
58         self.order = order
59 
60 
61 profile_tab = SettingsTab('profile', gettext_lazy("Profile"), order=100)
62 notifications_tab = SettingsTab('notifications', gettext_lazy("Notifications"), order=200)
63 
64 
65 # Panels
66 
67 class BaseSettingsPanel:
68     name = ''
69     title = ''
70     tab = profile_tab
71     help_text = None
72     template_name = 'wagtailadmin/account/settings_panels/base.html'
73     form_class = None
74     form_object = 'user'
75 
76     def __init__(self, request, user, profile):
77         self.request = request
78         self.user = user
79         self.profile = profile
80 
81     def is_active(self):
82         """
83         Returns True to display the panel.
84         """
85         return True
86 
87     def get_form(self):
88         """
89         Returns an initialised form.
90         """
91         kwargs = {
92             'instance': self.profile if self.form_object == 'profile' else self.user,
93             'prefix': self.name
94         }
95 
96         if self.request.method == 'POST':
97             return self.form_class(self.request.POST, self.request.FILES, **kwargs)
98         else:
99             return self.form_class(**kwargs)
100 
101     def get_context_data(self):
102         """
103         Returns the template context to use when rendering the template.
104         """
105         return {
106             'form': self.get_form()
107         }
108 
109     def render(self):
110         """
111         Renders the panel using the template specified in .template_name and context from .get_context_data()
112         """
113         return render_to_string(self.template_name, self.get_context_data(), request=self.request)
114 
115 
116 class NameEmailSettingsPanel(BaseSettingsPanel):
117     name = 'name_email'
118     title = gettext_lazy('Name and Email')
119     order = 100
120     form_class = NameEmailForm
121 
122 
123 class AvatarSettingsPanel(BaseSettingsPanel):
124     name = 'avatar'
125     title = gettext_lazy('Profile picture')
126     order = 300
127     template_name = 'wagtailadmin/account/settings_panels/avatar.html'
128     form_class = AvatarPreferencesForm
129     form_object = 'profile'
130 
131 
132 class NotificationsSettingsPanel(BaseSettingsPanel):
133     name = 'notifications'
134     title = gettext_lazy('Notifications')
135     tab = notifications_tab
136     order = 100
137     form_class = NotificationPreferencesForm
138     form_object = 'profile'
139 
140     def is_active(self):
141         # Hide the panel if the user can't edit or publish pages
142         user_perms = UserPagePermissionsProxy(self.request.user)
143         if not user_perms.can_edit_pages() and not user_perms.can_publish_pages():
144             return False
145 
146         # Hide the panel if there are no notification preferences
147         return self.get_form().fields
148 
149 
150 class LocaleSettingsPanel(BaseSettingsPanel):
151     name = 'locale'
152     title = gettext_lazy('Locale')
153     order = 400
154     form_class = LocalePreferencesForm
155     form_object = 'profile'
156 
157     def is_active(self):
158         return len(get_available_admin_languages()) > 1 or len(get_available_admin_time_zones()) > 1
159 
160 
161 class ChangePasswordPanel(BaseSettingsPanel):
162     name = 'password'
163     title = gettext_lazy('Password')
164     order = 500
165     form_class = PasswordChangeForm
166 
167     def is_active(self):
168         return password_management_enabled() and self.user.has_usable_password()
169 
170     def get_form(self):
171         # Note: don't bind the form unless a field is specified
172         # This prevents the validation error from displaying if the user wishes to ignore this
173         bind_form = False
174         if self.request.method == 'POST':
175             bind_form = any([
176                 self.request.POST.get(self.name + '-old_password'),
177                 self.request.POST.get(self.name + '-new_password1'),
178                 self.request.POST.get(self.name + '-new_password2'),
179             ])
180 
181         if bind_form:
182             return self.form_class(self.user, self.request.POST, prefix=self.name)
183         else:
184             return self.form_class(self.user, prefix=self.name)
185 
186 
187 # Views
188 
189 @sensitive_post_parameters()
190 def account(request):
191     # Fetch the user and profile objects once and pass into each panel
192     # We need to use the same instances for all forms so they don't overwrite each other
193     user = request.user
194     profile = UserProfile.get_for_user(user)
195 
196     # Panels
197     panels = [
198         NameEmailSettingsPanel(request, user, profile),
199         AvatarSettingsPanel(request, user, profile),
200         NotificationsSettingsPanel(request, user, profile),
201         LocaleSettingsPanel(request, user, profile),
202         ChangePasswordPanel(request, user, profile),
203     ]
204     for fn in hooks.get_hooks('register_account_settings_panel'):
205         panel = fn(request, user, profile)
206         if panel and panel.is_active():
207             panels.append(panel)
208 
209     panels = [panel for panel in panels if panel.is_active()]
210 
211     # Get tabs and order them
212     tabs = list(set(panel.tab for panel in panels))
213     tabs.sort(key=lambda tab: tab.order)
214 
215     # Get dict of tabs to ordered panels
216     panels_by_tab = OrderedDict([(tab, []) for tab in tabs])
217     for panel in panels:
218         panels_by_tab[panel.tab].append(panel)
219     for tab, tab_panels in panels_by_tab.items():
220         tab_panels.sort(key=lambda panel: panel.order)
221 
222     if request.method == 'POST':
223         panel_forms = [panel.get_form() for panel in panels]
224 
225         if all(form.is_valid() or not form.is_bound for form in panel_forms):
226             with transaction.atomic():
227                 for form in panel_forms:
228                     if form.is_bound:
229                         form.save()
230 
231             # Prevent a password change from logging this user out
232             update_session_auth_hash(request, user)
233 
234             # Override the language when creating the success message
235             # If the user has changed their language in this request, the message should
236             # be in the new language, not the existing one
237             with override(profile.get_preferred_language()):
238                 messages.success(request, _("Your account settings have been changed successfully!"))
239 
240             return redirect('wagtailadmin_account')
241 
242     # Menu items
243     menu_items = []
244     for fn in hooks.get_hooks('register_account_menu_item'):
245         item = fn(request)
246         if item:
247             menu_items.append(item)
248 
249     return TemplateResponse(request, 'wagtailadmin/account/account.html', {
250         'panels_by_tab': panels_by_tab,
251         'menu_items': menu_items,
252     })
253 
254 
255 class PasswordResetEnabledViewMixin:
256     """
257     Class based view mixin that disables the view if password reset is disabled by one of the following settings:
258     - WAGTAIL_PASSWORD_RESET_ENABLED
259     - WAGTAIL_PASSWORD_MANAGEMENT_ENABLED
260     """
261     def dispatch(self, *args, **kwargs):
262         if not password_reset_enabled():
263             raise Http404
264 
265         return super().dispatch(*args, **kwargs)
266 
267 
268 class PasswordResetView(PasswordResetEnabledViewMixin, auth_views.PasswordResetView):
269     template_name = 'wagtailadmin/account/password_reset/form.html'
270     email_template_name = 'wagtailadmin/account/password_reset/email.txt'
271     subject_template_name = 'wagtailadmin/account/password_reset/email_subject.txt'
272     form_class = PasswordResetForm
273     success_url = reverse_lazy('wagtailadmin_password_reset_done')
274 
275 
276 class PasswordResetDoneView(PasswordResetEnabledViewMixin, auth_views.PasswordResetDoneView):
277     template_name = 'wagtailadmin/account/password_reset/done.html'
278 
279 
280 class PasswordResetConfirmView(PasswordResetEnabledViewMixin, auth_views.PasswordResetConfirmView):
281     template_name = 'wagtailadmin/account/password_reset/confirm.html'
282     success_url = reverse_lazy('wagtailadmin_password_reset_complete')
283 
284 
285 class PasswordResetCompleteView(PasswordResetEnabledViewMixin, auth_views.PasswordResetCompleteView):
286     template_name = 'wagtailadmin/account/password_reset/complete.html'
287 
288 
289 class LoginView(auth_views.LoginView):
290     template_name = 'wagtailadmin/login.html'
291 
292     def get_success_url(self):
293         return self.get_redirect_url() or reverse('wagtailadmin_home')
294 
295     def get(self, *args, **kwargs):
296         # If user is already logged in, redirect them to the dashboard
297         if self.request.user.is_authenticated and self.request.user.has_perm('wagtailadmin.access_admin'):
298             return redirect(self.get_success_url())
299 
300         return super().get(*args, **kwargs)
301 
302     def get_form_class(self):
303         return get_user_login_form()
304 
305     def get_context_data(self, **kwargs):
306         context = super().get_context_data(**kwargs)
307 
308         context['show_password_reset'] = password_reset_enabled()
309 
310         from django.contrib.auth import get_user_model
311         User = get_user_model()
312         context['username_field'] = User._meta.get_field(User.USERNAME_FIELD).verbose_name
313 
314         return context
315 
316 
317 class LogoutView(auth_views.LogoutView):
318     next_page = 'wagtailadmin_login'
319 
320     def dispatch(self, request, *args, **kwargs):
321         response = super().dispatch(request, *args, **kwargs)
322 
323         messages.success(self.request, _('You have been successfully logged out.'))
324         # By default, logging out will generate a fresh sessionid cookie. We want to use the
325         # absence of sessionid as an indication that front-end pages are being viewed by a
326         # non-logged-in user and are therefore cacheable, so we forcibly delete the cookie here.
327         response.delete_cookie(
328             settings.SESSION_COOKIE_NAME,
329             domain=settings.SESSION_COOKIE_DOMAIN,
330             path=settings.SESSION_COOKIE_PATH
331         )
332 
333         # HACK: pretend that the session hasn't been modified, so that SessionMiddleware
334         # won't override the above and write a new cookie.
335         self.request.session.modified = False
336 
337         return response
338
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/wagtail/admin/views/account.py b/wagtail/admin/views/account.py
--- a/wagtail/admin/views/account.py
+++ b/wagtail/admin/views/account.py
@@ -5,6 +5,7 @@
 from django.contrib.auth import update_session_auth_hash
 from django.contrib.auth import views as auth_views
 from django.db import transaction
+from django.forms import Media
 from django.http import Http404
 from django.shortcuts import redirect
 from django.template.loader import render_to_string
@@ -219,8 +220,9 @@
     for tab, tab_panels in panels_by_tab.items():
         tab_panels.sort(key=lambda panel: panel.order)
 
+    panel_forms = [panel.get_form() for panel in panels]
+
     if request.method == 'POST':
-        panel_forms = [panel.get_form() for panel in panels]
 
         if all(form.is_valid() or not form.is_bound for form in panel_forms):
             with transaction.atomic():
@@ -239,6 +241,10 @@
 
             return redirect('wagtailadmin_account')
 
+    media = Media()
+    for form in panel_forms:
+        media += form.media
+
     # Menu items
     menu_items = []
     for fn in hooks.get_hooks('register_account_menu_item'):
@@ -249,6 +255,7 @@
     return TemplateResponse(request, 'wagtailadmin/account/account.html', {
         'panels_by_tab': panels_by_tab,
         'menu_items': menu_items,
+        'media': media,
     })

{"golden_diff": "diff --git a/wagtail/admin/views/account.py b/wagtail/admin/views/account.py\n--- a/wagtail/admin/views/account.py\n+++ b/wagtail/admin/views/account.py\n@@ -5,6 +5,7 @@\n from django.contrib.auth import update_session_auth_hash\n from django.contrib.auth import views as auth_views\n from django.db import transaction\n+from django.forms import Media\n from django.http import Http404\n from django.shortcuts import redirect\n from django.template.loader import render_to_string\n@@ -219,8 +220,9 @@\n     for tab, tab_panels in panels_by_tab.items():\n         tab_panels.sort(key=lambda panel: panel.order)\n \n+    panel_forms = [panel.get_form() for panel in panels]\n+\n     if request.method == 'POST':\n-        panel_forms = [panel.get_form() for panel in panels]\n \n         if all(form.is_valid() or not form.is_bound for form in panel_forms):\n             with transaction.atomic():\n@@ -239,6 +241,10 @@\n \n             return redirect('wagtailadmin_account')\n \n+    media = Media()\n+    for form in panel_forms:\n+        media += form.media\n+\n     # Menu items\n     menu_items = []\n     for fn in hooks.get_hooks('register_account_menu_item'):\n@@ -249,6 +255,7 @@\n     return TemplateResponse(request, 'wagtailadmin/account/account.html', {\n         'panels_by_tab': panels_by_tab,\n         'menu_items': menu_items,\n+        'media': media,\n     })\n", "issue": "Form media are not included in account edition with settings panels\n### Issue Summary\r\n\r\nI added some fields including a `RichTextField` to a custom `User` model. Then I added a `UserSettingsPanel(BaseSettingsPanel)` in a new tab, registered via `register_account_settings_panel`.\r\n\r\nAll worked but I was unable to get the Draftail editor.\r\n\r\nThe bug is very close to #4466. But I am unsure what is the way to properly fix it, due to panels. Moreover it seems that `form` is an undefined value in `wagtail/admin/templates/wagtailadmin/account/account.html`\r\n\r\n### Steps to Reproduce\r\n\r\n1. Start a new project with `wagtail start myproject`\r\n2. Custom the User model and account forms this way: https://forge.cliss21.org/cliss21/services_pour_tous/commit/4ab71c92b999fd3dc2872d491f952404c2a935f8\r\n3. Go to `/admin/account/`\r\n3. I locally overcame it this way: https://forge.cliss21.org/cliss21/services_pour_tous/commit/669bf290b0b58a6bc532cd17c2523d3b23afe554\r\n\r\nI have confirmed that this issue can be reproduced as described on a fresh Wagtail project.\r\n\r\nPS: a link to https://docs.wagtail.io/en/stable/reference/hooks.html#register-account-settings-panel could be done from https://docs.wagtail.io/en/stable/advanced_topics/customisation/custom_user_models.html.\r\n\r\n### Technical details\r\n\r\n```\r\n$ python --version\r\nPython 3.9.2\r\n$ pip show django | grep Version\r\nVersion: 3.2.4\r\n$ pip show wagtail | grep Version:\r\nVersion: 2.13.2\r\n```\n", "before_files": [{"content": "from collections import OrderedDict\n\nfrom django.conf import settings\nfrom django.contrib import messages\nfrom django.contrib.auth import update_session_auth_hash\nfrom django.contrib.auth import views as auth_views\nfrom django.db import transaction\nfrom django.http import Http404\nfrom django.shortcuts import redirect\nfrom django.template.loader import render_to_string\nfrom django.template.response import TemplateResponse\nfrom django.urls import reverse, reverse_lazy\nfrom django.utils.translation import gettext as _\nfrom django.utils.translation import gettext_lazy, override\nfrom django.views.decorators.debug import sensitive_post_parameters\n\nfrom wagtail.admin.forms.account import (\n    AvatarPreferencesForm, LocalePreferencesForm, NameEmailForm, NotificationPreferencesForm)\nfrom wagtail.admin.forms.auth import LoginForm, PasswordChangeForm, PasswordResetForm\nfrom wagtail.admin.localization import get_available_admin_languages, get_available_admin_time_zones\nfrom wagtail.core import hooks\nfrom wagtail.core.models import UserPagePermissionsProxy\nfrom wagtail.users.models import UserProfile\nfrom wagtail.utils.loading import get_custom_form\n\n\ndef get_user_login_form():\n    form_setting = 'WAGTAILADMIN_USER_LOGIN_FORM'\n    if hasattr(settings, form_setting):\n        return get_custom_form(form_setting)\n    else:\n        return LoginForm\n\n\n# Helper functions to check password management settings to enable/disable views as appropriate.\n# These are functions rather than class-level constants so that they can be overridden in tests\n# by override_settings\n\ndef password_management_enabled():\n    return getattr(settings, 'WAGTAIL_PASSWORD_MANAGEMENT_ENABLED', True)\n\n\ndef email_management_enabled():\n    return getattr(settings, 'WAGTAIL_EMAIL_MANAGEMENT_ENABLED', True)\n\n\ndef password_reset_enabled():\n    return getattr(settings, 'WAGTAIL_PASSWORD_RESET_ENABLED', password_management_enabled())\n\n\n# Tabs\n\n\nclass SettingsTab:\n    def __init__(self, name, title, order=0):\n        self.name = name\n        self.title = title\n        self.order = order\n\n\nprofile_tab = SettingsTab('profile', gettext_lazy(\"Profile\"), order=100)\nnotifications_tab = SettingsTab('notifications', gettext_lazy(\"Notifications\"), order=200)\n\n\n# Panels\n\nclass BaseSettingsPanel:\n    name = ''\n    title = ''\n    tab = profile_tab\n    help_text = None\n    template_name = 'wagtailadmin/account/settings_panels/base.html'\n    form_class = None\n    form_object = 'user'\n\n    def __init__(self, request, user, profile):\n        self.request = request\n        self.user = user\n        self.profile = profile\n\n    def is_active(self):\n        \"\"\"\n        Returns True to display the panel.\n        \"\"\"\n        return True\n\n    def get_form(self):\n        \"\"\"\n        Returns an initialised form.\n        \"\"\"\n        kwargs = {\n            'instance': self.profile if self.form_object == 'profile' else self.user,\n            'prefix': self.name\n        }\n\n        if self.request.method == 'POST':\n            return self.form_class(self.request.POST, self.request.FILES, **kwargs)\n        else:\n            return self.form_class(**kwargs)\n\n    def get_context_data(self):\n        \"\"\"\n        Returns the template context to use when rendering the template.\n        \"\"\"\n        return {\n            'form': self.get_form()\n        }\n\n    def render(self):\n        \"\"\"\n        Renders the panel using the template specified in .template_name and context from .get_context_data()\n        \"\"\"\n        return render_to_string(self.template_name, self.get_context_data(), request=self.request)\n\n\nclass NameEmailSettingsPanel(BaseSettingsPanel):\n    name = 'name_email'\n    title = gettext_lazy('Name and Email')\n    order = 100\n    form_class = NameEmailForm\n\n\nclass AvatarSettingsPanel(BaseSettingsPanel):\n    name = 'avatar'\n    title = gettext_lazy('Profile picture')\n    order = 300\n    template_name = 'wagtailadmin/account/settings_panels/avatar.html'\n    form_class = AvatarPreferencesForm\n    form_object = 'profile'\n\n\nclass NotificationsSettingsPanel(BaseSettingsPanel):\n    name = 'notifications'\n    title = gettext_lazy('Notifications')\n    tab = notifications_tab\n    order = 100\n    form_class = NotificationPreferencesForm\n    form_object = 'profile'\n\n    def is_active(self):\n        # Hide the panel if the user can't edit or publish pages\n        user_perms = UserPagePermissionsProxy(self.request.user)\n        if not user_perms.can_edit_pages() and not user_perms.can_publish_pages():\n            return False\n\n        # Hide the panel if there are no notification preferences\n        return self.get_form().fields\n\n\nclass LocaleSettingsPanel(BaseSettingsPanel):\n    name = 'locale'\n    title = gettext_lazy('Locale')\n    order = 400\n    form_class = LocalePreferencesForm\n    form_object = 'profile'\n\n    def is_active(self):\n        return len(get_available_admin_languages()) > 1 or len(get_available_admin_time_zones()) > 1\n\n\nclass ChangePasswordPanel(BaseSettingsPanel):\n    name = 'password'\n    title = gettext_lazy('Password')\n    order = 500\n    form_class = PasswordChangeForm\n\n    def is_active(self):\n        return password_management_enabled() and self.user.has_usable_password()\n\n    def get_form(self):\n        # Note: don't bind the form unless a field is specified\n        # This prevents the validation error from displaying if the user wishes to ignore this\n        bind_form = False\n        if self.request.method == 'POST':\n            bind_form = any([\n                self.request.POST.get(self.name + '-old_password'),\n                self.request.POST.get(self.name + '-new_password1'),\n                self.request.POST.get(self.name + '-new_password2'),\n            ])\n\n        if bind_form:\n            return self.form_class(self.user, self.request.POST, prefix=self.name)\n        else:\n            return self.form_class(self.user, prefix=self.name)\n\n\n# Views\n\n@sensitive_post_parameters()\ndef account(request):\n    # Fetch the user and profile objects once and pass into each panel\n    # We need to use the same instances for all forms so they don't overwrite each other\n    user = request.user\n    profile = UserProfile.get_for_user(user)\n\n    # Panels\n    panels = [\n        NameEmailSettingsPanel(request, user, profile),\n        AvatarSettingsPanel(request, user, profile),\n        NotificationsSettingsPanel(request, user, profile),\n        LocaleSettingsPanel(request, user, profile),\n        ChangePasswordPanel(request, user, profile),\n    ]\n    for fn in hooks.get_hooks('register_account_settings_panel'):\n        panel = fn(request, user, profile)\n        if panel and panel.is_active():\n            panels.append(panel)\n\n    panels = [panel for panel in panels if panel.is_active()]\n\n    # Get tabs and order them\n    tabs = list(set(panel.tab for panel in panels))\n    tabs.sort(key=lambda tab: tab.order)\n\n    # Get dict of tabs to ordered panels\n    panels_by_tab = OrderedDict([(tab, []) for tab in tabs])\n    for panel in panels:\n        panels_by_tab[panel.tab].append(panel)\n    for tab, tab_panels in panels_by_tab.items():\n        tab_panels.sort(key=lambda panel: panel.order)\n\n    if request.method == 'POST':\n        panel_forms = [panel.get_form() for panel in panels]\n\n        if all(form.is_valid() or not form.is_bound for form in panel_forms):\n            with transaction.atomic():\n                for form in panel_forms:\n                    if form.is_bound:\n                        form.save()\n\n            # Prevent a password change from logging this user out\n            update_session_auth_hash(request, user)\n\n            # Override the language when creating the success message\n            # If the user has changed their language in this request, the message should\n            # be in the new language, not the existing one\n            with override(profile.get_preferred_language()):\n                messages.success(request, _(\"Your account settings have been changed successfully!\"))\n\n            return redirect('wagtailadmin_account')\n\n    # Menu items\n    menu_items = []\n    for fn in hooks.get_hooks('register_account_menu_item'):\n        item = fn(request)\n        if item:\n            menu_items.append(item)\n\n    return TemplateResponse(request, 'wagtailadmin/account/account.html', {\n        'panels_by_tab': panels_by_tab,\n        'menu_items': menu_items,\n    })\n\n\nclass PasswordResetEnabledViewMixin:\n    \"\"\"\n    Class based view mixin that disables the view if password reset is disabled by one of the following settings:\n    - WAGTAIL_PASSWORD_RESET_ENABLED\n    - WAGTAIL_PASSWORD_MANAGEMENT_ENABLED\n    \"\"\"\n    def dispatch(self, *args, **kwargs):\n        if not password_reset_enabled():\n            raise Http404\n\n        return super().dispatch(*args, **kwargs)\n\n\nclass PasswordResetView(PasswordResetEnabledViewMixin, auth_views.PasswordResetView):\n    template_name = 'wagtailadmin/account/password_reset/form.html'\n    email_template_name = 'wagtailadmin/account/password_reset/email.txt'\n    subject_template_name = 'wagtailadmin/account/password_reset/email_subject.txt'\n    form_class = PasswordResetForm\n    success_url = reverse_lazy('wagtailadmin_password_reset_done')\n\n\nclass PasswordResetDoneView(PasswordResetEnabledViewMixin, auth_views.PasswordResetDoneView):\n    template_name = 'wagtailadmin/account/password_reset/done.html'\n\n\nclass PasswordResetConfirmView(PasswordResetEnabledViewMixin, auth_views.PasswordResetConfirmView):\n    template_name = 'wagtailadmin/account/password_reset/confirm.html'\n    success_url = reverse_lazy('wagtailadmin_password_reset_complete')\n\n\nclass PasswordResetCompleteView(PasswordResetEnabledViewMixin, auth_views.PasswordResetCompleteView):\n    template_name = 'wagtailadmin/account/password_reset/complete.html'\n\n\nclass LoginView(auth_views.LoginView):\n    template_name = 'wagtailadmin/login.html'\n\n    def get_success_url(self):\n        return self.get_redirect_url() or reverse('wagtailadmin_home')\n\n    def get(self, *args, **kwargs):\n        # If user is already logged in, redirect them to the dashboard\n        if self.request.user.is_authenticated and self.request.user.has_perm('wagtailadmin.access_admin'):\n            return redirect(self.get_success_url())\n\n        return super().get(*args, **kwargs)\n\n    def get_form_class(self):\n        return get_user_login_form()\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n\n        context['show_password_reset'] = password_reset_enabled()\n\n        from django.contrib.auth import get_user_model\n        User = get_user_model()\n        context['username_field'] = User._meta.get_field(User.USERNAME_FIELD).verbose_name\n\n        return context\n\n\nclass LogoutView(auth_views.LogoutView):\n    next_page = 'wagtailadmin_login'\n\n    def dispatch(self, request, *args, **kwargs):\n        response = super().dispatch(request, *args, **kwargs)\n\n        messages.success(self.request, _('You have been successfully logged out.'))\n        # By default, logging out will generate a fresh sessionid cookie. We want to use the\n        # absence of sessionid as an indication that front-end pages are being viewed by a\n        # non-logged-in user and are therefore cacheable, so we forcibly delete the cookie here.\n        response.delete_cookie(\n            settings.SESSION_COOKIE_NAME,\n            domain=settings.SESSION_COOKIE_DOMAIN,\n            path=settings.SESSION_COOKIE_PATH\n        )\n\n        # HACK: pretend that the session hasn't been modified, so that SessionMiddleware\n        # won't override the above and write a new cookie.\n        self.request.session.modified = False\n\n        return response\n", "path": "wagtail/admin/views/account.py"}], "after_files": [{"content": "from collections import OrderedDict\n\nfrom django.conf import settings\nfrom django.contrib import messages\nfrom django.contrib.auth import update_session_auth_hash\nfrom django.contrib.auth import views as auth_views\nfrom django.db import transaction\nfrom django.forms import Media\nfrom django.http import Http404\nfrom django.shortcuts import redirect\nfrom django.template.loader import render_to_string\nfrom django.template.response import TemplateResponse\nfrom django.urls import reverse, reverse_lazy\nfrom django.utils.translation import gettext as _\nfrom django.utils.translation import gettext_lazy, override\nfrom django.views.decorators.debug import sensitive_post_parameters\n\nfrom wagtail.admin.forms.account import (\n    AvatarPreferencesForm, LocalePreferencesForm, NameEmailForm, NotificationPreferencesForm)\nfrom wagtail.admin.forms.auth import LoginForm, PasswordChangeForm, PasswordResetForm\nfrom wagtail.admin.localization import get_available_admin_languages, get_available_admin_time_zones\nfrom wagtail.core import hooks\nfrom wagtail.core.models import UserPagePermissionsProxy\nfrom wagtail.users.models import UserProfile\nfrom wagtail.utils.loading import get_custom_form\n\n\ndef get_user_login_form():\n    form_setting = 'WAGTAILADMIN_USER_LOGIN_FORM'\n    if hasattr(settings, form_setting):\n        return get_custom_form(form_setting)\n    else:\n        return LoginForm\n\n\n# Helper functions to check password management settings to enable/disable views as appropriate.\n# These are functions rather than class-level constants so that they can be overridden in tests\n# by override_settings\n\ndef password_management_enabled():\n    return getattr(settings, 'WAGTAIL_PASSWORD_MANAGEMENT_ENABLED', True)\n\n\ndef email_management_enabled():\n    return getattr(settings, 'WAGTAIL_EMAIL_MANAGEMENT_ENABLED', True)\n\n\ndef password_reset_enabled():\n    return getattr(settings, 'WAGTAIL_PASSWORD_RESET_ENABLED', password_management_enabled())\n\n\n# Tabs\n\n\nclass SettingsTab:\n    def __init__(self, name, title, order=0):\n        self.name = name\n        self.title = title\n        self.order = order\n\n\nprofile_tab = SettingsTab('profile', gettext_lazy(\"Profile\"), order=100)\nnotifications_tab = SettingsTab('notifications', gettext_lazy(\"Notifications\"), order=200)\n\n\n# Panels\n\nclass BaseSettingsPanel:\n    name = ''\n    title = ''\n    tab = profile_tab\n    help_text = None\n    template_name = 'wagtailadmin/account/settings_panels/base.html'\n    form_class = None\n    form_object = 'user'\n\n    def __init__(self, request, user, profile):\n        self.request = request\n        self.user = user\n        self.profile = profile\n\n    def is_active(self):\n        \"\"\"\n        Returns True to display the panel.\n        \"\"\"\n        return True\n\n    def get_form(self):\n        \"\"\"\n        Returns an initialised form.\n        \"\"\"\n        kwargs = {\n            'instance': self.profile if self.form_object == 'profile' else self.user,\n            'prefix': self.name\n        }\n\n        if self.request.method == 'POST':\n            return self.form_class(self.request.POST, self.request.FILES, **kwargs)\n        else:\n            return self.form_class(**kwargs)\n\n    def get_context_data(self):\n        \"\"\"\n        Returns the template context to use when rendering the template.\n        \"\"\"\n        return {\n            'form': self.get_form()\n        }\n\n    def render(self):\n        \"\"\"\n        Renders the panel using the template specified in .template_name and context from .get_context_data()\n        \"\"\"\n        return render_to_string(self.template_name, self.get_context_data(), request=self.request)\n\n\nclass NameEmailSettingsPanel(BaseSettingsPanel):\n    name = 'name_email'\n    title = gettext_lazy('Name and Email')\n    order = 100\n    form_class = NameEmailForm\n\n\nclass AvatarSettingsPanel(BaseSettingsPanel):\n    name = 'avatar'\n    title = gettext_lazy('Profile picture')\n    order = 300\n    template_name = 'wagtailadmin/account/settings_panels/avatar.html'\n    form_class = AvatarPreferencesForm\n    form_object = 'profile'\n\n\nclass NotificationsSettingsPanel(BaseSettingsPanel):\n    name = 'notifications'\n    title = gettext_lazy('Notifications')\n    tab = notifications_tab\n    order = 100\n    form_class = NotificationPreferencesForm\n    form_object = 'profile'\n\n    def is_active(self):\n        # Hide the panel if the user can't edit or publish pages\n        user_perms = UserPagePermissionsProxy(self.request.user)\n        if not user_perms.can_edit_pages() and not user_perms.can_publish_pages():\n            return False\n\n        # Hide the panel if there are no notification preferences\n        return self.get_form().fields\n\n\nclass LocaleSettingsPanel(BaseSettingsPanel):\n    name = 'locale'\n    title = gettext_lazy('Locale')\n    order = 400\n    form_class = LocalePreferencesForm\n    form_object = 'profile'\n\n    def is_active(self):\n        return len(get_available_admin_languages()) > 1 or len(get_available_admin_time_zones()) > 1\n\n\nclass ChangePasswordPanel(BaseSettingsPanel):\n    name = 'password'\n    title = gettext_lazy('Password')\n    order = 500\n    form_class = PasswordChangeForm\n\n    def is_active(self):\n        return password_management_enabled() and self.user.has_usable_password()\n\n    def get_form(self):\n        # Note: don't bind the form unless a field is specified\n        # This prevents the validation error from displaying if the user wishes to ignore this\n        bind_form = False\n        if self.request.method == 'POST':\n            bind_form = any([\n                self.request.POST.get(self.name + '-old_password'),\n                self.request.POST.get(self.name + '-new_password1'),\n                self.request.POST.get(self.name + '-new_password2'),\n            ])\n\n        if bind_form:\n            return self.form_class(self.user, self.request.POST, prefix=self.name)\n        else:\n            return self.form_class(self.user, prefix=self.name)\n\n\n# Views\n\n@sensitive_post_parameters()\ndef account(request):\n    # Fetch the user and profile objects once and pass into each panel\n    # We need to use the same instances for all forms so they don't overwrite each other\n    user = request.user\n    profile = UserProfile.get_for_user(user)\n\n    # Panels\n    panels = [\n        NameEmailSettingsPanel(request, user, profile),\n        AvatarSettingsPanel(request, user, profile),\n        NotificationsSettingsPanel(request, user, profile),\n        LocaleSettingsPanel(request, user, profile),\n        ChangePasswordPanel(request, user, profile),\n    ]\n    for fn in hooks.get_hooks('register_account_settings_panel'):\n        panel = fn(request, user, profile)\n        if panel and panel.is_active():\n            panels.append(panel)\n\n    panels = [panel for panel in panels if panel.is_active()]\n\n    # Get tabs and order them\n    tabs = list(set(panel.tab for panel in panels))\n    tabs.sort(key=lambda tab: tab.order)\n\n    # Get dict of tabs to ordered panels\n    panels_by_tab = OrderedDict([(tab, []) for tab in tabs])\n    for panel in panels:\n        panels_by_tab[panel.tab].append(panel)\n    for tab, tab_panels in panels_by_tab.items():\n        tab_panels.sort(key=lambda panel: panel.order)\n\n    panel_forms = [panel.get_form() for panel in panels]\n\n    if request.method == 'POST':\n\n        if all(form.is_valid() or not form.is_bound for form in panel_forms):\n            with transaction.atomic():\n                for form in panel_forms:\n                    if form.is_bound:\n                        form.save()\n\n            # Prevent a password change from logging this user out\n            update_session_auth_hash(request, user)\n\n            # Override the language when creating the success message\n            # If the user has changed their language in this request, the message should\n            # be in the new language, not the existing one\n            with override(profile.get_preferred_language()):\n                messages.success(request, _(\"Your account settings have been changed successfully!\"))\n\n            return redirect('wagtailadmin_account')\n\n    media = Media()\n    for form in panel_forms:\n        media += form.media\n\n    # Menu items\n    menu_items = []\n    for fn in hooks.get_hooks('register_account_menu_item'):\n        item = fn(request)\n        if item:\n            menu_items.append(item)\n\n    return TemplateResponse(request, 'wagtailadmin/account/account.html', {\n        'panels_by_tab': panels_by_tab,\n        'menu_items': menu_items,\n        'media': media,\n    })\n\n\nclass PasswordResetEnabledViewMixin:\n    \"\"\"\n    Class based view mixin that disables the view if password reset is disabled by one of the following settings:\n    - WAGTAIL_PASSWORD_RESET_ENABLED\n    - WAGTAIL_PASSWORD_MANAGEMENT_ENABLED\n    \"\"\"\n    def dispatch(self, *args, **kwargs):\n        if not password_reset_enabled():\n            raise Http404\n\n        return super().dispatch(*args, **kwargs)\n\n\nclass PasswordResetView(PasswordResetEnabledViewMixin, auth_views.PasswordResetView):\n    template_name = 'wagtailadmin/account/password_reset/form.html'\n    email_template_name = 'wagtailadmin/account/password_reset/email.txt'\n    subject_template_name = 'wagtailadmin/account/password_reset/email_subject.txt'\n    form_class = PasswordResetForm\n    success_url = reverse_lazy('wagtailadmin_password_reset_done')\n\n\nclass PasswordResetDoneView(PasswordResetEnabledViewMixin, auth_views.PasswordResetDoneView):\n    template_name = 'wagtailadmin/account/password_reset/done.html'\n\n\nclass PasswordResetConfirmView(PasswordResetEnabledViewMixin, auth_views.PasswordResetConfirmView):\n    template_name = 'wagtailadmin/account/password_reset/confirm.html'\n    success_url = reverse_lazy('wagtailadmin_password_reset_complete')\n\n\nclass PasswordResetCompleteView(PasswordResetEnabledViewMixin, auth_views.PasswordResetCompleteView):\n    template_name = 'wagtailadmin/account/password_reset/complete.html'\n\n\nclass LoginView(auth_views.LoginView):\n    template_name = 'wagtailadmin/login.html'\n\n    def get_success_url(self):\n        return self.get_redirect_url() or reverse('wagtailadmin_home')\n\n    def get(self, *args, **kwargs):\n        # If user is already logged in, redirect them to the dashboard\n        if self.request.user.is_authenticated and self.request.user.has_perm('wagtailadmin.access_admin'):\n            return redirect(self.get_success_url())\n\n        return super().get(*args, **kwargs)\n\n    def get_form_class(self):\n        return get_user_login_form()\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n\n        context['show_password_reset'] = password_reset_enabled()\n\n        from django.contrib.auth import get_user_model\n        User = get_user_model()\n        context['username_field'] = User._meta.get_field(User.USERNAME_FIELD).verbose_name\n\n        return context\n\n\nclass LogoutView(auth_views.LogoutView):\n    next_page = 'wagtailadmin_login'\n\n    def dispatch(self, request, *args, **kwargs):\n        response = super().dispatch(request, *args, **kwargs)\n\n        messages.success(self.request, _('You have been successfully logged out.'))\n        # By default, logging out will generate a fresh sessionid cookie. We want to use the\n        # absence of sessionid as an indication that front-end pages are being viewed by a\n        # non-logged-in user and are therefore cacheable, so we forcibly delete the cookie here.\n        response.delete_cookie(\n            settings.SESSION_COOKIE_NAME,\n            domain=settings.SESSION_COOKIE_DOMAIN,\n            path=settings.SESSION_COOKIE_PATH\n        )\n\n        # HACK: pretend that the session hasn't been modified, so that SessionMiddleware\n        # won't override the above and write a new cookie.\n        self.request.session.modified = False\n\n        return response\n", "path": "wagtail/admin/views/account.py"}]}