Skip to main content

العربية • Deutsch • English • Español • Français • Italiano • 日本語 • 한국어 • Nederlands • Polski • Português (BR) • Русский • Türkçe • 简体中文

Escalated for Django

TestsPython Django PyPI Downloads License: MIT Views

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.

escalated.dev — Learn more, view demos, and compare Cloud vs Self-Hosted options.

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
  • Ticket splitting — Split a reply into a new standalone ticket while preserving the original context
  • Ticket snooze — Snooze tickets with presets (1h, 4h, tomorrow, next week); python manage.py wake_snoozed_tickets management command auto-wakes them on schedule
  • Saved views / custom queues — Save, name, and share filter presets as reusable ticket views
  • Embeddable support widget — Lightweight <script> widget with KB search, ticket form, and status check
  • Email threading — Outbound emails include proper In-Reply-To and References headers for correct threading in mail clients
  • Inbound email — Mailgun, Postmark, SES webhooks and IMAP polling create tickets and replies. See Inbound email
  • Branded email templates — Configurable logo, primary color, and footer text for all outbound emails
  • Real-time broadcasting — Opt-in broadcasting via Django Channels with automatic polling fallback
  • Knowledge base toggle — Enable or disable the public knowledge base from admin settings

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.

Translations (i18n)

Translations come from the central escalated-locale project, which publishes pre-compiled gettext artifacts at escalated_locale/locale/<lang>/LC_MESSAGES/django.{po,mo} that plug directly into Django's translation system via LOCALE_PATHS.

pip install escalated-django bundles a copy of those catalogues (escalated/_central_locale/, synced from a tagged escalated-locale release), so translations work with no extra install. To use the standalone package instead, install the locale extra; when escalated_locale is importable its catalogues win over the bundled copy:

pip install "escalated-django[locale]"

Wire the catalogues into your project's settings.py:

# settings.py
from escalated.locale_paths import get_locale_paths

LOCALE_PATHS = get_locale_paths(
    # Optional: your project's own override directory (highest priority)
    BASE_DIR / "locale",
)

LOCALE_PATHS is searched in order, so the layering becomes:

  1. Your project's locale/ (if you passed one to get_locale_paths)
  2. The plugin-local escalated/locale/ override directory
  3. The central baseline: the installed escalated_locale/locale/ if present, otherwise the bundled escalated/_central_locale/locale/

Translation contributions should be sent to escalated-locale. The plugin-local escalated/locale/ directory is reserved for Django-specific overrides that shouldn't ship to other framework plugins.

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

Agent actions taken in the cloud portal come back through a signed webhook. Set the site's signing secret (shown on the connected site in the cloud dashboard) and give the cloud this URL:

ESCALATED = {
    "MODE": "synced",
    "HOSTED_API_URL": "https://cloud.escalated.dev/api/v1",
    "HOSTED_API_KEY": "your-api-key",
    "HOSTED_SIGNING_SECRET": "whsec_...",  # webhook URL: https://your-app.example.com/support/cloud/webhook/
}

The receiver verifies X-Escalated-Signature (HMAC-SHA256 of the raw body), ignores replayed event_ids for 24 hours, and applies subject, description, priority and status from ticket.updated / ticket.status_changed to the local ticket whose reference matches the projection's external_id. Changes run through the local driver, so your signal handlers fire, and are not echoed back to the cloud.

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,
    "ATTACHMENT_DOWNLOAD_TTL_MINUTES": 10,  # see docs/private-attachments.md
    # 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,
    # Email threading: domain for Message-IDs, secret that signs the Reply-To
    "EMAIL_DOMAIN": "support.example.com",
    "EMAIL_INBOUND_SECRET": "",
    # Cloud/Synced mode
    "HOSTED_API_URL": "https://cloud.escalated.dev/api/v1",
    "HOSTED_API_KEY": None,
}

Inbound email

Inbound mail is matched to a ticket like this:

  • Thread matching: when EMAIL_INBOUND_SECRET is set, outbound mail carries a signed Reply-To (reply+{id}.{hmac8}@domain) and only that address links an inbound email to a ticket. Without a secret, the In-Reply-To / References Message-IDs and the subject reference (e.g. [ESC-A1B2C3]) are used instead.
  • Sender check: a matched email becomes a reply only when the From address is the ticket's requester (the guest email or the requester's email, case-insensitive). It is posted as that requester. Staff identity is never taken from the From header, so agents reply in the app.
  • Anything else (no match, or a sender who is not the requester) creates a new ticket, so mail is never dropped.
  • Reopen: an accepted reply reopens a resolved or closed ticket.

