diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index c075a7b2a1..297b5b9533 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -24,6 +24,7 @@ Changelog
* Added `heading` kwarg to `InlinePanel` to allow heading to be set independently of button label (Adrian Turjak)
* The value type returned from a `StructBlock` can now be customised (LB (Ben Johnston))
* Added `bgcolor` image operation (Karl Hobley)
+ * Added `WAGTAILADMIN_USER_LOGIN_FORM` setting for overriding the admin login form (Mike Dingjan)
* Fix: Do not remove stopwords when generating slugs from non-ASCII titles, to avoid issues with incorrect word boundaries (Sævar Öfjörð Magnússon)
* Fix: The PostgreSQL search backend now preserves ordering of the `QuerySet` when searching with `order_by_relevance=False` (Bertrand Bordage)
* Fix: Using `modeladmin_register` as a decorator no longer replaces the decorated class with `None` (Tim Heap)
diff --git a/docs/advanced_topics/settings.rst b/docs/advanced_topics/settings.rst
index 4d69bd09c6..f0cb3f3058 100644
--- a/docs/advanced_topics/settings.rst
+++ b/docs/advanced_topics/settings.rst
@@ -224,6 +224,13 @@ Dashboard
This setting lets you change the number of items shown at 'Your most recent edits' on the dashboard.
+.. code-block:: python
+
+ WAGTAILADMIN_USER_LOGIN_FORM = 'users.forms.LoginForm'
+
+Allows the default ``LoginForm`` to be extended with extra fields.
+
+
Images
------
diff --git a/docs/releases/2.0.rst b/docs/releases/2.0.rst
index efd96a4260..d618da7d39 100644
--- a/docs/releases/2.0.rst
+++ b/docs/releases/2.0.rst
@@ -37,6 +37,7 @@ Other features
* Added ``heading`` kwarg to ``InlinePanel`` to allow heading to be set independently of button label (Adrian Turjak)
* The value type returned from a ``StructBlock`` can now be customised. See :ref:`custom_value_class_for_structblock` (LB (Ben Johnston))
* Added `bgcolor` image operation (Karl Hobley)
+ * Added ``WAGTAILADMIN_USER_LOGIN_FORM`` setting for overriding the admin login form (Mike Dingjan)
Bug fixes
~~~~~~~~~
diff --git a/wagtail/admin/forms.py b/wagtail/admin/forms.py
index 5c76dbea43..812c4131b9 100644
--- a/wagtail/admin/forms.py
+++ b/wagtail/admin/forms.py
@@ -64,19 +64,24 @@ class EmailLinkChooserForm(forms.Form):
class LoginForm(AuthenticationForm):
username = forms.CharField(
- max_length=254,
- widget=forms.TextInput(attrs={'tabindex': '1'}),
- )
+ max_length=254, widget=forms.TextInput(attrs={'tabindex': '1'}))
+
password = forms.CharField(
- widget=forms.PasswordInput(attrs={'placeholder': ugettext_lazy("Enter password"),
- 'tabindex': '2',
- }),
- )
+ widget=forms.PasswordInput(attrs={
+ 'tabindex': '2',
+ 'placeholder': ugettext_lazy("Enter password"),
+ }))
def __init__(self, request=None, *args, **kwargs):
super().__init__(request=request, *args, **kwargs)
- self.fields['username'].widget.attrs['placeholder'] = ugettext_lazy("Enter your %s") \
- % self.username_field.verbose_name
+ self.fields['username'].widget.attrs['placeholder'] = (
+ ugettext_lazy("Enter your %s") % self.username_field.verbose_name)
+
+ @property
+ def extra_fields(self):
+ for field_name, field in self.fields.items():
+ if field_name not in ['username', 'password']:
+ yield field_name, field
class PasswordResetForm(PasswordResetForm):
diff --git a/wagtail/admin/templates/wagtailadmin/login.html b/wagtail/admin/templates/wagtailadmin/login.html
index 4a44bf4056..ae293c4bd4 100644
--- a/wagtail/admin/templates/wagtailadmin/login.html
+++ b/wagtail/admin/templates/wagtailadmin/login.html
@@ -56,6 +56,18 @@
{% trans "Forgotten it?" %}
{% endif %}
+
+ {% block extra_fields %}
+ {% for field_name, field in form.extra_fields %}
+
+ {{ field.label_tag }}
+
+ {{ field }}
+
+
+ {% endfor %}
+ {% endblock extra_fields %}
+
{% comment %}
Removed until functionality exists
diff --git a/wagtail/admin/tests/test_forms.py b/wagtail/admin/tests/test_forms.py
new file mode 100644
index 0000000000..065a8d07ce
--- /dev/null
+++ b/wagtail/admin/tests/test_forms.py
@@ -0,0 +1,18 @@
+from django.forms.fields import CharField
+from django.test import TestCase
+
+from wagtail.admin.forms import LoginForm
+
+
+class CustomLoginForm(LoginForm):
+ captcha = CharField(
+ label='Captcha', help_text="should be in extra_fields()")
+
+
+class TestLoginForm(TestCase):
+
+ def test_extra_fields(self):
+ form = CustomLoginForm()
+ self.assertEqual(list(form.extra_fields), [
+ ('captcha', form.fields['captcha'])
+ ])
diff --git a/wagtail/admin/views/account.py b/wagtail/admin/views/account.py
index caddccbbda..fceda444b7 100644
--- a/wagtail/admin/views/account.py
+++ b/wagtail/admin/views/account.py
@@ -17,6 +17,15 @@ from wagtail.admin.utils import get_available_admin_languages
from wagtail.core.models import UserPagePermissionsProxy
from wagtail.users.forms import NotificationPreferencesForm, PreferredLanguageForm
from wagtail.users.models import UserProfile
+from wagtail.utils.loading import get_custom_form
+
+
+def get_user_login_form():
+ form_setting = 'WAGTAILADMIN_USER_LOGIN_FORM'
+ if hasattr(settings, form_setting):
+ return get_custom_form(form_setting)
+ else:
+ return forms.LoginForm
# Helper functions to check password management settings to enable/disable views as appropriate.
@@ -136,7 +145,7 @@ def login(request):
return auth_views.login(
request,
template_name='wagtailadmin/login.html',
- authentication_form=forms.LoginForm,
+ authentication_form=get_user_login_form(),
extra_context={
'show_password_reset': password_reset_enabled(),
'username_field': get_user_model().USERNAME_FIELD,
diff --git a/wagtail/users/views/users.py b/wagtail/users/views/users.py
index c3e1522ed2..2181e9da39 100644
--- a/wagtail/users/views/users.py
+++ b/wagtail/users/views/users.py
@@ -1,13 +1,12 @@
from django.conf import settings
from django.contrib.auth import get_user_model
-from django.core.exceptions import ImproperlyConfigured
from django.db.models import Q
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
-from django.utils.module_loading import import_string
from django.utils.translation import ugettext as _
from django.views.decorators.vary import vary_on_headers
+from wagtail.utils.loading import get_custom_form
from wagtail.utils.pagination import paginate
from wagtail.admin import messages
from wagtail.admin.forms import SearchForm
@@ -28,20 +27,10 @@ change_user_perm = "{0}.change_{1}".format(AUTH_USER_APP_LABEL, AUTH_USER_MODEL_
delete_user_perm = "{0}.delete_{1}".format(AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME.lower())
-def get_custom_user_form(form_setting):
- try:
- return import_string(getattr(settings, form_setting))
- except ImportError:
- raise ImproperlyConfigured(
- "%s refers to a form '%s' that is not available" %
- (form_setting, getattr(settings, form_setting))
- )
-
-
def get_user_creation_form():
form_setting = 'WAGTAIL_USER_CREATION_FORM'
if hasattr(settings, form_setting):
- return get_custom_user_form(form_setting)
+ return get_custom_form(form_setting)
else:
return UserCreationForm
@@ -49,7 +38,7 @@ def get_user_creation_form():
def get_user_edit_form():
form_setting = 'WAGTAIL_USER_EDIT_FORM'
if hasattr(settings, form_setting):
- return get_custom_user_form(form_setting)
+ return get_custom_form(form_setting)
else:
return UserEditForm
diff --git a/wagtail/utils/loading.py b/wagtail/utils/loading.py
new file mode 100644
index 0000000000..a1964791eb
--- /dev/null
+++ b/wagtail/utils/loading.py
@@ -0,0 +1,14 @@
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.utils.module_loading import import_string
+
+
+def get_custom_form(form_setting):
+ """Return custom form class if defined and available"""
+ try:
+ return import_string(getattr(settings, form_setting))
+ except ImportError:
+ raise ImproperlyConfigured(
+ "%s refers to a form '%s' that is not available" %
+ (form_setting, getattr(settings, form_setting))
+ )