Skip to content

escalated-dev/escalated-django

Repository files navigation

Escalated for Django

A full-featured, embeddable support ticket system for Django. Drop it into any app — get a complete helpdesk with SLA tracking, escalation rules, agent workflows, and a customer portal. No external services required.

Three hosting modes. Run entirely self-hosted, sync to a central cloud for multi-app visibility, or proxy everything to the cloud. Switch modes with a single config change.

Features

  • Ticket lifecycle — Create, assign, reply, resolve, close, reopen with configurable status transitions
  • SLA engine — Per-priority response and resolution targets, business hours calculation, automatic breach detection
  • Escalation rules — Condition-based rules that auto-escalate, reprioritize, reassign, or notify
  • Agent dashboard — Ticket queue with filters, bulk actions, internal notes, canned responses
  • Customer portal — Self-service ticket creation, replies, and status tracking
  • Admin panel — Manage departments, SLA policies, escalation rules, tags, and view reports
  • File attachments — Drag-and-drop uploads with configurable storage and size limits
  • Activity timeline — Full audit log of every action on every ticket
  • Email notifications — Configurable per-event notifications with webhook support
  • Department routing — Organize agents into departments with auto-assignment (round-robin)
  • Tagging system — Categorize tickets with colored tags
  • Inertia.js + Vue 3 UI — Shared frontend via @escalated-dev/escalated

v0.4.0 — Advanced Features

  • Bulk actions — Assign, change status/priority, add tags, close, or delete multiple tickets at once
  • Macros — Reusable multi-step automations (set status + assign + add note in one click)
  • Ticket followers — Agents follow tickets and receive the same notifications as the assignee
  • Satisfaction ratings — 1-5 star CSAT ratings with optional comments after resolution
  • Pinned notes — Pin important internal notes to the top of the ticket thread
  • Keyboard shortcuts — Full keyboard navigation for power users
  • Quick filters — One-click filter chips (My Tickets, Unassigned, Urgent, SLA Breaching)
  • Presence indicators — See who else is viewing a ticket in real-time
  • Enhanced dashboard — CSAT metrics, resolution times, SLA breach tracking

Requirements

  • Python 3.10+
  • Django 4.2+
  • Node.js 18+ (for frontend assets)

Quick Start

pip install escalated-django
npm install @escalated-dev/escalated

1. Add to INSTALLED_APPS

INSTALLED_APPS = [
    # ...
    'django.contrib.contenttypes',
    'inertia',
    'escalated',
]

2. Include URLs

from django.urls import path, include

urlpatterns = [
    # ...
    path("support/", include("escalated.urls")),
]

3. Run migrations

python manage.py migrate escalated

Visit /support — you're live.

Frontend Setup

Escalated uses Inertia.js with Vue 3. The frontend components are provided by the @escalated-dev/escalated npm package.

Tailwind Content

Add the Escalated package to your Tailwind content config so its classes aren't purged:

// tailwind.config.js
content: [
    // ... your existing paths
    './node_modules/@escalated-dev/escalated/src/**/*.vue',
],

Page Resolver

Add the Escalated pages to your Inertia page resolver:

// frontend/main.js
import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'

createInertiaApp({
  resolve: name => {
    if (name.startsWith('Escalated/')) {
      const escalatedPages = import.meta.glob(
        '../node_modules/@escalated-dev/escalated/src/pages/**/*.vue',
        { eager: true }
      )
      const pageName = name.replace('Escalated/', '')
      return escalatedPages[`../node_modules/@escalated-dev/escalated/src/pages/${pageName}.vue`]
    }

    const pages = import.meta.glob('./pages/**/*.vue', { eager: true })
    return pages[`./pages/${name}.vue`]
  },
  setup({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) })
      .use(plugin)
      .mount(el)
  },
})

Theming (Optional)

Register the EscalatedPlugin to render Escalated pages inside your app's layout — no page duplication needed:

import { EscalatedPlugin } from '@escalated-dev/escalated'
import BaseLayout from '@/layouts/BaseLayout.vue'

createInertiaApp({
  setup({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) })
      .use(plugin)
      .use(EscalatedPlugin, {
        layout: BaseLayout,
        theme: {
          primary: '#3b82f6',
          radius: '0.75rem',
        }
      })
      .mount(el)
  },
})

Your layout component must accept a #header slot and a default slot. Escalated will render its sub-navigation in the header and page content in the default slot. Without the plugin, Escalated uses its own standalone layout.

See the @escalated-dev/escalated README for full theming documentation and CSS custom properties.

