Giter Club home page Giter Club logo

inertia-django's Introduction

image

Inertia.js Django Adapter

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 for you to each Inertia response. Because the default names Django users for the CSRF headers don't match Axios (the Javascript request library Inertia uses), we'll need to either modify Axios's defaults OR Django's settings.

You only need to choose one of the following options, just pick whichever makes the most sense to you!

In your entry.js file

axios.defaults.xsrfHeaderName = "X-CSRFToken"
axios.defaults.xsrfCookieName = "csrftoken"

OR

In your Django settings.py file

CSRF_HEADER_NAME = 'HTTP_X_XSRF_TOKEN'
CSRF_COOKIE_NAME = 'XSRF-TOKEN'

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(),
  }

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

Lazy 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 Lazy props. Lazy props aren't evaluated unless they're specifically requested by name in a partial reload.

from inertia import lazy, inertia

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

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.

SSR

Backend

Enable SSR via the INERTIA_SSR_URL and INERTIA_SSR_ENABLED settings

Frontend

Coming Soon!

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

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

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.

Maintained and sponsored by the team at bellaWatt

bellaWatt Logo

inertia-django's People

Contributors

brandonshar avatar mercuryseries avatar nootr avatar pauldiepold avatar pmdevita avatar svengt avatar swarakaka avatar xzya avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

inertia-django's Issues

How To Use Form Using inertia-django?

Not an issue but just asking help. I got confused about implementing Django's form to use in my Vue.js front-end using Inertia.js, could someone give me an example about it? Here i got a simple model in models.py:

from django.db import models


class Post(models.Model):
    title = models.CharField(max_length=150)

    def __str__(self):
        return f"{self.id}. {self.title}"

and PostForm class in forms.py:

from django import forms
from .models import Post


class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = [
            'title',
        ]

When I'm using the form as props/context I got error: Object of type PostForm is not JSON serializable. I've tried using useForm() that Inertia.js have, I got an empty querydict but the response status is 200.

When are frontend docs coming?

Thanks for the great build - really appreciate it!

When is the official guide for setting up Django with Inertia coming? I've managed to get it going with React and Vue, but I'm struggling with Svelte. I'd appreciate some documentation.

Flash messages

I've spent the day thinking about how to best integrate Django's flash messages into Inertia's framework. I'm opening this issue to invite discussion before I submit a PR. I'm kind of a nub here, so please don't hesitate to tell me where I am wrong.

The goal of a PR to this adapter, I think, would be to get the Django messages into the page props and to retain the expected Django behavior of clearing the messages afterward. On it's face, it seems like the share() function invoked during middleware response phase would be a good candidate. But there are a couple issues I've identified with that approach:

  1. If the view added a message and it's a first access of a page, i.e., there's no X-Inertia header, I think the message would be lost. The props are already rendered in the data attribute of the div where the React app will be mounted. By the time the middleware consumes the messages during the response phase, the template has already been rendered.

  2. If it's an Inertia request where there is an X-Inertia header, we'd have to ensure that the middleware appeared after the middleware included in this package to ensure that the messages are consumed before the page props are prepared. If we get the order wrong, the messages would be lost.

One solution would be to augment the render function to automatically include a messages prop in the page props, subject perhaps to a configuration flag. The render function would consume the messages in the same way that an ordinary Django template would consume messages by iterating over them. I welcome thoughts on this proposed solution or my understanding of the problem.

Is there a reason why render was made a function rather than a class?

It makes it so much harder to override anything, I want to extend the base functionality to add things like server driven modals, preserving browser url on demand and other things and I find myself needing to do a lot of workarounds due to not being able to inherit from render and extend its functionality so I was wondering if there is a rationale behind this as I'm planning to fork this project just to make the render function into a class.

Related models in the response not converting to json

How would we include related models through select_related() or prefetch_related() in the json output? The model_to_dict() method used in the encoder doesn't seem to include them. I'm realizing I almost need a DRF serializer lite setup.

Is this something the default encoder could handle (instead of needing to make a custom one) or would it be easier for me just to use something like values() to select properties?

Any future support for FastAPI?

Hi - I know this is the Django project, but is there any plans to support FastAPI? With the AI boom, a lot of companies are using FastAPI.

Add Support for Django 5.X

I would to use inertia with django 5.X with Poetry i cannot installed package becouse inertia-django only support django>=4

Issue using orm "values" on share middleware

Hi, so I had issues when trying to limit the data returned in the share method called inside a middleware. I was doing something like the following:

    def middleware(request):
        if request.user:
            share(
                request,
                tenants=lambda: request.user.profiles.select_related(
                    "organization"
                ).values("organization_id", "organization__name"),
            )

        return get_response(request)

The issue happens inside the InertiaJsonEncoder on here. As it was trying to pass a dictionary to the method model_to_dict.

The solution I found for this was to add a custom Json encoder as explained in the documentation and do the following:

    def default(self, value):
        if isinstance(value, QuerySet):
            return [self.default(model) for model in value]

        if isinstance(value, dict):
            return value

        return super().default(value)

SSR not working

I just tried turning on ssr for vue and disabled js it is not working

Dialogs feature

Hello @BrandonShar

as you can see, Inertiajs version 1.0.0 will be available soon. Most likely, as they have identified for this version, the dialogs feature will be made available. Are there any plans to add that feature to Django as well?

Dialogs

[Feature Request] Django requests not compatible with interia request payloads?

I've run into another snag. When attempting to post interia js forms the django request only includes the payload in a raw bytestring via request.body.

const form = useForm({
    key: 'value'
});
form.post(usePage().url);

request.POST is empty as that's only populated from traditional form submissions. I'm unable to access key param in the view without processing the request.body bytestring first, which seems totally out of scope for individual projects to handle. DRF has solutions for this, but DRF shouldn't necessarily be required for this adapter to work I wouldn't think? Is there a way for the adapter to handle this?

Can't get it running with Django-Vite

I tried to set up Django with Inertia and Vue 3 by forking and cloning the Django Vite example repo and then adding the Inertia config. However, I have run into a problem - Vite fails with the following error:

failed to load config from D:\Projects\Python\django-vite-example\vite.config.js
error when starting dev server:
TypeError: vite.createFilter is not a function
    at vuePlugin (D:\Projects\Python\django-vite-example\node_modules\@vitejs\plugin-vue\dist\index.cjs:2549:23)
    at Object.<anonymous> (D:\Projects\Python\django-vite-example\vite.config.js:24:43)
    at Module._compile (node:internal/modules/cjs/loader:1105:14)
    at Object.require.extensions.<computed> [as .js] (D:\Projects\Python\django-vite-example\node_modules\vite\dist\node\chunks\dep-bc228bbb.js:70789:20)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:1005:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at loadConfigFromBundledFile (D:\Projects\Python\django-vite-example\node_modules\vite\dist\node\chunks\dep-bc228bbb.js:70797:17)
    at loadConfigFromFile (D:\Projects\Python\django-vite-example\node_modules\vite\dist\node\chunks\dep-bc228bbb.js:70718:32)
error Command failed with exit code 1.

I'm not sure what's going on here as googling the error message didn't lead to any relevant information.

You can check my setup here.

Setup method not working while testing

So, I would like to use the very handy setup method in order to make some test cases without repeating myself.

class RegisterViewTest(InertiaTestCase):
    def setUp(self):
        # Setup run before every test method.
        self.test_user = User.objects.create_user(username='testuser', password='testpassword')

But unfortunately i get this error:

Traceback (most recent call last):
  File "/home/pvpmartins/nutrify/myenv/lib/python3.11/site-packages/inertia/test.py", line 25, in tearDown
    self.mock_inertia.stop()
    ^^^^^^^^^^^^^^^^^
AttributeError: 'RegisterViewTest' object has no attribute 'mock_inertia'

======================================================================
ERROR: test_register_and_login_pro_user (nutrifyapp.tests.RegisterViewTest.test_register_and_login_pro_user)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/pvpmartins/nutrify/myenv/lib/python3.11/site-packages/inertia/test.py", line 25, in tearDown
    self.mock_inertia.stop()
    ^^^^^^^^^^^^^^^^^
