Skip to main content

Brings full stack components to Django.

Project description

Interlace: Reactive Server-Side Components for Django

Project Overview

Interlace brings reactive, server-driven components to Django inspired by Laravel Livewire and Phoenix LiveView. Build interactive web applications using Python and Django templates—no JavaScript build tools required.

Key Features

Server-Driven Architecture: All state lives in Python, components render on the server, and only HTML changes are sent to the client. Write reactive UIs entirely in Python with full access to the Django ORM and ecosystem.

Efficient DOM Morphing: Uses morphdom to intelligently update only changed elements, preserving focus states, scroll positions, and CSS transitions. No full page reloads, no virtual DOM overhead.

Reactive Data Binding: Two-way binding with lace:model, instant updates with .live modifiers, debouncing with .debounce, and automatic form handling. Components re-render automatically when properties change.

Rich Component Features: Lifecycle hooks (mount, updated, rendered), nested components with parent-child communication, lazy loading with viewport detection, polling for real-time updates, and built-in validation.

Zero Build Step: Minimal client-side JavaScript (~30KB), no npm, no webpack, no build pipeline. Drop into existing Django projects with minimal configuration.

Installation & Setup

Prerequisites

  • Python 3.10+
  • Django 4.2+

Quick Installation

pip install django-interlace

Or with Poetry:

poetry add django-interlace

The distribution is django-interlace on PyPI; the import package is interlace (from interlace import Component).

Django Configuration

Add to INSTALLED_APPS in settings.py:

INSTALLED_APPS = [
    # ...
    'interlace',
]

Include Interlace URLs in urls.py:

from django.urls import path, include

urlpatterns = [
    # ...
    # Mount under interlace/ (the client JS posts to a fixed /interlace/message).
    path('interlace/', include('interlace.urls')),
]

Add Interlace's JavaScript to your base template:

{% load static %}
<script src="{% static 'interlace/interlace.js' %}" defer></script>

Quick Start

Create a Component

# app_name/interlace/Counter.py
from interlace import Component

class Counter(Component):
    count: int = 0

    def increment(self):
        self.count += 1

    def decrement(self):
        self.count -= 1

    def inline_template(self):
        return """
        <div>
            <h2>Count: {{ count }}</h2>
            <button lace:click="increment">+</button>
            <button lace:click="decrement">-</button>
        </div>
        """

Use in Templates

{% load interlace %}

<div>
    <h1>My Counter App</h1>
    {% interlace "Counter" %}
</div>

That's it! The counter is now reactive—clicking buttons updates the count without page reloads.

Component Architecture

Dataclass-Style Components

Components use type hints and default values for clean property definitions:

class UserProfile(Component):
    username: str = ""
    email: str = ""
    is_admin: bool = False
    tags: list = []

    def mount(self):
        # Called when component initializes
        user = User.objects.get(id=self.user_id)
        self.username = user.username
        self.email = user.email

Template Options

Inline templates:

def inline_template(self):
    return """<div>{{ content }}</div>"""

External template files:

# Looks for app_name/interlace/templates/component_name.html
template_name = "user_profile.html"

Event Directives

  • lace:click="method" - Handle clicks
  • lace:model="property" - Two-way data binding
  • lace:model.live="property" - Instant updates
  • lace:model.debounce.300ms="property" - Debounced updates
  • lace:init="method" - Run on component mount
  • lace:poll.2s="method" - Poll every 2 seconds

Lifecycle Hooks

def mount(self):
    # Called once when component initializes
    pass

def updated_property_name(self, value):
    # Called when specific property changes
    pass

def updated(self, property, value):
    # Called when any property changes
    pass

def rendered(self):
    # Called after component renders
    pass

Resetting Properties

Restore properties to their declared defaults with reset() — handy for clearing a form after submit. Mutable defaults (lists, dicts) are reset to a fresh copy, never the shared class default.

def submit(self):
    if self.validate():
        self.save()
        self.reset()                    # reset every property
        # self.reset("name", "email")   # ...or just the named ones

Example Project Structure

