Ludic Logo
Edit

Delete Row

This example shows how to implement a delete button that removes a table row upon completion. For this example, we use have a sample database of people which we display in a table. Each row in the table can be deleted by clicking a button and a confirmation prompt.

Demo

#

Implementation

#

Since we want to display people as rows in a table, we can create a dedicated class-based endpoint which can render itself like a component. Before we do that, we also need to declare what kind of data this endpoint handles:

 1   from ludic.attrs import Attrs
 2   
 3   class PersonAttrs(Attrs):
 4       id: str
 5       name: str
 6       email: str
 7       active: bool
 8   
 9   class PeopleAttrs(Attrs):
10       people: list[PersonAttrs]

Now we can use these attributes in our class-based endpoint:

 1   from typing import override
 2   
 3   from ludic.catalog.tables import Table, TableRow
 4   from ludic.catalog.buttons import ButtonDanger
 5   from ludic.components import Component
 6   from ludic.web import Endpoint, LudicApp
 7   
 8   from your_app.attrs import PersonAttrs
 9   from your_app.database import db
10   
11   app = LudicApp()
12   
13   @app.endpoint("/people/{id}")
14   class PersonRow(Endpoint[PersonAttrs]):
15       @classmethod
16       def delete(cls, id: str) -> None:
17           try:
18               db.people.pop(id)
19           except KeyError:
20               raise NotFoundError("Person not found")
21   
22       @override
23       def render(self) -> TableRow:
24           return TableRow(
25               self.attrs["name"],
26               self.attrs["email"],
27               "Active" if self.attrs["active"] else "Inactive",
28               ButtonDanger(
29                   "Delete",
30                   hx_delete=self.url_for(PersonRow),
31                   classes=["small"]
32               ),
33           )

We created a class-based endpoint with the following methods:

  • delete – handles the DELETE request which removes a person from the database. We don't need to return anything in this case.
  • render – handles rendering of the table. Apart from the textual columns, we also render an action button to delete a row which is hooked to the PersonRow.delete() method.

The last remaining part is the table with people itself. We can create another class-based endpoint for this task:

 1   from typing import override
 2   
 3   from ludic.catalog.tables import Table, TableRow
 4   from ludic.catalog.buttons import ButtonDanger
 5   from ludic.components import Component
 6   from ludic.web import Endpoint, LudicApp
 7   
 8   from your_app.attrs import PeopleAttrs
 9   from your_app.database import db
10   
11   # ... the PersonRow class is omitted here
12   
13   @app.endpoint("/people/")
14   class PeopleTable(Endpoint[PeopleAttrs]):
15       styles = {
16           "tr.htmx-swapping td": {
17               "opacity": "0",
18               "transition": "opacity 1s ease-out",
19           }
20       }
21   
22       @classmethod
23       def get(cls) -> Self:
24           return cls(people=[person.dict() for person in db.people.values()])
25   
26       @override
27       def render(self) -> Table[TableHead, PersonRow]:
28           return Table(
29               TableHead("Name", "Email", "Active", ""),
30               *(PersonRow(**person) for person in self.attrs["people"]),
31               body_attrs=HtmxAttrs(
32                   hx_confirm="Are you sure?",
33                   hx_target="closest tr",
34                   hx_swap="outerHTML swap:1s",
35               ),
36               classes=["text-align-center"],
37           )

We created a class-based endpoint with the following methods:

  • get – handles the GET request which returns an instance of the PeopleTable filled with a list of people fetched from database.
  • render – handles rendering of the table of people. We use a special body_attrs attribute to configure HTMX operations on the tbody element.
Made with Ludic and HTMX and 🐍 • DiscordGitHub