Implemented reservations

This commit is contained in:
Maximilian Giller 2024-10-12 12:41:42 +02:00
parent 0d54983850
commit 82bc06817b
8 changed files with 222 additions and 87 deletions

16
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,16 @@
{
"configurations": [
{
"name": "Python Debugger: FastAPI",
"type": "debugpy",
"request": "launch",
"module": "uvicorn",
"args": [
"main:app",
"--reload"
],
"jinja": true,
"cwd": "${workspaceFolder}/src"
}
]
}

View file

@ -0,0 +1,8 @@
{
"reservations": [
{
"item_id": "77b3c3a0-303b-482f-a1e0-67c29030ec69",
"name": "Max"
}
]
}

View file

@ -1,8 +1,14 @@
import uuid import uuid
from fastapi import FastAPI, Request, Response from fastapi import Depends, FastAPI, Request, Response
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from wishlist import read_all_wishlists from wishlist import (
read_all_wishlists,
ReservationRequest,
add_reservation,
remove_reservation,
ItemReservation,
)
WISHLISTS_DIR = "../data" WISHLISTS_DIR = "../data"
@ -10,13 +16,37 @@ app = FastAPI()
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="templates")
@app.get("/{id}/view", response_class=HTMLResponse) wishlist_cache = {}
async def read_item(request: Request, id: uuid.UUID):
wishlists = read_all_wishlists(WISHLISTS_DIR)
if str(id) not in wishlists.keys():
@app.post("/reservation/update")
async def update_reservation(
reservation_request: ReservationRequest = Depends(
ReservationRequest.parse_reservation_request
),
):
global wishlist_cache
wishlist_dir: str = wishlist_cache[reservation_request.wishlist_id].directory
if reservation_request.reserved:
new_reservation = ItemReservation()
new_reservation.name = reservation_request.name
new_reservation.item_id = str(reservation_request.item_id)
add_reservation(wishlist_dir, new_reservation)
else:
remove_reservation(wishlist_dir, reservation_request.item_id)
@app.get("/{wishlist_id}/view", response_class=HTMLResponse)
async def show_wishlist(request: Request, wishlist_id: uuid.UUID):
global wishlist_cache
wishlists = read_all_wishlists(WISHLISTS_DIR)
wishlist_cache = wishlists
if wishlist_id not in wishlists.keys():
return Response(status_code=404) return Response(status_code=404)
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, name="wishlist_view.html", context={"wishlist": wishlists[str(id)]} request=request,
name="wishlist_view.html",
context={"wishlist": wishlists[wishlist_id]},
) )

View file

@ -0,0 +1,25 @@
<!-- item.html -->
<li class="{{ 'reserved-item' if item.reserved else '' }}">
<div>
<h2>{{ item.name }}{% if item.reservation %} (Reserved){% endif %}</h2>
<p>{{ item.description }}</p>
<p class="price">€{{ "%.2f"|format(item.price) }}</p>
<a href="{{ item.shop }}" target="_blank">Shop Link</a>
<!-- Reserve or Unreserve Form -->
<form method="POST" action="/reservation/update">
<input type="hidden" name="wishlist_id" value="{{ wishlist.config.id }}">
<input type="hidden" name="item_id" value="{{ item.id }}">
<input type="hidden" name="reserved" value="{{ 'false' if item.reserved else 'true' }}">
{% if item.reserved %}
<input type="text" name="name">
<button type="submit" class="reserve-button">Unreserve</button>
{% else %}
<input type="text" name="name" placeholder="Your name" required>
<button type="submit" class="reserve-button">Reserve</button>
{% endif %}
</form>
</div>
<img src="{{ item.image }}" alt="{{ item.name }}">
</li>
<hr>

View file

