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.

custom_object_actions lets you attach one-click action buttons to both the list view (per-row) and the detail view of any registered model. Common use cases include approving records, sending transactional emails, toggling status flags, triggering webhooks, or generating reports — all without leaving the CMS interface.

Defining Actions

Set custom_object_actions to a list of action dictionaries on your VarCMSModelAdmin subclass. Each dictionary describes a single button:
KeyRequiredDescription
nameUnique string identifier used in the action URL
labelButton text displayed to the user
action_fnCallable or string name of a method on the admin class
classCSS button style — "btn-primary", "btn-green", "btn-blue", "btn-danger", "btn-ghost"
iconLucide icon name displayed beside the button label (e.g., "check-circle", "mail", "shuffle")
class CompanyAdmin(VarCMSModelAdmin):
    custom_object_actions = [
        {
            "name":      "approve",
            "label":     "Approve",
            "class":     "btn-green",
            "icon":      "check-circle",
            "action_fn": "approve_company",   # method name as a string
        }
    ]

The action_fn Callable

The function referenced by action_fn receives three arguments: self (the admin instance), request (the current HttpRequest), and obj (the model instance the button was clicked on).
def action_fn(self, request, obj):
    # ... your logic here
    return None  # or return an HttpResponse
Return value behaviour:
  • Return None (or nothing) → the user is automatically redirected back to HTTP_REFERER, or to the model’s list page if no referrer is set.
  • Return an HttpResponse (redirect, JSON, file download, etc.) → that response is sent directly to the browser, giving you full control over routing.
action_fn can be provided either as a string (the name of a method on the admin class) or as a direct callable. Using a string is recommended for class-based organisation, but passing a standalone function works equally well:
def my_standalone_action(self, request, obj):
    obj.is_active = True
    obj.save()

class MyModelAdmin(VarCMSModelAdmin):
    custom_object_actions = [
        {"name": "activate", "label": "Activate", "action_fn": my_standalone_action}
    ]

URL Pattern

Each action is registered at a predictable URL. You can construct it manually or use the var_cms_action_url template tag:
/var-cms/{app_label}/{model_name}/{pk}/action/{action_name}/
For example, the "approve" action on company 42 in a crm app resolves to:
/var-cms/crm/company/42/action/approve/

Permissions

The action view requires edit permission (has_permission(request, "edit")) by default. A user who does not have edit access on the model will receive a 403 Forbidden response when attempting to trigger any action.

Example 1 — Approve Company

This example checks whether a company has uploaded all required documents before marking it as approved. It uses Django’s message framework to surface errors or confirmations back to the user.
class CompanyAdmin(VarCMSModelAdmin):
    list_display = ["name", "is_approved", "documents_uploaded"]

    custom_object_actions = [
        {
            "name":      "approve",
            "label":     "Approve",
            "class":     "btn-green",
            "icon":      "check-circle",
            "action_fn": "approve_company",
        }
    ]

    def approve_company(self, request, obj):
        from django.contrib import messages

        # Guard: documents must be uploaded before approval
        if not obj.documents_uploaded:
            messages.error(
                request,
                f"Cannot approve {obj.name} — documents have not been uploaded yet."
            )
            return None  # redirect back with the error message visible

        obj.is_approved = True
        obj.save()
        messages.success(request, f"Company '{obj.name}' has been successfully approved.")
        return None  # redirect back with the success message visible


var_cms_site.register(Company, CompanyAdmin)

Example 2 — Send Welcome Email

This example manually triggers a welcome email to a newsletter subscriber via Django’s send_mail. Exceptions during delivery are caught and surfaced as error messages instead of crashing the request.
class SubscriberAdmin(VarCMSModelAdmin):
    list_display = ["email", "first_name", "status"]

    custom_object_actions = [
        {
            "name":      "send_welcome",
            "label":     "Send Welcome",
            "class":     "btn-blue",
            "icon":      "mail",
            "action_fn": "send_welcome_email",
        }
    ]

    def send_welcome_email(self, request, obj):
        from django.core.mail import send_mail
        from django.contrib import messages

        try:
            send_mail(
                subject="Welcome to our newsletter!",
                message=f"Hello {obj.first_name},\nThank you for joining us!",
                from_email="noreply@example.com",
                recipient_list=[obj.email],
            )
            messages.success(request, f"Welcome email sent successfully to {obj.email}.")
        except Exception as e:
            messages.error(request, f"Failed to send email to {obj.email}: {e}")

        return None


var_cms_site.register(Subscriber, SubscriberAdmin)

Example 3 — Toggle Active Status (from the Demo App)

The demo app’s CategoryAdmin includes a "Toggle Active" action that flips the is_active boolean and saves the object in a single click:
class CategoryAdmin(VarCMSModelAdmin):
    list_display  = ["name", "slug", "is_active", "created_at"]
    list_filter   = ["is_active"]
    search_fields = ["name", "slug", "description"]
    icon          = "folder"

    custom_object_actions = [
        {
            "name":      "toggle_active",
            "label":     "Toggle Active",
            "class":     "btn-blue",
            "icon":      "shuffle",
            "action_fn": "toggle_active_status",
        }
    ]

    def toggle_active_status(self, request, obj):
        from django.contrib import messages
        obj.is_active = not obj.is_active
        obj.save()
        messages.success(
            request,
            f"Category '{obj.name}' active status toggled to {obj.is_active}."
        )
        return None


var_cms_site.register(Category, CategoryAdmin)

Dashboard Card Buttons — card_buttons

While custom_object_actions places buttons on individual object rows, card_buttons places navigation buttons at the bottom of a model’s dashboard card. Each button is a dict with the following keys:
KeyDescription
labelButton text
actionBuilt-in action: "list" (go to list view) or "add" (go to add form)
urlAny custom relative or absolute URL (used when action is not set)
classOptional CSS class: "btn-primary", "btn-ghost", "btn-danger", "btn-green", "btn-blue"

Default buttons

When card_buttons is not set (or is an empty list), django-var-cms automatically generates two default buttons for the card, subject to the user’s permissions:
  • “View List” (btn-ghost) — shown if the user has list permission
  • “Add New” (btn-primary) — shown if the user has add permission

Custom buttons

Define card_buttons to replace the defaults with your own set. When action is "list" or "add", django-var-cms resolves the correct CMS URL automatically:
class ArticleAdmin(VarCMSModelAdmin):
    icon           = "file-text"
    dashboard_card = True

    card_buttons = [
        {"label": "All Articles",  "action": "list"},
        {"label": "Write Draft",   "action": "add"},
    ]
You can also supply a raw url for buttons that point outside the CMS:
class ArticleAdmin(VarCMSModelAdmin):
    icon           = "file-text"
    dashboard_card = True

    card_buttons = [
        {"label": "All Articles",  "action": "list"},
        {"label": "Write Draft",   "action": "add"},
        {"label": "External Docs", "url": "https://example.com/docs", "class": "btn-ghost"},
    ]
card_buttons controls navigation shortcuts on the dashboard card — it does not restrict which users can access those views. Permission enforcement is handled separately by the permissions list on your admin class.

Build docs developers (and LLMs) love