Help & Support

🚀Installation & Setup

1. Install

pip install django-var-cms pillow whitenoise

2. Add to INSTALLED_APPS

INSTALLED_APPS = [
    ...
    "var_cms",
]

3. Add URLs

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)

4. Set login redirect

LOGIN_URL          = "/var-cms/login/"
LOGIN_REDIRECT_URL = "/var-cms/"

⚙️settings.py Configuration

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"
💡 All VAR_CMS_* settings are optional. Built-in defaults will be used for any key you omit.

📚Register Models

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)

🔒How Roles Work

Roles in django-var-cms map directly to Django Groups. A user's group name becomes their role name inside the CMS.

ConceptDjango EquivalentHow it's used
RoleGroupThe group name string is matched against RolePermission("role_name", ...)
Superuseris_superuser=TrueAlways resolves to the "superuser" role
Per-user overrideUsername matchUserPermission("alice", ...) matches by username exactly
⚠️ If a user belongs to multiple groups, the first group that matches a defined permission role is used.

➕Creating Roles (Groups)

A "role" is simply a Django Group. Create them via Django Admin:

  1. Go to /admin/auth/group/add/
  2. Enter a name, e.g. manager
  3. Save (no need to assign Django permissions — var-cms handles its own)
  4. Reference the same name in your RolePermission
from 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),
]
💡 You can also create groups programmatically in a migration or management command:
Group.objects.get_or_create(name="manager")

🤝Assigning Users to Roles

Assign a user to a group in Django Admin:

  1. Go to /admin/auth/user/ → click a user
  2. Scroll to Groups section
  3. Select your group (e.g. manager) and save

That user will now have the permissions you defined in RolePermission("manager", ...).

Per-user override (no group needed)

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
]

🔒Security

Authentication

  • All CMS views require login — unauthenticated users are redirected to the login page.
  • Optional OTP 2FA (email-based 6-digit code) via VAR_CMS_ENABLE_OTP = True.
  • Passwords are hashed using Django's standard PBKDF2 algorithm.

Authorization

  • Every view (add / list / view / edit / delete) checks permission before rendering.
  • Unauthorized access raises PermissionDenied (HTTP 403), never silently redirects.
  • Edit button is shown as disabled (not hidden) in the list view so users understand the restriction.
  • Field-level: role_editable_fields restricts which fields each role may modify on the form.
  • readonly_fields are always non-editable regardless of role.

CSRF & XSS

  • All forms include Django's CSRF token.
  • All output is auto-escaped by Django's template engine. Only explicitly marked safe values (file previews, icons) bypass escaping.

File Uploads

  • Files are validated by Django's ImageField / FileField.
  • Uploads go to your configured MEDIA_ROOT — never executed as code.
  • Image crop and convert endpoints require login and operate only on files already in MEDIA_ROOT.
⚠️ Always run with DEBUG = False in production and set a strong SECRET_KEY. Use HTTPS to protect sessions.

💻Form Column Widths

Use form_field_widths to set how wide each field is in the 12-column grid.

ValueColumnsTypical use
"full"12 / 12Textarea, rich text, addresses
"half"6 / 12Default for most fields
"one-third"4 / 12Status, category, short codes
"two-thirds"8 / 12Title, description
"one-fourth"3 / 12Compact numeric fields
"three-fourths"9 / 12Wide + companion narrow
class InvoiceAdmin(VarCMSModelAdmin):
    form_field_widths = {
        "title":  "two-thirds",
        "status": "one-third",
        "notes":  "full",
    }

💻Row Grouping

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.

✏️Placeholders & Help Text

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."),
    }

📊Dashboard Cards

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 or hide from dashboard

# 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

Globally in settings.py

# 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

Custom card buttons

class InvoiceAdmin(VarCMSModelAdmin):
    card_buttons = [
        {"label": "All Invoices", "action": "list"},
        {"label": "New Invoice",  "action": "add"},
        {"label": "Reports",      "url": "/reports/", "class": "btn-ghost"},
    ]

✨Branding

# 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%.

Live Accent Color Theme Preview

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.

Presets:
Custom: 250, 95%, 72%
VAR_CMS_ACCENT_COLOR = "250, 95%, 72%"