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
from fastapi import FastAPI, Request, Response
from fastapi import Depends, FastAPI, Request, Response
from fastapi.responses import HTMLResponse
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"
@ -10,13 +16,37 @@ app = FastAPI()
templates = Jinja2Templates(directory="templates")
@app.get("/{id}/view", response_class=HTMLResponse)
async def read_item(request: Request, id: uuid.UUID):
wishlists = read_all_wishlists(WISHLISTS_DIR)
wishlist_cache = {}
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 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>
<ul>
{% for item_id, item in wishlist.items.items() if not item.reserved %}
<li>
<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>
{% for item_id, item in wishlist.items.items() if not item.reservation %}
{% include 'components/wishlist_item.html' %}
{% endfor %}
</ul>
<!-- Collapsible Reserved Items -->
<div class="collapsible" onclick="toggleReservedItems()">Show Reserved Items</div>
<ul id="reserved-items">
{% for item_id, item in wishlist.items.items() if item.reserved %}
<li class="reserved-item">
<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>
{% for item_id, item in wishlist.items.items() if item.reservation %}
{% include 'components/wishlist_item.html' %}
{% endfor %}
</ul>

View file

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

View file

@ -1,13 +1,34 @@
import uuid
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:
def __init__(self) -> None:
self.item_id: uuid.UUID = uuid.uuid4()
self.name: str = ""
class WishlistItem:
def __init__(self) -> None:
self.id: uuid = uuid.uuid4()
self.id: uuid.UUID = uuid.uuid4()
self.name: str = ""
self.description: str = ""
self.shop: str = ""
@ -21,13 +42,13 @@ class WishlistItem:
class WishlistConfig:
def __init__(self) -> None:
self.id: uuid = uuid.uuid4()
self.id: uuid.UUID = uuid.uuid4()
self.title: str = ""
self.deadlines: dict[str, date] = {}
self.deadlines: dict[uuid.UUID, date] = {}
self.deadline_offset_days: int = 0
class Wishlist:
def __init__(self) -> None:
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

View file

@ -1,12 +1,17 @@
from .models import Wishlist, WishlistConfig, WishlistItem
from .models import Wishlist, WishlistConfig, WishlistItem, ItemReservation
from datetime import date
import uuid
import json
import csv
import os
CONFIG_NAME = "config.json"
WISHLIST_NAME = "wishlist.csv"
RESERVATIONS_NAME = "reservations.json"
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:
json_config = json.load(fp)
@ -20,9 +25,12 @@ def read_wishlist_config(wishlist_dir: str) -> WishlistConfig:
return config
def read_wishlist_items(wishlist_dir: str) -> dict[str, WishlistItem]:
items: dict[str, WishlistItem] = {}
path: str = f"{wishlist_dir}/wishlist.csv"
def read_wishlist_items(wishlist_dir: str) -> dict[uuid.UUID, WishlistItem]:
reservations = read_all_reservations(wishlist_dir)
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)
@ -35,7 +43,10 @@ def read_wishlist_items(wishlist_dir: str) -> dict[str, WishlistItem]:
item.image = row.get("image")
item.price = float(row.get("price"))
items[str(item.id)] = item
if item.id in reservations.keys():
item.reservation = reservations[item.id]
items[item.id] = item
return items
@ -49,12 +60,66 @@ def read_wishlist(wishlist_dir: str) -> 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]
wishlists: dict[str, Wishlist] = {}
wishlists: dict[uuid.UUID, Wishlist] = {}
for dir in wishlist_dirs:
wishlist: Wishlist = read_wishlist(f"{parent_dir}/{dir}")
wishlists[str(wishlist.config.id)] = wishlist
wishlists[wishlist.config.id] = wishlist
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)