django-unfold-fobi integrates django-fobi
into django-unfold.
It provides an Unfold-native admin experience for managing Fobi forms, plus a
small compatibility and patch layer for modern Django/DRF stacks.
This package is an integration layer, not a replacement for standalone Fobi usage. You can still use standard Fobi public forms and existing plugins; this project focuses on making the admin and integration surface fit naturally into Unfold-based back offices.
- Unfold-native admin integration for Fobi form management.
- Unfold widget styling applied across Fobi admin/plugin form surfaces.
- DRF-oriented improvements for Fobi API submissions and handler behavior.
- Admin workflow enhancements (import/clone/popup behavior, staff editing).
- Optional Sites extension (
unfold_fobi.contrib.sites) for site-scoped forms, queryset scoping, and reusable admin mixins. - Optional django CMS plugin (
unfold_fobi.contrib.cms) for embedding forms in CMS placeholders, with site-aware form selection. - Optional ALTCHA protection (
unfold_fobi.contrib.altcha) for proof-of-work anti-bot verification on public form submissions. - Fobi AppConfig overrides with
AutoFieldpinning and i18nverbose_namelabels for projects usingBigAutoField.
- Python
>=3.10,<4.0 - Django
>=5.0 - django-unfold
>=0.80.0 - django-fobi
>=0.19 - django-crispy-forms
>=2.0 - djangorestframework
>=3.14 - django-unfold-modal
>=0.1.0
unfold_fobi applies a few startup patches to keep Fobi integration stable on
newer dependency versions:
- DRF compatibility shim restores
rest_framework.fields.set_valuefor DRF 3.15+ where Fobi still expects it. - DRF
db_storeimprovements include active-date enforcement and safer integration behavior. - Admin patches align popup/modal responses with Unfold workflows.
- Additional Fobi patches improve widget consistency, staff editing behavior, and mail sender handler field handling.
- Main focus is DRF + admin integration; this package does not try to redesign every standalone Fobi rendering path.
- For REST-based usage, you need to implement your own frontend form rendering using the API metadata returned for each form.
- Non-Unfold form rendering should still work, but may not be styled like Unfold unless your project applies matching templates/styles.
- Form wizards are currently linked/redirected, but not fully covered as a first-class Unfold-integrated workflow.
Install and enable the integration with minimum settings.
- Install package:
pip install django-unfold-fobi- Add apps (order matters):
INSTALLED_APPS = [
"unfold", # must be before django.contrib.admin
"crispy_forms",
"fobi.contrib.themes.simple",
"unfold_fobi",
# If using DEFAULT_AUTO_FIELD = "BigAutoField", use these instead of
# bare "fobi" and "fobi.contrib.plugins.form_handlers.db_store":
"unfold_fobi.fobi_app_configs.FobiConfig",
"unfold_fobi.fobi_app_configs.FobiDbStoreConfig",
]- Add required template hooks:
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"fobi.context_processors.theme",
"unfold_fobi.context_processors.admin_site",
],
"builtins": [
"unfold_fobi.templatetags.unfold_fobi_tags",
],
},
},
]- Enable theme settings:
FOBI_THEME = "unfold"
FOBI_DEFAULT_THEME = "unfold"
CRISPY_TEMPLATE_PACK = "unfold_crispy"
CRISPY_ALLOWED_TEMPLATE_PACKS = ["unfold_crispy"]- Add URLs (keep
admin/fobi/beforeadmin.site.urls):
from django.urls import include, path
urlpatterns = [
path("api/", include("fobi.contrib.apps.drf_integration.urls")),
path("api/", include("unfold_fobi.api.urls")),
path("fobi/", include("unfold_fobi.urls.public")),
path("admin/fobi/", include("unfold_fobi.urls.admin")),
]- Run migrations:
python manage.py migrateUse this when you need the full integration, including optional Sites support.
- Follow standard
django-fobiinstallation first (plugins, handlers, base settings). - Then add
unfold_fobiand the Quickstart settings from above.
Enable only if your project needs form-to-site bindings.
INSTALLED_APPS += [
"django.contrib.sites",
"unfold_fobi.contrib.sites",
]
SITE_ID = 1
UNFOLD_FOBI_SITES_FOR_USER = "myproject.site_permissions.sites_for_user"Then re-register your form admins with the mixins from
unfold_fobi.contrib.sites.admin:
SiteAwareFormEntryMixinRelationSiteScopeAdminMixin
These provide site assignment UI, queryset scoping, and reusable site-scoped permission helpers. Your project still owns user-to-site policy and project-specific permission decisions.
Enable only if your project uses django CMS and wants to embed Fobi forms in CMS placeholders.
Requires django-unfold-extra>=0.2.1.
INSTALLED_APPS += [
"unfold_fobi.contrib.cms",
]Then run migrations:
python manage.py migrateThe plugin appears as "Form" (module "Forms") in the CMS plugin picker. It renders the selected form using fobi's standard form rendering.
When unfold_fobi.contrib.sites is also enabled, the form selection dropdown
is automatically filtered by the current site and the editor's allowed sites.
If djangocms-rest is installed, the plugin serializes the form reference as
{"form_entry": {"name": "...", "slug": "..."}} instead of a bare FK integer.
Enable to require proof-of-work verification on public form submissions.
Only public forms (is_public=True) are protected; non-public/preview forms
are unaffected.
Requires the altcha Python package (>=2.0).
pip install altchaINSTALLED_APPS += [
"unfold_fobi.contrib.altcha",
]
UNFOLD_FOBI_ALTCHA_HMAC_SECRET = "your-secret-key" # required to enableAdd the challenge endpoint URL:
urlpatterns += [
path("api/", include("unfold_fobi.contrib.altcha.urls")),
]Optional settings:
UNFOLD_FOBI_ALTCHA_MAX_NUMBER = 100_000 # difficulty (default: 100000)
UNFOLD_FOBI_ALTCHA_ALGORITHM = "SHA-256" # hash algorithm
UNFOLD_FOBI_ALTCHA_FIELD_NAME = "altcha" # payload field name in PUT body
UNFOLD_FOBI_ALTCHA_CACHE_ALIAS = "default" # cache backend for replay protection
UNFOLD_FOBI_ALTCHA_CHALLENGE_EXPIRY = 300 # challenge TTL in secondsHow it works:
GET /api/fobi-form-fields/<slug>/includes anAltchaFieldentry for public forms when ALTCHA is enabled.- Frontend fetches a challenge from
GET /api/altcha-challenge/. - Frontend solves the challenge client-side (e.g. using the
altchaweb component) and includes the base64 payload as"altcha"in the PUT body. - Backend verifies the payload before processing the form submission.
Missing, invalid, expired, or replayed payloads return
400.
The Fobi content_text plugin renders as an Unfold-styled Trix WYSIWYG editor
out of the box. Bleach is widened to keep Trix's output (p, h1–h3, div,
s, u, etc.) on save. No setup required.
Optional settings (all unset = full upstream toolbar, no overrides):
# Restrict which buttons appear (subset of the names below).
# Available: p, underline, bold, italic, strike, link,
# heading1, heading2, heading3, heading4,
# quote, code, bullet, number, indent, outdent, undo, redo.
UNFOLD_FOBI_CONTENT_TEXT_TOOLBAR_BUTTONS = [
"bold", "italic", "link", "heading2", "heading3", "bullet", "number",
]
# Ship fully custom toolbar markup. The partial must wrap its content in
# <template id="trix-toolbar">…</template>.
UNFOLD_FOBI_CONTENT_TEXT_TOOLBAR_TEMPLATE = "myproject/trix_toolbar.html"
# Override the bleach allowlist used by ContentTextForm.clean_text. When set,
# the package leaves these alone; pair with the toolbar buttons you keep so
# saved markup matches what the editor can produce.
FOBI_PLUGIN_CONTENT_TEXT_ALLOWED_TAGS = ["a", "b", "br", "em", "li", "ol", "p", "strong", "ul"]
FOBI_PLUGIN_CONTENT_TEXT_ALLOWED_ATTRIBUTES = {"a": ["href", "title", "target", "rel"]}- Include both Fobi DRF URLs and
unfold_fobi.api.urls. - Use
GET /api/fobi-form-fields/<slug>/to fetch per-form field metadata (including available field types/widgets/choices) for frontend rendering. - Ensure each form has the
db_storehandler enabled for persisted API submissions.
If your project uses DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField",
replace the bare fobi entries in INSTALLED_APPS with the package-provided
configs. These pin AutoField (fobi lacks a BigAutoField migration) and add
translatable verbose_name labels.
INSTALLED_APPS = [
# Replace "fobi" with:
"unfold_fobi.fobi_app_configs.FobiConfig",
# Replace "fobi.contrib.plugins.form_handlers.db_store" with:
"unfold_fobi.fobi_app_configs.FobiDbStoreConfig",
]unfold_fobi.apps.UnfoldFobiConfig.ready()runs during Django app startup and performs integration bootstrap in this order: DRF compatibility shim, Fobi admin re-registration for Unfold, monkey-patches, and signal registration.- If you want explicit app config wiring, use this in
INSTALLED_APPS:
INSTALLED_APPS = [
# ...
"unfold_fobi.apps.UnfoldFobiConfig",
]- Startup signals auto-attach a
db_storehandler to saved forms and deduplicate duplicates created during JSON import. - The Unfold theme for Fobi is registered as
uid="unfold".