diff --git a/config/settings/base.py b/config/settings/base.py index 2595b48..47631c6 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -20,7 +20,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-jqgf*tx%8h!t6o#)tl-7(hsc_gkjdosmko*u@)8+4r-frc27r1' +SECRET_KEY = "django-insecure-jqgf*tx%8h!t6o#)tl-7(hsc_gkjdosmko*u@)8+4r-frc27r1" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -31,54 +31,56 @@ ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - - 'reinheit.apps.brew', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django_bootstrap5", + "reinheit.apps.brew", + "reinheit.apps.styles", + "reinheit.apps.ingredients", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'config.urls' +ROOT_URLCONF = "config.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'config.wsgi.application' +WSGI_APPLICATION = "config.wsgi.application" # Database # https://docs.djangoproject.com/en/5.0/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", } } @@ -88,16 +90,16 @@ DATABASES = { AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -105,9 +107,9 @@ AUTH_PASSWORD_VALIDATORS = [ # Internationalization # https://docs.djangoproject.com/en/5.0/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -117,9 +119,9 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.0/howto/static-files/ -STATIC_URL = 'static/' +STATIC_URL = "static/" # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/reinheit/apps/brew/admin.py b/reinheit/apps/brew/admin.py index 8c38f3f..775a611 100644 --- a/reinheit/apps/brew/admin.py +++ b/reinheit/apps/brew/admin.py @@ -1,3 +1,17 @@ from django.contrib import admin +from .models import Brew, Addition + # Register your models here. + + +class AdditionInline(admin.TabularInline): + model = Addition + + +@admin.register(Brew) +class BrewAdmin(admin.ModelAdmin): + inlines = [AdditionInline] + + list_display = ["name", "style", "pitch_date"] + date_hierarchy = "pitch_date" diff --git a/reinheit/apps/brew/migrations/0001_initial.py b/reinheit/apps/brew/migrations/0001_initial.py new file mode 100644 index 0000000..cd33970 --- /dev/null +++ b/reinheit/apps/brew/migrations/0001_initial.py @@ -0,0 +1,77 @@ +# Generated by Django 5.0.6 on 2024-06-24 20:32 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("ingredients", "0001_initial"), + ("styles", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="Brew", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255)), + ("pitch_date", models.DateField(null=True)), + ("bottling_date", models.DateField(null=True)), + ( + "fermenter_volume", + models.FloatField( + help_text="Volume of liquid in fermenter prior to piching yeast" + ), + ), + ( + "bottled_volume", + models.FloatField(help_text="Volume of liquid bottled"), + ), + ( + "style", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="styles.style" + ), + ), + ], + ), + migrations.CreateModel( + name="Addition", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("mass", models.FloatField(help_text="The mass in kg added")), + ("added", models.DateTimeField(null=True)), + ( + "ingredient", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="ingredients.ingredient", + ), + ), + ( + "brew", + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="brew.brew"), + ), + ], + ), + ] diff --git a/reinheit/apps/brew/migrations/0002_brew_priming_sugar.py b/reinheit/apps/brew/migrations/0002_brew_priming_sugar.py new file mode 100644 index 0000000..2c52628 --- /dev/null +++ b/reinheit/apps/brew/migrations/0002_brew_priming_sugar.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.6 on 2024-06-24 20:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("brew", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="brew", + name="priming_sugar", + field=models.FloatField( + default=0, help_text="Mass of priming sugar for the whole batch" + ), + preserve_default=False, + ), + ] diff --git a/reinheit/apps/brew/migrations/0003_brew_notes.py b/reinheit/apps/brew/migrations/0003_brew_notes.py new file mode 100644 index 0000000..891ceed --- /dev/null +++ b/reinheit/apps/brew/migrations/0003_brew_notes.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-06-24 20:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("brew", "0002_brew_priming_sugar"), + ] + + operations = [ + migrations.AddField( + model_name="brew", + name="notes", + field=models.TextField(blank=True, default=""), + ), + ] diff --git a/reinheit/apps/brew/models.py b/reinheit/apps/brew/models.py index 71a8362..f1c74ac 100644 --- a/reinheit/apps/brew/models.py +++ b/reinheit/apps/brew/models.py @@ -1,3 +1,36 @@ from django.db import models # Create your models here. + + +class Brew(models.Model): + + name = models.CharField(max_length=255) + + pitch_date = models.DateField(null=True) + bottling_date = models.DateField(null=True) + + style = models.ForeignKey("styles.Style", on_delete=models.PROTECT) + + fermenter_volume = models.FloatField( + help_text="Volume of liquid in fermenter prior to piching yeast" + ) + bottled_volume = models.FloatField(help_text="Volume of liquid bottled") + + priming_sugar = models.FloatField(help_text="Mass of priming sugar for the whole batch") + + notes = models.TextField(default="", blank=True) + + def __str__(self): + return f"{self.style}: {self.name}" + + +class Addition(models.Model): + ingredient = models.ForeignKey("ingredients.Ingredient", on_delete=models.PROTECT) + brew = models.ForeignKey("Brew", on_delete=models.CASCADE) + + mass = models.FloatField(help_text="The mass in kg added") + added = models.DateTimeField(null=True) + + def __str__(self): + return f"{self.mass}kg of {self.ingredient} in {self.brew}" diff --git a/reinheit/apps/ingredients/__init__.py b/reinheit/apps/ingredients/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/reinheit/apps/ingredients/admin.py b/reinheit/apps/ingredients/admin.py new file mode 100644 index 0000000..9717638 --- /dev/null +++ b/reinheit/apps/ingredients/admin.py @@ -0,0 +1,24 @@ +from django.contrib import admin + +from .models import Ingredient, Producer + +# Register your models here. + + +@admin.register(Ingredient) +class IngredientAdmin(admin.ModelAdmin): + list_display = ["name", "kind", "producer"] + list_filter = ["kind", "producer"] + + search_fields = ["name", "kind", "producer__name"] + + +class IngredientInline(admin.TabularInline): + model = Ingredient + extra = 1 + + +@admin.register(Producer) +class ProducerAdmin(admin.ModelAdmin): + list_display = ["name"] + inlines = [IngredientInline] diff --git a/reinheit/apps/ingredients/apps.py b/reinheit/apps/ingredients/apps.py new file mode 100644 index 0000000..3637b78 --- /dev/null +++ b/reinheit/apps/ingredients/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class IngredientsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "reinheit.apps.ingredients" diff --git a/reinheit/apps/ingredients/migrations/0001_initial.py b/reinheit/apps/ingredients/migrations/0001_initial.py new file mode 100644 index 0000000..ed9f82a --- /dev/null +++ b/reinheit/apps/ingredients/migrations/0001_initial.py @@ -0,0 +1,62 @@ +# Generated by Django 5.0.6 on 2024-06-24 20:32 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Producer", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name="Ingredient", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "kind", + models.CharField( + choices=[ + ("YEAST", "Yeast"), + ("FERM", "Fermentable"), + ("HOP", "Hop"), + ], + max_length=5, + ), + ), + ("name", models.CharField(max_length=255)), + ( + "producer", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="ingredients.producer", + ), + ), + ], + ), + ] diff --git a/reinheit/apps/ingredients/migrations/0002_ingredient_description.py b/reinheit/apps/ingredients/migrations/0002_ingredient_description.py new file mode 100644 index 0000000..81ed15c --- /dev/null +++ b/reinheit/apps/ingredients/migrations/0002_ingredient_description.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0.6 on 2024-06-24 20:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("ingredients", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="ingredient", + name="description", + field=models.TextField(default=""), + preserve_default=False, + ), + ] diff --git a/reinheit/apps/ingredients/migrations/0003_alter_ingredient_description.py b/reinheit/apps/ingredients/migrations/0003_alter_ingredient_description.py new file mode 100644 index 0000000..069e656 --- /dev/null +++ b/reinheit/apps/ingredients/migrations/0003_alter_ingredient_description.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-06-24 20:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("ingredients", "0002_ingredient_description"), + ] + + operations = [ + migrations.AlterField( + model_name="ingredient", + name="description", + field=models.TextField(blank=True, default=""), + ), + ] diff --git a/reinheit/apps/ingredients/migrations/0004_alter_ingredient_description.py b/reinheit/apps/ingredients/migrations/0004_alter_ingredient_description.py new file mode 100644 index 0000000..70c979e --- /dev/null +++ b/reinheit/apps/ingredients/migrations/0004_alter_ingredient_description.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-06-24 20:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("ingredients", "0003_alter_ingredient_description"), + ] + + operations = [ + migrations.AlterField( + model_name="ingredient", + name="description", + field=models.CharField(blank=True, default="", max_length=255), + ), + ] diff --git a/reinheit/apps/ingredients/migrations/0005_alter_ingredient_kind.py b/reinheit/apps/ingredients/migrations/0005_alter_ingredient_kind.py new file mode 100644 index 0000000..543c8fd --- /dev/null +++ b/reinheit/apps/ingredients/migrations/0005_alter_ingredient_kind.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.6 on 2024-06-24 20:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("ingredients", "0004_alter_ingredient_description"), + ] + + operations = [ + migrations.AlterField( + model_name="ingredient", + name="kind", + field=models.CharField( + choices=[ + ("YEAST", "Yeast"), + ("FERM", "Fermentable"), + ("HOP", "Hop"), + ("CHEM", "Chemical"), + ], + max_length=5, + ), + ), + ] diff --git a/reinheit/apps/ingredients/migrations/__init__.py b/reinheit/apps/ingredients/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/reinheit/apps/ingredients/models.py b/reinheit/apps/ingredients/models.py new file mode 100644 index 0000000..1ae0d98 --- /dev/null +++ b/reinheit/apps/ingredients/models.py @@ -0,0 +1,28 @@ +from django.db import models + +# Create your models here. + + +class Ingredient(models.Model): + + class Type(models.TextChoices): + YEAST = "YEAST", "Yeast" + FERMENTABLE = "FERM", "Fermentable" + HOP = "HOP", "Hop" + CHEMICAL = "CHEM", "Chemical" + + kind = models.CharField(max_length=5, choices=Type) + + name = models.CharField(max_length=255) + description = models.CharField(max_length=255, blank=True, default="") + producer = models.ForeignKey("Producer", on_delete=models.CASCADE) + + def __str__(self): + return f"{self.get_kind_display()}: {self.name}" + + +class Producer(models.Model): + name = models.CharField(max_length=255) + + def __str__(self): + return self.name diff --git a/reinheit/apps/ingredients/tests.py b/reinheit/apps/ingredients/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/reinheit/apps/ingredients/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/reinheit/apps/ingredients/views.py b/reinheit/apps/ingredients/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/reinheit/apps/ingredients/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/reinheit/apps/styles/__init__.py b/reinheit/apps/styles/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/reinheit/apps/styles/admin.py b/reinheit/apps/styles/admin.py new file mode 100644 index 0000000..2639ac3 --- /dev/null +++ b/reinheit/apps/styles/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +from .models import Style + +# Register your models here. + + +admin.site.register(Style) diff --git a/reinheit/apps/styles/apps.py b/reinheit/apps/styles/apps.py new file mode 100644 index 0000000..d6b38db --- /dev/null +++ b/reinheit/apps/styles/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class StylesConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "reinheit.apps.styles" diff --git a/reinheit/apps/styles/migrations/0001_initial.py b/reinheit/apps/styles/migrations/0001_initial.py new file mode 100644 index 0000000..461e05b --- /dev/null +++ b/reinheit/apps/styles/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.6 on 2024-06-24 20:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Style", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255)), + ], + ), + ] diff --git a/reinheit/apps/styles/migrations/__init__.py b/reinheit/apps/styles/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/reinheit/apps/styles/models.py b/reinheit/apps/styles/models.py new file mode 100644 index 0000000..f616d85 --- /dev/null +++ b/reinheit/apps/styles/models.py @@ -0,0 +1,10 @@ +from django.db import models + +# Create your models here. + + +class Style(models.Model): + name = models.CharField(max_length=255) + + def __str__(self): + return self.name diff --git a/reinheit/apps/styles/tests.py b/reinheit/apps/styles/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/reinheit/apps/styles/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/reinheit/apps/styles/views.py b/reinheit/apps/styles/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/reinheit/apps/styles/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/requirements/base.in b/requirements/base.in new file mode 100644 index 0000000..37d30e1 --- /dev/null +++ b/requirements/base.in @@ -0,0 +1,2 @@ +django +django-bootstrap5 diff --git a/requirements/base.txt b/requirements/base.txt new file mode 100644 index 0000000..f86b21e --- /dev/null +++ b/requirements/base.txt @@ -0,0 +1,12 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile requirements/base.in +asgiref==3.8.1 + # via django +django==5.0.6 + # via + # -r requirements/base.in + # django-bootstrap5 +django-bootstrap5==24.2 + # via -r requirements/base.in +sqlparse==0.5.0 + # via django