1

I followed this super easy tutorial by @Nditah on how to make a to-do list app using Python and FastAPI.

https://dev.to/nditah/develop-a-simple-python-fastapi-todo-app-in-1-minute-8dg

Then I modified it to become an interactive list. users can now pick an answer via buttons and it changes the status of the list entry (the segment color changes and it adds a database entry).

Now I was wondering, how can I update the db and the list once a user clicks a button in the frontend, without refreshing the complete website?

The problem I have right now is that the page fully refreshes on every button click and takes the user back to the top of the list.

I googled and looked at gazillion answers but they don't seem to be what I'm looking for. Anyone has an idea how to easily update my db and change the color of the segment, without fully reloading the whole page?

main.py:

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()

@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_201_CREATED)

@app.get("/approved/{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
    todo.denied = False
    todo.indoubt = False
    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>More time for fun stuff.</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">More time for fun stuff</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>
                <a class="ui green small button" href="/approved/{{ todo.id }}" action="">Yes</a>
                <a class="ui red small button" href="/denied/{{ todo.id }}">No</a>
                <a class="ui yellow small button" href="/indoubt/{{ todo.id }}">In doubt</a>
               <!--  <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>
                <a class="ui green small button" href="/approved/{{ todo.id }}">Yes</a>
                <a class="ui red small button" href="/denied/{{ todo.id }}">No</a>
                <a class="ui yellow small button" href="/indoubt/{{ todo.id }}">In doubt</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>
                <a class="ui green small button" href="/approved/{{ todo.id }}">Yes</a>
                <a class="ui red small button" href="/denied/{{ todo.id }}">No</a>
                <a class="ui yellow small button" href="/indoubt/{{ todo.id }}">In doubt</a>
               <!-- <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>
                    <a class="ui green small button" href="/approved/{{ todo.id }}">Yes</a>
                    <a class="ui red small button" href="/denied/{{ todo.id }}">No</a>
                    <a class="ui yellow small button" href="/indoubt/{{ todo.id }}">In doubt</a>
               <!-- <span class="ui blue label">Not answered</span> -->
                {% endif %}
                 
            </div>
           
            {% endfor %}
        </div>


    </body>

</html>
UGN
  • 41
  • 7
  • Related answers can also be found [here](https://stackoverflow.com/a/73358937/17865804) and [here](https://stackoverflow.com/a/71665594/17865804). – Chris Nov 07 '22 at 07:36

0 Answers0