How to override and extend basic Django admin templates?
Asked Answered
B

12

173

How do I override an admin template (e.g. admin/index.html) while at the same time extending it (see https://docs.djangoproject.com/en/dev/ref/contrib/admin/#overriding-vs-replacing-an-admin-template)?

First - I know that this question has been asked and answered before (see Django: Overriding AND extending an app template) but as the answer says it isn't directly applicable if you're using the app_directories template loader (which is most of the time).

My current workaround is to make copies and extend from them instead of extending directly from the admin templates. This works great but it's really confusing and adds extra work when the admin templates change.

It could think of some custom extend-tag for the templates but I don't want to reinvent the wheel if there already exists a solution.

On a side note: Does anybody know if this problem will be addressed by Django itself?

Bailment answered 5/7, 2011 at 13:59 Comment(2)
Copying the admin templates, extending them and overriding/adding blocks is the most efficient, although not optimal workflow given the current state of Django. I haven't seen any other way to do what you're trying to do in three years of working with it :)Trident
Well - I don't know if this is a good thing or not but at least people like you have come to the same conclusion. That's good to hear. :)Bailment
N
130

Update:

Read the Docs for your version of Django, e.g. the latest version or old LTS versions: 3.2, 2.2, 1.11

Original answer from 2011:

I had the same issue about a year and a half ago and I found a nice template loader on djangosnippets.org that makes this easy. It allows you to extend a template in a specific app, giving you the ability to create your own admin/index.html that extends the admin/index.html template from the admin app. Like this:

{% extends "admin:admin/index.html" %}

{% block sidebar %}
    {{block.super}}
    <div>
        <h1>Extra links</h1>
        <a href="/admin/extra/">My extra link</a>
    </div>
{% endblock %}

I've given a full example on how to use this template loader in a blog post on my website.

Notional answered 5/7, 2011 at 16:48 Comment(4)
For reference; the snippet in question has been converted to a django app, and is available in PyPi (pip/easy_install) as django-apptemplates: pypi.python.org/pypi/django-apptemplatesSedulous
Just to be 100% explicit: the above solution WILL NO LONGER WORK for recent versions of Django (at least 1.4), as one of the functions the script uses is depreciated. You can find the updated source on hereNannie
Note that with Django 1.8 this will still work, but the setup needs to be made in a special way (see app_namespace.Loader setup as an example). django-app-namespace-template-loader is also a working alternative to django-apptemplates if it may stop working one day.Acidhead
This answer was very good for the older Django versions. But as of now, Another answer by Cheng is more relevant. https://mcmap.net/q/142678/-how-to-override-and-extend-basic-django-admin-templatesEndure
S
86

As for Django 1.8 being the current release, there is no need to symlink, copy the admin/templates to your project folder, or install middlewares as suggested by the answers above. Here is what to do:

  1. create the following tree structure(recommended by the official documentation)

    your_project
         |-- your_project/
         |-- myapp/
         |-- templates/
              |-- admin/
                  |-- myapp/
                      |-- change_form.html  <- do not misspell this
    

Note: The location of this file is not important. You can put it inside your app and it will still work. As long as its location can be discovered by django. What's more important is the name of the HTML file has to be the same as the original HTML file name provided by django.

  1. Add this template path to your settings.py:

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [os.path.join(BASE_DIR, 'templates')], # <- add this line
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]
    
  2. Identify the name and block you want to override. This is done by looking into django's admin/templates directory. I am using virtualenv, so for me, the path is here:

    ~/.virtualenvs/edge/lib/python2.7/site-packages/django/contrib/admin/templates/admin
    