AttributeError: 'RegisterViewTest' object has no attribute 'mock_inertia'

Can't get shared_data to work

The exact example in the docs just doesn't work in my case, or was anything omitted in the docs? The idea of having to pass the same data to all views, which is now the only alternative, doesn't seem quite nice.

CSRF token not being sent automatically

I'm at my wits' end trying to get Django to send the CSRF token to the backend (project here).

The CSRF token is being sent in the cookie, but I cannot set an X-XSRF-Token header as a global header on Axios.

Would it be possible to include this by default in inertia-django, like the Laravel implementation already does?

Create a partial template

How can I create only a small sub-set of a Django Template View to be handled by inertia?
Or is that not possible?

Essentially lets say that I have a Form that I want to be fully handled by Django, but in that form there is a multi dropdown element that I want to be handled by inertia (as the dropdown would send and requests to the server, and would use react to populate the dropdown).
How can I accomplish something like that ? Or is this not the scope of inertia?

Call get_token() automatically

The middleware should call django.middleware.csrf.get_token(request). This will ensure a CSRF token is generated for the front end to find. Without some call to this method, the cookie will not be set.

CsrfViewMiddleware sends this cookie with the response whenever django.middleware.csrf.get_token() is called.

https://docs.djangoproject.com/en/4.1/ref/csrf/

Modify axios xsrfHeaderName for the usage with Django

First, thanks for the great framework.

Since the form submission and redirect with django provided me a little bit of headache, I wanted to share the following experience which could help other developers.

Django uses a different CSRF-Header name than set by axios as default. Therefore, it needs to be corrected during the creation of the inertia app:

axios.defaults.xsrfHeaderName = "X-CSRFToken"
axios.defaults.xsrfCookieName = "csrftoken"

This fixes axios but inertiajs still ended in an csrf verification error for my case. I found out that the axios version used by inertiajs deviated from the axios verison in the application. In consequence, the corrected token names are not taken (inertiajs/inertia#1046). Installing the same axios version in the application finally fixed the issue.

This is an issue special to inertia-django. Nevertheless, the correction would need to happen in inertiajs.

tl;dr: In case of CSRF Verifcation failed, add the two lines to the configuration of axios and make sure that axios has the same version as axios used by inertiajs.

Validation & Error Bags

This doesn't seem to be implemented. Is anybody working on it? If not, I can submit a PR myself. I'm thinking something like this will work just fine:

from django import forms
from django.http import HttpRequest, HttpResponse
from django.views import View
from django.shortcuts import redirect

import inertia


class ExampleForm(forms.Form):
    name = forms.CharField(max_length=100)
    email = forms.EmailField()


class ExampleView(View):
    def post(self, request: HttpRequest) -> HttpResponse:
        form = ExampleForm(request.POST)

        # Option A, more explicit
        if not form.is_valid():
            raise inertia.ValidationError(form.errors)

        # Option B, automatically raise the error
        inertia.validate(form)

        # use form.data here...

        return redirect("some_route_name")

For this to work properly in all cases we need to know the route where the user is coming from. Django doesn't store the previous route in the session like Laravel does, so there's no equivalent of Laravel's return back()->withErrors(). You can emulate the return back() part using the HTTP Referer header as suggested here or by sending a next=URL query param in the request as suggested here.

However, the Referer header can't be trusted in all cases and the next query param requires changing the frontend code. So I'm thinking of 2 possible solutions, either implement Laravel's behavior of storing the previous URL in the session to provide a return back() function or just provide the route at validation time:

inertia.validate(form, error_url="some_route_name_or_url")

Second option is more verbose but easier to implement, and it can coexist with the first option since we could make error_url=None by default and grab the route from the session. I guess all this could be a separate middleware that basically catches the inertia.ValidationError, allowing the users to opt-in by adding the middleware to settings.py. Otherwise it could be integrated into the current inertia.middleware.InertiaMiddleware.

Edit:
Issue #21 is related to this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.