Skip to main content

image

Inertia.js Django Adapter

Official Docs and installation instructions are available here

Installation

Backend

Install the following python package via pip

pip install inertia-django

Add the Inertia app to your INSTALLED_APPS in settings.py

INSTALLED_APPS = [
  # django apps,
  'inertia',
  # your project's apps,
]

Add the Inertia middleware to your MIDDLEWARE in settings.py

MIDDLEWARE = [
  # django middleware,
  'inertia.middleware.InertiaMiddleware',
  # your project's middleware,
]

Finally, create a layout which exposes {% block inertia %}{% endblock %} in the body and set the path to this layout as INERTIA_LAYOUT in your settings.py file.

Now you're all set!

Frontend

Django specific frontend docs coming soon. For now, we recommend installing django_vite and following the commits on the Django Vite example repo. Once Vite is setup with your frontend of choice, just replace the contents of entry.js with this file (example in react)

You can also check out the official Inertia docs at https://inertiajs.com/.

CSRF

Django's CSRF tokens are tightly coupled with rendering templates, so Inertia Django automatically handles adding the CSRF cookie to every response — including non-visit XHR requests made via the useHttp hook introduced in Inertia v3.

The Inertia v3 HTTP client defaults to Laravel's XSRF-TOKEN cookie and X-XSRF-TOKEN header, while Django defaults to csrftoken and X-CSRFToken. Configure the client with Django's names (or use your project's custom CSRF names):

createInertiaApp({
  // ...
  http: {
    xsrfCookieName: 'csrftoken',
    xsrfHeaderName: 'X-CSRFToken',
  },
})

Upgrading to Inertia v3

See the Inertia v3 upgrade guide for the required client, template, partial reload, flash, redirect, and cache changes.

Usage

Responses

Render Inertia responses is simple, you can either use the provided inertia render function or, for the most common use case, the inertia decorator. The render function accepts four arguments, the first is your request object. The second is the name of the component you want to render from within your pages directory (without extension). The third argument is a dict of props that should be provided to your components. The final argument is template_data, for any variables you want to provide to your template, but this is much less common.

from inertia import render
from .models import Event

def index(request):
  return render(request, 'Event/Index', props={
    'events': Event.objects.all()
  })

Or use the simpler decorator for the most common use cases

from inertia import inertia
from .models import Event

@inertia('Event/Index')
def index(request):
  return {
    'events': Event.objects.all(),
  }

If you need more control, you can also directly return the InertiaResponse class. It has the same arguments as the render method and subclasses HttpResponse to accept of all its arguments as well.

from inertia import InertiaResponse
from .models import Event

def index(request):
  return InertiaResponse(
    request,
    'Event/Index',
    props={
      'events': Event.objects.all()
    }
  )

Shared Data

If you have data that you want to be provided as a prop to every component (a common use-case is information about the authenticated user) you can use the share method. A common place to put this would be in some custom middleware.

from inertia import share
from django.conf import settings
from .models import User

def inertia_share(get_response):
  def middleware(request):
    share(request,
      app_name=settings.APP_NAME,
      user_count=lambda: User.objects.count(), # evaluated lazily at render time
      user=lambda: request.user, # evaluated lazily at render time
    )

    return get_response(request)
  return middleware

Prop Serialization

Unlike Rails and Laravel, Django does not handle converting objects to JSON by default so Inertia Django offers two different ways to handle prop serialization.

InertiaJsonEncoder

The default behavior is via the InertiaJsonEncoder. The InertiaJsonEncoder is a barebones implementation that extends the DjangoJSONEncoder with the ability to handle QuerySets and models. Models are JSON encoded via Django's model_to_dict method excluding the field password. This method has limitations though, as model_to_dict does not include fields where editable=False (such as automatic timestamps).

InertiaMeta

Starting in Inertia Django v1.2, Inertia Django supports an InertiaMeta nested class. Similar to Django Rest Framework's serializers, any class (not just models) can contain an InertiaMeta class which can specify how that class should be serialized to JSON. At this time, in only supports fields, but this may be extended in future versions.

class User(models.Model):
  name = models.CharField(max_length=255)
  password = models.CharField(max_length=255)
  created_at = models.DateField(auto_now_add=True)

  class InertiaMeta:
    fields = ('name', 'created_at')

External Redirects

It is possible to redirect to an external website, or even another non-Inertia endpoint in your app while handling an Inertia request. This can be accomplished using a server-side initiated window.location visit via the location method:

from inertia import location

def external():
    return location("http://foobar.com/")

It will generate a 409 Conflict response and include the destination URL in the X-Inertia-Location header. When this response is received client-side, Inertia will automatically perform a window.location = url visit.

Optional Props

On the front end, Inertia supports the concept of "partial reloads" where only the props requested are returned by the server. Sometimes, you may want to use this flow to avoid processing a particularly slow prop on the intial load. In this case, you can use Optional props. Optional props aren't evaluated unless they're specifically requested by name in a partial reload.

from inertia import optional, inertia

@inertia('ExampleComponent')
def example(request):
  return {
    'name': lambda: 'Brandon', # this will be rendered on the first load as usual
    'data': optional(lambda: some_long_calculation()), # this will only be run when specifically requested by partial props and WILL NOT be included on the initial load
  }

Use always() for data that must be included even when the client requests a different subset of props:

from inertia import always

return {
  'currentUser': always(lambda: request.user.username),
  'reports': optional(load_reports),
}

Deferred Props

As of version 2.0, Inertia supports the ability to defer the fetching of props until after the page has been initially rendered. Essentially this is similar to the concept of Optional props however Inertia provides convenient frontend components to automatically fetch the deferred props after the page has initially loaded, instead of requiring the user to initiate a reload. For more info, see Deferred props in the Inertia documentation.

To mark props as deferred on the server side use the defer function.

from inertia import defer, inertia

@inertia('ExampleComponent')
def example(request):
  return {
    'name': lambda: 'Brandon', # this will be rendered on the first load as usual
    'data': defer(lambda: some_long_calculation()), # this will only be run after the frontend has initially loaded and inertia requests this prop
  }

Grouping requests

By default, all deferred props get fetched in one request after the initial page is rendered, but you can choose to fetch data in parallel by grouping props together.

from inertia import defer, inertia

@inertia('ExampleComponent')
def example(request):
  return {
    'name': lambda: 'Brandon', # this will be rendered on the first load as usual
    'data': defer(lambda: some_long_calculation()),
    'data1': defer(lambda: some_long_calculation1(), group='group'),
    'data2': defer(lambda: some_long_calculation1(), 'group'),
  }

In the example above, the data1, and data2 props will be fetched in one request, while the data prop will be fetched in a separate request in parallel. Group names are arbitrary strings and can be anything you choose.

Merge Props

By default, Inertia overwrites props with the same name when reloading a page. However, there are instances, such as pagination or infinite scrolling, where that is not the desired behavior. In these cases, you can merge props instead of overwriting them.

from inertia import merge, inertia

@inertia('ExampleComponent')
def example(request):
  return {
    'name': lambda: 'Brandon',
    'data': merge(Paginator(objects, 3)),
  }

You can also combine deferred props with mergeable props to defer the loading of the prop and ultimately mark it as mergeable once it's loaded.

from inertia import defer, inertia

@inertia('ExampleComponent')
def example(request):
  return {
    'name': lambda: 'Brandon',
    'data': defer(lambda: Paginator(objects, 3), merge=True),
  }

For Inertia v3, merge() can target nested arrays and match incoming records by a stable key. deep_merge() merges nested objects and arrays recursively:

from inertia import deep_merge, merge

return {
  'feed': merge(load_feed, append='items', match_on='items.id'),
  'chat': deep_merge(load_chat, match_on='messages.id'),
}

Scroll Props

scroll() adds the pagination metadata used by Inertia v3's infinite-scroll components. Pass defer=True to load its first page after the initial render.

from inertia import scroll

return {
  'players': scroll(
    load_players,
    {'pageName': 'page', 'previousPage': None, 'nextPage': 2, 'currentPage': 1},
    defer=True,
  ),
}

Once Props

Some data rarely changes, is expensive to compute, or is simply large. Rather than sending it on every response, you can use once props. Once props are resolved on the first visit and remembered by the client. On subsequent visits the server skips re-resolving them, saving both CPU and bandwidth.

from inertia import once, inertia

@inertia('ExampleComponent')
def example(request):
  return {
    'name': lambda: 'Brandon',
    'plans': once(lambda: Plan.objects.all()), # resolved once, then cached client-side
  }

Pass fresh=True to force re-resolution on every request, regardless of whether the client already holds the value:

@inertia('Billing/Plans')
def plans(request):
  return {
    'plans': once(lambda: Plan.objects.all(), fresh=True),
  }

Once props also work in shared data:

share(request, countries=once(lambda: list(Country.objects.values('code', 'name'))))

Flash Data

Django messages are automatically exposed through the page object's top-level flash.messages field.

from django.contrib import messages

messages.success(request, 'Brandon scored!')
return redirect('players:index')

Preserve Fragment

When a user visits a URL with a fragment (e.g. /article/old-slug#section) and the server redirects to a different URL, the fragment is normally lost. Call preserve_fragment() before returning the redirect to carry the fragment to the new URL:

from django.shortcuts import redirect

from inertia import preserve_fragment

def rename_article(request, slug):
    article = Article.objects.get(slug=slug)
    article.slug = request.POST['new_slug']
    article.save()
    preserve_fragment(request)
    return redirect('articles:show', slug=article.slug)

The client will navigate to /article/new-slug#section instead of /article/new-slug.

Json Encoding

Inertia Django ships with a custom JsonEncoder at inertia.utils.InertiaJsonEncoder that extends Django's DjangoJSONEncoder with additional logic to handle encoding models and Querysets. If you have other json encoding logic you'd prefer, you can set a new JsonEncoder via the settings.

History Encryption

Inertia.js supports history encryption to protect sensitive data in the browser's history state. This is useful when your pages contain sensitive information that shouldn't be stored in plain text in the browser's history. This feature requires HTTPS since it relies on window.crypto.subtle which is only available in secure contexts.

You can enable history encryption globally via the INERTIA_ENCRYPT_HISTORY setting in your settings.py:

INERTIA_ENCRYPT_HISTORY = True

For more granular control, you can enable encryption on specific views:

from inertia import encrypt_history, inertia

@inertia('TestComponent')
def encrypt_history_test(request):
    encrypt_history(request)
    return {}

# If you have INERTIA_ENCRYPT_HISTORY = True but want to disable encryption for specific views:
@inertia('PublicComponent')
def public_view(request):
    encrypt_history(request, False)  # Explicitly disable encryption for this view
    return {}

When users log out, you might want to clear the history to ensure no sensitive data can be accessed. You can do this by extending the logout view:

from inertia import clear_history
from django.contrib.auth import views as auth_views

class LogoutView(auth_views.LogoutView):
    def dispatch(self, request, *args, **kwargs):
        response = super().dispatch(request, *args, **kwargs)
        clear_history(request)
        return response

SSR

The SSR is handled by a separate Node server. Your backend acts like a proxy, receiving requests from the frontend, calling the Node server (SSR server), and returning the rendered HTML to the frontend.

Look at the examples section to see working examples of SSR with Django and React or Svelte.

Backend

  • Ensure requests is installed, so inertia-django can do SSR requests.
    • requests is configured as a dependency if you install the [ssr] extra, e.g. inertia-django[ssr] in your requirements.
  • Enable SSR via the INERTIA_SSR_URL and INERTIA_SSR_ENABLED settings.

Frontend

Follow the current Inertiajs docs for setting up SSR.

Settings

Inertia Django has a few different settings options that can be set from within your project's settings.py file. Some of them have defaults.

The default config is shown below

INERTIA_VERSION = '1.0' # defaults to '1.0'
INERTIA_LAYOUT = 'layout.html' # required and has no default
INERTIA_JSON_ENCODER = CustomJsonEncoder # defaults to inertia.utils.InertiaJsonEncoder
INERTIA_SSR_URL = 'http://localhost:13714' # defaults to http://localhost:13714
INERTIA_SSR_ENABLED = False # defaults to False
INERTIA_ENCRYPT_HISTORY = False # defaults to False

Testing

Inertia Django ships with a custom TestCase to give you some nice helper methods and assertions. To use it, just make sure your TestCase inherits from InertiaTestCase. InertiaTestCase inherits from Django's django.test.TestCase so it includes transaction support and a client.

from inertia.test import InertiaTestCase

class ExampleTestCase(InertiaTestCase):
  def test_show_assertions(self):
    self.client.get('/events/')

    # check the component
    self.assertComponentUsed('Event/Index')

    # access the component name
    self.assertEqual(self.component(), 'Event/Index')

    # props (including shared props)
    self.assertHasExactProps({name: 'Brandon', sport: 'hockey'})
    self.assertIncludesProps({sport: 'hockey'})

    # access props
    self.assertEquals(self.props()['name'], 'Brandon')

    # template data
    self.assertHasExactTemplateData({name: 'Brian', sport: 'basketball'})
    self.assertIncludesTemplateData({sport: 'basketball'})

    # access template data
    self.assertEquals(self.template_data()['name'], 'Brian')

The inertia test helper also includes a special inertia client that pre-sets the inertia headers for you to simulate an inertia response. You can access and use it just like the normal client with commands like self.inertia.get('/events/'). When using the inertia client, inertia custom assertions are not enabled though, so only use it if you want to directly assert against the json response.

Examples

  • Django Svelte Template - A Django template and example project demonstrating Inertia with Svelte and SSR.
  • Django React: A Django + React project including CRUD operations, form handling, authentication, deployment using Docker, SSR, and more.

Thank you

A huge thank you to the community members who have worked on InertiaJS for Django before us. Parts of this repo were particularly inspired by Andres Vargas and Samuel Girardin. Additional thanks to Andres for the Pypi project.

Download files

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

Source Distribution

inertia_django-2.0.0.tar.gz (62.1 kB view details)

Uploaded Source

Built Distribution

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

inertia_django-2.0.0-py3-none-any.whl (73.3 kB view details)

Uploaded Python 3

File details

Details for the file inertia_django-2.0.0.tar.gz.

File metadata

  • Download URL: inertia_django-2.0.0.tar.gz
  • Upload date:
  • Size: 62.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for inertia_django-2.0.0.tar.gz
Algorithm Hash digest
SHA256 89ab1ea1a1f45c3441010ecb3d0bca0b0256404d1b1f84e1f1abd734e21006f5
MD5 ac306d91e58bbd1a75035f485c445d94
BLAKE2b-256 51aeabfa389f0944526a813e9c3ab644de091b22c7cf8b5284f4381e066509db

See more details on using hashes here.

Provenance

The following attestation bundles were made for inertia_django-2.0.0.tar.gz:

Publisher: release.yml on inertiajs/inertia-django

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file inertia_django-2.0.0-py3-none-any.whl.

File metadata

  • Download URL: inertia_django-2.0.0-py3-none-any.whl
  • Upload date:
  • Size: 73.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for inertia_django-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0d8d2200b0d273c39939015d9a2a42b69b7744e2e3b2a9a875bc9fa81df90e73
MD5 4ac4ff62823491161545c0b601a00832
BLAKE2b-256 ab6a1c28b9b189b1dc5767b34165b01f65ef46aaab6a38ccc5254a0e3717ccf2

See more details on using hashes here.

Provenance

The following attestation bundles were made for inertia_django-2.0.0-py3-none-any.whl:

Publisher: release.yml on inertiajs/inertia-django

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

2.0.0 This release

2 files

1.2.0

2 files

1.1.0

2 files

1.0.0

2 files

0.6.0

2 files

0.5.3

2 files

0.5.2

2 files

0.5.1

2 files

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.2

2 files

0.3.1

1 file

0.3.0

1 file

0.2.7

1 file

0.2.6

1 file

0.2.5

1 file

0.2.4

1 file

0.2.3

1 file

0.2.2

2 files

0.2.1

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page