Skip to main content

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.

The var_cms.permissions module provides a lightweight, framework-independent set of dataclasses and pure-function helpers for role- and user-level access control. All permission evaluation in VarCMSModelAdmin goes through these primitives.
from var_cms.permissions import RolePermission, GroupPermission, UserPermission
from var_cms.permissions import resolve_permission, resolve_editable_fields

ACTIONS Constant

ACTIONS = ("add", "list", "view", "edit", "delete")
The five actions recognised by the permissions system. Any string outside this tuple is automatically denied by resolve_permission.
ActionCMS view gated
"add"Add / create form
"list"Paginated list view
"view"Read-only detail view
"edit"Edit form
"delete"Delete confirmation

RolePermission

Maps a role name to a set of allowed actions.
@dataclass
class RolePermission:
    role: str
    add: bool    = False
    list: bool   = True
    view: bool   = True
    edit: bool   = False
    delete: bool = False
role
str
required
The role identifier. Matched against:
  1. "superuser"request.user.is_superuser is True.
  2. Any Django group name the user belongs to (request.user.groups).
  3. Custom role logic — override VarCMSModelAdmin._get_user_role() for non-group-based roles.
add
bool
default:"False"
Permits creating new objects.
list
bool
default:"True"
Permits viewing the paginated list.
view
bool
default:"True"
Permits viewing the read-only detail page.
edit
bool
default:"False"
Permits modifying existing objects.
delete
bool
default:"False"
Permits deleting objects.

allows(action: str) → bool

Returns the boolean value of the named action attribute.
perm = RolePermission("editor", add=True, list=True, view=True, edit=True, delete=False)
perm.allows("edit")    # True
perm.allows("delete")  # False

Role Matching Details

def _get_user_role(self, request) -> str:
    if request.user.is_superuser:
        return "superuser"
    groups = list(request.user.groups.values_list("name", flat=True))
    # First group that appears in the permissions list wins
    if self.permissions:
        defined_roles = {p.role for p in self.permissions if hasattr(p, "role")}
        for g in groups:
            if g in defined_roles:
                return g
    return groups[0] if groups else "anonymous"
A user’s first matching group (in the order returned by Django) is used as their role. If a user belongs to both "editor" and "viewer" and both are in your permissions list, the group that appears first in the database result wins. Keep group assignments unambiguous.

GroupPermission

An alias for RolePermission that communicates intent — the role value explicitly refers to a Django auth group name.
@dataclass
class GroupPermission(RolePermission):
    """Alias — identical to RolePermission, role matches a Django group name."""
    pass
GroupPermission and RolePermission are interchangeable at runtime. Use whichever makes your intent clearer.
from var_cms.permissions import GroupPermission

permissions = [
    GroupPermission("editors",    add=True, list=True, view=True, edit=True,  delete=False),
    GroupPermission("moderators", add=False,list=True, view=True, edit=False, delete=False),
]

UserPermission

Per-user permission override. Takes priority over all role and group permissions when the authenticated user’s username matches.
@dataclass
class UserPermission:
    username: str
    add: bool    = False
    list: bool   = True
    view: bool   = True
    edit: bool   = False
    delete: bool = False
username
str
required
The username to match against. Resolution uses get_username() or falls back to the model’s USERNAME_FIELD attribute (configurable via VAR_CMS_USERNAME_FIELD / var_cms_site.username_field).
add
bool
default:"False"
Permits creating new objects for this specific user.
list
bool
default:"True"
Permits viewing the paginated list.
view
bool
default:"True"
Permits the read-only detail page.
edit
bool
default:"False"
Permits modifying existing objects.
delete
bool
default:"False"
Permits deleting objects.

allows(action: str) → bool

Returns the boolean value of the named action attribute — identical signature to RolePermission.allows.

Example: per-user override

permissions = [
    RolePermission("viewer", add=False, list=True, view=True, edit=False, delete=False),
    # alice is technically a viewer, but gets full delete rights
    UserPermission("alice",  add=True,  list=True, view=True, edit=True,  delete=True),
]
UserPermission entries are checked before RolePermission entries. The first UserPermission whose username matches the requesting user short-circuits all further checks — no role rules are consulted for that user.

resolve_permission()

def resolve_permission(
    permissions: List[Union[RolePermission, UserPermission]],
    role: str,
    action: str,
    username: str = "",
) -> bool:
Walk the permissions list and return True if the action is allowed.
ParameterTypeDescription
permissionsList[RolePermission | UserPermission]The permissions list from the model admin.
rolestrThe resolved role name for the current user (from _get_user_role).
actionstrOne of the values in ACTIONS. Any other value returns False immediately.
usernamestrThe current user’s username, used for UserPermission matching. Defaults to "".
Resolution order:
  1. UserPermission match — iterate the list; the first UserPermission whose .username == username wins and its allows(action) is returned immediately.
  2. RolePermission match — iterate the list; the first RolePermission whose .role == role wins and its allows(action) is returned.
  3. Default deny — if no match is found, False is returned.
from var_cms.permissions import RolePermission, UserPermission, resolve_permission