Set a secret in production; Message-IDs and ticket references can be guessed.

UUID / string user keys

Escalated works with any AUTH_USER_MODEL primary key type — integer, UUID, or string. References to your users via ForeignKey(AUTH_USER_MODEL) (assignee, follower, author, etc.) already match your user PK automatically. The remaining host-user references — Contact.user_id and the generic-relation object_id columns for ticket requester, activity causer, satisfaction rated_by, and API token owner — are stored as CharField, so they hold an integer id (as its string form) or a UUID/string id transparently. No configuration is required; just run migrations.

Ticket subjects

A ticket has a requester (who raised it) and a subject line (free text). You can also attach host-app entities the ticket is about — a project, customer, or asset — so agents see context and can jump back into your app.

Implement the presentation contract on any model (or use the mixin for defaults):

from django.db import models

from escalated.contracts import TicketSubjectMixin


class Project(models.Model, TicketSubjectMixin):
    name = models.CharField(max_length=255)

    def ticket_subject_subtitle(self):
        return f"Project · {self.customer.name}"

    def ticket_subject_url(self):
        return reverse("projects:detail", args=[self.pk])

Attach, detach, or replace subjects on a ticket:

ticket.attach_subject(project, role="project")
ticket.attach_subject(customer, role="account")
ticket.sync_subjects([project, (customer, "account")])
ticket.detach_subject(project)

Each link is serialized on the ticket as { type, id, role, title, subtitle, url, color, icon, missing }. object_id is stored as a string so integer, UUID, and string primary keys all work.

To allow attaching via the agent or REST API (and block arbitrary model resolution), list permitted models in settings:

ESCALATED = (
    {
        # ...
        "TICKET_SUBJECT_TYPES": [
            "myapp.Project",
            "myapp.Customer",
        ],
    },
)

Leave TICKET_SUBJECT_TYPES empty to disable API attach; programmatic attach_subject() still works for any model when the allowlist is empty.

Agent/admin routes: POST /…/tickets/<id>/subjects, DELETE /…/tickets/<id>/subjects/<link_id>. REST API: POST /tickets/<reference>/subjects, DELETE /tickets/<reference>/subjects/<link_id>.

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.

Custom Ticket Actions

Host projects can add custom buttons to the agent ticket screen and handle clicks with a normal Django signal receiver. Register actions in settings:

# settings.py
ESCALATED = {
    # ...
    "TICKET_ACTIONS": [
        {
            "key": "sync-crm",
            "label": "Sync CRM",
            "variant": "primary",  # primary | secondary | danger
            "confirmation": "Sync this ticket to the CRM?",
            "metadata": {"icon": "refresh-cw"},
            # visible / enabled may be a bool or a callable(ticket, user)
            "enabled": lambda ticket, user: not ticket.metadata.get("crm_synced"),
        },
    ],
}

Visible actions are exposed on the agent ticket page as customActions and on the API ticket detail response as custom_actions (each with a url and method). Triggering one (POST /support/agent/tickets/<id>/actions/<key>/ or the API route /<prefix>/tickets/<reference>/actions/<key>/) validates the action is visible (404) and enabled (403), then sends the custom_action_triggered signal:

from django.dispatch import receiver
from escalated.signals import custom_action_triggered


@receiver(custom_action_triggered)
def on_custom_action(sender, ticket, user, action_key, payload, metadata, **kwargs):
    if action_key != "sync-crm":
        return
    # your handler

Escalated also records an internal note on the ticket whenever an action fires, for auditability.

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.

Plugin SDK

Escalated supports framework-agnostic plugins built with the Plugin SDK. Plugins are written once in TypeScript and work across all Escalated backends.

Requirements

  • Node.js 20+
  • @escalated-dev/plugin-runtime installed in your project

Installing Plugins

npm install @escalated-dev/plugin-runtime
npm install @escalated-dev/plugin-slack
npm install @escalated-dev/plugin-jira

Enabling SDK Plugins

# settings.py
ESCALATED = {
    # ... existing config ...
    "SDK_ENABLED": True,
}

How It Works

SDK plugins run as a long-lived Node.js subprocess managed by @escalated-dev/plugin-runtime, communicating with Django over JSON-RPC 2.0 via stdio. Every ticket lifecycle signal is dual-dispatched — first to Django signal handlers, then forwarded to the plugin runtime.

Building Your Own Plugin

import { definePlugin } from '@escalated-dev/plugin-sdk'

