django_utils.admin package¶
Submodules¶
django_utils.admin.export module¶
Streaming CSV/JSON export actions for the admin.
Dependency-free (stdlib csv/json), streaming
(StreamingHttpResponse + queryset_iterator, so a million-row
export never materialises in memory), and scoped on purpose: CSV and
JSON only, export only. For XLSX, import flows, resource classes and
the rest, use django-import-export -- this module exists for the 90%
case without its weight.
CSV injection: spreadsheet applications execute cell values starting
with =, +, -, @, a tab (\t) or a carriage return
(\r) as formulas (OWASP's full dangerous-prefix list -- the last
two are less obvious than the arithmetic operators since some CSV
parsers strip leading whitespace before a spreadsheet application ever
sees the cell, but not all do). Exported text values starting with any
of those characters are prefixed with a single quote (documented OWASP
mitigation); numbers and other non-str values are left untouched.
Both actions run against the queryset the changelist hands them --
already narrowed by list_filter, search, and this package's own
admin filters (django_utils.admin.filters) -- so the intended
flow is filter first, select second, export third: the export never
sees a row the changelist wouldn't have shown.
- class django_utils.admin.export.ExportMixin[source]¶
Bases:
objectAdds
export_as_csv/export_as_jsonadmin actions.Both stream via
StreamingHttpResponse: rows are pulled one at a time fromdjango_utils.queryset.queryset_iterator, so exporting a million rows never materialises the table in memory. They run against whatever queryset the changelist hands them -- see the module docstring for the filter-then-export composition story.export_fieldspicks the exported columns; left unset, it defaults to every concrete field's attname (tuple(f.attname for f in model._meta.concrete_fields)), so foreign keys export as<name>_idvalues rather than related objects:class IngredientAdmin(ExportMixin, admin.ModelAdmin): export_fields = ('name', 'stock')
actions = ('export_as_csv', 'export_as_json')is a plain class attribute here, picked up byModelAdmin's defaultget_actions()-- nothing to override. An admin that declares its ownactionslist replaces this one outright (Python attribute lookup, not a merge), so include both names in it:class IngredientAdmin(ExportMixin, admin.ModelAdmin): actions = ('export_as_csv', 'export_as_json', 'my_action')
- actions = ('export_as_csv', 'export_as_json')¶
django_utils.admin.filters module¶
- class django_utils.admin.filters.AllValuesFieldListFilter(field, request, params, model, model_admin, field_path)[source]¶
Bases:
FieldListFilter- choices(changelist)[source]¶
Return choices ready to be output in the template.
changelist is the ChangeList to be displayed.
- class django_utils.admin.filters.BooleanFieldListFilter(field, request, params, model, model_admin, field_path)[source]¶
Bases:
FieldListFilter- choices(changelist)[source]¶
Return choices ready to be output in the template.
changelist is the ChangeList to be displayed.
- class django_utils.admin.filters.ChoicesFieldListFilter(field, request, params, model, model_admin, field_path)[source]¶
Bases:
FieldListFilter- choices(changelist)[source]¶
Return choices ready to be output in the template.
changelist is the ChangeList to be displayed.
- class django_utils.admin.filters.DateFieldListFilter(field, request, params, model, model_admin, field_path)[source]¶
Bases:
FieldListFilter- choices(changelist)[source]¶
Return choices ready to be output in the template.
changelist is the ChangeList to be displayed.
- class django_utils.admin.filters.FieldListFilter(field, request, params, model, model_admin, field_path)[source]¶
Bases:
FacetsMixin,ListFilter- list_separator = ','¶
- class django_utils.admin.filters.ListFilter(request, params, model, model_admin)[source]¶
Bases:
object- choices(changelist)[source]¶
Return choices ready to be output in the template.
changelist is the ChangeList to be displayed.
- expected_parameters()[source]¶
Return the list of parameter names that are expected from the request's query string and that will be used by this filter.
- template = 'admin/filter.html'¶
- title = None¶
- class django_utils.admin.filters.RelatedFieldListFilter(field, request, params, model, model_admin, field_path)[source]¶
Bases:
FieldListFilter- choices(changelist)[source]¶
Return choices ready to be output in the template.
changelist is the ChangeList to be displayed.
- expected_parameters()[source]¶
Return the list of parameter names that are expected from the request's query string and that will be used by this filter.
- field_admin_ordering(field, request, model_admin)[source]¶
Return the model admin's ordering for related field, if provided.
- property include_empty_choice¶
Return True if a "(None)" choice should be included, which filters out everything except empty relationships.
- class django_utils.admin.filters.RelatedOnlyFieldListFilter(field, request, params, model, model_admin, field_path)[source]¶
Bases:
RelatedFieldListFilter
- class django_utils.admin.filters.SimpleListFilter(request, params, model, model_admin)[source]¶
Bases:
FacetsMixin,ListFilter- choices(changelist)[source]¶
Return choices ready to be output in the template.
changelist is the ChangeList to be displayed.
- expected_parameters()[source]¶
Return the list of parameter names that are expected from the request's query string and that will be used by this filter.
- lookups(request, model_admin)[source]¶
Must be overridden to return a list of tuples (value, verbose value)
- parameter_name = None¶
django_utils.admin.mixins module¶
Opt-in ModelAdmin mixins.
ReadOnlyModelAdminMixin turns any existing admin, including one
using this package's filters and widgets, into a safe read-only view:
add/change/delete are denied for everyone (superusers included), every
concrete field and many-to-many relation becomes read-only, and the
list configuration
(list_display, list_filter, search_fields) keeps working
untouched. Built exclusively on stable public ModelAdmin API.
CountColumnMixin adds sortable list_display columns for related
object counts (count_columns = ('review',) gives a review_count
column), annotated via django_utils.aggregates.SubqueryCount so
combining several relations never fans out through a JOIN.
- class django_utils.admin.mixins.CountColumnMixin(*args: Any, **kwargs: Any)[source]¶
Bases:
objectSortable related-object count columns, without JOIN fan-out.
- class django_utils.admin.mixins.ReadOnlyModelAdminMixin[source]¶
Bases:
objectDeny add/change/delete; make every field read-only.
Governs the parent admin's own permissions only: an editable
inlinesentry on the wrapped admin is not made read-only by this mixin. Django still denies the write: the change view's POST path is gated on the parent admin'shas_change_permission, which this mixin hard-denies -- so nothing is actually editable through it. But the change-form UI still renders its widgets as if it were, which can mislead a user into thinking edits are possible. Apply this mixin to inline admin classes too if you want their rendered UI to match.
django_utils.admin.widgets module¶
Admin widgets for working with JSONField.
- class django_utils.admin.widgets.JSONWidget(attrs: dict[str, Any] | None = None)[source]¶
Bases:
TextareaA
JSONFieldtextarea that pretty-prints and validates client-side.Django renders a stored value on a single line (
{"b": 2, "a": [1, 2]}) and reports parse errors only after a submit round-trip. This widget indents and key-sorts the value, and reports parse errors inline as you type.Malformed input is not reformatted: Django already round-trips it through
InvalidJSONInputand that behaviour is preserved here.- format_value(value: Any) Any[source]¶
Return a value as it should appear when rendered in a template.
- property media¶
- class django_utils.admin.widgets.JSONWidgetMixin[source]¶
Bases:
objectOpt a
ModelAdminintoJSONWidgetfor its JSON fields.class MyAdmin(JSONWidgetMixin, admin.ModelAdmin): pass
Nothing is patched globally: a project that installs this package for the filters alone sees no change to its forms.
This works by declaring a class-level
formfield_overridesand relies on normal Python attribute lookup to make it visible on the final class, so it is a silent no-op in two situations:The
ModelAdmindeclares its ownformfield_overrides. That dict replaces this mixin's entirely rather than merging with it (regular class-attribute shadowing, not a dict merge), somodels.JSONFieldis no longer mapped toJSONWidgetand Django's default JSON textarea is used instead -- no error, no warning.The mixin is listed after
admin.ModelAdminin the class's bases, e.g.class MyAdmin(admin.ModelAdmin, JSONWidgetMixin).admin.ModelAdminalready definesformfield_overrides(as an empty dict), so MRO resolves the attribute there first and this mixin's mapping is never consulted.
If your
ModelAdminneeds its ownformfield_overridesfor other fields, merge this mixin'sformfield_overridesmapping in explicitly instead of overriding it outright, and keep this mixin first in the base list.