Optional extras¶
Heavy app-wide packages stay opt-in. Admin integration auto-activates when the dep is installed.
Available extras¶
pip install django-yp-admin[history] # django-simple-history (>=3.5)
pip install django-yp-admin[import-export] # django-import-export (>=4.0)
pip install django-yp-admin[full] # both
Activation semantics¶
Each integration module probes for the optional dep with importlib.util.find_spec:
# django_yp_admin/contrib/history_admin.py
import importlib.util
if importlib.util.find_spec("simple_history") is not None:
from simple_history.admin import SimpleHistoryAdmin
class HistoryAdmin(SimpleHistoryAdmin):
...
else:
HistoryAdmin = None
If the dep is missing, the wrapper class is None. You can either:
- Guard before subclassing:
from django_yp_admin.contrib.history_admin import HistoryAdmin if HistoryAdmin is not None: class MyAdmin(HistoryAdmin, ModelAdmin): ... - Import unconditionally and let
pip install django-yp-admin[history]be the obvious fix when the import fails or the class isNone.
Available wrappers¶
| Module | Class | Activated by |
|---|---|---|
django_yp_admin.contrib.history_admin |
HistoryAdmin |
pip install django-yp-admin[history] |
django_yp_admin.contrib.import_export_admin |
ImportExportAdmin |
pip install django-yp-admin[import-export] |
Both wrappers preserve the upstream API and add our htmx-aware templates.
Alpine.js: optional bundle¶
Alpine is opt-in. The frontend build emits two artifacts under
django_yp_admin/static/yp_admin/js/:
| File | Contents | Approx. minified size |
|---|---|---|
yp-admin.js |
htmx + Tom Select + our TS. Alpine resolved to a no-op stub. | ~124 KB (~40 KB gzip) |
yp-admin-alpine.js |
Same as above plus Alpine.js. | ~171 KB (~55 KB gzip) |
Default ModelAdmin.media references yp-admin.js. To enable Alpine-driven
local UI state in your project, swap the file in your overriding Media class:
class MyAdmin(admin.ModelAdmin):
class Media:
js = ("yp_admin/js/yp-admin-alpine.js",)
A Meta.use_alpine = True flag on ModelAdmin is planned to automate the
swap in a future release.
How the split works¶
frontend/build.ts runs Bun.build twice. The slim variant uses a Bun
onResolve plugin that redirects import Alpine from "alpinejs" to a tiny
no-op proxy stub, so Alpine's ~50 KB source never reaches the slim bundle.
The full variant resolves alpinejs normally. The build define
__WITH_ALPINE__ is also exposed for any future if (__WITH_ALPINE__) gates
in user code.
Autocomplete XSS policy¶
Autocomplete options render as text by default. Tom Select's built-in
option/item renderers escape the text field automatically, and the
related-object popup payload (event.detail.newRepr) is additionally
sanitized in autocomplete.ts before reaching addOption.
If you customize Tom Select via a render config that returns an HTML
string, you must escape every user-provided field with the escape()
helper exported from frontend/src/autocomplete.ts:
import { escape } from "./autocomplete";
new TomSelect(el, {
render: {
option: (data) => `<div class="opt">${escape(data.text)}</div>`,
// ^^^^^^^ required to prevent XSS
},
});
Returning data.text interpolated directly into a template literal is an
XSS sink. Reviewers should reject any patch that does so.