Hosting Modes

Self-Hosted (default)

Everything stays in your database. No external calls. Full autonomy.

ESCALATED = {
    "MODE": "self_hosted",
}

Synced

Local database + automatic sync to cloud.escalated.dev for unified inbox across multiple apps. If the cloud is unreachable, your app keeps working — events queue and retry.

ESCALATED = {
    "MODE": "synced",
    "HOSTED_API_URL": "https://cloud.escalated.dev/api/v1",
    "HOSTED_API_KEY": "your-api-key",
}

Cloud

All ticket data proxied to the cloud API. Your app handles auth and renders UI, but storage lives in the cloud.

ESCALATED = {
    "MODE": "cloud",
    "HOSTED_API_URL": "https://cloud.escalated.dev/api/v1",
    "HOSTED_API_KEY": "your-api-key",
}

All three modes share the same views, UI, and business logic. The driver pattern handles the rest.

Configuration

Add to your settings.py:

ESCALATED = {
    "MODE": "self_hosted",              # self_hosted | synced | cloud
    "TABLE_PREFIX": "escalated_",
    "ROUTE_PREFIX": "support",
    "DEFAULT_PRIORITY": "medium",

    # Tickets
    "ALLOW_CUSTOMER_CLOSE": True,
    "AUTO_CLOSE_RESOLVED_AFTER_DAYS": 7,
    "MAX_ATTACHMENTS": 5,
    "MAX_ATTACHMENT_SIZE_KB": 10240,

    # SLA
    "SLA": {
        "ENABLED": True,
        "BUSINESS_HOURS_ONLY": False,
        "BUSINESS_HOURS": {
            "START": "09:00",
            "END": "17:00",
            "TIMEZONE": "UTC",
            "DAYS": [1, 2, 3, 4, 5],
        },
    },

    # Notifications
    "NOTIFICATION_CHANNELS": ["email"],
    "WEBHOOK_URL": None,

    # Cloud/Synced mode
    "HOSTED_API_URL": "https://cloud.escalated.dev/api/v1",
    "HOSTED_API_KEY": None,
}

Management Commands

# Check SLA deadlines and fire breach notifications
python manage.py check_sla

# Evaluate escalation rules against open tickets
python manage.py evaluate_escalations

# Auto-close tickets resolved more than N days ago
python manage.py close_resolved --days 7

# Purge old activity logs
python manage.py purge_activities --days 90

Schedule these with cron, Celery Beat, or django-crontab for automated enforcement.

Routes

All routes use the configurable prefix (default: support).

Route Method Description
/support/tickets/ GET Customer ticket list
/support/tickets/create/ GET New ticket form
/support/tickets/<id>/ GET Ticket detail
/support/agent/ GET Agent dashboard
/support/agent/tickets/ GET Agent ticket queue
/support/agent/tickets/<id>/ GET Agent ticket view
/support/admin/reports/ GET Admin reports
/support/admin/departments/ GET Department management
/support/admin/sla-policies/ GET SLA policy management
/support/admin/escalation-rules/ GET Escalation rule management
/support/admin/tags/ GET Tag management
/support/admin/canned-responses/ GET Canned response management
/support/agent/tickets/bulk/ POST Bulk actions on multiple tickets
/support/agent/tickets/<id>/follow/ POST Follow/unfollow a ticket
/support/agent/tickets/<id>/macro/ POST Apply a macro to a ticket
/support/agent/tickets/<id>/presence/ POST Update presence on a ticket
/support/agent/tickets/<id>/pin/<reply_id>/ POST Pin/unpin an internal note
/support/tickets/<id>/rate/ POST Submit satisfaction rating

Signals

Connect to ticket lifecycle events:

from escalated.signals import ticket_created, ticket_resolved

@receiver(ticket_created)
def on_ticket_created(sender, ticket, user, **kwargs):
    print(f"New ticket: {ticket.reference}")

@receiver(ticket_resolved)
def on_ticket_resolved(sender, ticket, user, **kwargs):
    print(f"Resolved: {ticket.reference}")

Available signals: ticket_created, ticket_updated, ticket_status_changed, ticket_assigned, ticket_unassigned, ticket_priority_changed, ticket_escalated, ticket_resolved, ticket_closed, ticket_reopened, reply_created, internal_note_added, sla_breached, sla_warning, tag_added, tag_removed, department_changed.

Also Available For

Same architecture, same Vue UI, same three hosting modes — for every major backend framework.

Development

pip install -e ".[dev]"
pytest

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published