{% extends "var_cms/base.html" %} {% load var_cms_tags %} {% block title %}Help & Documentation — {{ var_cms_site.site_header }}{% endblock %} {% block breadcrumb %} › Help & Documentation {% endblock %} {% block extra_head %} {% endblock %} {% block content %}

pip install django-var-cms pillow whitenoise
INSTALLED_APPS = [
...
"var_cms",
]
from django.urls import path, include
from django.conf.urls.static import static
urlpatterns = [
path("var-cms/", include("var_cms.urls", namespace="var_cms")),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
LOGIN_URL = "/var-cms/login/" LOGIN_REDIRECT_URL = "/var-cms/"

Put all global CMS config in your settings.py using the VAR_CMS_ prefix. Your var_cms_admin.py files should only contain model registrations.
# settings.py VAR_CMS_SITE_HEADER = "My App" # Top-left brand name VAR_CMS_SITE_SUB = "ADMIN PANEL" # Subtitle under brand VAR_CMS_SITE_URL = "https://myapp.com" VAR_CMS_LOGO_URL = "/static/logo.png" VAR_CMS_ACCENT_COLOR = "250, 95%, 72%" # HSL e.g. purple VAR_CMS_ENABLE_OTP = False # True to require email OTP on login VAR_CMS_USERNAME_FIELD = "email" # if your user model uses email as login # Dashboard card visibility (optional) VAR_CMS_HIDDEN_DASHBOARD_CARDS = ["logentry", "demo.category"] VAR_CMS_SHOWN_DASHBOARD_CARDS = ["invoice", "customer"] # show ONLY these # Developer credits shown in Help page VAR_CMS_DEVELOPER_NAME = "Your Name" VAR_CMS_DEVELOPER_EMAIL = "you@example.com" VAR_CMS_DEVELOPER_WEBSITE = "https://example.com" VAR_CMS_DEVELOPER_GITHUB = "https://github.com/yourname" VAR_CMS_DEVELOPER_IMAGE = "https://example.com/avatar.png"


Create var_cms_admin.py in any app. It is auto-discovered on startup. Only put model-related config here.
from var_cms.registry import var_cms_site, VarCMSModelAdmin
from var_cms.permissions import RolePermission, UserPermission
from .models import Invoice, Customer
class InvoiceAdmin(VarCMSModelAdmin):
# ── List view ─────────────────────────────────────────────
list_display = ["number", "customer", "amount", "status", "created_at"]
list_filter = ["status"]
search_fields = ["number", "customer__name"]
ordering = ["-created_at"]
list_per_page = 30
icon = "file-invoice" # Lucide icon name
# ── Always read-only ──────────────────────────────────────
readonly_fields = ["created_at", "updated_at"]
# ── Hide from form entirely ───────────────────────────────
exclude_fields = ["internal_notes"]
# ── Permissions ───────────────────────────────────────────
permissions = [
RolePermission("superuser", add=True, list=True, view=True, edit=True, delete=True),
RolePermission("manager", add=True, list=True, view=True, edit=True, delete=False),
RolePermission("viewer", add=False, list=True, view=True, edit=False, delete=False),
UserPermission("alice", add=True, list=True, view=True, edit=True, delete=True),
]
# ── What each role can edit ────────────────────────────────
role_editable_fields = {
"superuser": "__all__",
"manager": ["status", "amount", "due_date"],
}
var_cms_site.register(Invoice, InvoiceAdmin)
var_cms_site.register(Customer)

Roles in django-var-cms map directly to Django Groups. A user's group name becomes their role name inside the CMS.
| Concept | Django Equivalent | How it's used |
|---|---|---|
| Role | Group | The group name string is matched against RolePermission("role_name", ...) |
| Superuser | is_superuser=True | Always resolves to the "superuser" role |
| Per-user override | Username match | UserPermission("alice", ...) matches by username exactly |


A "role" is simply a Django Group. Create them via Django Admin:
managerRolePermissionfrom var_cms.permissions import RolePermission
permissions = [
RolePermission("manager", add=True, list=True, view=True, edit=True, delete=False),
RolePermission("accountant", add=False, list=True, view=True, edit=False, delete=False),
]

Group.objects.get_or_create(name="manager")

Assign a user to a group in Django Admin:
manager) and saveThat user will now have the permissions you defined in RolePermission("manager", ...).
If you want to grant one specific user extra access independently of their group, use UserPermission:
from var_cms.permissions import UserPermission
permissions = [
RolePermission("viewer", add=False, list=True, view=True, edit=False, delete=False),
UserPermission("alice", add=True, list=True, view=True, edit=True, delete=True),
# alice gets full access even though she may be in the viewer group
]