@ -132,52 +132,16 @@
<h1>{{ wishlist.config.title }}</h1> <h1>{{ wishlist.config.title }}</h1>
<ul> <ul>
{% for item_id, item in wishlist.items.items() if not item.reserved %} {% for item_id, item in wishlist.items.items() if not item.reservation %}
<li> {% include 'components/wishlist_item.html' %}
<div>
<h2>{{ item.name }}</h2>
<p>{{ item.description }}</p>
<p class="price">€{{ "%.2f"|format(item.price) }}</p>
<a href="{{ item.shop }}" target="_blank">Shop Link</a>
<!-- Reserve Form -->
<form method="POST" action="/reserve-item">
<input type="hidden" name="wishlist_id" value="{{ wishlist.config.id }}">
<input type="hidden" name="item_id" value="{{ item.id }}">
<input type="hidden" name="reserved" value="true">
<input type="text" name="reserver_name" placeholder="Your name" required>
<button type="submit" class="reserve-button">Reserve</button>
</form>
</div>
<img src="{{ item.image }}" alt="{{ item.name }}">
</li>
<hr>
{% endfor %} {% endfor %}
</ul> </ul>
<!-- Collapsible Reserved Items --> <!-- Collapsible Reserved Items -->
<div class="collapsible" onclick="toggleReservedItems()">Show Reserved Items</div> <div class="collapsible" onclick="toggleReservedItems()">Show Reserved Items</div>
<ul id="reserved-items"> <ul id="reserved-items">
{% for item_id, item in wishlist.items.items() if item.reserved %} {% for item_id, item in wishlist.items.items() if item.reservation %}
<li class="reserved-item"> {% include 'components/wishlist_item.html' %}
<div>
<h2>{{ item.name }} (Reserved)</h2>
<p>{{ item.description }}</p>
<p class="price">€{{ "%.2f"|format(item.price) }}</p>
<a href="{{ item.shop }}" target="_blank">Shop Link</a>
<!-- Unreserve Form -->
<form method="POST" action="/reserve-item">
<input type="hidden" name="wishlist_id" value="{{ wishlist.config.id }}">
<input type="hidden" name="item_id" value="{{ item.id }}">
<input type="hidden" name="reserved" value="false">
<input type="text" name="reserver_name" value="{{ item.reserver_name }}" readonly>
<button type="submit" class="reserve-button">Unreserve</button>
</form>
</div>
<img src="{{ item.image }}" alt="{{ item.name }}">
</li>
<hr>
{% endfor %} {% endfor %}
</ul> </ul>

View file

@ -1,2 +1,8 @@
from .models import Wishlist, WishlistConfig, WishlistItem from .models import (
from .storage import read_all_wishlists Wishlist,
WishlistConfig,
WishlistItem,
ReservationRequest,
ItemReservation,
)
from .storage import read_all_wishlists, add_reservation, remove_reservation

View file

@ -1,13 +1,34 @@
import uuid import uuid
from datetime import date from datetime import date
from fastapi import Form
from pydantic import BaseModel
class ReservationRequest(BaseModel):
wishlist_id: uuid.UUID
item_id: uuid.UUID
reserved: bool
name: str
async def parse_reservation_request(
wishlist_id: uuid.UUID = Form(...),
item_id: uuid.UUID = Form(...),
reserved: bool = Form(...),
name: str = Form(...),
) -> "ReservationRequest":
return ReservationRequest(
wishlist_id=wishlist_id, item_id=item_id, reserved=reserved, name=name
)
class ItemReservation: class ItemReservation:
def __init__(self) -> None: def __init__(self) -> None:
self.item_id: uuid.UUID = uuid.uuid4()
self.name: str = "" self.name: str = ""
class WishlistItem: class WishlistItem:
def __init__(self) -> None: def __init__(self) -> None:
self.id: uuid = uuid.uuid4() self.id: uuid.UUID = uuid.uuid4()
self.name: str = "" self.name: str = ""
self.description: str = "" self.description: str = ""
self.shop: str = "" self.shop: str = ""
@ -21,13 +42,13 @@ class WishlistItem:
class WishlistConfig: class WishlistConfig:
def __init__(self) -> None: def __init__(self) -> None:
self.id: uuid = uuid.uuid4() self.id: uuid.UUID = uuid.uuid4()
self.title: str = "" self.title: str = ""
self.deadlines: dict[str, date] = {} self.deadlines: dict[uuid.UUID, date] = {}
self.deadline_offset_days: int = 0 self.deadline_offset_days: int = 0
class Wishlist: class Wishlist:
def __init__(self) -> None: def __init__(self) -> None:
self.directory: str = "" self.directory: str = ""
self.items: dict[str, WishlistItem] = {} # Mapping Item ID to item self.items: dict[uuid.UUID, WishlistItem] = {} # Mapping Item ID to item
self.config: WishlistConfig = None self.config: WishlistConfig = None

View file

