Documentation Index
Fetch the complete documentation index at: https://mintlify.com/rahul-baberwal/django-var-cms/llms.txt
Use this file to discover all available pages before exploring further.
VarCMSModelAdmin is the base class you subclass and pass to var_cms_site.register(). Every attribute is optional — sensible defaults apply out of the box so you can start with a minimal class and layer in customization as needed.
Constructor
__init__(model, site)
Called automatically by var_cms_site.register(). Sets self.model and self.site. If list_display is empty at construction time, it is auto-populated with the first six concrete (non-many-to-many, non-one-to-many) model fields.
| Parameter | Type | Description |
|---|---|---|
model | Type[Model] | The Django model class being registered. |
site | VarCMSSite | The site instance that owns this admin. |
List View Attributes
These attributes control the paginated list view rendered for each registered model.Column fields shown in the list view table. Each entry is a model field name or a double-underscore traversal (e.g.,
"category__name"). When left empty, the first six concrete model fields are used automatically (populated in __init__).Fields used to generate filter sidebar widgets. The widget type is inferred from the field type:
BooleanField → toggle, DateField / DateTimeField → date-range, ForeignKey → select, IntegerField / FloatField → number-range, everything else → text input.Fields searched with a case-insensitive
icontains lookup. Multiple fields are ORed together.Default queryset ordering passed directly to Django’s
order_by(). Prefix a field with - to sort descending.Number of rows displayed per page. The built-in paginator uses this value.
When
True, calls .select_related() on the base queryset, eagerly loading related objects and reducing N+1 queries for ForeignKey columns in list_display.Thumbnail width in pixels for
ImageField columns rendered in the list view.Thumbnail height in pixels for
ImageField columns rendered in the list view.Form Attributes
These attributes shape the add/edit form — which fields appear, how they look, and how they are validated.Fields that are always shown on the form but are never editable by anyone (including superusers). Their rendered values are displayed as read-only text alongside the form.
Fields hidden from the form entirely. Unlike
readonly_fields, excluded fields are not shown at all.Fields rendered with the integrated Quill.js rich-text editor instead of a plain
<textarea>. Only applicable to TextField fields.Per-field regex validation applied both server-side (via Django’s
RegexValidator) and client-side (via the HTML pattern attribute).The value for each field is either a plain regex string or a (pattern, error_message) tuple:Width presets for individual fields. The form uses a 12-column grid. Available values:
| Preset | Columns | Description |
|---|---|---|
"full" | 12 | Full form width. Default for checkboxes and textareas. |
"half" | 6 | Half width. Default for most other fields. |
"one-third" | 4 | One-third width. |
"two-thirds" | 8 | Two-thirds width. |
"one-fourth" | 3 | One-fourth width. |
"three-fourths" | 9 | Three-fourths width. |
Group multiple fields into a single visual row. Fields within the same row automatically share the available width equally (e.g., three fields → 4 columns each on a 12-column grid). Row groupings override any individual
form_field_widths entry for those fields.Override the widget rendered for a specific field. Useful for
ForeignKey, CharField with choices, and ManyToManyField fields.| Widget type | Rendered as |
|---|---|
"select" | Standard HTML <select> dropdown (default; no override needed). |
"select_search" | Searchable dropdown with a live-filter input. |
"multiselect" | Checkbox list for multi-selection (no search). |
"multiselect_search" | Checkbox list with a search input above it. |
multiselect and multiselect_search work best with ManyToManyField fields. Use select_search for ForeignKey fields.Extra CSS classes applied to the container
<div> that wraps each field (label + input).Inline CSS applied to the container
<div> that wraps each field.CSS classes injected directly on the
<input> / <select> / <textarea> element itself (not the wrapper div).Placeholder text rendered inside each input element.
Help text displayed below each field, overriding any help text set on the model field itself.
Permissions Attributes
A list of
RolePermission, GroupPermission, or UserPermission objects defining which actions each role or user may perform.When this list is empty the legacy allow_add / allow_edit / allow_delete flags are used as a fallback, and only authenticated superusers have unrestricted access.Maps each role name to the subset of fields that role is allowed to edit. Use the string
"__all__" to permit editing every non-readonly field.The special key "*" acts as a wildcard for any role not explicitly listed. An empty list [] denies all edits.Legacy fallback only. Permits the add action when
permissions = []. Ignored when permissions is populated.Legacy fallback only. Permits the edit action when
permissions = []. Ignored when permissions is populated.Legacy fallback only. Permits the delete action when
permissions = []. Ignored when permissions is populated.Display Attributes
A Lucide icon name displayed next to the model in the sidebar navigation and on dashboard cards.
When
True, a summary card for this model appears on the dashboard. The card shows a live object count and the buttons defined in card_buttons.Quick-action buttons rendered at the bottom of the dashboard card. Each button is a dict with the following keys:
When
| Key | Required | Description |
|---|---|---|
label | ✅ | Button text. |
action | — | Built-in shortcut: "list" or "add". The URL is resolved automatically. |
url | — | Explicit URL (used when action is not set). |
class | — | CSS class: "btn-primary", "btn-ghost", "btn-danger", etc. |
card_buttons is empty and dashboard_card = True, a View List and (if permitted) Add New button are added automatically.Action buttons rendered in list view row menus and on detail view pages. Each action is a dict:
| Key | Required | Description |
|---|---|---|
name | ✅ | Unique identifier used in the URL (/var-cms/{app}/{model}/{pk}/action/{name}/). |
label | ✅ | Button text. |
action_fn | ✅ | A callable or a string naming a method on the admin class. Receives (request, obj). Return None to redirect back, or return an HttpResponse. |
class | — | CSS class: "btn-primary", "btn-green", "btn-blue", "btn-danger", "btn-ghost". |
icon | — | Lucide icon name shown on the button. |
Methods
get_queryset(request) → QuerySet
Returns the base queryset used for all list and detail views. Override to filter records, annotate fields, or apply custom ordering.
self.model._default_manager.all(), optionally calls .select_related() when list_select_related = True, and applies ordering if set.
apply_search(qs, query: str) → QuerySet
Filters the queryset by applying a case-insensitive icontains lookup across all search_fields, OR-joined into a single Q object. Returns qs unchanged when query is empty or search_fields is empty.
apply_filters(qs, params: dict) → QuerySet
Applies filter parameters (typically from request.GET) to the queryset. Keys ending in __gte or __lte are applied directly as range filters. Any remaining key that matches a concrete model field name is applied as an exact match. The reserved keys q, page, o, and ot are skipped.
has_permission(request, action: str) → bool
Returns True if the current user is allowed to perform action on this model.
action must be one of: "add", "list", "view", "edit", "delete".
Resolution order:
- If
permissionsis empty → falls back toallow_add/allow_edit/allow_deleteflags (listandviewalways returnTruein fallback mode). - If
permissionsis populated → delegates toresolve_permission()with the user’s role and username.
Unauthenticated users always receive
False regardless of the permissions list.get_editable_fields(request) → Union[List[str], str]
Returns the fields the current user is allowed to edit: either a list of field names, or "__all__" (meaning every non-readonly field).
The method determines the user’s role via _get_user_role(request) and delegates to resolve_editable_fields().
When role_editable_fields is empty, "__all__" is returned for every user.
get_form(request, instance=None) → ModelForm
Builds and returns a bound or unbound ModelForm for the model. Called by both the add and edit views.
- Excludes
exclude_fieldsandreadonly_fieldsfrom the form fields. - Applies
regex_validatorsas server-sideRegexValidatorinstances and adds the HTMLpattern/titleattributes to the corresponding widgets. - Injects
form_widget_classes,form_field_placeholders, andform_field_help_textsinto widget attributes. - For
POSTrequests, bindsrequest.POSTandrequest.FILESto the form. - Disables fields that fall outside the current user’s
get_editable_fields()result.
save_model(request, obj, form, change: bool)
Called after form.is_valid() on both the add and edit views. Use this hook to run custom pre- or post-save logic.
| Parameter | Type | Description |
|---|---|---|
request | HttpRequest | The current request. |
obj | Model instance | The unsaved model instance (form data already applied). |
form | ModelForm | The validated form instance. |
change | bool | False on add, True on edit. |
slug field from title, name, or headline (in that priority) if slug is blank, then calls obj.save().
delete_model(request, obj)
Called when a delete is confirmed. Override to add cascading cleanup, audit logging, or soft-delete logic. The default implementation calls obj.delete().
get_card_buttons(request) → List[Dict[str, Any]]
Returns the list of button dicts rendered on the dashboard card. When card_buttons is empty, it auto-generates a View List button (if the user has list permission) and an Add New button (if the user has add permission). When card_buttons is set, each button with an action key of "add" or "list" has its URL resolved automatically — other button dicts are passed through unchanged.
get_column_header(field_name: str) → str
Returns the display label for a list view column. For a real model field, returns field.verbose_name.title(). For a traversal like "category__name", the lookup is done on the first segment. For unmapped names (computed properties, etc.), the field name is formatted by replacing underscores and double-underscores with spaces/arrows and title-casing.
verbose_name (property)
Returns self.model._meta.verbose_name. Read-only.
verbose_name_plural (property)
Returns self.model._meta.verbose_name_plural. Read-only.