export default definePlugin({
  name: 'my-plugin',
  version: '1.0.0',
  actions: {
    'ticket.created': async (event, ctx) => {
      ctx.log.info('New ticket!', event)
    },
  },
})

Resources

See the detailed SDK Plugin Bridge section below for the full architecture, supported ctx.* callbacks, hook event mapping, and resilience documentation.

SDK Plugin Bridge

The Plugin Bridge connects your Django app to the Node.js @escalated-dev/plugin-runtime process via JSON-RPC 2.0 over stdio. It enables SDK plugins — JavaScript/TypeScript packages that hook into ticket lifecycle events, expose custom API endpoints, and persist data through the host ORM — without requiring any Node.js code in your Django project.

How it works

  1. On startup Django spawns node @escalated-dev/plugin-runtime as a long-lived subprocess.
  2. A protocol handshake is performed and plugin manifests are exchanged.
  3. URL patterns for plugin pages, API endpoints, and webhooks are dynamically registered.
  4. Every ticket lifecycle signal (created, replied, resolved, etc.) is dual-dispatched — first to the standard Django signal handlers and then to the bridge, which forwards the event to the runtime.
  5. Plugin code can call back into Django via ctx.* methods (ctx.tickets.find, ctx.store.set, ctx.config.get, etc.) over the same bidirectional JSON-RPC channel.

Requirements

  • Node.js 18+
  • @escalated-dev/plugin-runtime installed in your project's node_modules

Quick start

1. Install the runtime

npm install @escalated-dev/plugin-runtime

2. Enable the bridge in settings

ESCALATED = {
    # ... existing config ...
    # SDK plugin bridge
    "SDK_ENABLED": True,
    # Optional overrides (defaults shown):
    # "RUNTIME_COMMAND": "node node_modules/@escalated-dev/plugin-runtime/dist/index.js",
    # "RUNTIME_CWD": BASE_DIR,  # working directory for the Node subprocess
}

3. Run the migration

python manage.py migrate escalated

This creates the escalated_plugin_store table used by ctx.store.* and ctx.config.* callbacks.

Routes registered by the bridge

Plugin manifests can declare three types of routes. All are automatically registered under the configured ROUTE_PREFIX (default support):

Category URL pattern Auth
Pages /{prefix}/admin/plugins/{plugin}/{route} Admin required
Endpoints /{prefix}/api/plugins/{plugin}/{path} Admin required
Webhooks /{prefix}/webhooks/plugins/{plugin}/{path} None (public)

Supported ctx.* callbacks

Method Description
ctx.config.all / ctx.config.get / ctx.config.set Per-plugin config blob
ctx.store.get / ctx.store.set / ctx.store.query / ctx.store.insert / ctx.store.update / ctx.store.delete Per-plugin key/value store
ctx.tickets.find / ctx.tickets.query / ctx.tickets.create / ctx.tickets.update Ticket ORM access
ctx.replies.find / ctx.replies.query / ctx.replies.create Reply ORM access
ctx.contacts.find / ctx.contacts.findByEmail / ctx.contacts.create User model access
ctx.tags.all / ctx.tags.create Tag access
ctx.departments.all / ctx.departments.find Department access
ctx.agents.all / ctx.agents.find Agent (user) access
ctx.broadcast.toChannel / ctx.broadcast.toUser / ctx.broadcast.toTicket Django Channels broadcast (optional)
ctx.emit Fire another action hook from inside a plugin
ctx.log Log to Django's logger

Hook events dispatched to the bridge

Every ticket signal fires a corresponding SDK hook:

Django signal SDK hook
ticket_created ticket.created
ticket_updated ticket.updated
ticket_status_changed ticket.status_changed
ticket_assigned ticket.assigned
ticket_priority_changed ticket.priority_changed
ticket_resolved ticket.resolved
ticket_closed ticket.closed
ticket_escalated ticket.escalated
reply_created reply.created
sla_breached sla.breached

Resilience

  • The bridge is spawned lazily on first use — health-check requests are never slowed down.
  • If the Node.js runtime crashes it is automatically restarted with exponential backoff (up to 5 minutes between attempts).
  • Action hooks degrade gracefully (drop with a warning) when the runtime is unavailable. Filter hooks return the unmodified value.
  • The action queue is capped at 1 000 in-flight entries to prevent memory growth.

Also Available For

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

Development

pip install -e ".[dev]"
pytest

Newsletters (optional, disabled by default)

