Service App¶
A case management module for tracking customer issues from creation through resolution — with configurable statuses, resolution types, and built-in Metadata API integration for customizable list views and detail pages.
Key Features¶
- Case Tracking — Create and manage support cases with subject, description, status, and resolution fields
- Configurable Statuses — Define case lifecycle stages (e.g., New → Working → Closed) via settings
- Resolution Types — Track how cases are resolved (e.g., Done, Won't Do) with configurable options
- Metadata API Integration — Register the Case model with the Metadata API for auto-generated list views, detail pages, and bulk actions
- Ownership & Scoping — Every case has an
ownerfield and ascopedmanager inherited fromBaseModel - Flexible Metadata — JSONField on every case allows storing custom fields without schema changes
Architecture¶
┌─────────────────────────────────────────────────────────────┐
│ Your Application │
│ (app/service/metadata.py) │
└─────────────────────────────────────────────────────────────┘
│
Metadata API Registration
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Case Model │
│ (grit/service/models.py) │
│ │
│ ┌──────────┐ status ┌────────────────────┐ │
│ │ New │ ──────────▶ │ Working │ │
│ └──────────┘ └────────────────────┘ │
│ │ │
│ status │
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │ Closed │ │
│ │ (resolution) │ │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
inherits
│
▼
┌─────────────────────────────────────────────────────────────┐
│ BaseModel │
│ • UUID primary key • metadata (JSON) │
│ • created_at / updated_at • owner (FK to User) │
│ • scoped manager │
└─────────────────────────────────────────────────────────────┘
Quick Start¶
1. Register the Model with the Metadata API¶
In your app/service/metadata.py, register the Case model to enable auto-generated list and detail views:
from grit.core.metadata import metadata
from grit.service.models import Case
@metadata.register(Case)
class CaseMetadata(metadata.ModelMetadata):
list_display = ('subject', 'status', 'resolution', 'owner', 'created_at')
list_actions = [
[{'label': 'New Case', 'action': 'new'}]
]
fieldsets = (
('Case Information', {
'fields': ('subject', 'description', 'status', 'resolution')
}),
)
2. Configure the Admin Panel¶
In your app/service/admin.py, register the model with Django admin for back-office management:
from django.contrib import admin
from grit.service.models import Case
@admin.register(Case)
class CaseAdmin(admin.ModelAdmin):
list_display = ('subject', 'status', 'resolution', 'owner', 'created_at')
list_filter = ('status', 'resolution')
search_fields = ('subject', 'description')
ordering = ('-created_at',)
3. Create Cases Programmatically¶
from grit.service.models import Case
case = Case.objects.create(
subject="Cannot access dashboard",
description="User reports 500 error when loading the dashboard page.",
status="new",
owner=request.user
)
Core Model¶
Case¶
Represents a customer issue or support request. Cases move through configurable status stages and are resolved with a resolution type.
| Field | Type | Description |
|---|---|---|
subject |
CharField(255) |
Case title (required) |
description |
TextField |
Detailed description of the issue |
status |
CharField |
Lifecycle stage — choices from settings (default: new) |
resolution |
CharField |
How the case was resolved — choices from settings |
Inherited from BaseModel:
| Field | Type | Description |
|---|---|---|
id |
UUIDField |
Auto-generated unique identifier |
metadata |
JSONField |
Flexible JSON storage for custom fields |
created_at |
DateTimeField |
Auto-set on creation |
updated_at |
DateTimeField |
Auto-set on save |
owner |
ForeignKey |
Record owner (links to user) |
Configuration Reference¶
Case Statuses¶
Case statuses are configurable via APP_METADATA_SETTINGS in your Django settings. The is_closed flag indicates whether a status represents a terminal state:
# settings.py
APP_METADATA_SETTINGS = {
'CHOICES': {
'case_status': {
'new': {'label': 'New', 'is_closed': False},
'working': {'label': 'Working', 'is_closed': False},
'closed': {'label': 'Closed', 'is_closed': True},
}
}
}
The first status in the dictionary is used as the default value for new cases.
Resolution Types¶
Resolution types indicate how a case was closed: