DocsUnidad 2: datos

Cree una aplicación de notas con Nexy, parte 8: edite y elimine una nota

La parte 8 coloca dos formularios en la página de una nota: uno guarda texto nuevo, el otro elimina la nota y ambos son acciones como la que lo crea.

Antes de comenzar: la aplicación de la parte 7.

Cambiar y eliminar una nota en la base de datos.

src/notas.py
from sqlmodel import Session, select from src.configs.db import enginefrom src.models.note_model import Note  def all_notes() -> list[Note]:    with Session(engine) as session:        return list(session.exec(select(Note).order_by(Note.id)))  def find(note_id: int) -> Note | None:    with Session(engine) as session:        return session.get(Note, note_id)  def add_note(text: str) -> Note:    note = Note(text=text)    with Session(engine) as session:        session.add(note)        session.commit()        session.refresh(note)    return note  def change_note(note_id: int, text: str) -> bool:    with Session(engine) as session:        note = session.get(Note, note_id)        if note is None:            return False        note.text = text        session.commit()    return True  def remove_note(note_id: int) -> bool:    with Session(engine) as session:        note = session.get(Note, note_id)        if note is None:            return False        session.delete(note)        session.commit()    return True
src/notas.py
from sqlmodel import Session, select from src.apps.notes.note_model import Notefrom src.configs.db import engine  def all_notes() -> list[Note]:    with Session(engine) as session:        return list(session.exec(select(Note).order_by(Note.id)))  def find(note_id: int) -> Note | None:    with Session(engine) as session:        return session.get(Note, note_id)  def add_note(text: str) -> Note:    note = Note(text=text)    with Session(engine) as session:        session.add(note)        session.commit()        session.refresh(note)    return note  def change_note(note_id: int, text: str) -> bool:    with Session(engine) as session:        note = session.get(Note, note_id)        if note is None:            return False        note.text = text        session.commit()    return True  def remove_note(note_id: int) -> bool:    with Session(engine) as session:        note = session.get(Note, note_id)        if note is None:            return False        session.delete(note)        session.commit()    return True

Dos acciones más

update_note verifica el texto con las mismas reglas que create_note y envía un error de regreso a la página de la nota. delete_note vuelve a la lista:

src/acciones/notas.py
from urllib.parse import urlencode from nexy import notFoundfrom nexy.decorators import Actionfrom nexy.http import RedirectResponsefrom src.notes import add_note, change_note, remove_note MAX_LENGTH = 200  def back(path: str = "/notes", error: str = "") -> RedirectResponse:    query = f"?{urlencode({'error': error})}" if error else ""    return RedirectResponse(f"{path}{query}", status_code=303)  def problem(text: str) -> str:    if not text:        return "A note cannot be blank."    if len(text) > MAX_LENGTH:        return f"A note is at most {MAX_LENGTH} characters."    return ""  @Actionasync def create_note(text: str) -> RedirectResponse:    text = text.strip()    if error := problem(text):        return back(error=error)    add_note(text)    return back()  @Actionasync def update_note(note_id: int, text: str) -> RedirectResponse:    text = text.strip()    if error := problem(text):        return back(f"/notes/{note_id}", error)    if not change_note(note_id, text):        notFound()    return back(f"/notes/{note_id}")  @Actionasync def delete_note(note_id: int) -> RedirectResponse:    if not remove_note(note_id):        notFound()    return back()

Las reglas de create_note se trasladaron a problem, por lo que las dos acciones que requieren un texto no pueden separarse. Una nota que ya no existe responde 404, como su página.

Las formas de la página de notas.

El id viaja en un campo oculto llamado note_id, el nombre del parámetro:

src/rutas/notas/[id].nexy
---from nexy import notFound, useSearchParamsfrom src.notes import find id: prop[int]note = find(id)if note is None:    notFound()error = useSearchParams().get("error", "")---<section class="app-page">  <h1>{{ note.text | e }}</h1>   {% if error %}  <p class="note-error" role="alert">{{ error | e }}</p>  {% endif %}   <form method="post" action="{{ __actions_path['update_note'] }}">    <input type="hidden" name="note_id" value="{{ note.id }}">    <input name="text" value="{{ note.text | e }}" maxlength="200" required>    <button type="submit">Save</button>  </form>   <form method="post" action="{{ __actions_path['delete_note'] }}">    <input type="hidden" name="note_id" value="{{ note.id }}">    <button type="submit">Delete</button>  </form>   <a href="/notes">All notes</a></section>

La vista ahora necesita la nota completa, no sólo el texto:

src/apps/notes/note_view.nexy
---from nexy import useSearchParams note: prop[object]error = useSearchParams().get("error", "")---<section class="app-page">  <h1>{{ note.text | e }}</h1>   {% if error %}  <p class="note-error" role="alert">{{ error | e }}</p>  {% endif %}   <form method="post" action="{{ __actions_path['update_note'] }}">    <input type="hidden" name="note_id" value="{{ note.id }}">    <input name="text" value="{{ note.text | e }}" maxlength="200" required>    <button type="submit">Save</button>  </form>   <form method="post" action="{{ __actions_path['delete_note'] }}">    <input type="hidden" name="note_id" value="{{ note.id }}">    <button type="submit">Delete</button>  </form>   <a href="/notes">All notes</a></section>
src/apps/notes/notes_controller.py
from nexy import notFoundfrom nexy.decorators import Controllerfrom nexy.hooks import useViewsfrom src.notes import all_notes, find LAYOUT = "src/apps/layout.nexy"  def page(view: str, **context: object):    return useViews(LAYOUT, context={"children": useViews(view, context=context)})  @Controller("/notes")class NotesController:    async def get(self):        return page("src/apps/notes/notes_view.nexy")  @Controller("/notes/{id}")class NoteController:    async def get(self, id: int):        note = find(id)        if note is None:            notFound()        return page("src/apps/notes/note_view.nexy", note=note)  @Controller("/api/notes")class NotesApiController:    async def get(self) -> list[dict]:        return [note.model_dump() for note in all_notes()]

Abre http://localhost:3000/notes/1, cambia Comprar leche por Comprar leche de avena y presiona Guardar: la página regresa con el nuevo texto. Presione Eliminar: llegará a la lista, sin la nota.

lo que aprendiste

  • Una página puede contener varios formularios, cada uno de los cuales publica su propia Acción.
  • Un campo oculto lleva la identificación; su nombre es el nombre del parámetro de la Acción.
  • Las reglas compartidas por dos Acciones residen en una función, llamada por ambas.

Próximos pasos

Editar en GitHub

Enter abre el resultado, Esc lo cierra.