DocsUnidade 2: dados

Crie um aplicativo de notas com Nexy, parte 8: edite e exclua uma nota

A Parte 8 coloca dois formulários na página de uma nota: um salva o novo texto, o outro exclui a nota e ambos são ações como aquela que a cria.

Antes de começar: o aplicativo da parte 7.

Alterar e remover uma nota no banco de dados

src/notes.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/notes.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

Mais duas ações

update_note verifica o texto com as mesmas regras de create_note e envia uma falha de volta para a página da nota. delete_note volta à lista:

src/actions/notes.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()

As regras de create_note foram movidas para problem, portanto as duas ações que levam um texto não podem se separar. Uma nota que não existe mais responde 404, como sua página.

Os formulários da página de notas

O id viaja em um campo oculto chamado note_id, o nome do parâmetro:

src/rotas/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>

A visualização agora precisa da nota inteira, não apenas do 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()]

Abra http://localhost:3000/notes/1, altere Comprar leite para Comprar leite de aveia e pressione Salvar: a página volta com o novo texto. Pressione Excluir: você chega à lista, sem a nota.

O que você aprendeu

  • Uma página pode conter vários formulários, cada um postando em sua própria ação.
  • Um campo oculto carrega o id; seu nome é o nome do parâmetro da Ação.
  • As regras compartilhadas por duas Actions residem em uma função, chamada por ambas.

Próximas etapas

Editar no GitHub

Enter abre o resultado, Esc fecha.