Admin-only broadcast feature for sending Markdown emails to contacts. Off by default — set ESCALATED["enable_newsletters"] = True to turn it on.

# settings.py
ESCALATED = {
    "enable_newsletters": True,
    "app_url": "https://support.example.com",
    "newsletter_default_from": "hi@example.com",
    "newsletter_default_theme": "default",
    "newsletter_brand_accent": "#2563eb",
    "newsletter_brand_physical_address": "Acme Inc. · 123 Main St · Springfield USA",
    # Plug in a Markdown converter (markdown package, mistune, etc.)
    "newsletter_markdown_renderer": lambda md: __import__("markdown").markdown(md),
}

Schedule the dispatcher every minute (Celery beat, django-q, cron, etc.):

from escalated.services.newsletter import NewsletterDispatcher

NewsletterDispatcher().dispatch_batch()

Custom themes go in escalated/templates/escalated/newsletter_themes/<slug>.html. The context receives subject, body (pre-rendered safe HTML), unsubscribe_url, view_in_browser_url, and brand.

Database connection

By default Escalated's tables live on your project's default database. Name a different one to keep them somewhere else — a schema shared with a legacy system, a multi-tenant split, a separate reporting store, or simply out of your primary database.

Django has no per-model connection setting; routing is the mechanism, so this takes two pieces of configuration:

# settings.py
DATABASES = {
    "default": {...},
    "support": {...},
}

DATABASE_ROUTERS = ["escalated.routers.EscalatedRouter"]

ESCALATED = {
    "DATABASE": "support",
}

Leave DATABASE unset (or omit the router) for the default database — the historical behaviour, and what almost every project wants. The router returns "no opinion" for everything when nothing is configured, so adding it to a project that has not set DATABASE changes nothing.

Migrate Escalated's tables onto it with:

python manage.py migrate escalated --database=support

Foreign keys to your user table

Escalated's relations to AUTH_USER_MODEL are declared db_constraint=False. Django cannot create a foreign key across databases, so while those constraints existed the setting could not work at all — migrate failed on the first user-referencing table.

This does not change Django's behaviour. on_delete is implemented in Python by the deletion collector, not by a database ON DELETE clause, and Django never emits one; cascades and SET_NULL work exactly as before. What is given up is the database's own referential check against direct SQL writes that bypass the ORM — which is also the only way the two sides could ever live apart.

Migration 0028_drop_host_user_fk_constraints applies it. It runs on every install, including single-database ones.

Your user table does not move. The router has no opinion about it, so it stays wherever your project keeps it.

What cannot cross the boundary

Single-object traversal works: ticket.assigned_to is resolved with a second query, routed by the router, so a ticket on one database resolves its assignee on another.

A join cannot span two databases — nothing can do that. So a queryset that filters or select_relateds through the user model, like Ticket.objects.select_related("assigned_to") or .filter(assigned_to__email=...), is not available when the two are separated. Filter on the key instead (assigned_to_id=...) and load the users in a second query.

License

MIT - Copyright (c) Escalated.dev. See LICENSE.

Ticket access and routing

See ticket access and routing for opt-in department/account visibility, host account resolver hooks, and targeted SLAs.

Metadata

Release files for escalated-django 0.7.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for escalated-django 0.7.4
File Size Uploaded Provenance
escalated_django-0.7.4.tar.gz 1.4 MB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for escalated-django 0.7.4
File Interpreter ABI Platform
escalated_django-0.7.4-py3-none-any.whl Python 3 none any Details

Total release size: 2.9 MB

Release files / escalated_django-0.7.4.tar.gz

Download URL escalated_django-0.7.4.tar.gz
Size 1.4 MB
Tags Source
SHA-256 checksum
How to use checksums
67aec0eb0b67f60c105a0f69a1cebf0cb69aef515bc1153bf45f4a1a12f45b67
BLAKE2b-256 checksum
How to use checksums
49280ee0859b52265e9e0fb79b19ff9877c4478f7693a4c730810137b885e1d8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Release files / escalated_django-0.7.4-py3-none-any.whl

Download URL escalated_django-0.7.4-py3-none-any.whl
Size 1.5 MB
Tags Python 3
SHA-256 checksum
How to use checksums
28db4d77b2324dcb00297cac0d3694155e4e91778e38d1bee2e39b7bf742770f
BLAKE2b-256 checksum
How to use checksums
f1b7c47cd4a8108b967dc343c79b31541089b4a94d3749fa1351ebfb6bb3b07f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Security for escalated-django 0.7.4

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for escalated_django-0.7.4.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page