Skip to main content

Server-rendered Django views with HTMX + Alpine.js - No JavaScript required

Project description

Django Nitro

Server-rendered Django views with HTMX + Alpine.js - No JavaScript required

Django Nitro is a library of views, template tags, and components for building reactive Django applications using HTMX and Alpine.js. Server renders HTML, HTMX swaps it, Alpine handles local UI.

License: MIT Python 3.12+ Django 5.2+


Why Django Nitro?

  • Zero JavaScript - Build reactive UIs with Python views and template tags
  • Standard Django - Uses Django views, forms, and templates (no custom runtime)
  • HTMX powered - Server-rendered HTML swaps for reactive interactions
  • Lightweight - HTMX (~14KB) + Alpine.js (~15KB) via CDN
  • Batteries included - Search, filters, pagination, modals, slideovers, toasts, file uploads, wizards
  • Multi-tenant ready - Generic OrganizationMixin for multi-tenant apps

Table of Contents


Installation

pip install django-nitro

Requirements

  • Python 3.12+
  • Django 5.2+
  • HTMX and Alpine.js (loaded via CDN, no pip install needed)

Setup

1. Add to INSTALLED_APPS

# settings.py
INSTALLED_APPS = [
    # ...
    'nitro',
]

2. Add scripts to your base template

{% load nitro_tags %}
<!DOCTYPE html>
<html>
<head>
    {% nitro_scripts %}
    <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body>
    {% block content %}{% endblock %}
    {% nitro_toast %}
</body>
</html>

{% nitro_scripts %} includes HTMX, nitro.js, and alpine-components.js.


Quick Start

1. Define the View

# views.py
from nitro.views import NitroListView

class PropertyListView(NitroListView):
    model = Property
    template_name = 'properties/property_list.html'
    partial_template = 'properties/partials/property_list_content.html'
    search_fields = ['name', 'address']
    filter_fields = ['status', 'property_type']
    paginate_by = 20

2. Create the Template

<!-- property_list.html -->
{% load nitro_tags %}
{% extends "base.html" %}

{% block content %}
<div class="flex gap-4 mb-4">
    {% nitro_search target='#list-content' %}
    {% nitro_filter field='status' options=filter_options.status %}
</div>

<div id="list-content">
    {% include "properties/partials/property_list_content.html" %}
</div>
{% endblock %}
<!-- partials/property_list_content.html -->
{% load nitro_tags %}
{% for property in object_list %}
<div class="p-4 border rounded-lg">
    <h3>{{ property.name }}</h3>
    <p>{{ property.address }}</p>
    <span>{{ property.status|status_badge }}</span>
</div>
{% endfor %}
{% nitro_pagination page_obj target='#list-content' %}

3. Wire the URL

# urls.py
from .views import PropertyListView

urlpatterns = [
    path('properties/', PropertyListView.as_view(), name='property_list'),
]

That's it. Search with debounce, filters, and pagination all work via HTMX without writing JavaScript.


Views

NitroView

Base view with HTMX detection and toast helpers.

from nitro.views import NitroView

class DashboardView(NitroView):
    template_name = 'dashboard.html'
    partial_template = 'partials/dashboard_content.html'  # for HTMX requests

Methods:

  • self.is_htmx - Check if request is HTMX
  • self.toast(message, level) - Send toast notification
  • self.success(message) / self.error(message) - Toast shortcuts
  • self.htmx_redirect(url) - Client-side redirect via HTMX
  • self.htmx_refresh() - Refresh page via HTMX

NitroListView

List view with built-in search, filters, sorting, and pagination.

class TenantListView(NitroListView):
    model = Tenant
    template_name = 'tenants/list.html'
    partial_template = 'tenants/partials/list_content.html'
    search_fields = ['name', 'email', 'phone']
    filter_fields = ['status']
    sortable_fields = ['name', 'created_at']
    paginate_by = 25
    select_related = ['property']
    default_sort = '-created_at'

    def get_filter_options(self):
        return {
            'status': [('active', 'Activo'), ('inactive', 'Inactivo')],
        }

NitroFormView

Form handling with HTMX support.

class PropertyFormView(NitroFormView):
    form_class = PropertyForm
    template_name = 'properties/form.html'
    success_url = '/properties/'

