1

I don't know what's going on, I'm sure it's very simple but I can't find a solution.

I'm using FastAPI and have a form with a text entry field set to min_length=2. If I add less than two characters and submit, it loads a new page with the following JSON response:

{
"detail": [
{
"loc": [
"body",
"title"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}

How can I have a message pop up below the Form field saying something like: "Hey, please add more than two characters!" instead of a redirect with a JSON response?

I tried going through the "Handling Errors" section of the FastAPI documentation, but it basically only changed the JSON message output after the redirect, still have no idea how to show it embedded in my HTML frontend. https://fastapi.tiangolo.com/tutorial/handling-errors/#install-custom-exception-handlers

My app.py currently looks like this (the important bits are happening in "@app.exception_handler" and "@app.post("/add")":

from queue import Empty
from fastapi import FastAPI, Depends, Request, Form, status
from fastapi.staticfiles import StaticFiles

from starlette.responses import RedirectResponse, JSONResponse
from starlette.templating import Jinja2Templates

from sqlalchemy.orm import Session
from database import SessionLocal, engine
import models

models.Base.metadata.create_all(bind=engine)

templates = Jinja2Templates(directory="templates")

class UnicornException(Exception):
    def __init__(self, name: str):
        self.name = name

app = FastAPI()

app.mount("/js", StaticFiles(directory="js"), name="js")


def get_db():
    db = SessionLocal()
    try: 
        yield db
    finally:
        db.close()

## I tried catching the error here, but it basically just changed the message in the JSON response if setup in the correct way below
@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException):
    return JSONResponse(
        status_code=422,
        content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
    )

@app.get("/")
async def home(req: Request, db: Session = Depends(get_db)):
    todos = db.query(models.Todo).all()
    return templates.TemplateResponse("base.html", { "request": req, "todo_list": todos })

@app.post("/add")
def add(req: Request, title: str = Form(..., min_length=2, max_length=280), db: Session = Depends(get_db)):
    if title == "": ## I have no idea how to catch the empty string. 
        raise UnicornException(name=title) 
    else:
        new_todo = models.Todo(title=title)
        db.add(new_todo)
        db.commit()
        url = app.url_path_for("home")
        return RedirectResponse(url=url, status_code=status.HTTP_303_SEE_OTHER)

@app.get("/update/{todo_id}")
def add(req: Request, todo_id: int, db: Session = Depends(get_db)):
    todo = db.query(models.Todo).filter(models.Todo.id == todo_id).first()
    todo.complete = True
    db.commit()
    url = app.url_path_for("home")
    return RedirectResponse(url=url, status_code=status.HTTP_303_SEE_OTHER)

@app.get("/indoubt/{todo_id}")
def add(req: Request, todo_id: int, db: Session = Depends(get_db)):
    todo = db.query(models.Todo).filter(models.Todo.id == todo_id).first()
    todo.complete = False
    todo.denied = False
    todo.indoubt = True
    db.commit()
    url = app.url_path_for("home")
    return RedirectResponse(url=url, status_code=status.HTTP_303_SEE_OTHER)

@app.get("/denied/{todo_id}")
def add(req: Request, todo_id: int, db: Session = Depends(get_db)):
    todo = db.query(models.Todo).filter(models.Todo.id == todo_id).first()
    todo.complete = False
    todo.indoubt = False
    todo.denied = True
    db.commit()
    url = app.url_path_for("home")
    return RedirectResponse(url=url, status_code=status.HTTP_303_SEE_OTHER)


@app.get("/delete/{todo_id}")
def add(req: Request, todo_id: int, db: Session = Depends(get_db)):
    todo = db.query(models.Todo).filter(models.Todo.id == todo_id).first()
    db.delete(todo)
    db.commit()
    url = app.url_path_for("home")
    return RedirectResponse(url=url, status_code=status.HTTP_303_SEE_OTHER)

HTML:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Todo App - Fastapi</title>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
        <script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.js"></script>
        <script src="/js/script.js"></script>

    </head>
    <body>
        <div style="margin-top: 50px;" class="ui container">
            <h1 class="ui center aligned header">Fastask App</h1>

            <form class="ui form" action="/add" method="post">
                <div class="field">
                    <label>Task Title</label>
                    <input type="text" maxlength="280" name="title" placeholder="Enter new task..." onkeyup="countChars(this)";>
                    <br>
                </div>
                <p id="charNum">280 characters remaining</p>
                <button class="ui blue button" type="submit">Add</button>
            </form>

            <hr>

            {% for todo in todo_list %} 
                {% if todo.complete == True %}
                <div class="ui green segment">
                    <p class="ui green big header">{{ todo.id }} | {{ todo.title }}</p>
                <span class="ui green label">Yes</span>
                {% elif todo.denied == True %}
                <div class="ui red segment">
                    <p class="ui red big header">{{ todo.id }} | {{ todo.title }}</p>
                <a class="ui label" href="/delete/{{ todo.id }}">️</a>
                <span class="ui red label">No</span>
                {% elif todo.indoubt == True %}
                <div class="ui yellow segment">
                    <p class="ui yellow big header">{{ todo.id }} | {{ todo.title }}</p>
                <span class="ui yellow label">In doubt</span>
                {% else %}
                <div class="ui blue segment">
                    <p class="ui blue big header">{{ todo.id }} | {{ todo.title }}</p>
                <span class="ui blue label">Not answered</span>
                {% endif %}
                 
                <a class="ui green button" href="/update/{{ todo.id }}">Yes</a>
                <a class="ui red button" href="/denied/{{ todo.id }}">No</a>
                <a class="ui yellow button" href="/indoubt/{{ todo.id }}">In doubt</a>
            </div>
            {% endfor %}
        </div>


    </body>

</html>

As always any help or hint is appreciated.

UGN
  • 41
  • 7
  • To return a custom error see related posts [here](https://stackoverflow.com/a/72833284/17865804), [here](https://stackoverflow.com/a/71682274/17865804) and [here](https://stackoverflow.com/a/70954531/17865804), as well as [this](https://stackoverflow.com/a/71228281/17865804), [this](https://stackoverflow.com/a/72003724/17865804) and [this](https://stackoverflow.com/a/73151350/17865804). Note that using [Ellipsis (`...`)](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#required-with-ellipsis) as the `default` parameter in `Form`, you declare that a value is required. – Chris Nov 02 '22 at 09:22

0 Answers0