Add subscription functionality
This commit is contained in:
@@ -10,7 +10,8 @@ from fastapi.staticfiles import StaticFiles
|
|||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from sqlmodel import Session, SQLModel, create_engine, select
|
from sqlmodel import Session, SQLModel, create_engine, select
|
||||||
|
|
||||||
from models import Event, Household, Registration, TeamRegistration
|
from models import (Event, Household, Registration, Subscription,
|
||||||
|
TeamRegistration)
|
||||||
|
|
||||||
sqlite_file_name = "database.db"
|
sqlite_file_name = "database.db"
|
||||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||||
@@ -79,6 +80,75 @@ async def past_events(request: Request, session: SessionDep):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/subscribe")
|
||||||
|
async def subscribe(request: Request, session: SessionDep):
|
||||||
|
statement = select(Household)
|
||||||
|
households = session.exec(statement).all()
|
||||||
|
|
||||||
|
# filter out households with existing registrations
|
||||||
|
# households = [
|
||||||
|
# h
|
||||||
|
# for h in households
|
||||||
|
# if h.id not in [reg.household_id for reg in event.registrations]
|
||||||
|
# ]
|
||||||
|
|
||||||
|
subscriptions = session.exec(select(Subscription)).all()
|
||||||
|
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
request=request,
|
||||||
|
name="subscribe.html",
|
||||||
|
context={"households": households, "subscriptions": subscriptions},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/subscribe")
|
||||||
|
async def add_subscribe(request: Request, session: SessionDep):
|
||||||
|
form_data = await request.form()
|
||||||
|
# TODO: Make this return a nicer error message
|
||||||
|
try:
|
||||||
|
num_adult_meals = int(form_data["numAdults"]) if form_data["numAdults"] else 0
|
||||||
|
num_children_meals = int(form_data["numKids"]) if form_data["numKids"] else 0
|
||||||
|
num_small_children_meals = (
|
||||||
|
int(form_data["numSmallKids"]) if form_data["numSmallKids"] else 0
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError("All number fields must be integers")
|
||||||
|
|
||||||
|
subscription = Subscription(
|
||||||
|
household_id=form_data["household"],
|
||||||
|
num_adult_meals=num_adult_meals,
|
||||||
|
num_children_meals=num_children_meals,
|
||||||
|
num_small_children_meals=num_small_children_meals,
|
||||||
|
)
|
||||||
|
|
||||||
|
selected_days = form_data.getlist("days")
|
||||||
|
if selected_days:
|
||||||
|
subscription.monday = "1" in selected_days
|
||||||
|
subscription.tuesday = "2" in selected_days
|
||||||
|
subscription.wednesday = "3" in selected_days
|
||||||
|
subscription.thursday = "4" in selected_days
|
||||||
|
subscription.friday = "5" in selected_days
|
||||||
|
subscription.saturday = "6" in selected_days
|
||||||
|
subscription.sunday = "7" in selected_days
|
||||||
|
|
||||||
|
session.add(subscription)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
return RedirectResponse(url="/subscribe", status_code=status.HTTP_302_FOUND)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/subscribe/{household_id}/delete")
|
||||||
|
async def delete_subscription(request: Request, session: SessionDep, household_id: int):
|
||||||
|
|
||||||
|
statement = select(Subscription).where(Subscription.household_id == household_id)
|
||||||
|
sub = session.exec(statement).one()
|
||||||
|
|
||||||
|
session.delete(sub)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
return RedirectResponse(url="/subscribe", status_code=status.HTTP_302_FOUND)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/event/add")
|
@app.get("/event/add")
|
||||||
async def add_event_form(request: Request, session: SessionDep):
|
async def add_event_form(request: Request, session: SessionDep):
|
||||||
return templates.TemplateResponse(request=request, name="add_event.html")
|
return templates.TemplateResponse(request=request, name="add_event.html")
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import typing
|
import typing
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from xmlrpc.client import DateTime
|
||||||
|
|
||||||
from sqlmodel import Field, Relationship, SQLModel, String
|
from sqlmodel import Field, Relationship, SQLModel, String
|
||||||
|
|
||||||
@@ -79,3 +80,51 @@ class Registration(SQLModel, table=True):
|
|||||||
comment: str | None
|
comment: str | None
|
||||||
|
|
||||||
household: Household = Relationship()
|
household: Household = Relationship()
|
||||||
|
|
||||||
|
|
||||||
|
class Subscription(SQLModel, table=True):
|
||||||
|
household_id: int | None = Field(
|
||||||
|
default=None, foreign_key="household.id", primary_key=True
|
||||||
|
)
|
||||||
|
num_adult_meals: int
|
||||||
|
num_children_meals: int
|
||||||
|
num_small_children_meals: int
|
||||||
|
comment: str | None
|
||||||
|
|
||||||
|
last_modified: datetime = Field(default_factory=datetime.now, nullable=False)
|
||||||
|
|
||||||
|
monday: bool = True
|
||||||
|
tuesday: bool = True
|
||||||
|
wednesday: bool = True
|
||||||
|
thursday: bool = True
|
||||||
|
friday: bool = True
|
||||||
|
saturday: bool = True
|
||||||
|
sunday: bool = True
|
||||||
|
|
||||||
|
household: Household = Relationship()
|
||||||
|
|
||||||
|
def day_string_de(self) -> str:
|
||||||
|
"""
|
||||||
|
Generates a string representation of selected days in German short form.
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
|
||||||
|
if self.monday:
|
||||||
|
result.append("Mo")
|
||||||
|
if self.tuesday:
|
||||||
|
result.append("Di")
|
||||||
|
if self.wednesday:
|
||||||
|
result.append("Mi")
|
||||||
|
if self.thursday:
|
||||||
|
result.append("Do")
|
||||||
|
if self.friday:
|
||||||
|
result.append("Fr")
|
||||||
|
if self.saturday:
|
||||||
|
result.append("Sa")
|
||||||
|
if self.sunday:
|
||||||
|
result.append("So")
|
||||||
|
|
||||||
|
if len(result) < 7:
|
||||||
|
return ", ".join(result)
|
||||||
|
else:
|
||||||
|
return "Alle"
|
||||||
|
|||||||
@@ -8,7 +8,23 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="card bg-success-subtle text-success-emphasis border-0 shadow-sm text-center">
|
||||||
|
<div class="card-body py-4">
|
||||||
|
<i class="bi bi-calendar-heart fs-3 mb-2"></i>
|
||||||
|
<h5 class="card-title mb-2">Nie wieder die Anmeldung vergessen</h5>
|
||||||
|
<p class="card-text small mb-3">
|
||||||
|
Die Dauerhafte Anmeldung gilt für alle kommenden Kochabende.
|
||||||
|
</p>
|
||||||
|
<a href="/subscribe" class="btn btn-light btn-sm fw-semibold px-3">
|
||||||
|
Jetzt dauerhaft Anmelden
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
||||||
|
|
||||||
|
|
||||||
{% for event in events %}
|
{% for event in events %}
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card h-100 shadow-sm">
|
<div class="card h-100 shadow-sm">
|
||||||
|
|||||||
134
new-registration-app/templates/subscribe.html
Normal file
134
new-registration-app/templates/subscribe.html
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row mt-4">
|
||||||
|
<!-- Left column: subscription form -->
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col">
|
||||||
|
<div class="card shadow-sm mt-4">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h1 class="fs-5 mb-0">Dauerhafte Anmeldung zu allen Kochabenden</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="/subscribe" method="POST">
|
||||||
|
<div class="card-body">
|
||||||
|
<p>
|
||||||
|
Mit einer dauerhaften Anmeldung kannst du dich/euch für alle zukünftigen Kochabende
|
||||||
|
anmelden. Es ist möglich
|
||||||
|
diese Anmeldung auf bestimmte Wochentage zu beschränken.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Dauerhafte Anmeldungen werden eine Woche vor einem Kochabend als Anmeldungen für diesen
|
||||||
|
Abend eingetragen. Danach
|
||||||
|
können sie auch noch gelöscht bzw. bearbeitet werden.
|
||||||
|
</p>
|
||||||
|
<!-- Household selection -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<select name="household" class="form-select" required>
|
||||||
|
<option value="" disabled selected hidden>Wer?</option>
|
||||||
|
{% for household in households %}
|
||||||
|
<option value="{{household.id}}">{{household.name}}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">
|
||||||
|
Wenn dein Haushalt hier nicht auswählbar ist, besteht bereits eine dauerhafte Anmeldung.
|
||||||
|
Um Änderungen vorzunehmen, lösche die bestehende Anmeldung und lege eine neue an.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Person counts -->
|
||||||
|
<div class="row g-3 mb-3">
|
||||||
|
<div class="col">
|
||||||
|
<label for="InputAdults" class="form-label">Anzahl Erwachsene</label>
|
||||||
|
<input name="numAdults" id="InputAdults" type="number" class="form-control"
|
||||||
|
aria-label="Anzahl Erwachsene" min="0" step="1" inputmode="numeric">
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<label for="InputKids" class="form-label">Anzahl Kinder >7</label>
|
||||||
|
<input name="numKids" id="InputKids" type="number" class="form-control"
|
||||||
|
aria-label="Anzahl Kinder >7" min="0" step="1" inputmode="numeric">
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<label for="InputSmallKids" class="form-label">Anzahl Kinder <7</label>
|
||||||
|
<input name="numSmallKids" id="InputSmallKids" type="number" class="form-control"
|
||||||
|
aria-label="Anzahl Kinder <7" min="0" step="1" inputmode="numeric">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Days of the week -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Wochentage auswählen (optional)</label>
|
||||||
|
<p class="text-muted small mb-2">
|
||||||
|
Wenn du nur an bestimmten Tagen teilnehmen möchtest, wähle sie hier aus.
|
||||||
|
</p>
|
||||||
|
<div class="d-flex flex-wrap gap-3">
|
||||||
|
{% set days = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag",
|
||||||
|
"Sonntag"] %}
|
||||||
|
{% for day in days %}
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="days" value="{{loop.index}}"
|
||||||
|
id="day-{{loop.index}}">
|
||||||
|
<label class="form-check-label" for="day-{{loop.index}}">
|
||||||
|
{{day}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="card-footer d-flex justify-content-end gap-2">
|
||||||
|
<button type="submit" class="btn btn-primary">Dauerhaft anmelden</button>
|
||||||
|
<a href="/" class="btn btn-secondary">Abbrechen</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right column: existing registrations -->
|
||||||
|
<div class="col-12 col-lg-6 mt-4 mt-lg-0">
|
||||||
|
<p class="h4 m-2">Bestehende dauerhafte Anmeldungen</p>
|
||||||
|
{% if subscriptions | length == 0 %}
|
||||||
|
<p class="m-2">Es gibt noch keine dauerhaften Anmeldungen</p>
|
||||||
|
{% else %}
|
||||||
|
{% for sub in subscriptions %}
|
||||||
|
<div class="card mb-2">
|
||||||
|
<div class="card-body py-2 px-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||||
|
<h6 class="mb-0">{{ sub.household.name }}</h6>
|
||||||
|
<a href="/subscribe/{{sub.household.id}}/delete" class="text-danger">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<div class="text-muted" style="font-size: 0.7rem;">Erwachsene</div>
|
||||||
|
<div class="fw-bold small">{{ sub.num_adult_meals }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<div class="text-muted" style="font-size: 0.7rem;">Kinder</div>
|
||||||
|
<div class="fw-bold small">{{ sub.num_children_meals }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<div class="text-muted" style="font-size: 0.7rem;">Kleinkinder</div>
|
||||||
|
<div class="fw-bold small">{{ sub.num_small_children_meals }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<div class="text-muted" style="font-size: 0.7rem;">Tage</div>
|
||||||
|
<div class="fw-bold small">{{ sub.day_string_de() }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user