In this example, I want to modify the add new user form. The template responsiblve for this view is change_form.html. Open up the change_form.html and find the {% block %} that you want to extend.

  1. In your change_form.html, write somethings like this:

    {% extends "admin/change_form.html" %}
    {% block field_sets %}
         {# your modification here #}
    {% endblock %}
    
  2. Load up your page and you should see the changes

Stacistacia answered 2/5, 2015 at 2:29 Comment(4)
It is still not enough for extending the main "index.html" template, without copying all blocks. A solution is to write some ../ to "exetends" path and to specify the original path more unique {% extends "../../admin/templates/admin/index.html" %}. link to answerNasya
I think in TEMPLATES we should be using 'DIRS': [os.path.join(BASE_DIR, 'templates')],Kvass
This is the type of thread that perfectly illustrates the flaw in SO. A framework gets updated and the question is no longer relevant, it is in fact a deterrent from the proper path. Great answer here. RTFM kids.Exhume
Thanks for this answer. Except for "The location of this file is not important.", everything worked great.Kristianson
D
67

if you need to overwrite the admin/index.html, you can set the index_template parameter of the AdminSite.

e.g.

# urls.py
...
from django.contrib import admin

admin.site.index_template = 'admin/my_custom_index.html'
admin.autodiscover()

and place your template in <appname>/templates/admin/my_custom_index.html

Deafmute answered 26/12, 2013 at 19:21 Comment(2)
Brilliant! Doing this allows you to then do {% extends "admin/index.html" %} from my_custom_index.html and have that reference the django admin template without copying it. Thank you.Helicline
@Bailment should mark this as the correct answer, since it’s the simplest approach that uses built-in django features and doesn’t require using custom template loaders.Merri
S
23

With django 1.5 (at least) you can define the template you want to use for a particular modeladmin

see https://docs.djangoproject.com/en/1.5/ref/contrib/admin/#custom-template-options

You can do something like

class Myadmin(admin.ModelAdmin):
    change_form_template = 'change_form.htm'

With change_form.html being a simple html template extending admin/change_form.html (or not if you want to do it from scratch)

Spencerianism answered 21/6, 2013 at 9:57 Comment(0)
B
12

I couldn't find a single answer or a section in the official Django docs that had all the information I needed to override/extend the default admin templates, so I'm writing this answer as a complete guide, hoping that it would be helpful for others in the future.

Assuming the standard Django project structure:

mysite-container/         # project container directory
    manage.py
    mysite/               # project package
        __init__.py
        admin.py
        apps.py
        settings.py
        urls.py
        wsgi.py
    app1/
    app2/
    ...
    static/
    templates/

Here's what you need to do:

  1. In mysite/admin.py, create a sub-class of AdminSite:

    from django.contrib.admin import AdminSite
    
    
    class CustomAdminSite(AdminSite):
        # set values for `site_header`, `site_title`, `index_title` etc.
        site_header = 'Custom Admin Site'
        ...
    
        # extend / override admin views, such as `index()`
        def index(self, request, extra_context=None):
            extra_context = extra_context or {}
    
            # do whatever you want to do and save the values in `extra_context`
            extra_context['world'] = 'Earth'
    
            return super(CustomAdminSite, self).index(request, extra_context)
    
    
    custom_admin_site = CustomAdminSite()
    

    Make sure to import custom_admin_site in the admin.py of your apps and register your models on it to display them on your customized admin site (if you want to).

  2. In mysite/apps.py, create a sub-class of AdminConfig and set default_site to admin.CustomAdminSite from the previous step:

    from django.contrib.admin.apps import AdminConfig
    
    
    class CustomAdminConfig(AdminConfig):
        default_site = 'admin.CustomAdminSite'
    
  3. In mysite/settings.py, replace django.admin.site in INSTALLED_APPS with apps.CustomAdminConfig (your custom admin app config from the previous step).

  4. In mysite/urls.py, replace admin.site.urls from the admin URL to custom_admin_site.urls

    from .admin import custom_admin_site
    
    
    urlpatterns = [
        ...
        path('admin/', custom_admin_site.urls),
        # for Django 1.x versions: url(r'^admin/', include(custom_admin_site.urls)),
        ...
    ]
    
  5. Create the template you want to modify in your templates directory, maintaining the default Django admin templates directory structure as specified in the docs. For example, if you were modifying admin/index.html, create the file templates/admin/index.html.

    All of the existing templates can be modified this way, and their names and structures can be found in Django's source code.

  6. Now you can either override the template by writing it from scratch or extend it and then override/extend specific blocks.

    For example, if you wanted to keep everything as-is but wanted to override the content block (which on the index page lists the apps and their models that you registered), add the following to templates/admin/index.html:

    {% extends 'admin/index.html' %}
    
    {% block content %}
      <h1>
        Hello, {{ world }}!
      </h1>
    {% endblock %}
    

    To preserve the original contents of a block, add {{ block.super }} wherever you want the original contents to be displayed:

    {% extends 'admin/index.html' %}
    
    {% block content %}
      <h1>
        Hello, {{ world }}!
      </h1>
      {{ block.super }}
    {% endblock %}
    

    You can also add custom styles and scripts by modifying the extrastyle and extrahead blocks.

Beatabeaten answered 2/3, 2019 at 8:54 Comment(2)
do you have a source or documentation about this?Callender
Apart from the two references I've added in point 5, no, I don't have anything else.Beatabeaten
G
10

Chengs's answer is correct, howewer according to the admin docs not every admin template can be overwritten this way: https://docs.djangoproject.com/en/1.9/ref/contrib/admin/#overriding-admin-templates

Templates which may be overridden per app or model

Not every template in contrib/admin/templates/admin may be overridden per app or per model. The following can:

app_index.html
change_form.html
change_list.html
delete_confirmation.html
object_history.html

For those templates that cannot be overridden in this way, you may still override them for your entire project. Just place the new version in your templates/admin directory. This is particularly useful to create custom 404 and 500 pages

I had to overwrite the login.html of the admin and therefore had to put the overwritten template in this folder structure:

your_project
 |-- your_project/
 |-- myapp/
 |-- templates/
      |-- admin/
          |-- login.html  <- do not misspell this

(without the myapp subfolder in the admin) I do not have enough repution for commenting on Cheng's post this is why I had to write this as new answer.

Gouveia answered 18/8, 2016 at 21:41 Comment(2)
Thank you for the feedback hyneker I hope my answer is clearer and more straight to the point now.Gouveia
Yes, it is useful to know that templates can be customized on the project level even if some of them can be changed optionally on the application level.Nasya
P
6

The best way to do it is to put the Django admin templates inside your project. So your templates would be in templates/admin while the stock Django admin templates would be in say template/django_admin. Then, you can do something like the following:

templates/admin/change_form.html

{% extends 'django_admin/change_form.html' %}

Your stuff here

If you're worried about keeping the stock templates up to date, you can include them with svn externals or similar.

Perusal answered 5/7, 2011 at 14:26 Comment(3)
Using svn externals is a great idea. The problem this introduces is that all my translators are going to translate all those templates (because makemessages will collect the translation strings from all admin templates) which adds a lot of extra work if you're working with multiple languages. Maybe there is a way to exclude those templates from makemessages?Bailment
Use the --ignore argument with makemessages. See: docs.djangoproject.com/en/dev/ref/django-admin/#makemessagesPerusal
I think the the other answer fits my need better. But I like your solution and think it's a good alternative if you don't want to mess around with your template loaders.Bailment
N
4

for app index add this line to somewhere common py file like url.py

admin.site.index_template = 'admin/custom_index.html'

for app module index : add this line to admin.py

admin.AdminSite.app_index_template = "servers/servers-home.html"

for change list : add this line to admin class:

change_list_template = "servers/servers_changelist.html"

for app module form template : add this line to your admin class

change_form_template = "servers/server_changeform.html"

etc. and find other in same admin's module classes

Nichrome answered 12/1, 2019 at 2:28 Comment(0)
S
3

You can override django admin templates in several ways.

For example, there is django-project as shown below:

django-project
 |-core
 |  └-settings.py
 |-app1
 |  |-models.py
 |  └-admin.py
 |-app2
 └-templates

Then, BASE_DIR / 'templates' is set to DIRS in TEMPLATES in settings.py so that templates folder is recognized as shown below:

# "core/settings.py"

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            BASE_DIR / 'templates', # Here
        ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

And, there are Food and Drink models in app1/models.py as shown below:

# "app1/models.py"

class Food(models.Model):
    name = models.CharField(max_length=20)

class Drink(models.Model):
    name = models.CharField(max_length=20)

And, there are Food and Drink admins in app1/admin.py as shown below:

# "app1/admin.py"

@admin.register(Food)
class FoodAdmin(admin.ModelAdmin):
    pass

@admin.register(Drink)
class DrinkAdmin(admin.ModelAdmin):
    pass

Now, you can override one of the django admin templates change_form.html in templates/admin/, templates/admin/app1/ and templates/admin/app1/food/ as shown below. *You can copy django admin templates from django/contrib/admin/templates/admin/ in your virtual environment and some django admin templates cannot be overridden in templates/admin/app1/ or templates/admin/app1/food/ but these django admin templates can be overridden all in templates/admin/, templates/admin/app1/ and templates/admin/app1/food/ and you can see my answer explaining which django admin templates can be overridden in which directories.

change_form.html in templates/admin/ below can automatically apply to all admins in all apps. *The lowercase folder name admin works properly:

django-project
 |-core
 |  └-settings.py
 |-app1
 |  |-models.py
 |  └-admin.py
 |-app2
 └-templates
    └-admin
       └-change_form.html # Here

change_form.html in templates/admin/app1/ below can automatically apply to all admins in app1. *The lowercase folder name app1 works properly:

django-project
 |-core
 |  └-settings.py
 |-app1
 |  |-models.py
 |  └-admin.py
 |-app2
 └-templates
    └-admin
       |-app1
       |  └-change_form.html # Here
       └-app2

change_form.html in templates/admin/app1/food/ below can automatically apply to food admin in app1. *The lowercase folder name food works properly:

django-project
 |-core
 |  └-settings.py
 |-app1
 |  |-models.py
 |  └-admin.py
 |-app2
 └-templates
    └-admin
       |-app1
       |  |-food
       |  |  └-change_form.html # Here
       |  └-drink
       └-app2

And now, you can rename change_form.html to custom_change_form.html but custom_change_form.html in any folders cannot automatically apply to any admins in any apps. So, you need to manually apply custom_change_form.html to any admins in any apps which you want to apply custom_change_form.html to.

For custom_change_form.html in templates/admin/ below:

django-project
 |-core
 |  └-settings.py
 |-app1
 |  |-models.py
 |  └-admin.py
 |-app2
 └-templates
    └-admin
       └-custom_change_form.html # Here

Set admin/custom_change_form.html to change_form_template in Food and Drink admins as shown below. *You can find more custom template options:

# "app1/admin.py"

@admin.register(Food)
class FoodAdmin(admin.ModelAdmin):
    change_form_template = 'admin/custom_change_form.html'

@admin.register(Drink)
class DrinkAdmin(admin.ModelAdmin):
    change_form_template = 'admin/custom_change_form.html'

For custom_change_form.html in templates/admin/app1/ below:

django-project
 |-core
 |  └-settings.py
 |-app1
 |  |-models.py
 |  └-admin.py
 |-app2
 └-templates
    └-admin
       |-app1
       |  └-custom_change_form.html # Here
       └-app2

Set admin/app1/custom_change_form.html to change_form_template in Food and Drink admins as shown below:

# "app1/admin.py"

@admin.register(Food)
class FoodAdmin(admin.ModelAdmin):
    change_form_template = 'admin/app1/custom_change_form.html'

@admin.register(Drink)
class DrinkAdmin(admin.ModelAdmin):
    change_form_template = 'admin/app1/custom_change_form.html'

For custom_change_form.html in templates/admin/app1/food below:

django-project
 |-core
 |  └-settings.py
 |-app1
 |  |-models.py
 |  └-admin.py
 |-app2
 └-templates
    └-admin
       |-app1
       |  |-food
       |  |  └-custom_change_form.html # Here
       |  └-drink
       └-app2

Set admin/app1/food/custom_change_form.html to change_form_template in Food and Drink admins as shown below:

# "app1/admin.py"

@admin.register(Food)
class FoodAdmin(admin.ModelAdmin):
    change_form_template = 'admin/app1/food/custom_change_form.html'

@admin.register(Drink)
class DrinkAdmin(admin.ModelAdmin):
    change_form_template = 'admin/app1/food/custom_change_form.html'
Sweated answered 12/5, 2023 at 8:9 Comment(0)
M
1

I agree with Chris Pratt. But I think it's better to create the symlink to original Django folder where the admin templates place in:

ln -s /usr/local/lib/python2.7/dist-packages/django/contrib/admin/templates/admin/ templates/django_admin

and as you can see it depends on python version and the folder where the Django installed. So in future or on a production server you might need to change the path.

Manzoni answered 9/3, 2013 at 8:48 Comment(0)
P
0

This site had a simple solution that worked with my Django 1.7 configuration.

FIRST: Make a symlink named admin_src in your project's template/ directory to your installed Django templates. For me on Dreamhost using a virtualenv, my "source" Django admin templates were in:

~/virtualenvs/mydomain/lib/python2.7/site-packages/django/contrib/admin/templates/admin

SECOND: Create an admin directory in templates/

So my project's template/ directory now looked like this:

/templates/
   admin
   admin_src -> [to django source]
   base.html
   index.html
   sitemap.xml
   etc...

THIRD: In your new template/admin/ directory create a base.html file with this content:

{% extends "admin_src/base.html" %}

{% block extrahead %}
<link rel='shortcut icon' href='{{ STATIC_URL }}img/favicon-admin.ico' />
{% endblock %}

FOURTH: Add your admin favicon-admin.ico into your static root img folder.

Done. Easy.

Proscribe answered 18/3, 2015 at 7:6 Comment(0)
T
-1

You can use django-overextends, which provides circular template inheritance for Django.

It comes from the Mezzanine CMS, from where Stephen extracted it into a standalone Django extension.

More infos you find in "Overriding vs Extending Templates" (http:/mezzanine.jupo.org/docs/content-architecture.html#overriding-vs-extending-templates) inside the Mezzanine docs.

For deeper insides look at Stephens Blog "Circular Template Inheritance for Django" (http:/blog.jupo.org/2012/05/17/circular-template-inheritance-for-django).

And in Google Groups the discussion (https:/groups.google.com/forum/#!topic/mezzanine-users/sUydcf_IZkQ) which started the development of this feature.

Note:

I don't have the reputation to add more than 2 links. But I think the links provide interesting background information. So I just left out a slash after "http(s):". Maybe someone with better reputation can repair the links and remove this note.

Tui answered 4/2, 2016 at 22:16 Comment(1)
Since Django 1.9, this project hasn't been relevant, the maintainer just isn't advertising it, see code.djangoproject.com/ticket/15053 and github.com/stephenmcd/django-overextends/pull/37. To take complete control of which app a template is loaded from, there is django-apptemplates and django-app-namespace-template-loader, which are both still relevant if you want to extend from one app to another.Lychnis

© 2022 - 2024 — McMap. All rights reserved.