Django Integration¶
Overview¶
The Django bridge provides deep integration with Django, including:
Automatic component discovery from Django apps
Template tags for rendering components
Admin widget for embedding components
Live preview mixin for admin forms
Installation¶
Install wilco with Django support using the optional extra:
pip install wilco[django]
This installs wilco with Django (>= 4.2.0).
Configuration¶
Add wilco.bridges.django to your INSTALLED_APPS:
INSTALLED_APPS = [
# ...
"wilco.bridges.django",
]
Include the wilco URLs in your urls.py:
from django.urls import include, path
urlpatterns = [
path("api/", include("wilco.bridges.django.urls")),
# ...
]
Quick start¶
Components in Django apps¶
Place components in a components/ directory inside any Django app:
myapp/
├── models.py
├── views.py
└── components/
└── product/
├── __init__.py
├── index.tsx
└── schema.json
Components are automatically discovered and prefixed with the app label.
For example, myapp/components/product/ becomes myapp:product.
Explicit component sources¶
You can configure explicit component sources with optional prefixes:
# settings.py
WILCO_COMPONENT_SOURCES = [
(BASE_DIR / "shared_components", ""), # No prefix: button
(BASE_DIR / "store" / "components", "store"), # Prefixed: store:product
]
Settings¶
WILCO_COMPONENT_SOURCESList of
(path, prefix)tuples for explicit component sources. Optional.WILCO_AUTODISCOVERWhether to auto-discover components from Django apps (default:
True).WILCO_BUILD_DIRPath to the directory containing pre-built bundles and
manifest.json. When set, the bridge serves pre-built bundles instead of bundling at runtime. Used byWilcoBundleFinderforcollectstatic, thewilco_buildmanagement command, and the template tags for static mode detection.WILCO_BUILD_DIR = BASE_DIR / "dist" / "wilco"
Can also be set via the
WILCO_BUILD_DIRenvironment variable (takes precedence over the setting).
Admin widget¶
The WilcoComponentWidget class renders components in the Django admin.
Basic usage¶
from django.contrib import admin
from wilco.bridges.django import WilcoComponentWidget
from .models import Product
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
readonly_fields = ["preview"]
def preview(self, obj):
if not obj.pk:
return "Save to see preview"
return WilcoComponentWidget(
"store:product",
props={
"name": obj.name,
"price": float(obj.price),
"imageUrl": obj.image.url if obj.image else None,
},
).render()
Widget options¶
WilcoComponentWidget(
component_name, # Component name (e.g., "store:product")
props=None, # Dict of props to pass to the component
api_base="/api", # Base URL for the wilco API
live=False, # Enable live preview mode
validate_url=None, # URL for validation endpoint (required if live=True)
)
Live preview admin mixin¶
The LivePreviewAdminMixin adds automatic live preview to admin forms.
When users edit form fields, the preview updates in real-time.
Basic setup¶
from django.contrib import admin
from wilco.bridges.django import LivePreviewAdminMixin
from .models import Product
@admin.register(Product)
class ProductAdmin(LivePreviewAdminMixin, admin.ModelAdmin):
preview_component = "store:product"
readonly_fields = ["preview"]
fieldsets = (
("Details", {"fields": ["name", "price", "description"]}),
("Preview", {"fields": ["preview"]}),
)
def get_preview_props(self, form_data, instance=None):
"""Convert form data to component props."""
return {
"name": form_data.get("name", ""),
"price": float(form_data.get("price", 0) or 0),
"description": form_data.get("description", ""),
}
How it works¶
The mixin adds a
validate_preview/endpoint to the admin URLsWhen form fields lose focus, JavaScript POSTs the form data
The backend validates using Django’s form system
On success: new props are returned and the component re-renders
On failure: validation errors are displayed above the preview
Required implementation¶
You must implement get_preview_props:
def get_preview_props(self, form_data: dict, instance=None) -> dict:
"""Convert form data to component props.
Args:
form_data: Dictionary of cleaned form field values.
instance: The existing model instance (when editing).
Useful for file fields that aren't in the POST data.
Returns:
Dictionary of props for the component.
"""
Handling file fields¶
File fields require special handling since they’re not included in POST data when unchanged:
def get_preview_props(self, form_data, instance=None):
# Check form data first, fall back to instance
image_url = None
image = form_data.get("image")
if image and hasattr(image, "url"):
image_url = image.url
elif instance and instance.image:
image_url = instance.image.url
return {
"name": form_data.get("name", ""),
"imageUrl": image_url,
}
Customizing preview display¶
Override get_preview_props_from_obj for different logic when displaying
an existing object vs. processing form data:
def get_preview_props_from_obj(self, obj):
"""Get props from saved instance (initial display)."""
return {
"name": obj.name,
"price": float(obj.price),
# Include computed fields
"discountedPrice": float(obj.discounted_price),
}
def get_preview_props(self, form_data, instance=None):
"""Get props from form data (live updates)."""
price = float(form_data.get("price", 0) or 0)
return {
"name": form_data.get("name", ""),
"price": price,
# Compute discount manually
"discountedPrice": price * 0.9,
}
API endpoints¶
The Django bridge provides these endpoints:
GET /api/bundlesList all available component bundles.
GET /api/bundles/{name}.jsGet bundled JavaScript for a component.
GET /api/bundles/{name}/metadataGet component metadata including hash.
Bundle caching¶
Bundles are cached in memory with file modification time (mtime) invalidation. When a source file changes, the cache is automatically invalidated.
Complete example¶
Here’s a full example integrating wilco with Django Unfold admin:
# models.py
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=200)
description = models.TextField(blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
image = models.ImageField(upload_to="products/", blank=True)
# admin.py
from django.contrib import admin
from unfold.admin import ModelAdmin
from wilco.bridges.django import LivePreviewAdminMixin
from .models import Product
@admin.register(Product)
class ProductAdmin(LivePreviewAdminMixin, ModelAdmin):
preview_component = "store:product_preview"
list_display = ["name", "price"]
readonly_fields = ["preview"]
fieldsets = (
("Details", {
"classes": ["tab"],
"fields": ["name", "price", "description", "image"],
}),
("Preview", {
"classes": ["tab"],
"fields": ["preview"],
}),
)
def get_preview_props(self, form_data, instance=None):
price = form_data.get("price", 0)
if isinstance(price, str):
price = float(price) if price else 0
image_url = "https://via.placeholder.com/400"
image = form_data.get("image")
if image and hasattr(image, "url"):
image_url = image.url
elif instance and instance.image:
image_url = instance.image.url
return {
"name": form_data.get("name", ""),
"price": float(price),
"description": form_data.get("description", "") or "",
"imageUrl": image_url,
}
Component with both list and detail views:
// components/product_preview/index.tsx
import React from "react";
import Product from "../product";
interface Props {
name: string;
price: number;
description?: string;
imageUrl?: string;
}
export default function ProductPreview(props: Props) {
return (
<div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
<div>
<h3>List View</h3>
<Product {...props} mode="list" />
</div>
<div>
<h3>Detail View</h3>
<Product {...props} mode="detail" />
</div>
</div>
);
}
Production deployment¶
The Django examples include a production-like setup that mirrors how you would
deploy a real application. This is useful for testing wilco with pre-built
bundles, gunicorn, and DEBUG=False.
Configuration overview¶
The examples use environment variables to toggle between dev and prod modes:
# config/settings.py
import os
DEBUG = os.environ.get("DJANGO_DEBUG", "False") == "True"
ALLOWED_HOSTS = ["*"] if DEBUG else ["localhost", "127.0.0.1", "0.0.0.0"]
Dev mode (make start-dev): sets DJANGO_DEBUG=True, uses Django’s
built-in runserver with auto-reload. Components are bundled on-the-fly by
esbuild.
Prod mode (make start-prod): leaves DJANGO_DEBUG unset (defaults
to False), pre-builds component bundles, collects static files, then serves
through gunicorn.
Static files with WhiteNoise¶
With DEBUG=False, Django does not serve static files. The examples use
WhiteNoise to serve them directly
from the WSGI application:
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
# ...
]
WhiteNoise serves files from STATIC_ROOT (populated by collectstatic)
without needing nginx or a CDN. This includes pre-built wilco bundles, which
are collected via the WilcoBundleFinder.
Running in production mode¶
cd examples/django-unfold
make setup # Install deps, migrate, load fixtures
make start-prod # Build bundles, collect static, start gunicorn
This runs:
wilco_build— pre-compiles all components into hashed JS filescollectstatic— copies static files (including bundles) toSTATIC_ROOTgunicorn config.wsgi:application— serves the app on the configured port
WilcoBundleFinder¶
The WilcoBundleFinder is a Django static files finder that discovers
pre-built bundles from WILCO_BUILD_DIR. It must be added explicitly
to STATICFILES_FINDERS in your settings:
STATICFILES_FINDERS = [
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
"wilco.bridges.django.finders.WilcoBundleFinder",
]
During collectstatic, it copies:
bundles/*.jsfiles toSTATIC_ROOT/wilco/bundles/manifest.jsontoSTATIC_ROOT/wilco/manifest.json
This allows WhiteNoise (or nginx, or any static file server) to serve the
pre-built bundles at /static/wilco/bundles/{name}.{hash}.js.
wilco_build management command¶
The Django bridge provides a wilco_build management command as an
alternative to the wilco build CLI:
python manage.py wilco_build [--output DIR]
When --output is not specified, it uses the WILCO_BUILD_DIR setting.
The command discovers components using Django’s autodiscovery and configured
component sources.
Dependencies¶
The production setup adds two dependencies to each Django example:
gunicorn— production WSGI serverwhitenoise— static file serving middleware
Both are listed in the example’s pyproject.toml and installed by uv sync.
Example applications¶
Two complete Django example applications are available:
Django Unfold (django-unfold/)¶
The examples/django-unfold/ directory demonstrates Django with the Unfold admin theme:
Jinja2 template-based rendering with wilco components
Django Unfold admin with live preview (tabbed interface)
Django ORM models for products
Component discovery from Django apps
Production mode with gunicorn and WhiteNoise
cd examples/django-unfold
make setup # Install deps, migrate, load fixtures
make start-dev # Run development server (DEBUG=True, auto-reload)
make start-prod # Run production server (gunicorn, DEBUG=False)
Visit http://localhost:8000 for the store, http://localhost:8000/admin for the admin (credentials: admin/admin).
Django Vanilla (django-vanilla/)¶
The examples/django-vanilla/ directory demonstrates Django with the standard built-in admin:
Same features as Django Unfold but with standard admin UI
LivePreviewAdminMixinworks with both admin themesProduction mode with gunicorn and WhiteNoise
cd examples/django-vanilla
make setup # Install deps, migrate, load fixtures
make start-dev # Run development server (DEBUG=True, auto-reload)
make start-prod # Run production server (gunicorn, DEBUG=False)
Visit http://localhost:8100 for the store, http://localhost:8100/admin for the admin (credentials: admin/admin).