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 = [
|
DRINKS = [
|
||||||
# name, crates, btl/crate, size, price/crate, deposit/crate, sale/btl
|
# name, category, crates, btl/crate, size, price/crate, deposit/crate, sale/btl
|
||||||
("Sterni", 12, 20, 0.5, 10.99, 3.10, 2.00),
|
("Sternburg Export", "beer", 12, 20, 0.5, 10.99, 3.10, 2.00),
|
||||||
("Krosti", 5, 20, 0.5, 16.49, 3.10, 2.50),
|
("Ur-Krostitzer", "beer", 5, 20, 0.5, 16.49, 3.10, 2.50),
|
||||||
("Buddi", 5, 20, 0.5, 20.99, 3.10, 2.50),
|
("Budweiser", "beer", 5, 20, 0.5, 20.99, 3.10, 2.50),
|
||||||
("Helles", 5, 20, 0.5, 15.99, 4.50, 2.50),
|
("Altenburger Helles", "beer", 5, 20, 0.5, 15.99, 4.50, 2.50),
|
||||||
("Radler", 2, 20, 0.5, 14.99, 3.10, 2.50),
|
("Feldschl. Radler", "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),
|
("Lübzer Grapef. 0,0", "alc_free_radler", 1, 20, 0.5, 17.99, 3.10, 2.50),
|
||||||
("Freiberger 0,0", 4, 20, 0.5, 15.49, 3.10, 2.50),
|
("Freiberger 0,0", "alc_free_beer", 4, 20, 0.5, 15.49, 3.10, 2.50),
|
||||||
("Mate", 2, 20, 0.5, 17.49, 4.50, 2.50),
|
("Club Mate", "soft", 2, 20, 0.5, 17.49, 4.50, 2.50),
|
||||||
("Vita Cola", 2, 12, 1.0, 10.99, 3.30, 2.50),
|
("Vita Cola", "soft", 2, 12, 1.0, 10.99, 3.30, 2.50),
|
||||||
("Spezi", 2, 20, 0.5, 17.99, 3.10, 2.50),
|
("Paulaner Spezi", "soft", 2, 20, 0.5, 17.99, 3.10, 2.50),
|
||||||
("Wasser", 10, 12, 1.0, 5.99, 3.30, 1.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})")
|
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(
|
obj, created = Drink.objects.update_or_create(
|
||||||
name=name,
|
name=name,
|
||||||
year=2026,
|
year=2026,
|
||||||
defaults={
|
defaults={
|
||||||
|
"category": category,
|
||||||
"crates_ordered": crates,
|
"crates_ordered": crates,
|
||||||
"crates_purchased": crates,
|
"crates_purchased": crates,
|
||||||
"crates_returned": 0,
|
"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 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)
|
name = models.CharField("Name", max_length=32)
|
||||||
year = models.PositiveSmallIntegerField("Jahr", default=2024)
|
year = models.PositiveSmallIntegerField("Jahr", default=2024)
|
||||||
|
category = models.CharField(
|
||||||
|
"Kategorie",
|
||||||
|
max_length=16,
|
||||||
|
choices=Category.choices,
|
||||||
|
default=Category.beer,
|
||||||
|
)
|
||||||
crates_ordered = models.PositiveSmallIntegerField(
|
crates_ordered = models.PositiveSmallIntegerField(
|
||||||
"Kästen bestellt",
|
"Kästen bestellt",
|
||||||
help_text="nur zur Info, wie gut wir geplant haben — nicht die tatsächlich konsumierten/bezahlten Flaschen",
|
help_text="nur zur Info, wie gut wir geplant haben — nicht die tatsächlich konsumierten/bezahlten Flaschen",
|
||||||
|
|||||||
@@ -203,14 +203,58 @@ section {
|
|||||||
|
|
||||||
.drink-btn {
|
.drink-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
aspect-ratio: 4 / 3;
|
aspect-ratio: 3 / 2;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 4px;
|
gap: 2px;
|
||||||
padding: 12px 8px;
|
padding: 8px;
|
||||||
line-height: 1.2;
|
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 {
|
.drink-plus {
|
||||||
@@ -228,7 +272,7 @@ section {
|
|||||||
.drink-price {
|
.drink-price {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
opacity: 0.8;
|
opacity: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
.history {
|
.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 import authenticate, login, logout
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth import get_user_model
|
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.http import Http404, HttpResponseRedirect
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -173,7 +173,24 @@ def pin_view(request):
|
|||||||
@require_http_methods(["GET"])
|
@require_http_methods(["GET"])
|
||||||
def me_view(request):
|
def me_view(request):
|
||||||
phase = _require_open(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_drink = None
|
||||||
booked_id = request.GET.get("booked")
|
booked_id = request.GET.get("booked")
|
||||||
if booked_id:
|
if booked_id:
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
<form method="post" action="{% url 'suff:book' %}">
|
<form method="post" action="{% url 'suff:book' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="drink_id" value="{{ drink.id }}" />
|
<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-plus">+1</span>
|
||||||
<span class="drink-name">{{ drink.name }}</span>
|
<span class="drink-name">{{ drink.name }}</span>
|
||||||
<span class="drink-price">{{ drink.sale_price_per_bottle|floatformat:2 }} €</span>
|
<span class="drink-price">{{ drink.sale_price_per_bottle|floatformat:2 }} €</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user