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.
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 Truefrom 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 TrueDos 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:
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:
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:
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>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
- Parte 9: paginar las notas
- Formularios sin JavaScript: cómo un formulario llega a una Acción