perms = [
    RolePermission("superuser", add=True, list=True, view=True, edit=True, delete=True),
    RolePermission("editor",    add=True, list=True, view=True, edit=True, delete=False),
    UserPermission("alice",     add=True, list=True, view=True, edit=True, delete=True),
]

resolve_permission(perms, role="editor",    action="delete", username="bob")   # False
resolve_permission(perms, role="editor",    action="delete", username="alice") # True  (UserPermission wins)
resolve_permission(perms, role="superuser", action="delete", username="")      # True
resolve_permission(perms, role="anonymous", action="list",   username="")      # False (no match → deny)

resolve_editable_fields()

def resolve_editable_fields(
    role_editable_fields: Dict[str, Union[List[str], str]],
    role: str,
) -> Union[List[str], str]:
Return the fields a given role may edit from the role_editable_fields mapping.
ParameterTypeDescription
role_editable_fieldsDict[str, List[str] | str]The role_editable_fields dict from the model admin.
rolestrThe resolved role name for the current user.
Resolution order:
  1. If role is a key in role_editable_fields, return its value (a list or "__all__").
  2. If role == "superuser" and no explicit entry exists, return "__all__" (superusers always get full edit access unless explicitly restricted).
  3. If the key "*" (wildcard) exists in role_editable_fields, return its value.
  4. Return [] — deny all edits for unrecognised roles.
from var_cms.permissions import resolve_editable_fields

fields = {
    "superuser": "__all__",
    "editor":    ["title", "body", "status"],
    "author":    ["title", "body"],
    "*":         [],
}

resolve_editable_fields(fields, "superuser")   # "__all__"
resolve_editable_fields(fields, "editor")      # ["title", "body", "status"]
resolve_editable_fields(fields, "author")      # ["title", "body"]
resolve_editable_fields(fields, "viewer")      # []  (wildcard "*" → [])
resolve_editable_fields(fields, "anonymous")   # []
When role_editable_fields is an empty dict {} and get_editable_fields() falls through to resolve_editable_fields, the superuser fallback in step 2 still applies. Every other role receives [] in that case.

permission_summary()

def permission_summary(permissions: List[RolePermission]) -> List[Dict]:
Returns a serialisable list of dicts representing each permission entry. Useful for rendering permission tables in templates or returning them in an API response. The function iterates permissions and includes any entry that is an instance of RolePermission or UserPermission (checked via isinstance(p, (RolePermission, UserPermission))). Each included entry is rendered using p.role — this works for RolePermission and GroupPermission instances which have a .role attribute. Returns: A list of dicts, each containing role, add, list, view, edit, and delete keys.
from var_cms.permissions import RolePermission, permission_summary

perms = [
    RolePermission("superuser", add=True,  list=True, view=True, edit=True,  delete=True),
    RolePermission("editor",    add=True,  list=True, view=True, edit=True,  delete=False),
]

permission_summary(perms)
# [
#   {"role": "superuser", "add": True,  "list": True, "view": True, "edit": True,  "delete": True},
#   {"role": "editor",    "add": True,  "list": True, "view": True, "edit": True,  "delete": False},
# ]

Complete Permissions Example

# myapp/var_cms_admin.py
from var_cms.registry import var_cms_site, VarCMSModelAdmin
from var_cms.permissions import RolePermission, GroupPermission, UserPermission
from .models import Article


class ArticleAdmin(VarCMSModelAdmin):
    list_display     = ["title", "status", "author", "created_at"]
    readonly_fields  = ["created_at", "updated_at"]

    permissions = [
        # Role-based permissions
        RolePermission("superuser", add=True,  list=True, view=True, edit=True,  delete=True),
        RolePermission("editor",    add=True,  list=True, view=True, edit=True,  delete=False),
        RolePermission("author",    add=True,  list=True, view=True, edit=True,  delete=False),
        RolePermission("viewer",    add=False, list=True, view=True, edit=False, delete=False),

        # Group-based (identical behaviour, different import for readability)
        GroupPermission("moderators", add=False, list=True, view=True, edit=True, delete=False),

        # Per-user override — alice can delete despite being in the viewer group
        UserPermission("alice",     add=True,  list=True, view=True, edit=True,  delete=True),
    ]

    role_editable_fields = {
        "superuser":   "__all__",
        "editor":      ["title", "slug", "body", "status", "category", "is_featured"],
        "author":      ["title", "body", "status"],
        "moderators":  ["status"],
        "*":           [],   # all other authenticated roles: no edits
    }


var_cms_site.register(Article, ArticleAdmin)

Resolution Cheat-Sheet

ScenarioResult
UserPermission matches usernameThat entry wins; no further checks
RolePermission matches roleThat entry’s action value is returned
No match in permissions listFalse — access denied
role_editable_fields[role] existsThat value returned (list or "__all__")
role == "superuser" but no key set"__all__" returned automatically
Key "*" in role_editable_fieldsWildcard value returned for unknown roles
No match and no wildcard[] — no fields editable

Build docs developers (and LLMs) love