Use ModelForm for CRUD

Problem

You want create, update, and delete pages for a model.

Solution

Use one ModelForm for create and update, plus a tiny confirmation form for delete. Register one action per operation and place each action next to the page that triggers it.

Walkthrough

Define the form.

notes/forms.py
from django import forms
from next.forms import Form, ModelForm
from notes.models import Note

class NoteForm(ModelForm):
    class Meta:
        model = Note
        fields = ("title", "body")

class DeleteNoteForm(Form):
    confirm = forms.BooleanField()

Create Page

notes/pages/notes/new/page.py
from django.http import HttpResponseRedirect
from django.urls import reverse
from next.forms import action
from notes.forms import NoteForm

@action("create_note", form_class=NoteForm)
def create_note(form: NoteForm) -> HttpResponseRedirect:
    form.save()
    return HttpResponseRedirect(reverse("next:page_"))

URL names follow the page_{path} convention where path segments are joined with underscores and captured-parameter brackets are dropped. See File Router for the full naming rules.

Update Page

notes/pages/notes/[id]/edit/page.py
from types import SimpleNamespace

from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import reverse
from next.forms import action
from next.pages import context
from next.urls import DUrl
from notes.forms import NoteForm
from notes.models import Note

@context("update_note")
def edit_form(note_id: DUrl["id", int]) -> SimpleNamespace:
    note = get_object_or_404(Note, pk=note_id)
    return SimpleNamespace(form=NoteForm(instance=note))

@action("update_note", form_class=NoteForm)
def update_note(form: NoteForm, note_id: DUrl["id", int]) -> HttpResponseRedirect:
    form.instance = get_object_or_404(Note, pk=note_id)
    form.save()
    return HttpResponseRedirect(reverse("next:page_notes_id", kwargs={"id": note_id}))

Delete Action

notes/pages/notes/[id]/page.py
from django.http import HttpResponseRedirect
from django.urls import reverse
from next.forms import action
from next.urls import DUrl
from notes.forms import DeleteNoteForm
from notes.models import Note

@action("delete_note", form_class=DeleteNoteForm)
def delete_note(form: DeleteNoteForm, note_id: DUrl["id", int]) -> HttpResponseRedirect:
    Note.objects.filter(pk=note_id).delete()
    return HttpResponseRedirect(reverse("next:page_"))

Templates

notes/pages/notes/new/template.djx
{% form @action="create_note" %}
  {{ form.title }}
  {{ form.body }}
  <button type="submit">Create</button>
{% endform %}
notes/pages/notes/[id]/edit/template.djx
{% form @action="update_note" %}
  {{ form.title }}
  {{ form.body }}
  <button type="submit">Save</button>
{% endform %}
notes/pages/notes/[id]/template.djx
{% form @action="delete_note" %}
  <input type="hidden" name="confirm" value="on">
  <button type="submit" class="danger">Delete</button>
{% endform %}

Verification

Walk through the flow once. Create a note, edit it, delete it, and confirm the index reflects each step.

Tests assert the same flow.

tests/test_crud.py
from next.testing.client import NextClient
from notes.models import Note

def test_crud_flow(db) -> None:
    client = NextClient()
    client.post_action("create_note", {"title": "T", "body": ""})
    note = Note.objects.get(title="T")
    client.post_action("update_note", {"title": "T2", "body": "", "_url_param_id": note.id})
    assert Note.objects.get(pk=note.id).title == "T2"
    client.post_action("delete_note", {"confirm": "on", "_url_param_id": note.id})
    assert not Note.objects.filter(pk=note.id).exists()

The _url_param_id key in the POST data mirrors the hidden field that the {% form %} tag emits for a captured URL parameter. The dispatcher casts each recovered value to int when the cast succeeds and falls back to the original string when it does not, so a handler can declare DUrl["id", int] and receive the integer on both the initial render and the re-render. Annotate the parameter as DUrl["id", str] to opt out of the int-first coercion.

See Also

See also

ModelForms for the ModelForm topic guide. Validation and Re-render for the re-render flow.