aboutsummaryrefslogtreecommitdiff
path: root/web_interface
diff options
context:
space:
mode:
Diffstat (limited to 'web_interface')
-rw-r--r--web_interface/Makefile3
-rw-r--r--web_interface/drinks/admin.py7
-rw-r--r--web_interface/drinks/migrations/0001_initial_squashed_0006_alter_drink_author_squashed_0007_alter_ingredient_drink_alter_ingredient_unit.py83
-rw-r--r--web_interface/drinks/models.py89
-rw-r--r--web_interface/drinks/urls.py3
-rw-r--r--web_interface/drinks_machine/settings.py6
-rw-r--r--web_interface/drinks_machine/urls.py5
7 files changed, 176 insertions, 20 deletions
diff --git a/web_interface/Makefile b/web_interface/Makefile
index b5104fa..e5f4d7e 100644
--- a/web_interface/Makefile
+++ b/web_interface/Makefile
@@ -12,5 +12,8 @@ install:
migrate:
uv run manage.py migrate
+user:
+ uv run manage.py createsuperuser
+
run_dev:
uv run manage.py runserver
diff --git a/web_interface/drinks/admin.py b/web_interface/drinks/admin.py
index 8c38f3f..34e6e3f 100644
--- a/web_interface/drinks/admin.py
+++ b/web_interface/drinks/admin.py
@@ -1,3 +1,10 @@
from django.contrib import admin
+from . import models
# Register your models here.
+
+admin.site.register(models.Author)
+admin.site.register(models.Bottle)
+admin.site.register(models.Drink)
+admin.site.register(models.Ingredient)
+admin.site.register(models.Unit)
diff --git a/web_interface/drinks/migrations/0001_initial_squashed_0006_alter_drink_author_squashed_0007_alter_ingredient_drink_alter_ingredient_unit.py b/web_interface/drinks/migrations/0001_initial_squashed_0006_alter_drink_author_squashed_0007_alter_ingredient_drink_alter_ingredient_unit.py
new file mode 100644
index 0000000..bb3d845
--- /dev/null
+++ b/web_interface/drinks/migrations/0001_initial_squashed_0006_alter_drink_author_squashed_0007_alter_ingredient_drink_alter_ingredient_unit.py
@@ -0,0 +1,83 @@
+# Generated by Django 6.0 on 2025-12-07 11:29
+
+import colorfield.fields
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ replaces = [('drinks', '0001_initial_squashed_0006_alter_drink_author'), ('drinks', '0007_alter_ingredient_drink_alter_ingredient_unit')]
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Author',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('first_name', models.CharField(help_text='First name of the author of a drink', verbose_name='First name')),
+ ('last_name', models.CharField(help_text='Last name of the author of a drink', verbose_name='Last name')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Bottle',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(help_text='Name of the type bottle. e.g. Coca Cola, *Specific Whiskey*', verbose_name='Bottle')),
+ ('description', models.TextField(blank=True, help_text='Free text field that will be shown to the users', verbose_name='Description')),
+ ('picture', models.ImageField(blank=True, help_text='A picture for the overview', upload_to='', verbose_name='Picture')),
+ ('color', colorfield.fields.ColorField(blank=True, default='', help_text='Color of the drink, used in the UI', image_field='picture', max_length=25, samples=None, verbose_name='Color')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Unit',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('abbreviation', models.CharField(help_text='E.g: ml.', verbose_name='Abbreviation')),
+ ('name', models.CharField(blank=True, help_text='E.g: ml.', verbose_name='Name')),
+ ('pump_time', models.FloatField(blank=True, help_text='Amount of time to run the pump for to dispence 1 of these units', verbose_name='Pump time')),
+ ('valve_time', models.FloatField(blank=True, help_text='Amount of time to run the pump for to dispence 1 of these units', verbose_name='Pump time')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Drink',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(help_text='Name of the drink, for displaying in overview', verbose_name='Name')),
+ ('description', models.TextField(blank=True, help_text='Free text field that will be shown to the users', verbose_name='Description')),
+ ('picture', models.ImageField(blank=True, help_text='A picture for the overview', upload_to='', verbose_name='Picture')),
+ ('author', models.ForeignKey(blank=True, help_text='Author of the drink', on_delete=django.db.models.deletion.CASCADE, to='drinks.author', verbose_name='Author')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Ingredient',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('amount', models.IntegerField(help_text='In conjunction with unit it is an absolute amount', verbose_name='Amount')),
+ ('bottle', models.ForeignKey(help_text="This ingredient's bottle", on_delete=django.db.models.deletion.CASCADE, to='drinks.bottle', verbose_name='Bottle')),
+ ('unit', models.ForeignKey(default=1, help_text='The unit that the ammount is messured in, e.g. ml.', on_delete=django.db.models.deletion.CASCADE, to='drinks.unit', verbose_name='Unit')),
+ ('drink', models.ForeignKey(default=1, help_text='The drink this ingredient is part of', on_delete=django.db.models.deletion.CASCADE, to='drinks.drink', verbose_name='Drink')),
+ ],
+ ),
+ migrations.AddConstraint(
+ model_name='unit',
+ constraint=models.CheckConstraint(condition=models.Q(('pump_time__isnull', False), ('valve_time__isnull', False), _connector='OR'), name='one_of_valve_time_or_pump_time_must_be_filled'),
+ ),
+ migrations.AlterField(
+ model_name='drink',
+ name='author',
+ field=models.ForeignKey(blank=True, help_text='Author of the drink', null=True, on_delete=django.db.models.deletion.CASCADE, to='drinks.author', verbose_name='Author'),
+ ),
+ migrations.AlterField(
+ model_name='ingredient',
+ name='drink',
+ field=models.ForeignKey(help_text='The drink this ingredient is part of', on_delete=django.db.models.deletion.CASCADE, to='drinks.drink', verbose_name='Drink'),
+ ),
+ migrations.AlterField(
+ model_name='ingredient',
+ name='unit',
+ field=models.ForeignKey(help_text='The unit that the ammount is messured in, e.g. ml.', on_delete=django.db.models.deletion.CASCADE, to='drinks.unit', verbose_name='Unit'),
+ ),
+ ]
diff --git a/web_interface/drinks/models.py b/web_interface/drinks/models.py
index f1889bd..bb4b48a 100644
--- a/web_interface/drinks/models.py
+++ b/web_interface/drinks/models.py
@@ -1,5 +1,27 @@
from django.db import models
+from django.db.models import Q
+
from colorfield.fields import ColorField
+from django.utils.html import conditional_escape
+
+
+class Author(models.Model):
+ first_name = models.CharField(
+ verbose_name="First name",
+ help_text="First name of the author of a drink",
+ )
+
+ last_name = models.CharField(
+ verbose_name="Last name",
+ help_text="Last name of the author of a drink",
+ )
+
+ @property
+ def initials(self) -> str:
+ return f"{self.first_name[0].upper()}.{self.last_name[0].upper()}."
+
+ def __str__(self):
+ return self.initials
class Drink(models.Model):
@@ -11,35 +33,26 @@ class Drink(models.Model):
description = models.TextField(
verbose_name="Description",
help_text="Free text field that will be shown to the users",
+ blank=True,
)
picture = models.ImageField(
verbose_name="Picture",
help_text="A picture for the overview",
+ blank=True,
)
author = models.ForeignKey(
verbose_name="Author",
help_text="Author of the drink",
to=Author,
- on_delete=models.CASCADE
- )
-
-
-class Author(models.Model):
- first_name = models.CharField(
- verbose_name="First name",
- help_text="First name of the author of a drink",
- )
-
- last_name = models.CharField(
- verbose_name="Last name",
- help_text="Last name of the author of a drink",
+ on_delete=models.CASCADE,
+ blank=True,
+ null=True,
)
- @property
- def initials(self) -> str:
- return f"{self.first_name[0].upper()}.{self.last_name[0].upper()}."
+ def __str__(self):
+ return self.name
class Bottle(models.Model):
@@ -51,39 +64,60 @@ class Bottle(models.Model):
description = models.TextField(
verbose_name="Description",
help_text="Free text field that will be shown to the users",
+ blank=True,
)
picture = models.ImageField(
verbose_name="Picture",
help_text="A picture for the overview",
+ blank=True,
)
color = ColorField(
verbose_name="Color",
help_text="Color of the drink, used in the UI",
+ image_field="picture", # Auto-populate the color, calculated from the top-left pixel
+ blank=True,
)
+ def __str__(self):
+ return self.name
+
class Unit(models.Model):
abbreviation = models.CharField(
- verbose_name="Bottle",
+ verbose_name="Abbreviation",
help_text="E.g: ml.",
)
name = models.CharField(
- verbose_name="Bottle",
+ verbose_name="Name",
help_text="E.g: ml.",
+ blank=True,
)
pump_time = models.FloatField(
verbose_name="Pump time",
help_text="Amount of time to run the pump for to dispence 1 of these units",
+ blank=True,
)
valve_time = models.FloatField(
verbose_name="Pump time",
help_text="Amount of time to run the pump for to dispence 1 of these units",
+ blank=True,
)
+ class Meta:
+ constraints = [
+ models.CheckConstraint(
+ condition=Q(pump_time__isnull=False) | Q(valve_time__isnull=False),
+ name='one_of_valve_time_or_pump_time_must_be_filled'
+ ),
+ ]
+
+ def __str__(self):
+ return self.abbreviation
+
class Ingredient(models.Model):
bottle = models.ForeignKey(
@@ -93,8 +127,25 @@ class Ingredient(models.Model):
on_delete=models.CASCADE
)
+ drink = models.ForeignKey(
+ verbose_name="Drink",
+ help_text="The drink this ingredient is part of",
+ to=Drink,
+ on_delete=models.CASCADE
+ )
+
amount = models.IntegerField(
verbose_name="Amount",
- help_text=="In conjunction with unit it is an absolute amount",
+ help_text="In conjunction with unit it is an absolute amount",
)
+ unit = models.ForeignKey(
+ verbose_name="Unit",
+ help_text="The unit that the ammount is messured in, e.g. ml.",
+ to=Unit,
+ on_delete=models.CASCADE,
+ )
+
+ def __str__(self) -> str:
+ return f"{self.drink} {self.amount}{self.unit} {self.bottle}"
+
diff --git a/web_interface/drinks/urls.py b/web_interface/drinks/urls.py
index 5119061..33d413b 100644
--- a/web_interface/drinks/urls.py
+++ b/web_interface/drinks/urls.py
@@ -1,7 +1,10 @@
from django.urls import path
+from django.conf.urls.static import static
+from django.conf import settings
from . import views
urlpatterns = [
path("", views.index, name="index"),
]
+
diff --git a/web_interface/drinks_machine/settings.py b/web_interface/drinks_machine/settings.py
index a851434..cd6ac69 100644
--- a/web_interface/drinks_machine/settings.py
+++ b/web_interface/drinks_machine/settings.py
@@ -11,10 +11,14 @@ https://docs.djangoproject.com/en/6.0/ref/settings/
"""
from pathlib import Path
+import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
+MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
+MEDIA_URL = '/media/'
+
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
@@ -37,6 +41,8 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
+ 'drinks',
+ 'colorfield'
]
MIDDLEWARE = [
diff --git a/web_interface/drinks_machine/urls.py b/web_interface/drinks_machine/urls.py
index e76ad91..5424ace 100644
--- a/web_interface/drinks_machine/urls.py
+++ b/web_interface/drinks_machine/urls.py
@@ -17,9 +17,12 @@ Including another URLconf
from django.contrib import admin
from django.urls import path, include
from django.views.generic import RedirectView
+from django.conf.urls.static import static
+from django.conf import settings
urlpatterns = [
path('admin/', admin.site.urls),
path("drinks/", include("drinks.urls")),
path('', RedirectView.as_view(url='/drinks/', permanent=False), name='redirect'),
-]
+] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+