django-var-cms provides two independent layers of field access control that work together to give you precise RBAC over what each user can modify on a form. The first layer,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.
readonly_fields, is a hard global lock — those fields are shown to everyone but cannot be edited by anyone. The second layer, role_editable_fields, is a per-role allowlist that controls which of the remaining fields a user with a given role is actually allowed to change. Together they let you express rules like “authors can only touch the title, body, and status fields, while editors also control the slug and category.”
Field Access Attributes
readonly_fields
A list of field names that are always rendered as read-only regardless of who is logged in. These fields are excluded from the editable form and displayed separately in the detail section of the form template.
readonly_fields entries are excluded from the modelform_factory call entirely — they are never included in the form’s field set. They appear in a separate read-only section rendered by the form template. This means they are immune to any role_editable_fields configuration and cannot accidentally be re-enabled by a wildcard rule.exclude_fields
A list of field names that are hidden from the form entirely — not shown and not editable. Use this for internal or sensitive fields that should not be exposed in the CMS interface at all.
readonly_fields, excluded fields do not appear anywhere on the form. They are silently removed during form generation.
role_editable_fields
A dictionary that maps role names to the list of fields that role may edit. It is the primary mechanism for fine-grained, role-scoped field access.
| Key | Meaning |
|---|---|
"superuser" | Applies to users where is_superuser is True |
"editor", "author", etc. | Applies to users in the matching Django group |
"*" | Wildcard — applies to any role not matched by a specific key |
| Value | Meaning |
|---|---|
["field1", "field2", ...] | Only these fields are editable |
"__all__" | All non-readonly, non-excluded fields are editable |
[] | No fields are editable (form is fully read-only for this role) |
Resolution: resolve_editable_fields()
The resolution logic lives in var_cms/permissions.py:
- Exact role match — if the resolved role string has an entry in
role_editable_fields, use it. - Superuser default — if the role is
"superuser"and no explicit entry exists, return"__all__"automatically. - Wildcard
"*"— if neither of the above matched, check for a"*"key and use its value. - Deny all edits — if nothing matched, return
[], making the form completely read-only for this user.
How Fields Are Disabled in the Form
get_editable_fields(request) wraps resolve_editable_fields() and is called during form construction in get_form():
get_form(), after the Django ModelForm is instantiated, each field not in the editable list has disabled = True applied:
field.disabled = True on a Django form field renders the widget as disabled in HTML and causes Django to ignore any submitted value for that field — preventing client-side tampering even if a user manually edits the HTML.
Complete Example — ArticleAdmin
The demoArticleAdmin demonstrates a four-tier field access matrix:
What each role sees on the Article form
What each role sees on the Article form
| Role | Editable fields | Read-only fields (from readonly_fields) | Disabled fields |
|---|---|---|---|
| superuser | All fields | created_at, updated_at, view_count | None |
| editor | title, slug, body, status, category, is_featured | created_at, updated_at, view_count | author, rating |
| author | title, body, status | created_at, updated_at, view_count | slug, author, category, is_featured, rating |
| viewer | — (no edit permission) | created_at, updated_at, view_count | All fields |
| alice | — (role resolves to "viewer", hits "*": []) | created_at, updated_at, view_count | All fields |
alice is in the viewer group, so _get_user_role() returns "viewer". resolve_editable_fields only uses the resolved role — not the username — so her UserPermission entry has no effect on field access. The "*": [] wildcard applies, and all form fields are disabled even though her UserPermission grants edit=True at the action level.Example — CategoryAdmin
A simpler two-role setup — editors control the content fields, superusers get everything:The
slug field in CategoryAdmin has a regex validator (^[a-z0-9-]+$) but editors cannot edit it. Only superusers can modify the slug after a category is created, preventing accidental URL-breaking edits.Combining Permissions and Field Access for Full RBAC
role_editable_fields and permissions work at different levels and are designed to be used together:
permissionscontrols whether a user can reach the edit form at all (theeditaction gate).role_editable_fieldscontrols which fields are actually changeable once they are on the form.
author user:
- Can open the add/edit form (the
editpermission isTrue). - Can only modify
title,body, andstatus— all other fields render as disabled inputs. - Cannot delete the record (the
deletepermission isFalse).