17

I'm currently trying to override the default form used in Django 1.4 when logging in to the admin site (my site uses an additional 'token' field required for users who opt in to Two Factor Authentication, and is mandatory for site staff). Django's default form does not support what I need. Currently, I've got a file in my templates/ directory called templates/admin/login.html, which seems to be correctly overriding the template used with the one I use throughout the rest of my site. The contents of the file are simply as below:

# admin/login.html:
{% extends "login.html" %}

The actual login form is as below:

# login.html:
{% load url from future %}<!DOCTYPE html>
<html>
    <head>
        <title>Please log in</title>
    </head>
    <body>
        <div id="loginform">
            <form method="post" action="{% url 'id.views.auth' %}">
                {% csrf_token %}
                <input type="hidden" name="next" value="{{ next }}" />
                {{ form.username.label_tag }}<br/>
                {{ form.username }}<br/>
                {{ form.password.label_tag }}<br/>
                {{ form.password }}<br/>
                {{ form.token.label_tag }}<br/>
                {{ form.token }}<br/>
                <input type="submit" value="Log In" />
           </form>
        </div>
    </body>
</html>

My issue is that the form provided works perfectly fine when accessed using my normal login URLs because I supply my own AuthenticationForm as the form to display, but through the Django Admin login route, Django likes to supply it's own form to this template and thus only the username and password fields render. Is there any way I can make this work, or is this something I am just better off 'hard coding' the HTML fields into the form for?

Bill F
  • 723
  • 11
  • 23
TC Fox
  • 980
  • 4
  • 13
  • 25

6 Answers6

21

Holá
I found a very simple solution.
You just need to modify the urls.py fle of the project (note, not the application one)

  1. In your PROJECT folder locate the file urls.py.
  2. Add this line to the imports section
    from your_app_name import views
  3. Locate this line
    url(r'^admin/', include(admin.site.urls))
  4. Add above that line the following
    url(r'^admin/login/', views.your_login_view),

This is an example

from django.conf.urls import include, url
from django.contrib import admin

from your_app import views

urlpatterns = [
    url(r'^your_app_start/', include('your_app.urls',namespace="your_app_name")),

    url(r'^admin/login/', views.your_app_login),
    url(r'^admin/', include(admin.site.urls)),
]
ePi272314
  • 12,557
  • 5
  • 50
  • 36
14

AdminSite has a login_form attribute that you can use to override the form used.

Subclass AdminSite and then use an instance of your subclass instead of django.contrib.admin.site to register your models in the admin, in urls.py, etc.

It's all in the documentation.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • 1
    This nearly works: the only problem I'm having now is that overriding this class causes the .autodiscover() method to stop working. Removing this removes all of my models from the admin site. I'm not sure exactly how to explicitly import admin models from an admin.py module in a different app, is this possible (or better, can we enable autodiscover() for my new subclassed AdminSite?) – TC Fox Jul 15 '12 at 07:48
13

This code in urls.py works fine for me (Django version 1.5.1):

from django.contrib import admin
from my.forms import AuthenticationForm

admin.autodiscover()
admin.site.login_form = AuthenticationForm
dgk
  • 330
  • 3
  • 5
  • Working well for me on Django 1.6.1 as well. – Rodrigo Deodoro Dec 23 '13 at 21:16
  • 1
    Just to complete this answer: if you're somehow changing fields of the form, you'll also need to override the `admin/login.html` template, typically overriding the `{% block content %}`. – Caumons Nov 24 '17 at 12:02
2

I know this is an old thread, but I wanted to add something it helped me find, in case it can help others.

I'm using a special remote auth (Shibboleth) and overriding the admin login_form wouldn't be enough. I have a view that sets cookies and return variables and redirects to the remote auth provider, etc.

So the way I did it was:

from my_app.views import user_login, user_logout
admin.autodiscover()
admin.site.login = user_login
admin.site.logout = user_logout

Works great! Thanks to @dgk

Rob L
  • 3,634
  • 2
  • 19
  • 38
1

I redirect to a unique login url (I'm using django 2.1 and python 3.6 f-strings):

from apps import admin
from django.urls import path, include
from django.shortcuts import redirect

urlpatterns = [
    path(
        'admin/login/',
        lambda r: redirect(
            f"/login?{r.META['QUERY_STRING']}" if r.META['QUERY_STRING'] \
            else '/login'
        )
    ),
    path('admin/', admin.site.urls),
]
Felipe Buccioni
  • 19,109
  • 2
  • 28
  • 28
0

Maybe use a templatetag to obtain your custom AuthenticationForm?

scytale
  • 12,346
  • 3
  • 32
  • 46