Color-code drink buttons by category
Add a Drink.category field (beer, alc_free_beer, radler, alc_free_radler, soft, water) and apply per-category gradient backgrounds to the booking buttons so users can recognize drinks at a glance. Sort buttons by category, shrink them to a 3:2 aspect ratio, and switch labels to more verbose brand names (Sternburg Export, Ur-Krostitzer, etc.). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -18,18 +18,18 @@ PAYMENTS = [
|
||||
]
|
||||
|
||||
DRINKS = [
|
||||
# name, crates, btl/crate, size, price/crate, deposit/crate, sale/btl
|
||||
("Sterni", 12, 20, 0.5, 10.99, 3.10, 2.00),
|
||||
("Krosti", 5, 20, 0.5, 16.49, 3.10, 2.50),
|
||||
("Buddi", 5, 20, 0.5, 20.99, 3.10, 2.50),
|
||||
("Helles", 5, 20, 0.5, 15.99, 4.50, 2.50),
|
||||
("Radler", 2, 20, 0.5, 14.99, 3.10, 2.50),
|
||||
("Lübzer 0,0", 1, 20, 0.5, 17.99, 3.10, 2.50),
|
||||
("Freiberger 0,0", 4, 20, 0.5, 15.49, 3.10, 2.50),
|
||||
("Mate", 2, 20, 0.5, 17.49, 4.50, 2.50),
|
||||
("Vita Cola", 2, 12, 1.0, 10.99, 3.30, 2.50),
|
||||
("Spezi", 2, 20, 0.5, 17.99, 3.10, 2.50),
|
||||
("Wasser", 10, 12, 1.0, 5.99, 3.30, 1.50),
|
||||
# name, category, crates, btl/crate, size, price/crate, deposit/crate, sale/btl
|
||||
("Sternburg Export", "beer", 12, 20, 0.5, 10.99, 3.10, 2.00),
|
||||
("Ur-Krostitzer", "beer", 5, 20, 0.5, 16.49, 3.10, 2.50),
|
||||
("Budweiser", "beer", 5, 20, 0.5, 20.99, 3.10, 2.50),
|
||||
("Altenburger Helles", "beer", 5, 20, 0.5, 15.99, 4.50, 2.50),
|
||||
("Feldschl. Radler", "radler", 2, 20, 0.5, 14.99, 3.10, 2.50),
|
||||
("Lübzer Grapef. 0,0", "alc_free_radler", 1, 20, 0.5, 17.99, 3.10, 2.50),
|
||||
("Freiberger 0,0", "alc_free_beer", 4, 20, 0.5, 15.49, 3.10, 2.50),
|
||||
("Club Mate", "soft", 2, 20, 0.5, 17.49, 4.50, 2.50),
|
||||
("Vita Cola", "soft", 2, 12, 1.0, 10.99, 3.30, 2.50),
|
||||
("Paulaner Spezi", "soft", 2, 20, 0.5, 17.99, 3.10, 2.50),
|
||||
("Wasser", "water", 10, 12, 1.0, 5.99, 3.30, 1.50),
|
||||
]
|
||||
|
||||
|
||||
@@ -46,11 +46,12 @@ class Command(BaseCommand):
|
||||
)
|
||||
self.stdout.write(f"{'created' if created else 'updated'}: {obj.purpose} ({obj.date})")
|
||||
|
||||
for name, crates, btl, size, price, deposit, sale in DRINKS:
|
||||
for name, category, crates, btl, size, price, deposit, sale in DRINKS:
|
||||
obj, created = Drink.objects.update_or_create(
|
||||
name=name,
|
||||
year=2026,
|
||||
defaults={
|
||||
"category": category,
|
||||
"crates_ordered": crates,
|
||||
"crates_purchased": crates,
|
||||
"crates_returned": 0,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.5 on 2026-05-14 20:35
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gaehsnitz', '0006_user_payment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='drink',
|
||||
name='category',
|
||||
field=models.CharField(choices=[('beer', 'Bier'), ('radler', 'Radler'), ('alc_free_beer', 'Bier alkoholfrei'), ('soft', 'Softdrink'), ('water', 'Wasser')], default='beer', max_length=16, verbose_name='Kategorie'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.5 on 2026-05-14 20:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gaehsnitz', '0007_drink_category'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='drink',
|
||||
name='category',
|
||||
field=models.CharField(choices=[('beer', 'Bier'), ('radler', 'Radler'), ('alc_free_beer', 'Bier alkoholfrei'), ('alc_free_radler', 'Radler alkoholfrei'), ('soft', 'Softdrink'), ('water', 'Wasser')], default='beer', max_length=16, verbose_name='Kategorie'),
|
||||
),
|
||||
]
|
||||
@@ -83,8 +83,22 @@ class Payment(models.Model):
|
||||
|
||||
|
||||
class Drink(models.Model):
|
||||
class Category(models.TextChoices):
|
||||
beer = "beer", "Bier"
|
||||
radler = "radler", "Radler"
|
||||
alc_free_beer = "alc_free_beer", "Bier alkoholfrei"
|
||||
alc_free_radler = "alc_free_radler", "Radler alkoholfrei"
|
||||
soft = "soft", "Softdrink"
|
||||
water = "water", "Wasser"
|
||||
|
||||
name = models.CharField("Name", max_length=32)
|
||||
year = models.PositiveSmallIntegerField("Jahr", default=2024)
|
||||
category = models.CharField(
|
||||
"Kategorie",
|
||||
max_length=16,
|
||||
choices=Category.choices,
|
||||
default=Category.beer,
|
||||
)
|
||||
crates_ordered = models.PositiveSmallIntegerField(
|
||||
"Kästen bestellt",
|
||||
help_text="nur zur Info, wie gut wir geplant haben — nicht die tatsächlich konsumierten/bezahlten Flaschen",
|
||||
|
||||
@@ -203,14 +203,58 @@ section {
|
||||
|
||||
.drink-btn {
|
||||
width: 100%;
|
||||
aspect-ratio: 4 / 3;
|
||||
aspect-ratio: 3 / 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
padding: 12px 8px;
|
||||
gap: 2px;
|
||||
padding: 8px;
|
||||
line-height: 1.2;
|
||||
color: #161616;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.drink-btn-beer {
|
||||
background: linear-gradient(180deg, #f0c878 0%, #b8731a 100%);
|
||||
}
|
||||
.drink-btn-beer:hover, .drink-btn-beer:focus {
|
||||
background: linear-gradient(180deg, #f7d699 0%, #cc8520 100%);
|
||||
}
|
||||
|
||||
.drink-btn-radler {
|
||||
background: linear-gradient(180deg, #f7e36b 0%, #d8a02a 100%);
|
||||
}
|
||||
.drink-btn-radler:hover, .drink-btn-radler:focus {
|
||||
background: linear-gradient(180deg, #fff08c 0%, #e8b034 100%);
|
||||
}
|
||||
|
||||
.drink-btn-alc_free_beer {
|
||||
background: linear-gradient(180deg, #f0c878 0%, #5e8bbf 100%);
|
||||
}
|
||||
.drink-btn-alc_free_beer:hover, .drink-btn-alc_free_beer:focus {
|
||||
background: linear-gradient(180deg, #f7d699 0%, #7aa3d1 100%);
|
||||
}
|
||||
|
||||
.drink-btn-alc_free_radler {
|
||||
background: linear-gradient(180deg, #f7e36b 0%, #5e8bbf 100%);
|
||||
}
|
||||
.drink-btn-alc_free_radler:hover, .drink-btn-alc_free_radler:focus {
|
||||
background: linear-gradient(180deg, #fff08c 0%, #7aa3d1 100%);
|
||||
}
|
||||
|
||||
.drink-btn-soft {
|
||||
background: linear-gradient(180deg, #f0a35e 0%, #a85a22 100%);
|
||||
}
|
||||
.drink-btn-soft:hover, .drink-btn-soft:focus {
|
||||
background: linear-gradient(180deg, #f7b878 0%, #c06a2c 100%);
|
||||
}
|
||||
|
||||
.drink-btn-water {
|
||||
background: linear-gradient(180deg, #d5e8f4 0%, #95c2dc 100%);
|
||||
}
|
||||
.drink-btn-water:hover, .drink-btn-water:focus {
|
||||
background: linear-gradient(180deg, #e4f0f8 0%, #aad0e6 100%);
|
||||
}
|
||||
|
||||
.drink-plus {
|
||||
@@ -228,7 +272,7 @@ section {
|
||||
.drink-price {
|
||||
font-size: 0.85rem;
|
||||
font-weight: normal;
|
||||
opacity: 0.8;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.history {
|
||||
|
||||
+19
-2
@@ -6,7 +6,7 @@ from django.contrib.admin.views.decorators import staff_member_required
|
||||
from django.contrib.auth import authenticate, login, logout
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db.models import F, Sum
|
||||
from django.db.models import Case, F, IntegerField, Sum, Value, When
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.urls import reverse
|
||||
@@ -173,7 +173,24 @@ def pin_view(request):
|
||||
@require_http_methods(["GET"])
|
||||
def me_view(request):
|
||||
phase = _require_open(request)
|
||||
drinks = Drink.objects.filter(year=current_year()).order_by("name") if phase == "booking" else Drink.objects.none()
|
||||
drinks = (
|
||||
Drink.objects.filter(year=current_year())
|
||||
.annotate(
|
||||
category_order=Case(
|
||||
When(category="beer", then=Value(0)),
|
||||
When(category="alc_free_beer", then=Value(1)),
|
||||
When(category="radler", then=Value(2)),
|
||||
When(category="alc_free_radler", then=Value(3)),
|
||||
When(category="soft", then=Value(4)),
|
||||
When(category="water", then=Value(5)),
|
||||
default=Value(99),
|
||||
output_field=IntegerField(),
|
||||
)
|
||||
)
|
||||
.order_by("category_order", "name")
|
||||
if phase == "booking"
|
||||
else Drink.objects.none()
|
||||
)
|
||||
booked_drink = None
|
||||
booked_id = request.GET.get("booked")
|
||||
if booked_id:
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<form method="post" action="{% url 'suff:book' %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="drink_id" value="{{ drink.id }}" />
|
||||
<button type="submit" class="drink-btn">
|
||||
<button type="submit" class="drink-btn drink-btn-{{ drink.category }}">
|
||||
<span class="drink-plus">+1</span>
|
||||
<span class="drink-name">{{ drink.name }}</span>
|
||||
<span class="drink-price">{{ drink.sale_price_per_bottle|floatformat:2 }} €</span>
|
||||
|
||||
Reference in New Issue
Block a user