@ -1,12 +1,17 @@
from .models import Wishlist, WishlistConfig, WishlistItem from .models import Wishlist, WishlistConfig, WishlistItem, ItemReservation
from datetime import date from datetime import date
import uuid import uuid
import json import json
import csv import csv
import os import os
CONFIG_NAME = "config.json"
WISHLIST_NAME = "wishlist.csv"
RESERVATIONS_NAME = "reservations.json"
def read_wishlist_config(wishlist_dir: str) -> WishlistConfig: def read_wishlist_config(wishlist_dir: str) -> WishlistConfig:
path: str = f"{wishlist_dir}/config.json" path: str = f"{wishlist_dir}/{CONFIG_NAME}"
with open(path, "r", encoding="UTF-8") as fp: with open(path, "r", encoding="UTF-8") as fp:
json_config = json.load(fp) json_config = json.load(fp)
@ -20,22 +25,28 @@ def read_wishlist_config(wishlist_dir: str) -> WishlistConfig:
return config return config
def read_wishlist_items(wishlist_dir: str) -> dict[str, WishlistItem]:
items: dict[str, WishlistItem] = {}
path: str = f"{wishlist_dir}/wishlist.csv"
with open(path, "r", encoding = "UTF-8") as fp:
csvreader = csv.DictReader(fp)
for row in csvreader: def read_wishlist_items(wishlist_dir: str) -> dict[uuid.UUID, WishlistItem]:
item: WishlistItem = WishlistItem() reservations = read_all_reservations(wishlist_dir)
item.id = uuid.UUID(row.get("id"))
item.name = row.get("name")
item.description = row.get("description")
item.shop = row.get("shop")
item.image = row.get("image")
item.price = float(row.get("price"))
items[str(item.id)] = item items: dict[uuid.UUID, WishlistItem] = {}
path: str = f"{wishlist_dir}/{WISHLIST_NAME}"
with open(path, "r", encoding="UTF-8") as fp:
csvreader = csv.DictReader(fp)
for row in csvreader:
item: WishlistItem = WishlistItem()
item.id = uuid.UUID(row.get("id"))
item.name = row.get("name")
item.description = row.get("description")
item.shop = row.get("shop")
item.image = row.get("image")
item.price = float(row.get("price"))
if item.id in reservations.keys():
item.reservation = reservations[item.id]
items[item.id] = item
return items return items
@ -49,12 +60,66 @@ def read_wishlist(wishlist_dir: str) -> Wishlist:
return wishlist return wishlist
def read_all_wishlists(directory: str) -> dict[str, Wishlist]:
def read_all_wishlists(directory: str) -> dict[uuid.UUID, Wishlist]:
parent_dir, wishlist_dirs, _ = list(os.walk(directory))[0] parent_dir, wishlist_dirs, _ = list(os.walk(directory))[0]
wishlists: dict[str, Wishlist] = {} wishlists: dict[uuid.UUID, Wishlist] = {}
for dir in wishlist_dirs: for dir in wishlist_dirs:
wishlist: Wishlist = read_wishlist(f"{parent_dir}/{dir}") wishlist: Wishlist = read_wishlist(f"{parent_dir}/{dir}")
wishlists[str(wishlist.config.id)] = wishlist wishlists[wishlist.config.id] = wishlist
return wishlists return wishlists
def read_all_reservations(wishlist_dir: str) -> dict[uuid.UUID, ItemReservation]:
path: str = f"{wishlist_dir}/{RESERVATIONS_NAME}"
if not os.path.exists(path):
return {}
reservations_json = []
with open(path, "r", encoding="UTF-8") as fp:
reservations_json = json.load(fp)["reservations"]
reservations = {}
for r in reservations_json:
reservation = ItemReservation()
reservation.item_id = uuid.UUID(r["item_id"])
reservation.name = r["name"]
reservations[reservation.item_id] = reservation
return reservations
def write_all_reservations(
wishlist_dir: str, reservations: dict[uuid.UUID, ItemReservation]
):
path: str = f"{wishlist_dir}/{RESERVATIONS_NAME}"
reservations_json = []
for r in reservations.values():
reservations_json.append({"item_id": r.item_id, "name": r.name})
with open(path, "w", encoding="UTF-8") as fp:
json.dump({"reservations": reservations_json}, fp)
def add_reservation(wishlist_dir: str, reservation: ItemReservation):
all_reservations = read_all_reservations(wishlist_dir)
if reservation.item_id in all_reservations.keys():
raise Exception(f"Item [{reservation.item_id}] already reserved.")
all_reservations[reservation.item_id] = reservation
write_all_reservations(wishlist_dir, all_reservations)
def remove_reservation(wishlist_dir: str, item_id: uuid.UUID):
all_reservations = read_all_reservations(wishlist_dir)
if item_id not in all_reservations.keys():
return
del all_reservations[item_id]
write_all_reservations(wishlist_dir, all_reservations)