Skip to main content

django-admin-fieldsets-extra

Build status PyPI version PyPI - Python Version PyPI - Django Version Downloads

Extras for Django admin fieldsets:

  • Interleave fieldsets and inlines in the change form: put an inline right after the fields it belongs with, instead of always at the bottom.
  • Save a fieldset on its own with a "Save Contact" button, without submitting or reloading the rest of the page.

Books inline between the name and the contact fieldset, articles before the biography

  • One ordered list replaces fieldsets + inlines (with or without inlines in it).
  • No copy of Django's change_form.html: it only overrides its field_sets / inline_field_sets blocks, so it follows your Django version.
  • Nothing changes in the inlines' DOM: Django's inline JS ("Add another", autocomplete, date pickers…) and inline templates work as usual.
  • Works with django-admin-inline-controls (paginated / filterable inlines, a save button per inline).

Install

pip install django-admin-fieldsets-extra

Add to INSTALLED_APPS:

INSTALLED_APPS = [
    ...
    "django_admin_fieldsets_extra",
]

Usage

from django.contrib import admin

from django_admin_fieldsets_extra.mixins import FieldsetsExtraMixin


class BookInline(admin.TabularInline):
    model = Book


class ArticleInline(admin.StackedInline):
    model = Article


@admin.register(Author)
class AuthorAdmin(FieldsetsExtraMixin, admin.ModelAdmin):
    fieldsets_with_inlines = [
        (None, {"fields": ["name"]}),
        BookInline,
        ("Contact", {"fields": ["email", "phone"], "save_button": True}),
        ArticleInline,
        ("Biography", {"fields": ["bio"], "classes": ["collapse"]}),
    ]

Each entry is either a fieldset, exactly as in ModelAdmin.fieldsets, or an inline class, as in ModelAdmin.inlines. They are rendered in that order.

  • fieldsets and inlines are derived from it (in their relative order), so Django's admin checks and every hook that reads them keep working. Don't set them as well (admin_fieldsets_extra.E003).
  • Dynamic layouts: override get_fieldsets_with_inlines(request, obj=None); get_fieldsets() and get_inlines() follow it. Static fieldsets / inlines would be ignored then (admin_fieldsets_extra.W001 warns about it).
  • Permissions: an inline the user can't see is simply left out, as in the regular change form.
  • Anything not in the layout (e.g. extra fieldsets or inlines from your own get_fieldsets() / get_inlines()) is rendered after it.
  • Without fieldsets_with_inlines, the mixin does nothing: the regular change form.

Saving a fieldset

Add "save_button": True to a fieldset of the layout to give it a "Save Contact" button (named after the fieldset; "Save" if it has no name) that saves only its fields:

(("Contact", {"fields": ["email", "phone"], "save_button": True}),)

A fieldset with its own save button, after saving

save_button is an option of this package: it is removed before the fieldset reaches Django (whose Fieldset rejects unknown options), so AuthorAdmin.fieldsets and get_fieldsets() never contain it.

How it works

  • The button is rendered by the server under the fieldset, only on existing objects and for users with change permission. It is hidden while the fieldset is collapsed.
  • Only the fields inside that fieldset are sent (files included), to <object_id>/fieldsets/<n>/save/, n being its position in get_fieldsets().
  • The server builds get_form(request, obj, fields=<its editable fields>) on the object as stored in the database, validates it, calls save_form(), save_model() and form.save_m2m() in a transaction, and records the change in the history ("Changed Email and Phone."; nothing if nothing changed).
  • The fieldset is re-rendered and swapped in place: field errors on their fields, form-wide errors (from clean()) above the fieldset, "Saved." next to the button on success. The rest of the page (other fieldsets, inlines, unsaved edits) is left untouched. A fieldsets-extra:saved event bubbles from the new fieldset.

Caveats

  1. Validation: only this fieldset's fields are validated as fields, but Model.clean() / ModelForm.clean() still run, with the other fields' stored values. A rule that combines fields of two fieldsets sees the saved value of the other one, not what is unsaved on screen.
  2. save_model(request, obj, form, change) gets a form with only these fields. If you override it and read other fields from form.cleaned_data, it won't work from this button. save_related() and the inlines' formsets are not called.
  3. Concurrency: the object is re-read from the database right before saving, so the page's (possibly stale) values of other fields are never written back. As with the regular Save button, two people changing the same field at the same time: the last save wins.
  4. Read-only fields are never saved; a fieldset with only read-only fields has no button (the endpoint answers 404).

Customizing the template

