escalated-django 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- escalated_django-0.4.0/LICENSE +21 -0
- escalated_django-0.4.0/MANIFEST.in +3 -0
- escalated_django-0.4.0/PKG-INFO +330 -0
- escalated_django-0.4.0/README.md +310 -0
- escalated_django-0.4.0/escalated/__init__.py +1 -0
- escalated_django-0.4.0/escalated/apps.py +10 -0
- escalated_django-0.4.0/escalated/conf.py +78 -0
- escalated_django-0.4.0/escalated/drivers/__init__.py +22 -0
- escalated_django-0.4.0/escalated/drivers/api_client.py +157 -0
- escalated_django-0.4.0/escalated/drivers/cloud.py +86 -0
- escalated_django-0.4.0/escalated/drivers/local.py +379 -0
- escalated_django-0.4.0/escalated/drivers/synced.py +127 -0
- escalated_django-0.4.0/escalated/handlers.py +197 -0
- escalated_django-0.4.0/escalated/mail/__init__.py +3 -0
- escalated_django-0.4.0/escalated/mail/adapters/__init__.py +38 -0
- escalated_django-0.4.0/escalated/mail/adapters/base.py +48 -0
- escalated_django-0.4.0/escalated/mail/adapters/imap.py +218 -0
- escalated_django-0.4.0/escalated/mail/adapters/mailgun.py +131 -0
- escalated_django-0.4.0/escalated/mail/adapters/postmark.py +114 -0
- escalated_django-0.4.0/escalated/mail/adapters/ses.py +225 -0
- escalated_django-0.4.0/escalated/mail/inbound_message.py +27 -0
- escalated_django-0.4.0/escalated/management/__init__.py +0 -0
- escalated_django-0.4.0/escalated/management/commands/__init__.py +0 -0
- escalated_django-0.4.0/escalated/management/commands/check_sla.py +27 -0
- escalated_django-0.4.0/escalated/management/commands/close_resolved.py +64 -0
- escalated_django-0.4.0/escalated/management/commands/evaluate_escalations.py +18 -0
- escalated_django-0.4.0/escalated/management/commands/poll_imap.py +120 -0
- escalated_django-0.4.0/escalated/management/commands/purge_activities.py +49 -0
- escalated_django-0.4.0/escalated/middleware.py +52 -0
- escalated_django-0.4.0/escalated/migrations/0001_initial.py +531 -0
- escalated_django-0.4.0/escalated/migrations/0002_settings_and_guest_tickets.py +73 -0
- escalated_django-0.4.0/escalated/migrations/0003_inboundemail.py +107 -0
- escalated_django-0.4.0/escalated/migrations/0004_v040_advanced_features.py +185 -0
- escalated_django-0.4.0/escalated/migrations/__init__.py +0 -0
- escalated_django-0.4.0/escalated/models.py +771 -0
- escalated_django-0.4.0/escalated/permissions.py +141 -0
- escalated_django-0.4.0/escalated/serializers.py +324 -0
- escalated_django-0.4.0/escalated/services/__init__.py +0 -0
- escalated_django-0.4.0/escalated/services/assignment_service.py +103 -0
- escalated_django-0.4.0/escalated/services/attachment_service.py +100 -0
- escalated_django-0.4.0/escalated/services/escalation_service.py +189 -0
- escalated_django-0.4.0/escalated/services/inbound_email_service.py +414 -0
- escalated_django-0.4.0/escalated/services/macro_service.py +84 -0
- escalated_django-0.4.0/escalated/services/notification_service.py +311 -0
- escalated_django-0.4.0/escalated/services/sla_service.py +265 -0
- escalated_django-0.4.0/escalated/services/ticket_service.py +99 -0
- escalated_django-0.4.0/escalated/signals.py +28 -0
- escalated_django-0.4.0/escalated/templates/escalated/emails/assigned.html +61 -0
- escalated_django-0.4.0/escalated/templates/escalated/emails/escalated.html +48 -0
- escalated_django-0.4.0/escalated/templates/escalated/emails/new_ticket.html +54 -0
- escalated_django-0.4.0/escalated/templates/escalated/emails/reply.html +39 -0
- escalated_django-0.4.0/escalated/templates/escalated/emails/resolved.html +31 -0
- escalated_django-0.4.0/escalated/templates/escalated/emails/sla_breach.html +55 -0
- escalated_django-0.4.0/escalated/templates/escalated/emails/status_changed.html +38 -0
- escalated_django-0.4.0/escalated/urls.py +112 -0
- escalated_django-0.4.0/escalated/views/__init__.py +0 -0
- escalated_django-0.4.0/escalated/views/admin.py +1452 -0
- escalated_django-0.4.0/escalated/views/agent.py +696 -0
- escalated_django-0.4.0/escalated/views/customer.py +267 -0
- escalated_django-0.4.0/escalated/views/guest.py +212 -0
- escalated_django-0.4.0/escalated/views/inbound.py +84 -0
- escalated_django-0.4.0/escalated_django.egg-info/PKG-INFO +330 -0
- escalated_django-0.4.0/escalated_django.egg-info/SOURCES.txt +78 -0
- escalated_django-0.4.0/escalated_django.egg-info/dependency_links.txt +1 -0
- escalated_django-0.4.0/escalated_django.egg-info/requires.txt +9 -0
- escalated_django-0.4.0/escalated_django.egg-info/top_level.txt +2 -0
- escalated_django-0.4.0/pyproject.toml +31 -0
- escalated_django-0.4.0/setup.cfg +8 -0
- escalated_django-0.4.0/tests/__init__.py +0 -0
- escalated_django-0.4.0/tests/conftest.py +65 -0
- escalated_django-0.4.0/tests/factories.py +140 -0
- escalated_django-0.4.0/tests/integration/__init__.py +0 -0
- escalated_django-0.4.0/tests/integration/test_lifecycle.py +229 -0
- escalated_django-0.4.0/tests/integration/test_views.py +324 -0
- escalated_django-0.4.0/tests/settings.py +56 -0
- escalated_django-0.4.0/tests/unit/__init__.py +0 -0
- escalated_django-0.4.0/tests/unit/test_models.py +239 -0
- escalated_django-0.4.0/tests/unit/test_services.py +359 -0
- escalated_django-0.4.0/tests/urls.py +5 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Escalated Dev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: escalated-django
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Embeddable support ticket system for Django
|
|
5
|
+
Author-email: Escalated Dev <hello@escalated.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/escalated-dev/escalated-django
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: django>=4.2
|
|
12
|
+
Requires-Dist: inertia-django>=1.0
|
|
13
|
+
Requires-Dist: requests>=2.28
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
16
|
+
Requires-Dist: pytest-django>=4.5; extra == "dev"
|
|
17
|
+
Requires-Dist: factory-boy>=3.2; extra == "dev"
|
|
18
|
+
Requires-Dist: faker>=18.0; extra == "dev"
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# Escalated for Django
|
|
22
|
+
|
|
23
|
+
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.
|
|
24
|
+
|
|
25
|
+
**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.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- **Ticket lifecycle** — Create, assign, reply, resolve, close, reopen with configurable status transitions
|
|
30
|
+
- **SLA engine** — Per-priority response and resolution targets, business hours calculation, automatic breach detection
|
|
31
|
+
- **Escalation rules** — Condition-based rules that auto-escalate, reprioritize, reassign, or notify
|
|
32
|
+
- **Agent dashboard** — Ticket queue with filters, bulk actions, internal notes, canned responses
|
|
33
|
+
- **Customer portal** — Self-service ticket creation, replies, and status tracking
|
|
34
|
+
- **Admin panel** — Manage departments, SLA policies, escalation rules, tags, and view reports
|
|
35
|
+
- **File attachments** — Drag-and-drop uploads with configurable storage and size limits
|
|
36
|
+
- **Activity timeline** — Full audit log of every action on every ticket
|
|
37
|
+
- **Email notifications** — Configurable per-event notifications with webhook support
|
|
38
|
+
- **Department routing** — Organize agents into departments with auto-assignment (round-robin)
|
|
39
|
+
- **Tagging system** — Categorize tickets with colored tags
|
|
40
|
+
- **Inertia.js + Vue 3 UI** — Shared frontend via [`@escalated-dev/escalated`](https://github.com/escalated-dev/escalated)
|
|
41
|
+
|
|
42
|
+
### v0.4.0 — Advanced Features
|
|
43
|
+
|
|
44
|
+
- **Bulk actions** — Assign, change status/priority, add tags, close, or delete multiple tickets at once
|
|
45
|
+
- **Macros** — Reusable multi-step automations (set status + assign + add note in one click)
|
|
46
|
+
- **Ticket followers** — Agents follow tickets and receive the same notifications as the assignee
|
|
47
|
+
- **Satisfaction ratings** — 1-5 star CSAT ratings with optional comments after resolution
|
|
48
|
+
- **Pinned notes** — Pin important internal notes to the top of the ticket thread
|
|
49
|
+
- **Keyboard shortcuts** — Full keyboard navigation for power users
|
|
50
|
+
- **Quick filters** — One-click filter chips (My Tickets, Unassigned, Urgent, SLA Breaching)
|
|
51
|
+
- **Presence indicators** — See who else is viewing a ticket in real-time
|
|
52
|
+
- **Enhanced dashboard** — CSAT metrics, resolution times, SLA breach tracking
|
|
53
|
+
|
|
54
|
+
## Requirements
|
|
55
|
+
|
|
56
|
+
- Python 3.10+
|
|
57
|
+
- Django 4.2+
|
|
58
|
+
- Node.js 18+ (for frontend assets)
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install escalated-django
|
|
64
|
+
npm install @escalated-dev/escalated
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 1. Add to INSTALLED_APPS
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
INSTALLED_APPS = [
|
|
71
|
+
# ...
|
|
72
|
+
'django.contrib.contenttypes',
|
|
73
|
+
'inertia',
|
|
74
|
+
'escalated',
|
|
75
|
+
]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 2. Include URLs
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from django.urls import path, include
|
|
82
|
+
|
|
83
|
+
urlpatterns = [
|
|
84
|
+
# ...
|
|
85
|
+
path("support/", include("escalated.urls")),
|
|
86
|
+
]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 3. Run migrations
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
python manage.py migrate escalated
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Visit `/support` — you're live.
|
|
96
|
+
|
|
97
|
+
## Frontend Setup
|
|
98
|
+
|
|
99
|
+
Escalated uses Inertia.js with Vue 3. The frontend components are provided by the [`@escalated-dev/escalated`](https://github.com/escalated-dev/escalated) npm package.
|
|
100
|
+
|
|
101
|
+
### Tailwind Content
|
|
102
|
+
|
|
103
|
+
Add the Escalated package to your Tailwind `content` config so its classes aren't purged:
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
// tailwind.config.js
|
|
107
|
+
content: [
|
|
108
|
+
// ... your existing paths
|
|
109
|
+
'./node_modules/@escalated-dev/escalated/src/**/*.vue',
|
|
110
|
+
],
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Page Resolver
|
|
114
|
+
|
|
115
|
+
Add the Escalated pages to your Inertia page resolver:
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
// frontend/main.js
|
|
119
|
+
import { createApp, h } from 'vue'
|
|
120
|
+
import { createInertiaApp } from '@inertiajs/vue3'
|
|
121
|
+
|
|
122
|
+
createInertiaApp({
|
|
123
|
+
resolve: name => {
|
|
124
|
+
if (name.startsWith('Escalated/')) {
|
|
125
|
+
const escalatedPages = import.meta.glob(
|
|
126
|
+
'../node_modules/@escalated-dev/escalated/src/pages/**/*.vue',
|
|
127
|
+
{ eager: true }
|
|
128
|
+
)
|
|
129
|
+
const pageName = name.replace('Escalated/', '')
|
|
130
|
+
return escalatedPages[`../node_modules/@escalated-dev/escalated/src/pages/${pageName}.vue`]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const pages = import.meta.glob('./pages/**/*.vue', { eager: true })
|
|
134
|
+
return pages[`./pages/${name}.vue`]
|
|
135
|
+
},
|
|
136
|
+
setup({ el, App, props, plugin }) {
|
|
137
|
+
createApp({ render: () => h(App, props) })
|
|
138
|
+
.use(plugin)
|
|
139
|
+
.mount(el)
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Theming (Optional)
|
|
145
|
+
|
|
146
|
+
Register the `EscalatedPlugin` to render Escalated pages inside your app's layout — no page duplication needed:
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
import { EscalatedPlugin } from '@escalated-dev/escalated'
|
|
150
|
+
import BaseLayout from '@/layouts/BaseLayout.vue'
|
|
151
|
+
|
|
152
|
+
createInertiaApp({
|
|
153
|
+
setup({ el, App, props, plugin }) {
|
|
154
|
+
createApp({ render: () => h(App, props) })
|
|
155
|
+
.use(plugin)
|
|
156
|
+
.use(EscalatedPlugin, {
|
|
157
|
+
layout: BaseLayout,
|
|
158
|
+
theme: {
|
|
159
|
+
primary: '#3b82f6',
|
|
160
|
+
radius: '0.75rem',
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
.mount(el)
|
|
164
|
+
},
|
|
165
|
+
})
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
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.
|
|
169
|
+
|
|
170
|
+
See the [`@escalated-dev/escalated` README](https://github.com/escalated-dev/escalated) for full theming documentation and CSS custom properties.
|
|
171
|
+
|
|
172
|
+
## Hosting Modes
|
|
173
|
+
|
|
174
|
+
### Self-Hosted (default)
|
|
175
|
+
|
|
176
|
+
Everything stays in your database. No external calls. Full autonomy.
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
ESCALATED = {
|
|
180
|
+
"MODE": "self_hosted",
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Synced
|
|
185
|
+
|
|
186
|
+
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.
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
ESCALATED = {
|
|
190
|
+
"MODE": "synced",
|
|
191
|
+
"HOSTED_API_URL": "https://cloud.escalated.dev/api/v1",
|
|
192
|
+
"HOSTED_API_KEY": "your-api-key",
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Cloud
|
|
197
|
+
|
|
198
|
+
All ticket data proxied to the cloud API. Your app handles auth and renders UI, but storage lives in the cloud.
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
ESCALATED = {
|
|
202
|
+
"MODE": "cloud",
|
|
203
|
+
"HOSTED_API_URL": "https://cloud.escalated.dev/api/v1",
|
|
204
|
+
"HOSTED_API_KEY": "your-api-key",
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
All three modes share the same views, UI, and business logic. The driver pattern handles the rest.
|
|
209
|
+
|
|
210
|
+
## Configuration
|
|
211
|
+
|
|
212
|
+
Add to your `settings.py`:
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
ESCALATED = {
|
|
216
|
+
"MODE": "self_hosted", # self_hosted | synced | cloud
|
|
217
|
+
"TABLE_PREFIX": "escalated_",
|
|
218
|
+
"ROUTE_PREFIX": "support",
|
|
219
|
+
"DEFAULT_PRIORITY": "medium",
|
|
220
|
+
|
|
221
|
+
# Tickets
|
|
222
|
+
"ALLOW_CUSTOMER_CLOSE": True,
|
|
223
|
+
"AUTO_CLOSE_RESOLVED_AFTER_DAYS": 7,
|
|
224
|
+
"MAX_ATTACHMENTS": 5,
|
|
225
|
+
"MAX_ATTACHMENT_SIZE_KB": 10240,
|
|
226
|
+
|
|
227
|
+
# SLA
|
|
228
|
+
"SLA": {
|
|
229
|
+
"ENABLED": True,
|
|
230
|
+
"BUSINESS_HOURS_ONLY": False,
|
|
231
|
+
"BUSINESS_HOURS": {
|
|
232
|
+
"START": "09:00",
|
|
233
|
+
"END": "17:00",
|
|
234
|
+
"TIMEZONE": "UTC",
|
|
235
|
+
"DAYS": [1, 2, 3, 4, 5],
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
# Notifications
|
|
240
|
+
"NOTIFICATION_CHANNELS": ["email"],
|
|
241
|
+
"WEBHOOK_URL": None,
|
|
242
|
+
|
|
243
|
+
# Cloud/Synced mode
|
|
244
|
+
"HOSTED_API_URL": "https://cloud.escalated.dev/api/v1",
|
|
245
|
+
"HOSTED_API_KEY": None,
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Management Commands
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
# Check SLA deadlines and fire breach notifications
|
|
253
|
+
python manage.py check_sla
|
|
254
|
+
|
|
255
|
+
# Evaluate escalation rules against open tickets
|
|
256
|
+
python manage.py evaluate_escalations
|
|
257
|
+
|
|
258
|
+
# Auto-close tickets resolved more than N days ago
|
|
259
|
+
python manage.py close_resolved --days 7
|
|
260
|
+
|
|
261
|
+
# Purge old activity logs
|
|
262
|
+
python manage.py purge_activities --days 90
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Schedule these with cron, Celery Beat, or django-crontab for automated enforcement.
|
|
266
|
+
|
|
267
|
+
## Routes
|
|
268
|
+
|
|
269
|
+
All routes use the configurable prefix (default: `support`).
|
|
270
|
+
|
|
271
|
+
| Route | Method | Description |
|
|
272
|
+
|-------|--------|-------------|
|
|
273
|
+
| `/support/tickets/` | GET | Customer ticket list |
|
|
274
|
+
| `/support/tickets/create/` | GET | New ticket form |
|
|
275
|
+
| `/support/tickets/<id>/` | GET | Ticket detail |
|
|
276
|
+
| `/support/agent/` | GET | Agent dashboard |
|
|
277
|
+
| `/support/agent/tickets/` | GET | Agent ticket queue |
|
|
278
|
+
| `/support/agent/tickets/<id>/` | GET | Agent ticket view |
|
|
279
|
+
| `/support/admin/reports/` | GET | Admin reports |
|
|
280
|
+
| `/support/admin/departments/` | GET | Department management |
|
|
281
|
+
| `/support/admin/sla-policies/` | GET | SLA policy management |
|
|
282
|
+
| `/support/admin/escalation-rules/` | GET | Escalation rule management |
|
|
283
|
+
| `/support/admin/tags/` | GET | Tag management |
|
|
284
|
+
| `/support/admin/canned-responses/` | GET | Canned response management |
|
|
285
|
+
| `/support/agent/tickets/bulk/` | POST | Bulk actions on multiple tickets |
|
|
286
|
+
| `/support/agent/tickets/<id>/follow/` | POST | Follow/unfollow a ticket |
|
|
287
|
+
| `/support/agent/tickets/<id>/macro/` | POST | Apply a macro to a ticket |
|
|
288
|
+
| `/support/agent/tickets/<id>/presence/` | POST | Update presence on a ticket |
|
|
289
|
+
| `/support/agent/tickets/<id>/pin/<reply_id>/` | POST | Pin/unpin an internal note |
|
|
290
|
+
| `/support/tickets/<id>/rate/` | POST | Submit satisfaction rating |
|
|
291
|
+
|
|
292
|
+
## Signals
|
|
293
|
+
|
|
294
|
+
Connect to ticket lifecycle events:
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
from escalated.signals import ticket_created, ticket_resolved
|
|
298
|
+
|
|
299
|
+
@receiver(ticket_created)
|
|
300
|
+
def on_ticket_created(sender, ticket, user, **kwargs):
|
|
301
|
+
print(f"New ticket: {ticket.reference}")
|
|
302
|
+
|
|
303
|
+
@receiver(ticket_resolved)
|
|
304
|
+
def on_ticket_resolved(sender, ticket, user, **kwargs):
|
|
305
|
+
print(f"Resolved: {ticket.reference}")
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
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`.
|
|
309
|
+
|
|
310
|
+
## Also Available For
|
|
311
|
+
|
|
312
|
+
- **[Escalated for Laravel](https://github.com/escalated-dev/escalated-laravel)** — Laravel Composer package
|
|
313
|
+
- **[Escalated for Rails](https://github.com/escalated-dev/escalated-rails)** — Ruby on Rails engine
|
|
314
|
+
- **[Escalated for Django](https://github.com/escalated-dev/escalated-django)** — Django reusable app (you are here)
|
|
315
|
+
- **[Escalated for AdonisJS](https://github.com/escalated-dev/escalated-adonis)** — AdonisJS v6 package
|
|
316
|
+
- **[Escalated for Filament](https://github.com/escalated-dev/escalated-filament)** — Filament v3 admin panel plugin
|
|
317
|
+
- **[Shared Frontend](https://github.com/escalated-dev/escalated)** — Vue 3 + Inertia.js UI components
|
|
318
|
+
|
|
319
|
+
Same architecture, same Vue UI, same three hosting modes — for every major backend framework.
|
|
320
|
+
|
|
321
|
+
## Development
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
pip install -e ".[dev]"
|
|
325
|
+
pytest
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## License
|
|
329
|
+
|
|
330
|
+
MIT
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# Escalated for Django
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
**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.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Ticket lifecycle** — Create, assign, reply, resolve, close, reopen with configurable status transitions
|
|
10
|
+
- **SLA engine** — Per-priority response and resolution targets, business hours calculation, automatic breach detection
|
|
11
|
+
- **Escalation rules** — Condition-based rules that auto-escalate, reprioritize, reassign, or notify
|
|
12
|
+
- **Agent dashboard** — Ticket queue with filters, bulk actions, internal notes, canned responses
|
|
13
|
+
- **Customer portal** — Self-service ticket creation, replies, and status tracking
|
|
14
|
+
- **Admin panel** — Manage departments, SLA policies, escalation rules, tags, and view reports
|
|
15
|
+
- **File attachments** — Drag-and-drop uploads with configurable storage and size limits
|
|
16
|
+
- **Activity timeline** — Full audit log of every action on every ticket
|
|
17
|
+
- **Email notifications** — Configurable per-event notifications with webhook support
|
|
18
|
+
- **Department routing** — Organize agents into departments with auto-assignment (round-robin)
|
|
19
|
+
- **Tagging system** — Categorize tickets with colored tags
|
|
20
|
+
- **Inertia.js + Vue 3 UI** — Shared frontend via [`@escalated-dev/escalated`](https://github.com/escalated-dev/escalated)
|
|
21
|
+
|
|
22
|
+
### v0.4.0 — Advanced Features
|
|
23
|
+
|
|
24
|
+
- **Bulk actions** — Assign, change status/priority, add tags, close, or delete multiple tickets at once
|
|
25
|
+
- **Macros** — Reusable multi-step automations (set status + assign + add note in one click)
|
|
26
|
+
- **Ticket followers** — Agents follow tickets and receive the same notifications as the assignee
|
|
27
|
+
- **Satisfaction ratings** — 1-5 star CSAT ratings with optional comments after resolution
|
|
28
|
+
- **Pinned notes** — Pin important internal notes to the top of the ticket thread
|
|
29
|
+
- **Keyboard shortcuts** — Full keyboard navigation for power users
|
|
30
|
+
- **Quick filters** — One-click filter chips (My Tickets, Unassigned, Urgent, SLA Breaching)
|
|
31
|
+
- **Presence indicators** — See who else is viewing a ticket in real-time
|
|
32
|
+
- **Enhanced dashboard** — CSAT metrics, resolution times, SLA breach tracking
|
|
33
|
+
|
|
34
|
+
## Requirements
|
|
35
|
+
|
|
36
|
+
- Python 3.10+
|
|
37
|
+
- Django 4.2+
|
|
38
|
+
- Node.js 18+ (for frontend assets)
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install escalated-django
|
|
44
|
+
npm install @escalated-dev/escalated
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 1. Add to INSTALLED_APPS
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
INSTALLED_APPS = [
|
|
51
|
+
# ...
|
|
52
|
+
'django.contrib.contenttypes',
|
|
53
|
+
'inertia',
|
|
54
|
+
'escalated',
|
|
55
|
+
]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 2. Include URLs
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from django.urls import path, include
|
|
62
|
+
|
|
63
|
+
urlpatterns = [
|
|
64
|
+
# ...
|
|
65
|
+
path("support/", include("escalated.urls")),
|
|
66
|
+
]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Run migrations
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
python manage.py migrate escalated
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Visit `/support` — you're live.
|
|
76
|
+
|
|
77
|
+
## Frontend Setup
|
|
78
|
+
|
|
79
|
+
Escalated uses Inertia.js with Vue 3. The frontend components are provided by the [`@escalated-dev/escalated`](https://github.com/escalated-dev/escalated) npm package.
|
|
80
|
+
|
|
81
|
+
### Tailwind Content
|
|
82
|
+
|
|
83
|
+
Add the Escalated package to your Tailwind `content` config so its classes aren't purged:
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
// tailwind.config.js
|
|
87
|
+
content: [
|
|
88
|
+
// ... your existing paths
|
|
89
|
+
'./node_modules/@escalated-dev/escalated/src/**/*.vue',
|
|
90
|
+
],
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Page Resolver
|
|
94
|
+
|
|
95
|
+
Add the Escalated pages to your Inertia page resolver:
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
// frontend/main.js
|
|
99
|
+
import { createApp, h } from 'vue'
|
|
100
|
+
import { createInertiaApp } from '@inertiajs/vue3'
|
|
101
|
+
|
|
102
|
+
createInertiaApp({
|
|
103
|
+
resolve: name => {
|
|
104
|
+
if (name.startsWith('Escalated/')) {
|
|
105
|
+
const escalatedPages = import.meta.glob(
|
|
106
|
+
'../node_modules/@escalated-dev/escalated/src/pages/**/*.vue',
|
|
107
|
+
{ eager: true }
|
|
108
|
+
)
|
|
109
|
+
const pageName = name.replace('Escalated/', '')
|
|
110
|
+
return escalatedPages[`../node_modules/@escalated-dev/escalated/src/pages/${pageName}.vue`]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const pages = import.meta.glob('./pages/**/*.vue', { eager: true })
|
|
114
|
+
return pages[`./pages/${name}.vue`]
|
|
115
|
+
},
|
|
116
|
+
setup({ el, App, props, plugin }) {
|
|
117
|
+
createApp({ render: () => h(App, props) })
|
|
118
|
+
.use(plugin)
|
|
119
|
+
.mount(el)
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Theming (Optional)
|
|
125
|
+
|
|
126
|
+
Register the `EscalatedPlugin` to render Escalated pages inside your app's layout — no page duplication needed:
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
import { EscalatedPlugin } from '@escalated-dev/escalated'
|
|
130
|
+
import BaseLayout from '@/layouts/BaseLayout.vue'
|
|
131
|
+
|
|
132
|
+
createInertiaApp({
|
|
133
|
+
setup({ el, App, props, plugin }) {
|
|
134
|
+
createApp({ render: () => h(App, props) })
|
|
135
|
+
.use(plugin)
|
|
136
|
+
.use(EscalatedPlugin, {
|
|
137
|
+
layout: BaseLayout,
|
|
138
|
+
theme: {
|
|
139
|
+
primary: '#3b82f6',
|
|
140
|
+
radius: '0.75rem',
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
.mount(el)
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
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.
|
|
149
|
+
|
|
150
|
+
See the [`@escalated-dev/escalated` README](https://github.com/escalated-dev/escalated) for full theming documentation and CSS custom properties.
|
|
151
|
+
|
|
152
|
+
## Hosting Modes
|
|
153
|
+
|
|
154
|
+
### Self-Hosted (default)
|
|
155
|
+
|
|
156
|
+
Everything stays in your database. No external calls. Full autonomy.
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
ESCALATED = {
|
|
160
|
+
"MODE": "self_hosted",
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Synced
|
|
165
|
+
|
|
166
|
+
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.
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
ESCALATED = {
|
|
170
|
+
"MODE": "synced",
|
|
171
|
+
"HOSTED_API_URL": "https://cloud.escalated.dev/api/v1",
|
|
172
|
+
"HOSTED_API_KEY": "your-api-key",
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Cloud
|
|
177
|
+
|
|
178
|
+
All ticket data proxied to the cloud API. Your app handles auth and renders UI, but storage lives in the cloud.
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
ESCALATED = {
|
|
182
|
+
"MODE": "cloud",
|
|
183
|
+
"HOSTED_API_URL": "https://cloud.escalated.dev/api/v1",
|
|
184
|
+
"HOSTED_API_KEY": "your-api-key",
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
All three modes share the same views, UI, and business logic. The driver pattern handles the rest.
|
|
189
|
+
|
|
190
|
+
## Configuration
|
|
191
|
+
|
|
192
|
+
Add to your `settings.py`:
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
ESCALATED = {
|
|
196
|
+
"MODE": "self_hosted", # self_hosted | synced | cloud
|
|
197
|
+
"TABLE_PREFIX": "escalated_",
|
|
198
|
+
"ROUTE_PREFIX": "support",
|
|
199
|
+
"DEFAULT_PRIORITY": "medium",
|
|
200
|
+
|
|
201
|
+
# Tickets
|
|
202
|
+
"ALLOW_CUSTOMER_CLOSE": True,
|
|
203
|
+
"AUTO_CLOSE_RESOLVED_AFTER_DAYS": 7,
|
|
204
|
+
"MAX_ATTACHMENTS": 5,
|
|
205
|
+
"MAX_ATTACHMENT_SIZE_KB": 10240,
|
|
206
|
+
|
|
207
|
+
# SLA
|
|
208
|
+
"SLA": {
|
|
209
|
+
"ENABLED": True,
|
|
210
|
+
"BUSINESS_HOURS_ONLY": False,
|
|
211
|
+
"BUSINESS_HOURS": {
|
|
212
|
+
"START": "09:00",
|
|
213
|
+
"END": "17:00",
|
|
214
|
+
"TIMEZONE": "UTC",
|
|
215
|
+
"DAYS": [1, 2, 3, 4, 5],
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
# Notifications
|
|
220
|
+
"NOTIFICATION_CHANNELS": ["email"],
|
|
221
|
+
"WEBHOOK_URL": None,
|
|
222
|
+
|
|
223
|
+
# Cloud/Synced mode
|
|
224
|
+
"HOSTED_API_URL": "https://cloud.escalated.dev/api/v1",
|
|
225
|
+
"HOSTED_API_KEY": None,
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Management Commands
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
# Check SLA deadlines and fire breach notifications
|
|
233
|
+
python manage.py check_sla
|
|
234
|
+
|
|
235
|
+
# Evaluate escalation rules against open tickets
|
|
236
|
+
python manage.py evaluate_escalations
|
|
237
|
+
|
|
238
|
+
# Auto-close tickets resolved more than N days ago
|
|
239
|
+
python manage.py close_resolved --days 7
|
|
240
|
+
|
|
241
|
+
# Purge old activity logs
|
|
242
|
+
python manage.py purge_activities --days 90
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Schedule these with cron, Celery Beat, or django-crontab for automated enforcement.
|
|
246
|
+
|
|
247
|
+
## Routes
|
|
248
|
+
|
|
249
|
+
All routes use the configurable prefix (default: `support`).
|
|
250
|
+
|
|
251
|
+
| Route | Method | Description |
|
|
252
|
+
|-------|--------|-------------|
|
|
253
|
+
| `/support/tickets/` | GET | Customer ticket list |
|
|
254
|
+
| `/support/tickets/create/` | GET | New ticket form |
|
|
255
|
+
| `/support/tickets/<id>/` | GET | Ticket detail |
|
|
256
|
+
| `/support/agent/` | GET | Agent dashboard |
|
|
257
|
+
| `/support/agent/tickets/` | GET | Agent ticket queue |
|
|
258
|
+
| `/support/agent/tickets/<id>/` | GET | Agent ticket view |
|
|
259
|
+
| `/support/admin/reports/` | GET | Admin reports |
|
|
260
|
+
| `/support/admin/departments/` | GET | Department management |
|
|
261
|
+
| `/support/admin/sla-policies/` | GET | SLA policy management |
|
|
262
|
+
| `/support/admin/escalation-rules/` | GET | Escalation rule management |
|
|
263
|
+
| `/support/admin/tags/` | GET | Tag management |
|
|
264
|
+
| `/support/admin/canned-responses/` | GET | Canned response management |
|
|
265
|
+
| `/support/agent/tickets/bulk/` | POST | Bulk actions on multiple tickets |
|
|
266
|
+
| `/support/agent/tickets/<id>/follow/` | POST | Follow/unfollow a ticket |
|
|
267
|
+
| `/support/agent/tickets/<id>/macro/` | POST | Apply a macro to a ticket |
|
|
268
|
+
| `/support/agent/tickets/<id>/presence/` | POST | Update presence on a ticket |
|
|
269
|
+
| `/support/agent/tickets/<id>/pin/<reply_id>/` | POST | Pin/unpin an internal note |
|
|
270
|
+
| `/support/tickets/<id>/rate/` | POST | Submit satisfaction rating |
|
|
271
|
+
|
|
272
|
+
## Signals
|
|
273
|
+
|
|
274
|
+
Connect to ticket lifecycle events:
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
from escalated.signals import ticket_created, ticket_resolved
|
|
278
|
+
|
|
279
|
+
@receiver(ticket_created)
|
|
280
|
+
def on_ticket_created(sender, ticket, user, **kwargs):
|
|
281
|
+
print(f"New ticket: {ticket.reference}")
|
|
282
|
+
|
|
283
|
+
@receiver(ticket_resolved)
|
|
284
|
+
def on_ticket_resolved(sender, ticket, user, **kwargs):
|
|
285
|
+
print(f"Resolved: {ticket.reference}")
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
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`.
|
|
289
|
+
|
|
290
|
+
## Also Available For
|
|
291
|
+
|
|
292
|
+
- **[Escalated for Laravel](https://github.com/escalated-dev/escalated-laravel)** — Laravel Composer package
|
|
293
|
+
- **[Escalated for Rails](https://github.com/escalated-dev/escalated-rails)** — Ruby on Rails engine
|
|
294
|
+
- **[Escalated for Django](https://github.com/escalated-dev/escalated-django)** — Django reusable app (you are here)
|
|
295
|
+
- **[Escalated for AdonisJS](https://github.com/escalated-dev/escalated-adonis)** — AdonisJS v6 package
|
|
296
|
+
- **[Escalated for Filament](https://github.com/escalated-dev/escalated-filament)** — Filament v3 admin panel plugin
|
|
297
|
+
- **[Shared Frontend](https://github.com/escalated-dev/escalated)** — Vue 3 + Inertia.js UI components
|
|
298
|
+
|
|
299
|
+
Same architecture, same Vue UI, same three hosting modes — for every major backend framework.
|
|
300
|
+
|
|
301
|
+
## Development
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
pip install -e ".[dev]"
|
|
305
|
+
pytest
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## License
|
|
309
|
+
|
|
310
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
default_app_config = "escalated.apps.EscalatedConfig"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from django.apps import AppConfig
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class EscalatedConfig(AppConfig):
|
|
5
|
+
name = "escalated"
|
|
6
|
+
verbose_name = "Escalated Support"
|
|
7
|
+
default_auto_field = "django.db.models.BigAutoField"
|
|
8
|
+
|
|
9
|
+
def ready(self):
|
|
10
|
+
import escalated.handlers # noqa: F401 - connects signal handlers
|