Implemented reservations
This commit is contained in:
parent
0d54983850
commit
82bc06817b
8 changed files with 222 additions and 87 deletions
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
8
data/max/reservations.json
Normal file
8
data/max/reservations.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"reservations": [
|
||||
{
|
||||
"item_id": "77b3c3a0-303b-482f-a1e0-67c29030ec69",
|
||||
"name": "Max"
|
||||
}
|
||||
]
|
||||
}
|
44
src/main.py
44
src/main.py
|
@ -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]},
|
||||
)
|
25
src/templates/components/wishlist_item.html
Normal file
25
src/templates/components/wishlist_item.html
Normal 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>
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue