================== 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: .. code-block:: bash pip install wilco[django] This installs wilco with Django (>= 4.2.0). Configuration ------------- Add ``wilco.bridges.django`` to your ``INSTALLED_APPS``: .. code-block:: python INSTALLED_APPS = [ # ... "wilco.bridges.django", ] Include the wilco URLs in your ``urls.py``: .. code-block:: python 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: .. code-block:: text 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: .. code-block:: python # settings.py WILCO_COMPONENT_SOURCES = [ (BASE_DIR / "shared_components", ""), # No prefix: button (BASE_DIR / "store" / "components", "store"), # Prefixed: store:product ] Settings ======== ``WILCO_COMPONENT_SOURCES`` List of ``(path, prefix)`` tuples for explicit component sources. Optional. ``WILCO_AUTODISCOVER`` Whether to auto-discover components from Django apps (default: ``True``). ``WILCO_BUILD_DIR`` Path to the directory containing pre-built bundles and ``manifest.json``. When set, the bridge serves pre-built bundles instead of bundling at runtime. Used by ``WilcoBundleFinder`` for ``collectstatic``, the ``wilco_build`` management command, and the template tags for static mode detection. .. code-block:: python WILCO_BUILD_DIR = BASE_DIR / "dist" / "wilco" Can also be set via the ``WILCO_BUILD_DIR`` environment variable (takes precedence over the setting). Template tags ============= Wilco provides template tags for rendering components in Django templates. Loading components ------------------ .. code-block:: html+django {% load wilco_tags %}
{% wilco_component "myapp:product" name=product.name price=product.price %}
{% wilco_loader_script %} ``wilco_component`` ^^^^^^^^^^^^^^^^^^^ Renders a component placeholder that will be hydrated by JavaScript. **Arguments:** - First positional: component name (required) - ``api_base``: Base URL for the API (default: ``"/api"``) - ``**props``: Any additional keyword arguments become component props **Example:** .. code-block:: html+django {% wilco_component "store:product_card" name=product.name price=product.price|floatformat:2 imageUrl=product.image.url %} ``wilco_loader_script`` ^^^^^^^^^^^^^^^^^^^^^^^ Includes the wilco loader script. Call once at the end of your template. .. code-block:: html+django {% wilco_component "header" %}
{% block content %}{% endblock %}
{% wilco_component "footer" %} {% wilco_loader_script %} Admin widget ============ The ``WilcoComponentWidget`` class renders components in the Django admin. Basic usage ----------- .. code-block:: python 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 -------------- .. code-block:: python 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 ----------- .. code-block:: python 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 ------------ 1. The mixin adds a ``validate_preview/`` endpoint to the admin URLs 2. When form fields lose focus, JavaScript POSTs the form data 3. The backend validates using Django's form system 4. On success: new props are returned and the component re-renders 5. On failure: validation errors are displayed above the preview Required implementation ----------------------- You must implement ``get_preview_props``: .. code-block:: python 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: .. code-block:: python 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: .. code-block:: python 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/bundles`` List all available component bundles. ``GET /api/bundles/{name}.js`` Get bundled JavaScript for a component. ``GET /api/bundles/{name}/metadata`` Get 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: .. code-block:: python # 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: .. code-block:: tsx // 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 (

List View

Detail View

); } 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: .. code-block:: python # 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: .. code-block:: python 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 -------------------------- .. code-block:: bash cd examples/django-unfold make setup # Install deps, migrate, load fixtures make start-prod # Build bundles, collect static, start gunicorn This runs: 1. ``wilco_build`` — pre-compiles all components into hashed JS files 2. ``collectstatic`` — copies static files (including bundles) to ``STATIC_ROOT`` 3. ``gunicorn 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: .. code-block:: python STATICFILES_FINDERS = [ "django.contrib.staticfiles.finders.FileSystemFinder", "django.contrib.staticfiles.finders.AppDirectoriesFinder", "wilco.bridges.django.finders.WilcoBundleFinder", ] During ``collectstatic``, it copies: - ``bundles/*.js`` files to ``STATIC_ROOT/wilco/bundles/`` - ``manifest.json`` to ``STATIC_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: .. code-block:: bash 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 server - ``whitenoise`` — 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 .. code-block:: bash 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 - ``LivePreviewAdminMixin`` works with both admin themes - Production mode with gunicorn and WhiteNoise .. code-block:: bash 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).