my_app/
├── models.py                # Django models
├── views.py                 # Django views
├── urls.py                  # URL routing
├── admin.py                 # Admin configuration
├── interlace/                  # Interlace components
│   ├── __init__.py
│   ├── Counter.py           # Counter component
│   ├── TodoList.py          # Todo list component
│   ├── UserProfile.py       # User profile component
│   └── ProductFilter.py     # Product filter component
└── templates/
    └── interlace/              # Component templates (optional)
        ├── counter.html
        ├── todo_list.html
        └── user_profile.html

Advanced Features

Query Parameters

Sync component state to URL for shareable, bookmarkable pages:

class ProductFilter(Component):
    search: str = ""
    category: str = "all"
    query_params = ['search', 'category']

Lazy Loading

Defer expensive operations until component is visible:

{% interlace "HeavyComponent" lazy=True %}

Nested Components

Components can render other components and communicate via events:

def delete_item(self):
    self.emit_to('parent_list', 'item_deleted', {'id': self.item_id})

Computed Properties

Cache expensive calculations with @computed:

from interlace import Component, computed

class ReportBuilder(Component):
    start_date: str = ""
    end_date: str = ""

    @computed
    def filtered_events(self):
        # Cached within request - multiple template accesses won't re-query
        return Event.objects.filter(date__range=[self.start_date, self.end_date])

    @computed(persist=True, key=['start_date', 'end_date'], seconds=300)
    def performance_data(self):
        # Cached across requests for 5 minutes, per component instance
        return calculate_kpis(self.start_date, self.end_date)

    @computed(cache=True, key=['start_date'], seconds=1800)
    def daily_totals(self):
        # Global cache shared across all users
        return DailyStats.objects.filter(date=self.start_date).aggregate(...)

Database Integration

Direct ORM access in components:

def mount(self):
    self.posts = Post.objects.filter(published=True).select_related('author')

def search(self, query):
    self.posts = Post.objects.filter(title__icontains=query)

Documentation

  • Full API reference: interlace/AGENTS.md — a dense, example-first reference covering every directive, property type, lifecycle hook, and gotcha. It ships inside the package, so once installed it's also at site-packages/interlace/AGENTS.md (handy for AI/agent sessions in a consuming project).
  • Website: [Coming soon]
  • Examples: See /dev/example_project

Development

Running Tests

# In Docker (recommended)
docker exec -t django-web-app python manage.py test --parallel

# Locally
cd dev/example_project
python manage.py test

Creating Components

python manage.py interlace create ComponentName

Similar Projects

  • Laravel Livewire - PHP framework that inspired Interlace
  • Phoenix LiveView - Elixir framework with similar architecture
  • HTMX - Library for HTML-over-the-wire patterns
  • djust - Django + Rust alternative with similar goals

License

MIT License - see LICENSE file for details

Contributing

Contributions welcome! Please see the /dev directory for the development setup.


Built with ❤️ for the Django community

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_interlace-0.2.4.tar.gz (166.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_interlace-0.2.4-py3-none-any.whl (215.3 kB view details)

Uploaded Python 3

File details

Details for the file django_interlace-0.2.4.tar.gz.

File metadata

  • Download URL: django_interlace-0.2.4.tar.gz
  • Upload date:
  • Size: 166.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.4.1 CPython/3.11.0 Linux/6.17.0-1018-azure

File hashes

Hashes for django_interlace-0.2.4.tar.gz
Algorithm Hash digest
SHA256 c22353af822feefc721ad46e8fc7e19d29070a99f73ddd00bee199382feca7da
MD5 fbe472f10822b5f3603c18dd64a9682f
BLAKE2b-256 3ca71eb9222cb0b87bc1c2f1d25ebc6902b782f50b2dde45b47fb2555da776d1

See more details on using hashes here.

File details

Details for the file django_interlace-0.2.4-py3-none-any.whl.

File metadata

  • Download URL: django_interlace-0.2.4-py3-none-any.whl
  • Upload date:
  • Size: 215.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.4.1 CPython/3.11.0 Linux/6.17.0-1018-azure

File hashes

Hashes for django_interlace-0.2.4-py3-none-any.whl
Algorithm Hash digest
SHA256 a967a11d00fe975bf9442504cf13860e65253b0bca6a86f0ff447c49faed6ca0
MD5 92e8d75ac187501c2d7973b13fc6a696
BLAKE2b-256 dbc733cfbe82e2b3a1a1f259a6c057cca7dd2b8b767e57cb1b4703798d5445c5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page