NitroCreateView / NitroUpdateView / NitroDeleteView

CRUD views with slideover + toast pattern:

class PropertyCreateView(CompanyMixin, NitroCreateView):
    model = Property
    form_class = PropertyForm
    template_name = 'properties/form.html'
    # Automatically: closes slideover, shows toast, refreshes page

class PropertyUpdateView(CompanyMixin, NitroUpdateView):
    model = Property
    form_class = PropertyForm
    template_name = 'properties/form.html'

class PropertyDeleteView(CompanyMixin, NitroDeleteView):
    model = Property

    def can_delete(self, obj):
        if obj.leases.filter(status='active').exists():
            return False, 'Has active leases'
        return True, None

NitroModelView

Detail view for a single model instance.

class PropertyDetailView(NitroModelView):
    model = Property
    template_name = 'properties/detail.html'

NitroWizard

Multi-step form wizard with session-based data persistence.

from nitro.wizards import NitroWizard, WizardStep

class OnboardingWizard(NitroWizard):
    wizard_name = 'onboarding'
    steps = [
        WizardStep('company', CompanyForm, 'wizard/company.html', 'Company'),
        WizardStep('plan', PlanForm, 'wizard/plan.html', 'Plan'),
        WizardStep('confirm', None, 'wizard/confirm.html', 'Confirm'),
    ]

    def done(self, wizard_data):
        Company.objects.create(**wizard_data['company'])
        self.clear_wizard_data()
        return redirect('dashboard')

Template Tags

Load with {% load nitro_tags %}.

HTMX Action Tags

{% nitro_search target='#list' placeholder='Search...' %}
{% nitro_filter field='status' options=opts target='#list' %}
{% nitro_pagination page_obj target='#list' %}
{% nitro_sort 'name' 'Name' target='#list' %}
{% nitro_delete url=delete_url target='#list' confirm='Delete?' %}

Form Tags

{% nitro_field form.name %}
{% nitro_select form.category search_url='/api/search/' %}
{% nitro_form_footer slideover='create-item' label='Save' %}

Component Tags

{% nitro_modal id='create' title='New Item' %}...{% end_nitro_modal %}
{% nitro_slideover id='edit' title='Edit' size='lg' %}...{% end_nitro_slideover %}
{% nitro_tabs id='tabs' target='#content' %}
    {% nitro_tab name='info' label='Info' active=True %}
    {% nitro_tab name='docs' label='Documents' %}
{% end_nitro_tabs %}
{% nitro_empty_state icon='icon' title='No data' message='Add your first item' %}
{% nitro_stats_card icon='icon' label='Revenue' value=total change='+12%' change_type='positive' %}
{% nitro_avatar user size='md' %}
{% nitro_file_upload upload_url='/upload/' field_name='file' accept='image/*' multiple=True %}
{% nitro_toast %}

UI Helpers

