skip to content

How do you replace the Django admin's 'Django administration' header and page titles with a charity's own branding?

level: juniorimportance: should knowfreq 50%

answer

  1. attributes on the site object
  2. header, title, index title
  3. a subclass plus an app config
  4. base_site.html for the rest

basics

~10 s

Set site_header, site_title and index_title on the Django admin site, either on admin.site directly or on an AdminSite subclass made the default through an AdminConfig subclass's default_site. Logos and colours come from overriding admin/base_site.html.

solid answer

~40 s

The texts live on `AdminSite`: `site_header` (the banner, default "Django administration"), `site_title` (the end of each `<title>`, default "Django site admin") and `index_title` (the index heading, default "Site administration"); `site_url` controls the "View site" link and `None` removes it. The quick way is to assign them on `admin.site` in an `admin.py`. The cleaner way is a subclass, `class CharityAdminSite(admin.AdminSite)` with those attributes, made the project default by an `AdminConfig` subclass whose `default_site` points at it, listed in `INSTALLED_APPS` in place of `"django.contrib.admin"`. Every existing `admin.site.register()` call and `@admin.register` then lands on the branded site. For a logo, fonts or colours, override `admin/base_site.html` and fill its `branding` or `extrastyle` block; the admin's colours are CSS variables, so a short style block retheme it.

code

python · 29 lines
python
# charity/admin_site.py
from django.contrib import admin


class CharityAdminSite(admin.AdminSite):
    site_header = "Harbour Food Bank"
    site_title = "Food Bank admin"
    index_title = "Operations"
    site_url = None  # no "View site" link


# charity/apps.py
from django.contrib.admin.apps import AdminConfig


class CharityAdminConfig(AdminConfig):
    default_site = "charity.admin_site.CharityAdminSite"


# settings.py
INSTALLED_APPS = [
    "charity.apps.CharityAdminConfig",  # replaces "django.contrib.admin"
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "charity",
]

go deeper

for a junior

Recall the three text attributes on the admin site and that they can be set on admin.site in an admin.py.

for a middle

Explain the AdminSite subclass plus AdminConfig.default_site route, and why existing registrations follow it without edits.

for a senior

Keep branding and site-wide rules in one subclass, override template blocks rather than copying templates, and avoid forks that break on upgrades.

for a principal

Decide how far an internal admin should be branded at all, versus the cost of maintaining template overrides across Django releases.

## Where the default texts come from `django.contrib.admin` renders every page through `admin/base_site.html`, which reads a few values that `AdminSite.each_context()` puts into every template context. Those values are class attributes of **`AdminSite`**: | Attribute | Default | Where it shows | |---|---|---| | `site_header` | "Django administration" | The banner at the top of every page and the login page | | `site_title` | "Django site admin" | The end of each page's `<title>` | | `index_title` | "Site administration" | The heading of the admin index | | `site_url` | `"/"` | The "View site" link; `None` removes it | Changing these is the whole of "rebranding" for most projects. ## Option 1: set them on `admin.site` The default site is the object `django.contrib.admin.site`. Assigning attributes in any module that loads at startup — typically one app's `admin.py` — works immediately: - `admin.site.site_header = "Harbour Food Bank"` - `admin.site.site_title = "Food Bank admin"` - `admin.site.index_title = "Operations"` It is quick, but the configuration is scattered across whichever `admin.py` happens to do it. ## Option 2: an `AdminSite` subclass as the default site 1. Write `class CharityAdminSite(admin.AdminSite)` with the attributes as class attributes; any other site-wide behaviour, such as a stricter `has_permission()`, can live there too. 2. Write an app config: `class CharityAdminConfig(AdminConfig)` with `default_site = "charity.admin_site.CharityAdminSite"`. `AdminConfig` is the admin's default config and performs autodiscovery of every app's `admin.py`. 3. In `INSTALLED_APPS`, replace `"django.contrib.admin"` with `"charity.apps.CharityAdminConfig"`. `django.contrib.admin.site` is a lazy object that builds its site from the admin app config's `default_site` on first use, so every `admin.site.register(...)`, every `@admin.register(...)` and the `admin.site.urls` in your URLconf now use the branded class with no further edits. This is the documented way to "override the default admin site". ## Beyond text: logo and colours - Create `templates/admin/base_site.html` in a directory that is searched before the admin's own templates, `{% extends "admin/base.html" %}`, and override the `branding` block to add a logo image next to `{{ site_header }}`. - The admin defines its colours as **CSS variables** such as `--primary`; overriding them in the `extrastyle` block (keeping `{{ block.super }}`) retheme the whole admin, dark mode included, without touching individual rules. - `base_site.html` can only be overridden project-wide, not per app or model. ## Common mistakes 1. Setting `site_header` on a **new** `AdminSite()` instance that nothing is registered on — the header changes on an empty site while the real one stays unbranded. 2. Keeping both `"django.contrib.admin"` and the custom config in `INSTALLED_APPS`, which registers the same app label twice and fails at startup. 3. Copying all of `base.html` into the project to change one line; future Django releases then render with a stale copy. 4. Hard-coding the header text in templates instead of reading `site_header`, so the login page and the password pages disagree. ## Where the branding shows up Because the texts come from the site object through `each_context()`, one change reaches every admin page that uses it: - the **login** and **logout** pages served by the site; - the **password change** pages linked from the header; - every change list, change form, delete confirmation and history page; - custom admin views that build their context from `admin_site.each_context(request)`. Pages outside the admin do not get it automatically. The documented recipe for adding a password-reset flow to the admin, for example, passes `extra_context={"site_header": admin.site.site_header}` to the auth views so they show the same banner. ## A worked setup for the charity 1. Create `CharityAdminSite` with the header "Harbour Food Bank", the title "Food Bank admin", the index title "Operations" and `site_url = None`, because staff never need to jump to the public site from the admin. 2. Point `CharityAdminConfig.default_site` at it and swap it into `INSTALLED_APPS`. 3. Add `templates/admin/base_site.html` that extends `admin/base.html`, puts the charity's logo in the `branding` block and sets the brand colour on `--primary` in `extrastyle`. 4. Load the admin login page and one change form to confirm the header, the tab title and the colours all changed.

  • Why does a Django project's @admin.register(...) end up on the custom site after setting default_site, without changing any imports?
    `django.contrib.admin.site` is a lazy object, `DefaultAdminSite`, that instantiates the class named by the admin app config's `default_site` when first used. `@admin.register` without a `site` argument and `admin.site.register()` both go through that object, so they register on the subclass automatically.
  • How do you change the Django admin's colours without rewriting its stylesheets?
    Override `admin/base.html` or `admin/base_site.html`, extend the original, and in the `extrastyle` block, after `{{ block.super }}`, redefine the admin's CSS variables such as `--primary` for the light and dark themes. Every rule that uses the variables picks up the new values.

saying these in an interview costs you the question

  • The admin header can only be changed by editing Django's templates
  • A new AdminSite() instance automatically replaces admin.site
  • site_title sets the banner text at the top of every page
  • default_site is a Django setting in settings.py
  • Keep django.contrib.admin in INSTALLED_APPS alongside the custom AdminConfig