VAR_CMS_ENABLE_OTP = True.add / list / view / edit / delete) checks permission before rendering.PermissionDenied (HTTP 403), never silently redirects.role_editable_fields restricts which fields each role may modify on the form.readonly_fields are always non-editable regardless of role.ImageField / FileField.MEDIA_ROOT — never executed as code.MEDIA_ROOT.
DEBUG = False in production and set a strong SECRET_KEY. Use HTTPS to protect sessions.

Use form_field_widths to set how wide each field is in the 12-column grid.
| Value | Columns | Typical use |
|---|---|---|
"full" | 12 / 12 | Textarea, rich text, addresses |
"half" | 6 / 12 | Default for most fields |
"one-third" | 4 / 12 | Status, category, short codes |
"two-thirds" | 8 / 12 | Title, description |
"one-fourth" | 3 / 12 | Compact numeric fields |
"three-fourths" | 9 / 12 | Wide + companion narrow |
class InvoiceAdmin(VarCMSModelAdmin):
form_field_widths = {
"title": "two-thirds",
"status": "one-third",
"notes": "full",
}

Use form_field_rows to place multiple fields side-by-side in one visual row. Fields in the same row split the width equally.
class CustomerAdmin(VarCMSModelAdmin):
form_field_rows = [
["first_name", "last_name"], # 2 cols → each takes half
["mobile", "email", "date_of_birth"], # 3 cols → each takes one-third
["city", "state", "country", "pin"], # 4 cols → each takes one-fourth
]

form_field_rows takes priority over form_field_widths for listed fields.

Use form_field_widgets to control the dropdown style per field.
| Value | Renders as | Best for |
|---|---|---|
"select" | Standard HTML select | Short choice lists (default) |
"select_search" | Searchable dropdown | ForeignKey with many options |
"multiselect" | Checkbox list | ManyToManyField |
"multiselect_search" | Checkbox list + search | ManyToManyField with many items |
class OrderAdmin(VarCMSModelAdmin):
form_field_widgets = {
"status": "select", # plain dropdown (default)
"customer": "select_search", # searchable dropdown
"tags": "multiselect_search", # checkbox list with search
}

class ArticleAdmin(VarCMSModelAdmin):
form_field_placeholders = {
"title": "Enter the article headline…",
"slug": "auto-generated-from-title",
}
form_field_help_texts = {
"slug": "URL-safe identifier. Leave blank to auto-generate.",
"body": "Supports full HTML via the Quill editor.",
}
# HTML rich text editor on specific fields:
html_fields = ["body", "description"]
# Regex pattern validation:
regex_validators = {
"slug": (r"^[a-z0-9-]+$", "Only lowercase letters, numbers, hyphens."),
}

By default, dashboard cards are hidden (dashboard_card = False). Set it to True explicitly to show them, or configure globally in settings.py.
# Per model: show on dashboard (default is False, so set True to show)
class InvoiceAdmin(VarCMSModelAdmin):
dashboard_card = True # will appear on dashboard
# Per model: hide from dashboard (default)
class LogAdmin(VarCMSModelAdmin):
dashboard_card = False # won't appear on dashboard
# settings.py # Hide specific cards: VAR_CMS_HIDDEN_DASHBOARD_CARDS = ["logentry", "demo.category"] # Show ONLY these cards: VAR_CMS_SHOWN_DASHBOARD_CARDS = ["invoice", "customer"] # show ONLY these
class InvoiceAdmin(VarCMSModelAdmin):
card_buttons = [
{"label": "All Invoices", "action": "list"},
{"label": "New Invoice", "action": "add"},
{"label": "Reports", "url": "/reports/", "class": "btn-ghost"},
]

# settings.py VAR_CMS_SITE_HEADER = "VAR CMS" VAR_CMS_SITE_SUB = "CONTROL PANEL" VAR_CMS_LOGO_URL = "/static/myapp/logo.png" VAR_CMS_ACCENT_COLOR = "142, 72%, 45%" # Emerald green
Accent color is an HSL triplet (hue, saturation%, lightness%) — e.g. purple is 250, 95%, 72%, teal is 180, 60%, 50%.
Pick a custom color or choose one of our harmonized presets to see the entire CMS theme adapt immediately. Once you find a look you love, copy the configuration snippet below for your settings.py.
VAR_CMS_ACCENT_COLOR = "250, 95%, 72%"