{% nitro_open_modal 'create' %}       {# attrs for a button to open modal #}
{% nitro_close_modal 'create' %}
{% nitro_open_slideover 'edit' %}
{% nitro_close_slideover 'edit' %}
{% nitro_scripts %}                    {# include HTMX + Nitro JS #}

Transition Presets

<div x-show="open" {% nitro_transition 'fade' %}>...</div>
<div x-show="open" {% nitro_transition 'slide-up' '200' %}>...</div>
<div x-show="open" {% nitro_transition 'scale' %}>...</div>

Presets: fade, slide-up, slide-down, slide-right, slide-left, scale.

Keyboard Shortcuts

<body {% nitro_key 'meta.k' "$dispatch('focus-search')" %}>
<div {% nitro_key 'escape' 'open = false' %}>

Display Filters

{{ amount|currency }}                  {# RD$ 15,000.00 #}
{{ amount|currency:'USD' }}            {# US$ 15,000.00 #}
{{ item.status|status_badge }}         {# colored badge #}
{{ ticket.priority|priority_badge }}   {# colored badge #}
{{ phone|phone_format }}               {# (809) 555-1234 #}
{{ date|relative_date }}               {# Hoy, Ayer, Hace 3 dias #}
{{ uuid|truncate_id:8 }}               {# first 8 chars #}
{{ 4.5|rating }}                       {# star rating SVG #}
{{ count|pluralize_es:'item,items' }}  {# Spanish pluralization #}

Forms

from nitro.forms import NitroModelForm, NitroForm, PhoneField, CedulaField, CurrencyField

class PropertyForm(NitroModelForm):
    class Meta:
        model = Property
        fields = ['name', 'address', 'rent_amount']
    # All widgets get Tailwind classes automatically

class ContactForm(NitroForm):
    phone = PhoneField()
    cedula = CedulaField()
    amount = CurrencyField()

Components (Alpine.js)

Registered automatically via alpine-components.js:

  • loadingBtn - Spinner on submit during HTMX requests
  • fileUpload - Drag-and-drop file uploads
  • clipboard - Copy to clipboard
  • charCounter - Character counter for textareas
  • confirmAction - Confirm dialog
  • toggle - Collapsible sections
  • tabs - Client-side tabs
  • currencyInput - Auto-format currency
  • phoneInput - Auto-format phone numbers
  • dirtyForm - Unsaved changes warning

Multi-Tenancy

from nitro.mixins import OrganizationMixin

class CompanyMixin(OrganizationMixin):
    org_field = 'company'

    def get_organization(self):
        return self.request.user.company

# Use in views:
class PropertyListView(CompanyMixin, NitroListView):
    model = Property  # automatically filtered by company

Utilities

Currency

from nitro.utils import format_currency, parse_currency

format_currency(1234.5)           # "RD$ 1,234.50"
format_currency(1234.5, 'USD')    # "US$ 1,234.50"
parse_currency('RD$ 1,234.50')    # Decimal('1234.50')

Dates

from nitro.utils import today, relative_date, month_name, is_overdue, add_months

relative_date(some_date)   # "Hoy", "Ayer", "Hace 3 dias"
month_name(1)              # "Enero"
is_overdue(due_date)       # True/False
add_months(date, 3)        # date + 3 months

Configuration

# settings.py (all optional)
NITRO = {
    'TOAST_ENABLED': True,
    'TOAST_POSITION': 'top-right',
    'TOAST_DURATION': 3000,
    'DEBUG': False,
}

Migration from v0.7

v0.8.0 is a complete rewrite. The component-based architecture (NitroComponent, Pydantic state, Django Ninja API) has been replaced with standard Django views + HTMX.

What changed

v0.7 v0.8
NitroComponent classes Django views (NitroListView, NitroFormView)
Pydantic BaseModel state Django forms and template context
Django Ninja JSON API Server-rendered HTML + HTMX swaps
@register_component Standard Django URL routing
call('action') in templates hx-post/hx-get HTMX attributes
nitro_model two-way binding Alpine x-model (local) + form submit
nitro_action template tag HTMX attributes via template tags
requirements: pydantic, django-ninja requirements: Django only

Why the change

The component system added complexity (Pydantic state sync, JSON API, client-side rendering) that wasn't necessary for most Django apps. HTMX achieves the same reactive UX with standard Django patterns, is easier to debug, and has zero additional Python dependencies.


License

MIT License. See LICENSE for details.

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_nitro-0.8.0.tar.gz (87.9 kB view details)

Uploaded Source

Built Distribution

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

django_nitro-0.8.0-py3-none-any.whl (105.8 kB view details)

Uploaded Python 3

File details

Details for the file django_nitro-0.8.0.tar.gz.

File metadata

  • Download URL: django_nitro-0.8.0.tar.gz
  • Upload date:
  • Size: 87.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for django_nitro-0.8.0.tar.gz
Algorithm Hash digest
SHA256 b75724ac941c394b504c0d06cd36c3d23dd3dfcdaef518826deebca5e11564da
MD5 271ef00805f79c6c8c6602f450d5c20f
BLAKE2b-256 9df6b986af980149def2fc5d0830cc8e097e715614415d212056d31375868898

See more details on using hashes here.

File details

Details for the file django_nitro-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: django_nitro-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 105.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for django_nitro-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 dfab13d2007ce3df75f4125207b98ce17ebf7370b02694db527858c53c0097fd
MD5 71683f31fbd3c53f19a1206f402f6c0d
BLAKE2b-256 39abd65ed1c94394c4ff96c79ed8d1a6512b7d97699e56c0bbeff216d1669047

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