The mixin sets change_form_template to admin/fieldsets_extra/change_form.html, which extends admin/change_form.html. For your own change form template, extend this one instead:

{% extends "admin/fieldsets_extra/change_form.html" %}

{% block layout_inline %}
  <div class="my-inline">{{ block.super }}</div>
{% endblock %}
Block Contains
field_sets The whole layout (falls back to Django's when there is none).
layout_fieldset One fieldset (fieldset, and item.index, its position in get_fieldsets()); includes admin/fieldsets_extra/includes/fieldset.html.
layout_inline One inline (inline_admin_formset).
inline_field_sets Empty when there is a layout (the inlines are already rendered).

admin/fieldsets_extra/includes/fieldset.html renders one fieldset and, if it has one, its save button. Its blocks: non_field_errors, fieldset, save_bar and save_label. It is also the save endpoint's response (fieldset_response.html, block response; set fieldset_save_response_template on the ModelAdmin to use another one). The JS replaces the fieldset-with-save container by its id and relies on the data-fieldset-save-url attribute and the data-fieldset-save button: keep them when overriding blocks.

The layout is also in the context as fieldsets_with_inlines: a list of items with is_inline, fieldset, index, inline_admin_formset, save_url and save_label. get_layout_items() builds it, if you need to change how the layout is matched to the form.

With django-admin-inline-controls

Both mixins go on the ModelAdmin, InlineControlsAdminMixin first:

from django_admin_inline_controls.mixins import (
    InlineControlsAdminMixin,
    InlineControlsMixin,
)


class BookInline(InlineControlsMixin, admin.TabularInline):
    model = Book
    inline_per_page = 20
    inline_save_button = True


@admin.register(Author)
class AuthorAdmin(InlineControlsAdminMixin, FieldsetsExtraMixin, admin.ModelAdmin):
    fieldsets_with_inlines = [
        (None, {"fields": ["name"]}),
        BookInline,
        ("Contact", {"fields": ["email", "phone"], "save_button": True}),
    ]

Neither package knows about the other: the controls are part of each inline's own template, and the fieldset save buttons belong to the fieldsets, wherever the layout puts them. Fieldsets keep their position in get_fieldsets(), which is how the save endpoint finds them.

Themes

System checks

ID Problem
admin_fieldsets_extra.E001 fieldsets_with_inlines is not a list or tuple.
admin_fieldsets_extra.E002 An entry is neither a (name, {"fields": ...}) fieldset nor an inline class.
admin_fieldsets_extra.E003 fieldsets or inlines is set as well.
admin_fieldsets_extra.E004 A fieldset's save_button is not True or False.
admin_fieldsets_extra.E101 An Unfold tab fieldset ("classes": ["tab"]) has a save_button (UnfoldFieldsetsExtraMixin).
admin_fieldsets_extra.W001 get_fieldsets_with_inlines() is overridden and fieldsets or inlines is set too: they are ignored whenever it returns a layout (warning).

Translations

Ships a Spanish (es) translation: the save buttons, their tooltips and the status messages follow the admin's active language.

Demo

cd example
python manage.py migrate
python manage.py seed_demo      # admin/admin + an author with books
python manage.py runserver

Compatibility

Django 4.2 – 6.1, Python 3.10+.

License

MIT

Metadata

Release files for django-admin-fieldsets-extra 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-admin-fieldsets-extra 0.1.1
File Size Uploaded
django_admin_fieldsets_extra-0.1.1.tar.gz 373.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-admin-fieldsets-extra 0.1.1
File Interpreter ABI Platform
django_admin_fieldsets_extra-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 395.0 kB

Release files / django_admin_fieldsets_extra-0.1.1.tar.gz

Download URL django_admin_fieldsets_extra-0.1.1.tar.gz
Size 373.5 kB
Tags Source
SHA-256 checksum
How to use checksums
a2949f860876e2af87532ced6eee12b651ad1bdbed378924e9e6dc69439fbe55
BLAKE2b-256 checksum
How to use checksums
f3f07f6e8c9d8cbf773c00c7ec13016de677fb31ed048e60936b3678029949f1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release files / django_admin_fieldsets_extra-0.1.1-py3-none-any.whl

Download URL django_admin_fieldsets_extra-0.1.1-py3-none-any.whl
Size 21.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
12003b199389328f73a4e999f119488c712ceb99cf69fcfbc60405b2329422e1
BLAKE2b-256 checksum
How to use checksums
cd456ef1979b4dc798db86a93f0cfdd713f23fa02720ee846e0e732c91026531
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page