Compare commits
16 Commits
master
...
ingredient
Author | SHA1 | Date | |
---|---|---|---|
7c25b8924a | |||
b39bfdd267 | |||
53b7a1b128 | |||
e8c9196fc0 | |||
93b93a1448 | |||
3fd248d8b7 | |||
90bf91c3ed | |||
570ede627f | |||
07c881bdfe | |||
1d830eb22d | |||
1986d9fbd0 | |||
671ea27d9f | |||
b6be827d14 | |||
2f33a27e47 | |||
12fca700da | |||
ea803ed009 |
@ -2,7 +2,7 @@ from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
from django.apps import apps
|
||||
|
||||
from beer.models import Batch, BatchRecipe, Mash, MashStep, \
|
||||
from beer.models import Batch, Recipe, Mash, MashStep, \
|
||||
RecipeFermentable, RecipeHop, RecipeMisc, RecipeYeast
|
||||
from yeast.models import Yeast
|
||||
|
||||
@ -34,9 +34,9 @@ class StrainInline(admin.TabularInline):
|
||||
extra = 1
|
||||
|
||||
|
||||
@admin.register(BatchRecipe)
|
||||
class BatchRecipeAdmin(admin.ModelAdmin):
|
||||
list_display = ['name']
|
||||
@admin.register(Recipe)
|
||||
class RecipeAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'total_extract_kg']
|
||||
inlines = [
|
||||
FermentableInline,
|
||||
HopInline,
|
||||
@ -47,11 +47,40 @@ class BatchRecipeAdmin(admin.ModelAdmin):
|
||||
|
||||
@admin.register(Batch)
|
||||
class BeerBatchAdmin(admin.ModelAdmin):
|
||||
list_display = ['brewfather_id', 'batch_url']
|
||||
list_display = [
|
||||
'brewfather_id',
|
||||
'batch_url',
|
||||
'brewhouse_efficiency',
|
||||
'conversion_efficiency',
|
||||
]
|
||||
inlines = [
|
||||
SampleInline,
|
||||
]
|
||||
|
||||
fieldsets = [
|
||||
[None, {
|
||||
'fields': [
|
||||
('brewfather_id', 'brewfather_num', 'brewfather_name'),
|
||||
'recipe',
|
||||
]
|
||||
}],
|
||||
['Mash', {
|
||||
'fields': ['first_runnings', 'mash_ph'],
|
||||
}],
|
||||
['Boil', {
|
||||
'fields': [
|
||||
('pre_boil_vol', 'pre_boil_sg'),
|
||||
('post_boil_vol', 'post_boil_sg'),
|
||||
],
|
||||
}],
|
||||
['Ferment', {
|
||||
'fields': [
|
||||
('fermenter_topup_vol', 'fermenter_vol'),
|
||||
('original_sg', 'final_sg'),
|
||||
],
|
||||
}],
|
||||
]
|
||||
|
||||
def batch_url(self, obj):
|
||||
url_string = ('<a href="{root}/tabs/batches/batch/{batch_id}">'
|
||||
'Brewfather Batch ID: {batch_id}</a>')
|
||||
|
@ -9,6 +9,7 @@ RECIPE_URL = 'https://api.brewfather.app/v2/recipes'
|
||||
BATCH_URL = 'https://api.brewfather.app/v2/batches'
|
||||
PULL_LIMIT = 50
|
||||
|
||||
|
||||
BREWFATHER_CONVERT_LOOKUP = { # local_name: brewfather_name
|
||||
'all': {
|
||||
'name': 'name',
|
||||
@ -86,3 +87,77 @@ def get_batches(api_user, api_key, batch=''):
|
||||
data = data + get_batches(batch=last_id)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def sg_plato(sg):
|
||||
"""Convert specific gravtiy to °P."""
|
||||
sg = float(sg)
|
||||
return (-1*616.868) + (1111.14*sg) - (630.272*sg**2) + (135.997*sg**3)
|
||||
|
||||
|
||||
def plato_sg(plato):
|
||||
"""Convert °P to specific gravtiy."""
|
||||
return 1 + (plato / (258.6 - (plato/258.2) * 227.1))
|
||||
|
||||
|
||||
def kg_extract(v, sg):
|
||||
"""Calculate kg of extract based on volume and SG."""
|
||||
return float(v) * float(sg) * sg_plato(sg)/100
|
||||
|
||||
|
||||
def convert(value, start_unit, end_unit):
|
||||
"""Convert units."""
|
||||
|
||||
family_dict = unit_family(start_unit)
|
||||
|
||||
if family_dict:
|
||||
start = [val for key, val in family_dict.items()
|
||||
if start_unit in key.split(',')][0]
|
||||
|
||||
end = [val for key, val in family_dict.items()
|
||||
if end_unit in key.split(',')][0]
|
||||
|
||||
return float(value) * end / start
|
||||
|
||||
|
||||
def unit_family(unit_name):
|
||||
"""Find unit family base on unit name."""
|
||||
|
||||
unit_lookup = [
|
||||
{'name': 'length',
|
||||
'units': {
|
||||
'm,meter,meters': 1.0,
|
||||
'cm,centimeter,centimeters': 100,
|
||||
'mm,millimeter,millimeters': 1000,
|
||||
'in,inch,inches': 39.3701,
|
||||
'yd,yard,yards': 1.09361,
|
||||
'ft,foot,feet': 3.28084,
|
||||
'mi,mile,miles': 0.000621371,
|
||||
'km,killometer,killometers': .001,
|
||||
}},
|
||||
{'name': 'mass',
|
||||
'units': {
|
||||
'kg,kilogram,kilograms': 1.0,
|
||||
'g,gram,grams': 1000,
|
||||
'lb,pound,pounds': 2.20462,
|
||||
'oz,ounce,ounces': 35.274,
|
||||
'st,stone': 0.157473,
|
||||
}},
|
||||
{'name': 'volume',
|
||||
'units': {
|
||||
'l,liter,liters': 1.0,
|
||||
'ml,milliliter,milliliters': 1000,
|
||||
'floz,fluid ounce,fluid ounces': 33.814,
|
||||
'cup,cups': 4.22675,
|
||||
'qt,quart,quarts': 1.05669,
|
||||
'gal,gallon,gallons': 0.264172,
|
||||
'ft^3,cubic foot,cubic feet': 0.0353147,
|
||||
'pt,pint,pints': 4.22675/2,
|
||||
'tsp,teaspoon,teaspoons': 202.884,
|
||||
'tbsp,tablespoon,tablespoons': 202.884/3,
|
||||
}},
|
||||
]
|
||||
|
||||
for family in unit_lookup:
|
||||
if [key for key in family['units'] if unit_name in key.split(',')]:
|
||||
return family['units']
|
||||
|
17
beer/migrations/0010_alter_batchrecipe_options.py
Normal file
17
beer/migrations/0010_alter_batchrecipe_options.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-20 18:33
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0009_batchrecipe_equipment_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='batchrecipe',
|
||||
options={'verbose_name': 'Recipe', 'verbose_name_plural': 'Recipes'},
|
||||
),
|
||||
]
|
17
beer/migrations/0011_rename_batchrecipe_recipe.py
Normal file
17
beer/migrations/0011_rename_batchrecipe_recipe.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-20 18:59
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0010_alter_batchrecipe_options'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='BatchRecipe',
|
||||
new_name='Recipe',
|
||||
),
|
||||
]
|
18
beer/migrations/0012_recipe_fermentables.py
Normal file
18
beer/migrations/0012_recipe_fermentables.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-21 13:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0011_rename_batchrecipe_recipe'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='recipe',
|
||||
name='fermentables',
|
||||
field=models.ManyToManyField(through='beer.RecipeFermentable', to='beer.fermentable'),
|
||||
),
|
||||
]
|
@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-24 18:56
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0012_recipe_fermentables'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='batch',
|
||||
name='first_runnings',
|
||||
field=models.DecimalField(blank=True, decimal_places=4, max_digits=8, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='equipmentprofile',
|
||||
name='mash_ratio',
|
||||
field=models.DecimalField(decimal_places=2, default=2.6, max_digits=6),
|
||||
),
|
||||
]
|
@ -0,0 +1,33 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-25 15:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0013_batch_first_runnings_equipmentprofile_mash_ratio'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='batch',
|
||||
name='post_boil_sg',
|
||||
field=models.DecimalField(blank=True, decimal_places=4, max_digits=6, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='batch',
|
||||
name='post_boil_vol',
|
||||
field=models.DecimalField(blank=True, decimal_places=4, max_digits=8, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='fermentable',
|
||||
name='moisture',
|
||||
field=models.DecimalField(blank=True, decimal_places=4, default=0.04, max_digits=6, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='fermentable',
|
||||
name='potential',
|
||||
field=models.DecimalField(decimal_places=4, default=0.8, max_digits=6),
|
||||
),
|
||||
]
|
@ -0,0 +1,53 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-26 14:11
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0014_batch_post_boil_sg_batch_post_boil_vol_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='batch',
|
||||
name='fermenter_topup_vol',
|
||||
field=models.DecimalField(blank=True, decimal_places=4, max_digits=8, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='batch',
|
||||
name='fermenter_vol',
|
||||
field=models.DecimalField(blank=True, decimal_places=4, max_digits=8, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='batch',
|
||||
name='final_sg',
|
||||
field=models.DecimalField(blank=True, decimal_places=4, max_digits=5, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='batch',
|
||||
name='mash_ph',
|
||||
field=models.DecimalField(blank=True, decimal_places=3, max_digits=4, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='batch',
|
||||
name='original_sg',
|
||||
field=models.DecimalField(blank=True, decimal_places=4, max_digits=5, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='batch',
|
||||
name='pre_boil_sg',
|
||||
field=models.DecimalField(blank=True, decimal_places=4, max_digits=5, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='batch',
|
||||
name='pre_boil_vol',
|
||||
field=models.DecimalField(blank=True, decimal_places=4, max_digits=8, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='batch',
|
||||
name='post_boil_sg',
|
||||
field=models.DecimalField(blank=True, decimal_places=4, max_digits=5, null=True),
|
||||
),
|
||||
]
|
@ -0,0 +1,103 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-26 17:49
|
||||
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0015_batch_fermenter_topup_vol_batch_fermenter_vol_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelTableComment(
|
||||
name='equipmentprofile',
|
||||
table_comment='Volumes in liters and weights in kg.',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='batch',
|
||||
name='created_date',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='created_date',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='leaf_hop_trub',
|
||||
field=models.DecimalField(db_comment='liters/gram', decimal_places=10, default=0.0083454045, max_digits=12),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='pellet_hop_trub',
|
||||
field=models.DecimalField(db_comment='liters/gram', decimal_places=10, default=0.003338162, max_digits=12),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='fermentable',
|
||||
name='created_date',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hop',
|
||||
name='created_date',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mash',
|
||||
name='created_date',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mashstep',
|
||||
name='created_date',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='misc',
|
||||
name='created_date',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='recipe',
|
||||
name='created_date',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='recipefermentable',
|
||||
name='created_date',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='recipehop',
|
||||
name='created_date',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='recipemisc',
|
||||
name='created_date',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='recipeyeast',
|
||||
name='created_date',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplier',
|
||||
name='created_date',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unit',
|
||||
name='created_date',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='userprofile',
|
||||
name='created_date',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
|
||||
),
|
||||
]
|
@ -0,0 +1,68 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-26 18:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0016_alter_equipmentprofile_table_comment_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='batch_volume',
|
||||
field=models.DecimalField(decimal_places=2, default=5.5, help_text='liters', max_digits=6),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='grain_absorption',
|
||||
field=models.DecimalField(decimal_places=2, default=0.12, help_text='liters/kilogram', max_digits=6),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='hlt_deadspace',
|
||||
field=models.DecimalField(decimal_places=2, default=0.25, help_text='liters', max_digits=6),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='kettle_boil_rate',
|
||||
field=models.DecimalField(decimal_places=2, default=0.5, help_text='liters/hr', max_digits=6),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='kettle_deadspace',
|
||||
field=models.DecimalField(decimal_places=2, default=0.25, help_text='liters', max_digits=6),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='kettle_plumbing_loss',
|
||||
field=models.DecimalField(decimal_places=2, default=0.25, help_text='liters', max_digits=6),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='leaf_hop_trub',
|
||||
field=models.DecimalField(decimal_places=10, default=0.0083454045, help_text='liters/gram', max_digits=12),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='mash_ratio',
|
||||
field=models.DecimalField(decimal_places=2, default=2.6, help_text='liters/kilogram', max_digits=6),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='mt_capacity',
|
||||
field=models.DecimalField(decimal_places=2, default=10, help_text='liters', max_digits=6),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='mt_deadspace',
|
||||
field=models.DecimalField(decimal_places=2, default=0.25, help_text='liters', max_digits=6),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='pellet_hop_trub',
|
||||
field=models.DecimalField(decimal_places=10, default=0.003338162, help_text='liters/gram', max_digits=12),
|
||||
),
|
||||
]
|
@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-26 19:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0017_alter_equipmentprofile_batch_volume_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='recipe',
|
||||
name='bjcp_category_id',
|
||||
field=models.CharField(default='1', max_length=3),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='recipe',
|
||||
name='bjcp_style_id',
|
||||
field=models.CharField(default='1A', max_length=3),
|
||||
),
|
||||
]
|
18
beer/migrations/0019_alter_recipe_bjcp_style_id.py
Normal file
18
beer/migrations/0019_alter_recipe_bjcp_style_id.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-26 19:43
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0018_recipe_bjcp_category_id_recipe_bjcp_style_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='recipe',
|
||||
name='bjcp_style_id',
|
||||
field=models.CharField(choices=[('1A', 'American Light Lager'), ('1B', 'American Lager'), ('1C', 'Cream Ale'), ('1D', 'American Wheat Beer'), ('2A', 'International Pale Lager'), ('2B', 'International Amber Lager'), ('2C', 'International Dark Lager'), ('3A', 'Czech Pale Lager'), ('3B', 'Czech Premium Pale Lager'), ('3C', 'Czech Amber Lager'), ('3D', 'Czech Dark Lager'), ('4A', 'Munich Helles'), ('4B', 'Festbier'), ('4C', 'Helles Bock'), ('5A', 'German Leichtbier'), ('5B', 'Kölsch'), ('5C', 'German Helles Exportbier'), ('5D', 'German Pils'), ('6A', 'Märzen'), ('6B', 'Rauchbier'), ('6C', 'Dunkles Bock'), ('7A', 'Vienna Lager'), ('7B', 'Altbier'), ('8A', 'Munich Dunkel'), ('8B', 'Schwarzbier'), ('9A', 'Doppelbock'), ('9B', 'Eisbock'), ('9C', 'Baltic Porter'), ('10A', 'Weissbier'), ('10B', 'Dunkles Weissbier'), ('10C', 'Weizenbock'), ('11A', 'Ordinary Bitter'), ('11B', 'Best Bitter'), ('11C', 'Strong Bitter'), ('12A', 'British Golden Ale'), ('12B', 'Australian Sparkling Ale'), ('12C', 'English IPA'), ('13A', 'Dark Mild'), ('13B', 'British Brown Ale'), ('13C', 'English Porter'), ('14A', 'Scottish Light'), ('14B', 'Scottish Heavy'), ('14C', 'Scottish Export'), ('15A', 'Irish Red Ale'), ('15B', 'Irish Stout'), ('15C', 'Irish Extra Stout'), ('16A', 'Sweet Stout'), ('16B', 'Oatmeal Stout'), ('16C', 'Tropical Stout'), ('16D', 'Foreign Extra Stout'), ('17A', 'British Strong Ale'), ('17B', 'Old Ale'), ('17C', 'Wee Heavy'), ('17D', 'English Barley Wine'), ('18A', 'Blonde Ale'), ('18B', 'American Pale Ale'), ('19A', 'American Amber Ale'), ('19B', 'California Common'), ('19C', 'American Brown Ale'), ('20A', 'American Porter'), ('20B', 'American Stout'), ('20C', 'Imperial Stout'), ('21A', 'American IPA'), ('21B', 'Specialty IPA'), ('21C', 'Hazy IPA'), ('22A', 'Double IPA'), ('22B', 'American Strong Ale'), ('22C', 'American Barleywine'), ('22D', 'Wheatwine'), ('23A', 'Berliner Weisse'), ('23B', 'Flanders Red Ale'), ('23C', 'Oud Bruin'), ('23D', 'Lambic'), ('23E', 'Gueuze'), ('23F', 'Fruit Lambic'), ('23G', 'Gose'), ('24A', 'Witbier'), ('24B', 'Belgian Pale Ale'), ('24C', 'Bière de Garde'), ('25A', 'Belgian Blond Ale'), ('25B', 'Saison'), ('25C', 'Belgian Golden Strong Ale'), ('26A', 'Belgian Single'), ('26B', 'Belgian Dubbel'), ('26C', 'Belgian Tripel'), ('26D', 'Belgian Dark Strong Ale'), ('28A', 'Brett Beer'), ('28B', 'Mixed-Fermentation Sour Beer'), ('28C', 'Wild Specialty Beer'), ('28D', 'Straight Sour Beer'), ('29A', 'Fruit Beer'), ('29B', 'Fruit and Spice Beer'), ('29C', 'Specialty Fruit Beer'), ('29D', 'Grape Ale'), ('30A', 'Spice, Herb, or Vegetable Beer'), ('30B', 'Autumn Seasonal Beer'), ('30C', 'Winter Seasonal Beer'), ('30D', 'Specialty Spice Beer'), ('31A', 'Alternative Grain Beer'), ('31B', 'Alternative Sugar Beer'), ('32A', 'Classic Style Smoked Beer'), ('32B', 'Specialty Smoked Beer'), ('34A', 'Commercial Specialty Beer'), ('34B', 'Mixed-Style Beer'), ('34C', 'Experimental Beer'), ('X1', 'Dorada Pampeana'), ('X2', 'IPA Argenta'), ('X3', 'Italian Grape Ale'), ('X4', 'Catharina Sour'), ('X5', 'New Zealand Pilsner')], default='1A', max_length=3),
|
||||
),
|
||||
]
|
18
beer/migrations/0020_alter_recipe_bjcp_style_id.py
Normal file
18
beer/migrations/0020_alter_recipe_bjcp_style_id.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-26 19:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0019_alter_recipe_bjcp_style_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='recipe',
|
||||
name='bjcp_style_id',
|
||||
field=models.CharField(choices=[('1A', '1A: American Light Lager'), ('1B', '1B: American Lager'), ('1C', '1C: Cream Ale'), ('1D', '1D: American Wheat Beer'), ('2A', '2A: International Pale Lager'), ('2B', '2B: International Amber Lager'), ('2C', '2C: International Dark Lager'), ('3A', '3A: Czech Pale Lager'), ('3B', '3B: Czech Premium Pale Lager'), ('3C', '3C: Czech Amber Lager'), ('3D', '3D: Czech Dark Lager'), ('4A', '4A: Munich Helles'), ('4B', '4B: Festbier'), ('4C', '4C: Helles Bock'), ('5A', '5A: German Leichtbier'), ('5B', '5B: Kölsch'), ('5C', '5C: German Helles Exportbier'), ('5D', '5D: German Pils'), ('6A', '6A: Märzen'), ('6B', '6B: Rauchbier'), ('6C', '6C: Dunkles Bock'), ('7A', '7A: Vienna Lager'), ('7B', '7B: Altbier'), ('8A', '8A: Munich Dunkel'), ('8B', '8B: Schwarzbier'), ('9A', '9A: Doppelbock'), ('9B', '9B: Eisbock'), ('9C', '9C: Baltic Porter'), ('10A', '10A: Weissbier'), ('10B', '10B: Dunkles Weissbier'), ('10C', '10C: Weizenbock'), ('11A', '11A: Ordinary Bitter'), ('11B', '11B: Best Bitter'), ('11C', '11C: Strong Bitter'), ('12A', '12A: British Golden Ale'), ('12B', '12B: Australian Sparkling Ale'), ('12C', '12C: English IPA'), ('13A', '13A: Dark Mild'), ('13B', '13B: British Brown Ale'), ('13C', '13C: English Porter'), ('14A', '14A: Scottish Light'), ('14B', '14B: Scottish Heavy'), ('14C', '14C: Scottish Export'), ('15A', '15A: Irish Red Ale'), ('15B', '15B: Irish Stout'), ('15C', '15C: Irish Extra Stout'), ('16A', '16A: Sweet Stout'), ('16B', '16B: Oatmeal Stout'), ('16C', '16C: Tropical Stout'), ('16D', '16D: Foreign Extra Stout'), ('17A', '17A: British Strong Ale'), ('17B', '17B: Old Ale'), ('17C', '17C: Wee Heavy'), ('17D', '17D: English Barley Wine'), ('18A', '18A: Blonde Ale'), ('18B', '18B: American Pale Ale'), ('19A', '19A: American Amber Ale'), ('19B', '19B: California Common'), ('19C', '19C: American Brown Ale'), ('20A', '20A: American Porter'), ('20B', '20B: American Stout'), ('20C', '20C: Imperial Stout'), ('21A', '21A: American IPA'), ('21B', '21B: Specialty IPA'), ('21C', '21C: Hazy IPA'), ('22A', '22A: Double IPA'), ('22B', '22B: American Strong Ale'), ('22C', '22C: American Barleywine'), ('22D', '22D: Wheatwine'), ('23A', '23A: Berliner Weisse'), ('23B', '23B: Flanders Red Ale'), ('23C', '23C: Oud Bruin'), ('23D', '23D: Lambic'), ('23E', '23E: Gueuze'), ('23F', '23F: Fruit Lambic'), ('23G', '23G: Gose'), ('24A', '24A: Witbier'), ('24B', '24B: Belgian Pale Ale'), ('24C', '24C: Bière de Garde'), ('25A', '25A: Belgian Blond Ale'), ('25B', '25B: Saison'), ('25C', '25C: Belgian Golden Strong Ale'), ('26A', '26A: Belgian Single'), ('26B', '26B: Belgian Dubbel'), ('26C', '26C: Belgian Tripel'), ('26D', '26D: Belgian Dark Strong Ale'), ('28A', '28A: Brett Beer'), ('28B', '28B: Mixed-Fermentation Sour Beer'), ('28C', '28C: Wild Specialty Beer'), ('28D', '28D: Straight Sour Beer'), ('29A', '29A: Fruit Beer'), ('29B', '29B: Fruit and Spice Beer'), ('29C', '29C: Specialty Fruit Beer'), ('29D', '29D: Grape Ale'), ('30A', '30A: Spice, Herb, or Vegetable Beer'), ('30B', '30B: Autumn Seasonal Beer'), ('30C', '30C: Winter Seasonal Beer'), ('30D', '30D: Specialty Spice Beer'), ('31A', '31A: Alternative Grain Beer'), ('31B', '31B: Alternative Sugar Beer'), ('32A', '32A: Classic Style Smoked Beer'), ('32B', '32B: Specialty Smoked Beer'), ('34A', '34A: Commercial Specialty Beer'), ('34B', '34B: Mixed-Style Beer'), ('34C', '34C: Experimental Beer'), ('X1', 'X1: Dorada Pampeana'), ('X2', 'X2: IPA Argenta'), ('X3', 'X3: Italian Grape Ale'), ('X4', 'X4: Catharina Sour'), ('X5', 'X5: New Zealand Pilsner')], default='1A', max_length=3),
|
||||
),
|
||||
]
|
17
beer/migrations/0021_remove_recipe_bjcp_category_id.py
Normal file
17
beer/migrations/0021_remove_recipe_bjcp_category_id.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-26 19:47
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0020_alter_recipe_bjcp_style_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='recipe',
|
||||
name='bjcp_category_id',
|
||||
),
|
||||
]
|
72
beer/migrations/0022_bjcpstyle_alter_recipe_bjcp_style_id.py
Normal file
72
beer/migrations/0022_bjcpstyle_alter_recipe_bjcp_style_id.py
Normal file
@ -0,0 +1,72 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-27 13:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0021_remove_recipe_bjcp_category_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BjcpStyle',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.TextField(blank=True, null=True)),
|
||||
('category', models.TextField(blank=True, null=True)),
|
||||
('category_id', models.TextField(blank=True, null=True)),
|
||||
('style_id', models.TextField(blank=True, null=True)),
|
||||
('category_description', models.TextField(blank=True, null=True)),
|
||||
('overall_impression', models.TextField(blank=True, null=True)),
|
||||
('aroma', models.TextField(blank=True, null=True)),
|
||||
('appearance', models.TextField(blank=True, null=True)),
|
||||
('flavor', models.TextField(blank=True, null=True)),
|
||||
('mouthfeel', models.TextField(blank=True, null=True)),
|
||||
('comments', models.TextField(blank=True, null=True)),
|
||||
('history', models.TextField(blank=True, null=True)),
|
||||
('style_comparison', models.TextField(blank=True, null=True)),
|
||||
('tags', models.TextField(blank=True, null=True)),
|
||||
('original_gravity_minimum_unit', models.TextField(blank=True, null=True)),
|
||||
('original_gravity_minimum_value', models.DecimalField(blank=True, decimal_places=5, max_digits=10, null=True)),
|
||||
('original_gravity_maximum_unit', models.TextField(blank=True, null=True)),
|
||||
('original_gravity_maximum_value', models.DecimalField(blank=True, decimal_places=5, max_digits=10, null=True)),
|
||||
('international_bitterness_units_minimum_unit', models.TextField(blank=True, null=True)),
|
||||
('international_bitterness_units_minimum_value', models.DecimalField(blank=True, decimal_places=5, max_digits=10, null=True)),
|
||||
('international_bitterness_units_maximum_unit', models.TextField(blank=True, null=True)),
|
||||
('international_bitterness_units_maximum_value', models.DecimalField(blank=True, decimal_places=5, max_digits=10, null=True)),
|
||||
('final_gravity_minimum_unit', models.TextField(blank=True, null=True)),
|
||||
('final_gravity_minimum_value', models.DecimalField(blank=True, decimal_places=5, max_digits=10, null=True)),
|
||||
('final_gravity_maximum_unit', models.TextField(blank=True, null=True)),
|
||||
('final_gravity_maximum_value', models.DecimalField(blank=True, decimal_places=5, max_digits=10, null=True)),
|
||||
('alcohol_by_volume_minimum_unit', models.TextField(blank=True, null=True)),
|
||||
('alcohol_by_volume_minimum_value', models.DecimalField(blank=True, decimal_places=5, max_digits=10, null=True)),
|
||||
('alcohol_by_volume_maximum_unit', models.TextField(blank=True, null=True)),
|
||||
('alcohol_by_volume_maximum_value', models.DecimalField(blank=True, decimal_places=5, max_digits=10, null=True)),
|
||||
('color_minimum_unit', models.TextField(blank=True, null=True)),
|
||||
('color_minimum_value', models.DecimalField(blank=True, decimal_places=5, max_digits=10, null=True)),
|
||||
('color_maximum_unit', models.TextField(blank=True, null=True)),
|
||||
('color_maximum_value', models.DecimalField(blank=True, decimal_places=5, max_digits=10, null=True)),
|
||||
('ingredients', models.TextField(blank=True, null=True)),
|
||||
('examples', models.TextField(blank=True, null=True)),
|
||||
('style_guide', models.TextField(blank=True, null=True)),
|
||||
('type', models.TextField(blank=True, null=True)),
|
||||
('entry_instructions', models.TextField(blank=True, null=True)),
|
||||
('notes', models.TextField(blank=True, null=True)),
|
||||
('currently_defined_types', models.TextField(blank=True, null=True)),
|
||||
('strength_classifications', models.TextField(blank=True, null=True)),
|
||||
('vital_statistics', models.TextField(blank=True, null=True)),
|
||||
('profile', models.TextField(blank=True, null=True)),
|
||||
('comparison', models.TextField(blank=True, null=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'beer_bjcp',
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='recipe',
|
||||
name='bjcp_style_id',
|
||||
field=models.CharField(choices=[('1A', '1A: American Light Lager'), ('1B', '1B: American Lager'), ('1C', '1C: Cream Ale'), ('1D', '1D: American Wheat Beer'), ('2A', '2A: International Pale Lager'), ('2B', '2B: International Amber Lager'), ('2C', '2C: International Dark Lager'), ('3A', '3A: Czech Pale Lager'), ('3B', '3B: Czech Premium Pale Lager'), ('3C', '3C: Czech Amber Lager'), ('3D', '3D: Czech Dark Lager'), ('4A', '4A: Munich Helles'), ('4B', '4B: Festbier'), ('4C', '4C: Helles Bock'), ('5A', '5A: German Leichtbier'), ('5B', '5B: Kölsch'), ('5C', '5C: German Helles Exportbier'), ('5D', '5D: German Pils'), ('6A', '6A: Märzen'), ('6B', '6B: Rauchbier'), ('6C', '6C: Dunkles Bock'), ('7A', '7A: Vienna Lager'), ('7B', '7B: Altbier'), ('8A', '8A: Munich Dunkel'), ('8B', '8B: Schwarzbier'), ('9A', '9A: Doppelbock'), ('9B', '9B: Eisbock'), ('9C', '9C: Baltic Porter'), ('10A', '10A: Weissbier'), ('10B', '10B: Dunkles Weissbier'), ('10C', '10C: Weizenbock'), ('11A', '11A: Ordinary Bitter'), ('11B', '11B: Best Bitter'), ('11C', '11C: Strong Bitter'), ('12A', '12A: British Golden Ale'), ('12B', '12B: Australian Sparkling Ale'), ('12C', '12C: English IPA'), ('13A', '13A: Dark Mild'), ('13B', '13B: British Brown Ale'), ('13C', '13C: English Porter'), ('14A', '14A: Scottish Light'), ('14B', '14B: Scottish Heavy'), ('14C', '14C: Scottish Export'), ('15A', '15A: Irish Red Ale'), ('15B', '15B: Irish Stout'), ('15C', '15C: Irish Extra Stout'), ('16A', '16A: Sweet Stout'), ('16B', '16B: Oatmeal Stout'), ('16C', '16C: Tropical Stout'), ('16D', '16D: Foreign Extra Stout'), ('17A', '17A: British Strong Ale'), ('17B', '17B: Old Ale'), ('17C', '17C: Wee Heavy'), ('17D', '17D: English Barley Wine'), ('18A', '18A: Blonde Ale'), ('18B', '18B: American Pale Ale'), ('19A', '19A: American Amber Ale'), ('19B', '19B: California Common'), ('19C', '19C: American Brown Ale'), ('20A', '20A: American Porter'), ('20B', '20B: American Stout'), ('20C', '20C: Imperial Stout'), ('21A', '21A: American IPA'), ('21B', '21B: Specialty IPA'), ('21C', '21C: Hazy IPA'), ('22A', '22A: Double IPA'), ('22B', '22B: American Strong Ale'), ('22C', '22C: American Barleywine'), ('22D', '22D: Wheatwine'), ('23A', '23A: Berliner Weisse'), ('23B', '23B: Flanders Red Ale'), ('23C', '23C: Oud Bruin'), ('23D', '23D: Lambic'), ('23E', '23E: Gueuze'), ('23F', '23F: Fruit Lambic'), ('23G', '23G: Gose'), ('24A', '24A: Witbier'), ('24B', '24B: Belgian Pale Ale'), ('24C', '24C: Bière de Garde'), ('25A', '25A: Belgian Blond Ale'), ('25B', '25B: Saison'), ('25C', '25C: Belgian Golden Strong Ale'), ('26A', '26A: Belgian Single'), ('26B', '26B: Belgian Dubbel'), ('26C', '26C: Belgian Tripel'), ('26D', '26D: Belgian Dark Strong Ale'), ('28A', '28A: Brett Beer'), ('28B', '28B: Mixed-Fermentation Sour Beer'), ('28C', '28C: Wild Specialty Beer'), ('28D', '28D: Straight Sour Beer'), ('29A', '29A: Fruit Beer'), ('29B', '29B: Fruit and Spice Beer'), ('29C', '29C: Specialty Fruit Beer'), ('29D', '29D: Grape Ale'), ('30A', '30A: Spice, Herb, or Vegetable Beer'), ('30B', '30B: Autumn Seasonal Beer'), ('30C', '30C: Winter Seasonal Beer'), ('30D', '30D: Specialty Spice Beer'), ('31A', '31A: Alternative Grain Beer'), ('31B', '31B: Alternative Sugar Beer'), ('32A', '32A: Classic Style Smoked Beer'), ('32B', '32B: Specialty Smoked Beer'), ('34A', '34A: Commercial Specialty Beer'), ('34B', '34B: Mixed-Style Beer'), ('34C', '34C: Experimental Beer'), ('X5', 'X5: New Zealand Pilsner')], default='1A', max_length=3),
|
||||
),
|
||||
]
|
@ -0,0 +1,88 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-27 13:39
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0022_bjcpstyle_alter_recipe_bjcp_style_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='alcohol_by_volume_maximum_unit',
|
||||
field=models.TextField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='alcohol_by_volume_minimum_unit',
|
||||
field=models.TextField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='category',
|
||||
field=models.TextField(blank=True, max_length=50, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='category_id',
|
||||
field=models.TextField(blank=True, max_length=5, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='color_maximum_unit',
|
||||
field=models.TextField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='color_minimum_unit',
|
||||
field=models.TextField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='final_gravity_maximum_unit',
|
||||
field=models.TextField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='final_gravity_minimum_unit',
|
||||
field=models.TextField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='international_bitterness_units_maximum_unit',
|
||||
field=models.TextField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='international_bitterness_units_minimum_unit',
|
||||
field=models.TextField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='name',
|
||||
field=models.TextField(blank=True, max_length=50, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='original_gravity_maximum_unit',
|
||||
field=models.TextField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='original_gravity_minimum_unit',
|
||||
field=models.TextField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='style_guide',
|
||||
field=models.TextField(blank=True, max_length=20, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='style_id',
|
||||
field=models.TextField(blank=True, max_length=5, null=True),
|
||||
),
|
||||
]
|
@ -0,0 +1,88 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-27 13:40
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0023_alter_bjcpstyle_alcohol_by_volume_maximum_unit_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='alcohol_by_volume_maximum_unit',
|
||||
field=models.CharField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='alcohol_by_volume_minimum_unit',
|
||||
field=models.CharField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='category',
|
||||
field=models.CharField(blank=True, max_length=50, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='category_id',
|
||||
field=models.CharField(blank=True, max_length=5, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='color_maximum_unit',
|
||||
field=models.CharField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='color_minimum_unit',
|
||||
field=models.CharField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='final_gravity_maximum_unit',
|
||||
field=models.CharField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='final_gravity_minimum_unit',
|
||||
field=models.CharField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='international_bitterness_units_maximum_unit',
|
||||
field=models.CharField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='international_bitterness_units_minimum_unit',
|
||||
field=models.CharField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='name',
|
||||
field=models.CharField(blank=True, max_length=50, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='original_gravity_maximum_unit',
|
||||
field=models.CharField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='original_gravity_minimum_unit',
|
||||
field=models.CharField(blank=True, max_length=10, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='style_guide',
|
||||
field=models.CharField(blank=True, max_length=20, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='bjcpstyle',
|
||||
name='style_id',
|
||||
field=models.CharField(blank=True, max_length=5, null=True),
|
||||
),
|
||||
]
|
369
beer/models.py
369
beer/models.py
@ -1,18 +1,25 @@
|
||||
from django.db import models
|
||||
from django.db.models import Sum, Q
|
||||
from django.contrib.staticfiles import finders
|
||||
from django.utils import timezone
|
||||
from django_cryptography.fields import encrypt
|
||||
from django.core.validators import MinValueValidator
|
||||
|
||||
from config.extras import BREWFATHER_APP_ROOT
|
||||
from beer.extras import sg_plato, plato_sg, kg_extract, convert
|
||||
from django.conf import settings
|
||||
|
||||
import json
|
||||
import logging
|
||||
logger = logging.getLogger('django')
|
||||
|
||||
|
||||
class CustomModel(models.Model):
|
||||
""" Custom model class with default fields to use. """
|
||||
created_date = models.DateTimeField(default=timezone.now)
|
||||
created_date = models.DateTimeField(
|
||||
default=timezone.now,
|
||||
editable=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
@ -33,7 +40,78 @@ class Batch(CustomModel):
|
||||
brewfather_num = models.IntegerField(default=1)
|
||||
brewfather_name = models.CharField(max_length=500, default='name')
|
||||
recipe = models.OneToOneField(
|
||||
'BatchRecipe', on_delete=models.CASCADE, default=1)
|
||||
'Recipe', on_delete=models.CASCADE, default=1)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Brewday Measurements
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
# Mash
|
||||
first_runnings = models.DecimalField(
|
||||
max_digits=8, decimal_places=4, null=True, blank=True)
|
||||
mash_ph = models.DecimalField(
|
||||
max_digits=4, decimal_places=3, null=True, blank=True)
|
||||
|
||||
# Boil
|
||||
pre_boil_vol = models.DecimalField(
|
||||
max_digits=8, decimal_places=4, null=True, blank=True)
|
||||
pre_boil_sg = models.DecimalField(
|
||||
max_digits=5, decimal_places=4, null=True, blank=True)
|
||||
post_boil_vol = models.DecimalField(
|
||||
max_digits=8, decimal_places=4, null=True, blank=True)
|
||||
post_boil_sg = models.DecimalField(
|
||||
max_digits=5, decimal_places=4, null=True, blank=True)
|
||||
|
||||
# Ferment
|
||||
fermenter_topup_vol = models.DecimalField(
|
||||
max_digits=8, decimal_places=4, null=True, blank=True)
|
||||
fermenter_vol = models.DecimalField(
|
||||
max_digits=8, decimal_places=4, null=True, blank=True)
|
||||
original_sg = models.DecimalField(
|
||||
max_digits=5, decimal_places=4, null=True, blank=True)
|
||||
final_sg = models.DecimalField(
|
||||
max_digits=5, decimal_places=4, null=True, blank=True)
|
||||
|
||||
# Properties Needed:
|
||||
# braukaiser.com/wiki/index.php/Troubleshooting_Brewhouse_Efficiency
|
||||
# - Mash Efficiency
|
||||
# - ABV
|
||||
# - Attenuation
|
||||
# - Actual Trub/Chiller Loss
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Batch Stats
|
||||
# -------------------------------------------------------------------------
|
||||
@property
|
||||
def brewhouse_efficiency(self):
|
||||
try:
|
||||
return round(self.boil_extract_kg/self.recipe.total_extract_kg, 4)
|
||||
except ZeroDivisionError:
|
||||
return 0
|
||||
|
||||
@property
|
||||
def boil_extract_kg(self):
|
||||
return kg_extract(float(self.post_boil_vol)*.96, self.post_boil_sg)
|
||||
|
||||
@property
|
||||
def conversion_efficiency(self):
|
||||
""" Calculate conversion efficiency of mash."""
|
||||
if (self.first_runnings is None
|
||||
or self.recipe.fermentable_weight_kg == 0):
|
||||
return '-'
|
||||
|
||||
return round((sg_plato(self.first_runnings)/self.recipe.fw_max)
|
||||
* (100-self.recipe.fw_max)
|
||||
/ (100-sg_plato(self.first_runnings)), 4)
|
||||
|
||||
@property
|
||||
def boil_off_calcualted(self):
|
||||
return float(self.pre_boil_vol - self.post_boil_vol) * .96
|
||||
|
||||
@property
|
||||
def trub_loss_calculated(self):
|
||||
transfered_volume = self.fermenter_vol - self.fermenter_topup_vol
|
||||
return self.post_boil_vol-transfered_volume
|
||||
|
||||
@property
|
||||
def brewfather_url(self):
|
||||
@ -77,7 +155,10 @@ class Supplier(CustomModel):
|
||||
|
||||
class CustomIngredient(CustomModel):
|
||||
""" Custom model class with default fields to use. """
|
||||
created_date = models.DateTimeField(default=timezone.now)
|
||||
created_date = models.DateTimeField(
|
||||
default=timezone.now,
|
||||
editable=False
|
||||
)
|
||||
name = models.CharField(max_length=50)
|
||||
units = models.ForeignKey(Unit, on_delete=models.PROTECT)
|
||||
unit_cost = models.DecimalField(
|
||||
@ -93,7 +174,7 @@ class CustomIngredient(CustomModel):
|
||||
abstract = True
|
||||
|
||||
|
||||
class BatchRecipe(CustomModel):
|
||||
class Recipe(CustomModel):
|
||||
""" Recipe to be stored with a batch."""
|
||||
name = models.CharField(max_length=50)
|
||||
batch_recipe = models.BooleanField(null=True)
|
||||
@ -106,25 +187,80 @@ class BatchRecipe(CustomModel):
|
||||
max_digits=6, decimal_places=2, default=75)
|
||||
batch_size = models.DecimalField(
|
||||
max_digits=6, decimal_places=2, default=11)
|
||||
fermentables = models.ManyToManyField(
|
||||
'Fermentable', through='RecipeFermentable')
|
||||
|
||||
with open(finders.find('bjcp/2021.json'), encoding='utf-8', errors="ignore") as bjcp_file:
|
||||
bjcp = json.load(bjcp_file)
|
||||
styles = bjcp['styles']
|
||||
style_ids = dict([
|
||||
(x['style_id'], '{}: {}'.format(x['style_id'], x['name']))
|
||||
for x in styles
|
||||
])
|
||||
|
||||
bjcp_style_id = models.CharField(
|
||||
max_length=3, choices=style_ids, default='1A')
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Recipe'
|
||||
verbose_name_plural = 'Recipes'
|
||||
|
||||
@property
|
||||
def fermentables(self):
|
||||
return [x for x in list(self.recipefermentable_set.all())]
|
||||
def batch_size_display(self):
|
||||
return convert(self.batch_size, 'l', 'gal')
|
||||
|
||||
@property
|
||||
def fermentable_weight(self):
|
||||
return sum([x.quantity for x in self.fermentables])
|
||||
def fw_max(self):
|
||||
potential = 0
|
||||
weight = 0
|
||||
for ferm in self.recipefermentable_set.all():
|
||||
potential += ferm.fermentable.extract_percent*float(ferm.quantity)
|
||||
weight += float(ferm.quantity)
|
||||
|
||||
e_grain = potential / weight
|
||||
|
||||
ratio = float(self.equipment.mash_ratio)
|
||||
return 100*e_grain / (ratio+e_grain)
|
||||
|
||||
@property
|
||||
def hops(self):
|
||||
return [x for x in list(self.recipehop_set.all())]
|
||||
def total_fermentable_display(self):
|
||||
return convert(self.fermentable_weight_kg, 'kg', 'lb')
|
||||
|
||||
@property
|
||||
def fermentable_weight_kg(self):
|
||||
"""Weight of all fermentables attached to recipe."""
|
||||
aggregate = self.recipefermentable_set.all().aggregate(Sum('quantity'))
|
||||
|
||||
if aggregate['quantity__sum']:
|
||||
return aggregate['quantity__sum']
|
||||
else:
|
||||
return 0
|
||||
|
||||
@property
|
||||
def total_extract_kg(self):
|
||||
extract = 0
|
||||
for f in self.recipefermentable_set.all():
|
||||
extract += f.extract_weight_kg
|
||||
|
||||
return extract
|
||||
|
||||
@property
|
||||
def total_hop_display(self):
|
||||
return convert(self.hop_weight_g, 'g', 'oz')
|
||||
|
||||
@property
|
||||
def hop_weight_g(self):
|
||||
"""Weight of all fermentables attached to recipe."""
|
||||
aggregate = self.recipehop_set.all().aggregate(Sum('quantity'))
|
||||
|
||||
if aggregate['quantity__sum']:
|
||||
return aggregate['quantity__sum']
|
||||
else:
|
||||
return 0
|
||||
|
||||
@property
|
||||
def final_volume(self):
|
||||
"""Return final volume (after boil)."""
|
||||
return (float(self.batch_size)
|
||||
+ self.hop_water_loss
|
||||
+ self.net_kettle_deadspace
|
||||
@ -132,44 +268,50 @@ class BatchRecipe(CustomModel):
|
||||
|
||||
@property
|
||||
def sugar_yield(self):
|
||||
"""Return point yield of all non-mashed ingredients."""
|
||||
|
||||
ferms = self.recipefermentable_set.all().select_related('fermentable')
|
||||
sugars = ferms.filter(fermentable__fermentable_type=3)
|
||||
|
||||
ferm_yield = 0
|
||||
|
||||
sugars = (x for x in self.fermentables
|
||||
if x.fermentable.fermentable_type == 3) # Is sugar
|
||||
|
||||
for f in sugars:
|
||||
ferm_yield += f.quantity * (f.fermentable.potential - 1) * 1000
|
||||
ferm_yield += f.extract_weight_kg
|
||||
|
||||
return float(ferm_yield)
|
||||
|
||||
@property
|
||||
def mash_yield(self):
|
||||
"""Return point yield of all mashed ingredients."""
|
||||
|
||||
ferms = self.recipefermentable_set.all().select_related('fermentable')
|
||||
mashed = ferms.filter(~Q(fermentable__fermentable_type=3))
|
||||
|
||||
mash_yield = 0
|
||||
|
||||
mashed = (x for x in self.fermentables
|
||||
if x.fermentable.fermentable_type != 3) # Is not sugar
|
||||
|
||||
for f in mashed:
|
||||
mash_yield += (f.quantity * (self.efficiency / 100)
|
||||
* (f.fermentable.potential - 1) * 1000)
|
||||
mash_yield += f.extract_weight_kg * float(self.efficiency/100)
|
||||
|
||||
return float(mash_yield)
|
||||
|
||||
@property
|
||||
def original_sg(self):
|
||||
total_yield = self.sugar_yield + self.mash_yield
|
||||
return round(1 + total_yield / self.final_volume / 1000, 3)
|
||||
"""Return original gravity."""
|
||||
total_extract = self.sugar_yield + self.mash_yield
|
||||
plato = 100 * total_extract / (self.final_volume + total_extract)
|
||||
return round(plato_sg(plato), 3)
|
||||
|
||||
@property
|
||||
def pre_boil_sg(self):
|
||||
total_yield = self.sugar_yield + self.mash_yield
|
||||
return round(1 + total_yield / (self.final_volume
|
||||
+ self.boil_off_gph) / 1000, 3)
|
||||
"""Return pre-boil gravity."""
|
||||
total_extract = self.sugar_yield + self.mash_yield
|
||||
total_water = self.final_volume+self.boil_off_gph
|
||||
plato = 100 * total_extract / (total_water + total_extract)
|
||||
return round(plato_sg(plato), 3)
|
||||
|
||||
@property
|
||||
def hop_water_loss(self):
|
||||
hop_absorption = .025 # gallons per ounce
|
||||
return sum([float(x.quantity) * hop_absorption for x in self.hops])
|
||||
return float(sum(
|
||||
x.quantity*x.trub_volume for x in self.recipehop_set.all()
|
||||
))
|
||||
|
||||
@property
|
||||
def net_kettle_deadspace(self):
|
||||
@ -202,13 +344,18 @@ class BatchRecipe(CustomModel):
|
||||
return 0
|
||||
return float(self.equipment.kettle_boil_rate)
|
||||
|
||||
@property
|
||||
def ibu(self): # TODO: Multiple IBU formulas
|
||||
return self.ibu_tinseth
|
||||
|
||||
@property
|
||||
def ibu_tinseth(self):
|
||||
return sum([x.ibu_tinseth for x in self.hops])
|
||||
return sum(x.ibu_tinseth for x in self.recipehop_set.all())
|
||||
|
||||
@property
|
||||
def srm(self):
|
||||
color_total = sum([x.srm for x in self.fermentables])
|
||||
color_total = sum(x.lovibond_contributed
|
||||
for x in self.recipefermentable_set.all())
|
||||
return 1.4922*(color_total**0.6859)
|
||||
|
||||
@property
|
||||
@ -262,7 +409,8 @@ class Fermentable(CustomIngredient):
|
||||
fermentable_type = models.IntegerField(choices=types, default=1)
|
||||
diastatic_power = models.DecimalField(
|
||||
max_digits=6, decimal_places=4, null=True, blank=True)
|
||||
potential = models.DecimalField(max_digits=6, decimal_places=4)
|
||||
potential = models.DecimalField(
|
||||
max_digits=6, decimal_places=4, default=0.80)
|
||||
protein = models.DecimalField(
|
||||
max_digits=6, decimal_places=4, null=True, blank=True)
|
||||
attenuation = models.DecimalField(
|
||||
@ -272,28 +420,47 @@ class Fermentable(CustomIngredient):
|
||||
max_in_batch = models.DecimalField(
|
||||
max_digits=6, decimal_places=4, null=True, blank=True)
|
||||
moisture = models.DecimalField(
|
||||
max_digits=6, decimal_places=4, null=True, blank=True)
|
||||
max_digits=6, decimal_places=4, null=True, blank=True, default=0.04)
|
||||
non_fermentable = models.BooleanField(null=True, blank=True)
|
||||
ibu_per_unit = models.DecimalField(
|
||||
max_digits=6, decimal_places=4, default=0)
|
||||
|
||||
@property
|
||||
def extract_percent(self):
|
||||
return (((float(self.potential)-1)*1000/46.17)
|
||||
* (1-float(self.moisture)/100))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class RecipeFermentable(CustomModel):
|
||||
recipe = models.ForeignKey(BatchRecipe, on_delete=models.CASCADE)
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
fermentable = models.ForeignKey(Fermentable, on_delete=models.CASCADE)
|
||||
quantity = models.DecimalField(max_digits=6, decimal_places=4)
|
||||
|
||||
@property
|
||||
def percent(self):
|
||||
return float(100 * self.quantity / self.recipe.fermentable_weight)
|
||||
# Properties Needed:
|
||||
# - The weight of extract in the grist
|
||||
# extract in grist in kg = weight of grist in kg * extract potential
|
||||
|
||||
@property
|
||||
def srm(self):
|
||||
def quantity_display(self):
|
||||
return convert(self.quantity, 'kg', 'lb')
|
||||
|
||||
@property
|
||||
def extract_weight_kg(self):
|
||||
return float(self.quantity) * self.fermentable.extract_percent
|
||||
|
||||
@property
|
||||
def percent(self):
|
||||
return float(100 * self.quantity / self.recipe.fermentable_weight_kg)
|
||||
|
||||
@property
|
||||
def lovibond_contributed(self):
|
||||
srm_calc = (float(self.fermentable.lovibond)
|
||||
* float(self.quantity) / self.recipe.final_volume)
|
||||
* convert(self.quantity, 'kg', 'lb')
|
||||
/ convert(self.recipe.final_volume, 'l', 'gal'))
|
||||
|
||||
return round(srm_calc, 1)
|
||||
|
||||
|
||||
@ -328,12 +495,17 @@ class RecipeHop(CustomModel):
|
||||
4: 'Mash',
|
||||
5: 'First Wort'
|
||||
}
|
||||
recipe = models.ForeignKey(BatchRecipe, on_delete=models.CASCADE)
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
hop = models.ForeignKey(Hop, on_delete=models.CASCADE)
|
||||
quantity = models.DecimalField(max_digits=6, decimal_places=4)
|
||||
time = models.IntegerField(default=60, validators=[MinValueValidator(0)])
|
||||
use = models.IntegerField(choices=uses, default=1)
|
||||
|
||||
@property
|
||||
def quantity_display(self):
|
||||
"""Convert grams to ounces."""
|
||||
return convert(self.quantity, 'g', 'oz')
|
||||
|
||||
@property
|
||||
def ibu_tinseth(self):
|
||||
type_bonus = {
|
||||
@ -343,22 +515,32 @@ class RecipeHop(CustomModel):
|
||||
4: 1.4, # CO2 Extract
|
||||
}
|
||||
|
||||
ibu = 0
|
||||
hop_bonus = type_bonus[self.hop.hop_type]
|
||||
|
||||
average_wort_sg = (((self.recipe.pre_boil_sg - 1)
|
||||
+ (self.recipe.original_sg - 1)) / 2)
|
||||
average_wort_sg = (((self.recipe.pre_boil_sg-1)
|
||||
+ (self.recipe.original_sg-1)) / 2)
|
||||
|
||||
if self.use == 1:
|
||||
conc = (((float(self.hop.alpha) / 100)
|
||||
* float(self.quantity))
|
||||
* 7490 / self.recipe.final_volume)
|
||||
util = ((type_bonus[self.hop.hop_type]
|
||||
* 1.65 * (0.000125**average_wort_sg))
|
||||
* ((1-2.71828182845904**(-0.04 * self.time))/4.15))
|
||||
conc = (float(self.hop.alpha/100)
|
||||
* convert(self.quantity, 'g', 'oz')
|
||||
* 7490/convert(self.recipe.final_volume, 'l', 'gal'))
|
||||
util = (hop_bonus*1.65*0.000125**average_wort_sg
|
||||
* ((1-2.71828182845904**(-0.04*self.time)) / 4.15))
|
||||
ibu = conc * util
|
||||
else:
|
||||
ibu = 0
|
||||
|
||||
return float(ibu)
|
||||
|
||||
@property
|
||||
def trub_volume(self):
|
||||
if self.hop.hop_type == 1:
|
||||
return self.recipe.equipment.pellet_hop_trub
|
||||
elif self.hop.hop_type in [2, 3]:
|
||||
return self.recipe.equipment.leaf_hop_trub
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
class Misc(CustomIngredient):
|
||||
uses = {
|
||||
@ -390,7 +572,7 @@ class Misc(CustomIngredient):
|
||||
|
||||
|
||||
class RecipeMisc(CustomModel):
|
||||
recipe = models.ForeignKey(BatchRecipe, on_delete=models.CASCADE)
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
misc = models.ForeignKey(Misc, on_delete=models.CASCADE)
|
||||
quantity = models.DecimalField(max_digits=6, decimal_places=4)
|
||||
|
||||
@ -399,7 +581,7 @@ class RecipeMisc(CustomModel):
|
||||
|
||||
|
||||
class RecipeYeast(CustomModel):
|
||||
recipe = models.ForeignKey(BatchRecipe, on_delete=models.CASCADE)
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
yeast = models.ForeignKey('yeast.Strain', on_delete=models.CASCADE)
|
||||
|
||||
|
||||
@ -436,29 +618,96 @@ class EquipmentProfile(CustomModel):
|
||||
|
||||
# Water managment stuff
|
||||
hlt_deadspace = models.DecimalField(
|
||||
max_digits=6, decimal_places=2, default=0.25)
|
||||
max_digits=6, decimal_places=2, default=0.25, help_text='liters')
|
||||
mt_deadspace = models.DecimalField(
|
||||
max_digits=6, decimal_places=2, default=0.25)
|
||||
max_digits=6, decimal_places=2, default=0.25, help_text='liters')
|
||||
mt_capacity = models.DecimalField(
|
||||
max_digits=6, decimal_places=2, default=10)
|
||||
max_digits=6, decimal_places=2, default=10, help_text='liters')
|
||||
grain_absorption = models.DecimalField(
|
||||
max_digits=6, decimal_places=2, default=0.12) # gal/lb
|
||||
max_digits=6, decimal_places=2,
|
||||
default=0.12, help_text='liters/kilogram')
|
||||
kettle_deadspace = models.DecimalField(
|
||||
max_digits=6, decimal_places=2, default=0.25)
|
||||
max_digits=6, decimal_places=2, default=0.25, help_text='liters')
|
||||
kettle_plumbing_loss = models.DecimalField(
|
||||
max_digits=6, decimal_places=2, default=0.25)
|
||||
max_digits=6, decimal_places=2, default=0.25, help_text='liters')
|
||||
kettle_boil_rate = models.DecimalField(
|
||||
max_digits=6, decimal_places=2, default=0.5) # gal/hr
|
||||
max_digits=6, decimal_places=2,
|
||||
default=0.5, help_text='liters/hr')
|
||||
batch_volume = models.DecimalField(
|
||||
max_digits=6, decimal_places=2, default=5.5)
|
||||
max_digits=6, decimal_places=2, default=5.5, help_text='liters')
|
||||
leaf_hop_trub = models.DecimalField(
|
||||
max_digits=6, decimal_places=4, default=0.0625)
|
||||
max_digits=12, decimal_places=10,
|
||||
default=0.0083454045, help_text='liters/gram')
|
||||
pellet_hop_trub = models.DecimalField(
|
||||
max_digits=6, decimal_places=4, default=0.025)
|
||||
max_digits=12, decimal_places=10,
|
||||
default=0.0033381620, help_text='liters/gram')
|
||||
hops_remain_kettle = models.BooleanField(default=True)
|
||||
mash_ratio = models.DecimalField(
|
||||
max_digits=6, decimal_places=2,
|
||||
default=2.6, help_text='liters/kilogram')
|
||||
|
||||
# Thermal Properties
|
||||
mt_initial_hear = models.DecimalField(
|
||||
max_digits=6, decimal_places=4, default=0.74)
|
||||
mt_heat_loss_hour = models.DecimalField(
|
||||
max_digits=6, decimal_places=4, default=2.0)
|
||||
|
||||
class Meta:
|
||||
db_table_comment = 'Volumes in liters and weights in kg.'
|
||||
|
||||
class BjcpStyle(models.Model):
|
||||
name = models.CharField(max_length=50, blank=True, null=True)
|
||||
category = models.CharField(max_length=50, blank=True, null=True)
|
||||
category_id = models.CharField(max_length=5, blank=True, null=True)
|
||||
style_id = models.CharField(max_length=5, blank=True, null=True)
|
||||
category_description = models.TextField(blank=True, null=True)
|
||||
overall_impression = models.TextField(blank=True, null=True)
|
||||
aroma = models.TextField(blank=True, null=True)
|
||||
appearance = models.TextField(blank=True, null=True)
|
||||
flavor = models.TextField(blank=True, null=True)
|
||||
mouthfeel = models.TextField(blank=True, null=True)
|
||||
comments = models.TextField(blank=True, null=True)
|
||||
history = models.TextField(blank=True, null=True)
|
||||
style_comparison = models.TextField(blank=True, null=True)
|
||||
tags = models.TextField(blank=True, null=True)
|
||||
original_gravity_minimum_unit = models.CharField(max_length=10, blank=True, null=True)
|
||||
original_gravity_minimum_value = models.DecimalField(max_digits=10, decimal_places=5, blank=True, null=True) # max_digits and decimal_places have been guessed, as this database handles decimal fields as float
|
||||
original_gravity_maximum_unit = models.CharField(max_length=10, blank=True, null=True)
|
||||
original_gravity_maximum_value = models.DecimalField(max_digits=10, decimal_places=5, blank=True, null=True) # max_digits and decimal_places have been guessed, as this database handles decimal fields as float
|
||||
international_bitterness_units_minimum_unit = models.CharField(max_length=10, blank=True, null=True)
|
||||
international_bitterness_units_minimum_value = models.DecimalField(max_digits=10, decimal_places=5, blank=True, null=True) # max_digits and decimal_places have been guessed, as this database handles decimal fields as float
|
||||
international_bitterness_units_maximum_unit = models.CharField(max_length=10, blank=True, null=True)
|
||||
international_bitterness_units_maximum_value = models.DecimalField(max_digits=10, decimal_places=5, blank=True, null=True) # max_digits and decimal_places have been guessed, as this database handles decimal fields as float
|
||||
final_gravity_minimum_unit = models.CharField(max_length=10, blank=True, null=True)
|
||||
final_gravity_minimum_value = models.DecimalField(max_digits=10, decimal_places=5, blank=True, null=True) # max_digits and decimal_places have been guessed, as this database handles decimal fields as float
|
||||
final_gravity_maximum_unit = models.CharField(max_length=10, blank=True, null=True)
|
||||
final_gravity_maximum_value = models.DecimalField(max_digits=10, decimal_places=5, blank=True, null=True) # max_digits and decimal_places have been guessed, as this database handles decimal fields as float
|
||||
alcohol_by_volume_minimum_unit = models.CharField(max_length=10, blank=True, null=True)
|
||||
alcohol_by_volume_minimum_value = models.DecimalField(max_digits=10, decimal_places=5, blank=True, null=True) # max_digits and decimal_places have been guessed, as this database handles decimal fields as float
|
||||
alcohol_by_volume_maximum_unit = models.CharField(max_length=10, blank=True, null=True)
|
||||
alcohol_by_volume_maximum_value = models.DecimalField(max_digits=10, decimal_places=5, blank=True, null=True) # max_digits and decimal_places have been guessed, as this database handles decimal fields as float
|
||||
color_minimum_unit = models.CharField(max_length=10, blank=True, null=True)
|
||||
color_minimum_value = models.DecimalField(max_digits=10, decimal_places=5, blank=True, null=True) # max_digits and decimal_places have been guessed, as this database handles decimal fields as float
|
||||
color_maximum_unit = models.CharField(max_length=10, blank=True, null=True)
|
||||
color_maximum_value = models.DecimalField(max_digits=10, decimal_places=5, blank=True, null=True) # max_digits and decimal_places have been guessed, as this database handles decimal fields as float
|
||||
ingredients = models.TextField(blank=True, null=True)
|
||||
examples = models.TextField(blank=True, null=True)
|
||||
style_guide = models.CharField(max_length=20, blank=True, null=True)
|
||||
type = models.TextField(blank=True, null=True)
|
||||
entry_instructions = models.TextField(blank=True, null=True)
|
||||
notes = models.TextField(blank=True, null=True)
|
||||
currently_defined_types = models.TextField(blank=True, null=True)
|
||||
strength_classifications = models.TextField(blank=True, null=True)
|
||||
vital_statistics = models.TextField(blank=True, null=True)
|
||||
profile = models.TextField(blank=True, null=True)
|
||||
comparison = models.TextField(blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return '{} {}: {}'.format(
|
||||
self.category,
|
||||
self.style_id,
|
||||
self.name
|
||||
)
|
||||
|
||||
class Meta:
|
||||
db_table = 'beer_bjcp'
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
65
beer/templates/beer/batch.html
Normal file
65
beer/templates/beer/batch.html
Normal file
@ -0,0 +1,65 @@
|
||||
{% extends "no_jumbo_base.html" %}
|
||||
{% load mathfilters %}
|
||||
{% block title %}#{{ batch.brewfather_num }} {{ batch.brewfather_name }}{% endblock %}
|
||||
|
||||
{% block style %}
|
||||
.table td.fit,
|
||||
.table th.fit {
|
||||
white-space: nowrap;
|
||||
width: 1%;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block content %}
|
||||
<!-- Information Header -->
|
||||
<div class="container">
|
||||
<h1>{{ batch.brewfather_name }}</h1>
|
||||
</div> <!-- /container -->
|
||||
<!-- End Information Header -->
|
||||
|
||||
<!-- Main Data Container -->
|
||||
<div class="container mt-4 mb-4">
|
||||
<div class="row">
|
||||
|
||||
<!-- Tabbed Pages -->
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs nav-fill" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-bs-toggle="tab" href="#planning">Planning</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" href="#brewing">Brewing</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" href="#fermenting">Fermenting</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" href="#complete">Complete</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<div id="planning" class="container tab-pane active"><br>
|
||||
{% include "beer/planning.html" %}
|
||||
</div>
|
||||
<div id="brewing" class="container tab-pane fade"><br>
|
||||
|
||||
</div>
|
||||
<div id="fermenting" class="container tab-pane fade"><br>
|
||||
|
||||
</div>
|
||||
<div id="complete" class="container tab-pane fade"><br>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- End Tabbed Pages -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Main Data Container -->
|
||||
|
||||
{% endblock %}
|
58
beer/templates/beer/planning.html
Normal file
58
beer/templates/beer/planning.html
Normal file
@ -0,0 +1,58 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
|
||||
<div class="d-flex justify-content-between bg-dark text-white ms-2 my-1">
|
||||
Recipe Ingredients
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<table class="table table-sm table-hover">
|
||||
<tbody>
|
||||
{% for f in batch.recipe.recipefermentable_set.all %}
|
||||
<tr>
|
||||
<td>{{ f.quantity|floatformat:2 }} lb</td>
|
||||
<td>{{ f.fermentable.name }}<br>
|
||||
<span class="text-muted">{{ f.fermentable.get_fermentable_type_display }} {{ f.srm }} SRM</span>
|
||||
</td>
|
||||
<td>{{ f.percent|floatformat:1 }} %</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="d-flex justify-content-between bg-dark text-white ms-2 my-1">
|
||||
Batch Recipe
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<table class="table table-sm table-hover">
|
||||
<tbody>
|
||||
|
||||
<tr onclick="window.location='recipe/{{ batch.recipe.id }}';">
|
||||
<td><b>{{ batch.recipe.name }}</b><br>
|
||||
<span class="text-muted"><b>OG:</b> {{ batch.recipe.original_sg }} <b>IBU:</b> {{ batch.recipe.ibu|floatformat:0 }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between bg-dark text-white ms-2 my-1">
|
||||
Yeast
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<table class="table table-sm table-hover">
|
||||
<tbody>
|
||||
{% for y in batch.recipe.recipeyeast_set.all %}
|
||||
<tr>
|
||||
<td>{{ y.yeast.manufacturer.name }} {{ y.yeast.name }}<br>
|
||||
<span class="text-muted">{{ y.yeast.long_name }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -10,6 +10,11 @@ input, label {
|
||||
.container-beer {
|
||||
background: {{ recipe.srm_hex }};
|
||||
}
|
||||
|
||||
p.smaller {
|
||||
font-size: .8em;
|
||||
}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}Recipes{% endblock %}
|
||||
@ -19,7 +24,7 @@ input, label {
|
||||
{% block jumbotronsub %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg d-flex justify-content-between" style="border:1px solid #cecece; margin-right:.5em; margin-left:.5em">
|
||||
<div class="col-lg d-flex justify-content-between">
|
||||
<div>
|
||||
<div class="container-beer"><img src="{% static "beer_back.png" %}" alt="" class="img-responsive d-none d-sm-block"></div>
|
||||
</div>
|
||||
@ -32,23 +37,27 @@ input, label {
|
||||
<dd>All Grain</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-lg-4" style="border:1px solid #cecece; margin-right:.5em; margin-left:.5em">
|
||||
|
||||
<div class="text-truncate">Equipment: {{ recipe.equipment.name }}</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="d-flex justify-content-between bg-dark text-white">
|
||||
<div class="text-truncate ms-1 my-1">{{ recipe.equipment.name }}</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-dark btn-sm"><i class="fas fa-scale-unbalanced-flip"></i></button>
|
||||
<button type="button" class="btn btn-dark btn-sm"><i class="fas fa-arrow-right-arrow-left"></i></button>
|
||||
<button type="button" class="btn btn-dark btn-sm"><i class="fas fa-pen-to-square"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<b>Batch Size:</b> {{ recipe.batch_size }} gal <b>Actual Volume:</b> {{ recipe.final_volume|floatformat:2 }}<br>
|
||||
<b>Mash Efficiency:</b> {{ recipe.efficiency|floatformat:2 }} %
|
||||
-->
|
||||
<dl class="row">
|
||||
<dt>Batch Size</dt>
|
||||
<dd>{{ recipe.batch_size }} gal</dd>
|
||||
<dt>Actual Volume</dt>
|
||||
<dd>{{ recipe.final_volume|floatformat:2 }} gal</dd>
|
||||
<dd>{{ recipe.batch_size_display|floatformat:2 }} gal</dd>
|
||||
<dt>Mash Efficiency</dt>
|
||||
<dd>{{ recipe.efficiency|floatformat:2 }} %</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-xl-3" style="border:1px solid #cecece; margin-right:.5em; margin-left:.5em">
|
||||
<div class="col-xl-3">
|
||||
Style Data
|
||||
</div>
|
||||
</div>
|
||||
@ -64,25 +73,38 @@ input, label {
|
||||
<!-- Fermentables -->
|
||||
<div class="col-md">
|
||||
<div class="container-fluid">
|
||||
<div class="container-fluid bg-dark text-white">Fermentables ({{ fermentable_weight|floatformat:2 }} lbs)</div>
|
||||
<table class="table table-sm ">
|
||||
<div class="d-flex justify-content-between bg-dark text-white">
|
||||
<div class="ms-2 my-1">Fermentables ({{ recipe.total_fermentable_display|floatformat:2 }} lbs)</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-dark btn-sm"><b>%</b></button>
|
||||
<button type="button" class="btn btn-dark btn-sm"><b>OG</b></button>
|
||||
<button type="button" class="btn btn-dark btn-sm"><i class="fa fa-plus"></i><b>ADD</b></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-sm table-hover">
|
||||
|
||||
<caption>
|
||||
<p class="text-end smaller">
|
||||
Pre-Boil Gravity: <b>{{ recipe.pre_boil_sg|floatformat:3 }}</b><br>
|
||||
Original Gravity: <b>{{ recipe.original_sg|floatformat:3 }}</b><br>
|
||||
Color: <b>{{ recipe.srm|floatformat:0 }} SRM</b>
|
||||
</p>
|
||||
</caption>
|
||||
|
||||
<tbody>
|
||||
{% for f in recipe.recipefermentable_set.all %}
|
||||
<tr>
|
||||
<td>{{ f.quantity|floatformat:2 }} lb</td>
|
||||
<tr onclick="window.location='{% url 'beer:update_fermentable' f.fermentable.id %}';">
|
||||
<td>{{ f.quantity_display|floatformat:2 }} lb</td>
|
||||
<td>{{ f.fermentable.name }}<br>
|
||||
<span class="text-muted">{{ f.fermentable.get_fermentable_type_display }} {{ f.srm }} SRM</span>
|
||||
<span class="text-muted">{{ f.fermentable.get_fermentable_type_display }} {{ f.fermentable.lovibond|floatformat:0 }} °L</span>
|
||||
</td>
|
||||
<td>{{ f.percent|floatformat:1 }} %</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="text-end small">
|
||||
Pre-Boil Gravity: <b>{{ recipe.pre_boil_sg }}</b><br>
|
||||
Original Gravity: <b>{{ recipe.original_sg }}</b><br>
|
||||
Color: <b>{{ recipe.srm|floatformat:0 }} SRM</b>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -90,12 +112,25 @@ input, label {
|
||||
<!-- Hops -->
|
||||
<div class="col-md">
|
||||
<div class="container-fluid">
|
||||
<div class="container-fluid bg-dark text-white">Hops ({{ hop_weight|floatformat:2 }} oz)</div>
|
||||
<table class="table table-sm ">
|
||||
<div class="d-flex justify-content-between bg-dark text-white">
|
||||
<div class="ms-2 my-1">Hops ({{ recipe.total_hop_display|floatformat:2 }} oz)</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-dark btn-sm"><b>IBU</b></button>
|
||||
<button type="button" class="btn btn-dark btn-sm"><i class="fa fa-plus"></i><b>ADD</b></button>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-sm table-hover">
|
||||
<caption>
|
||||
<p class="text-end smaller">
|
||||
Total IBU: <b>{{ recipe.ibu_tinseth|floatformat:1 }}</b><br>
|
||||
BU/GU: <b>{{ recipe.bu_gu|floatformat:2 }}</b><br>
|
||||
RBR: <b>{{ recipe.rbr|floatformat:2 }}</b>
|
||||
</p>
|
||||
</caption>
|
||||
<tbody>
|
||||
{% for h in recipe.recipehop_set.all %}
|
||||
<tr>
|
||||
<td>{{ h.quantity|floatformat:2 }} oz</td>
|
||||
<tr onclick="window.location='{% url 'beer:update_hop' h.hop.id %}';">
|
||||
<td>{{ h.quantity_display|floatformat:2 }} oz</td>
|
||||
<td>{{ h.hop.name }} {{ h.hop.alpha|floatformat:1 }} %<br>
|
||||
<span class="text-muted">{{ h.hop.get_hop_type_display }} {{ h.ibu_tinseth|floatformat:1 }} IBU</span>
|
||||
</td>
|
||||
@ -103,11 +138,7 @@ input, label {
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="text-end small">
|
||||
Total IBU: <b>{{ recipe.ibu_tinseth|floatformat:1 }}</b><br>
|
||||
BU/GU: <b>{{ recipe.bu_gu|floatformat:2 }}</b><br>
|
||||
RBR: <b>{{ recipe.rbr|floatformat:2 }}</b>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -118,7 +149,12 @@ input, label {
|
||||
<!-- Misc -->
|
||||
<div class="col-md">
|
||||
<div class="container-fluid">
|
||||
<div class="container-fluid bg-dark text-white">Misc.</div>
|
||||
<div class="d-flex justify-content-between bg-dark text-white">
|
||||
<div class="ms-2 my-1">Misc.</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-dark btn-sm"><i class="fa fa-plus"></i><b>ADD</b></button>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-sm">
|
||||
<tbody>
|
||||
{% for m in recipe.recipemisc_set.all %}
|
||||
@ -131,7 +167,12 @@ input, label {
|
||||
<!-- Yeast -->
|
||||
<div class="col-md">
|
||||
<div class="container-fluid">
|
||||
<div class="container-fluid bg-dark text-white">Yeast</div>
|
||||
<div class="d-flex justify-content-between bg-dark text-white">
|
||||
<div class="ms-2 my-1">Yeast</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-dark btn-sm"><i class="fa fa-plus"></i><b>ADD</b></button>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-sm">
|
||||
<tbody>
|
||||
{% for y in recipe.recipeyeast_set.all %}
|
||||
@ -149,7 +190,13 @@ input, label {
|
||||
<!-- Mash -->
|
||||
<div class="col-md">
|
||||
<div class="container-fluid">
|
||||
<div class="container-fluid bg-dark text-white">Mash Profile {{ recipe.mash.name }}</div>
|
||||
<div class="d-flex justify-content-between bg-dark text-white">
|
||||
<div class="ms-2 my-1">Mash Profile: {{ recipe.mash.name }}</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-dark btn-sm"><i class="fas fa-arrow-right-arrow-left"></i></button>
|
||||
<button type="button" class="btn btn-dark btn-sm"><i class="fas fa-pen-to-square"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-sm ">
|
||||
<tbody>
|
||||
{% for step in recipe.mash.mashstep_set.all %}
|
||||
@ -165,14 +212,13 @@ input, label {
|
||||
<!-- Fermentation -->
|
||||
<div class="col-md">
|
||||
<div class="container-fluid">
|
||||
<div class="container-fluid bg-dark text-white">Fermentation Profile</div>
|
||||
<table class="table table-sm">
|
||||
<tbody>
|
||||
<!-- {% for y in recipe.recipeyeast_set.all %} -->
|
||||
<!-- <tr><td>{{ y.yeast.name }}</td></tr> -->
|
||||
<!-- {% endfor %} -->
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="d-flex justify-content-between bg-dark text-white">
|
||||
<div class="ms-2 my-1">Fermentation Profile</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-dark btn-sm"><i class="fas fa-arrow-right-arrow-left"></i></button>
|
||||
<button type="button" class="btn btn-dark btn-sm"><i class="fas fa-pen-to-square"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
14
beer/templates/beer/update_fermentable.html
Normal file
14
beer/templates/beer/update_fermentable.html
Normal file
@ -0,0 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
{% load mathfilters %}
|
||||
{% load funcs %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
|
||||
{% block title %}Recipes{% endblock %}
|
||||
|
||||
{% block jumbotron %}{{ data.name }}{% endblock %}
|
||||
|
||||
{% block jumbotronsub %}
|
||||
{{ data.potential }}
|
||||
{% endblock %}
|
14
beer/templates/beer/update_hop.html
Normal file
14
beer/templates/beer/update_hop.html
Normal file
@ -0,0 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
{% load mathfilters %}
|
||||
{% load funcs %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
|
||||
{% block title %}Recipes{% endblock %}
|
||||
|
||||
{% block jumbotron %}{{ data.name }}{% endblock %}
|
||||
|
||||
{% block jumbotronsub %}
|
||||
{{ data.alpha }} %
|
||||
{% endblock %}
|
@ -1,10 +1,15 @@
|
||||
from django.urls import path
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from .views import home, view_recipe
|
||||
from .views import home, view_recipe, view_batch, update_ferm, update_hop
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('', home, name='home'),
|
||||
path('recipes/<int:recipe_id>/', view_recipe, name='recipe'),
|
||||
path('batches/<int:batch_id>/recipe/<int:recipe_id>', view_recipe),
|
||||
path('batches/<int:batch_id>/', view_batch, name='batch'),
|
||||
path('ingredients/fermentables/<int:ferm_id>',
|
||||
update_ferm, name='update_fermentable'),
|
||||
path('ingredients/hops/<int:hop_id>', update_hop, name='update_hop'),
|
||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
@ -1,6 +1,6 @@
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
|
||||
from .models import UserProfile, BatchRecipe, Batch
|
||||
from .models import UserProfile, Recipe, Batch, Fermentable, Hop
|
||||
from .extras import get_batches
|
||||
|
||||
import json
|
||||
@ -20,7 +20,7 @@ def home(request):
|
||||
if Batch.objects.filter(brewfather_id=batch['_id']).first() is None:
|
||||
recipe_name = batch['recipe']['name']
|
||||
|
||||
recipe_obj = BatchRecipe(
|
||||
recipe_obj = Recipe(
|
||||
name=recipe_name,
|
||||
batch_recipe=True, recipe_json=json.dumps(batch['recipe'])
|
||||
)
|
||||
@ -36,18 +36,39 @@ def home(request):
|
||||
batch_obj.save()
|
||||
|
||||
context = {
|
||||
'recipes': BatchRecipe.objects.all(),
|
||||
'recipes': Recipe.objects.all(),
|
||||
}
|
||||
return render(request, 'beer/home.html', context)
|
||||
|
||||
|
||||
def view_recipe(request, recipe_id):
|
||||
recipe = get_object_or_404(BatchRecipe, pk=recipe_id)
|
||||
|
||||
def view_recipe(request, recipe_id, batch_id=None):
|
||||
context = {
|
||||
'recipe': recipe,
|
||||
'fermentable_weight': sum([x.quantity for x in recipe.fermentables]),
|
||||
'hop_weight': sum([x.quantity for x in recipe.hops]),
|
||||
'recipe': get_object_or_404(Recipe, pk=recipe_id),
|
||||
}
|
||||
|
||||
return render(request, 'beer/recipe.html', context)
|
||||
|
||||
def view_batch(request, batch_id):
|
||||
context = {
|
||||
'batch': get_object_or_404(Batch, pk=batch_id)
|
||||
}
|
||||
return render(request, 'beer/batch.html', context)
|
||||
|
||||
def update_ferm(request, ferm_id):
|
||||
fermentable = get_object_or_404(Fermentable, pk=ferm_id)
|
||||
|
||||
context = {
|
||||
'data': fermentable,
|
||||
}
|
||||
|
||||
return render(request, 'beer/update_fermentable.html', context)
|
||||
|
||||
|
||||
def update_hop(request, hop_id):
|
||||
hop = get_object_or_404(Hop, pk=hop_id)
|
||||
|
||||
context = {
|
||||
'data': hop,
|
||||
}
|
||||
|
||||
return render(request, 'beer/update_hop.html', context)
|
||||
|
@ -37,6 +37,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.flatpages',
|
||||
'fontawesomefree',
|
||||
'mathfilters',
|
||||
'yeast.apps.YeastLabConfig',
|
||||
'beer.apps.BeerConfig',
|
||||
|
12344
fixtures/bjcp.json
Normal file
12344
fixtures/bjcp.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@ django-cryptography-django5==2.2
|
||||
django-mathfilters==1.0.0
|
||||
docutils==0.21.2
|
||||
environs==11.0.0
|
||||
fontawesomefree==6.5.1
|
||||
gunicorn==22.0.0
|
||||
pip-chill==1.0.3
|
||||
psycopg2-binary==2.9.9
|
||||
|
8
run.sh
8
run.sh
@ -1,5 +1,9 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
source .env/bin/activate
|
||||
pip install --upgrade -r requirements.txt
|
||||
source .env/bin/activate || source .env/Scripts/activate
|
||||
find -name "*.py" -not -name "manage.py" -not -path "./.env/*" -not -path "*/migrations/*" -exec python -m flake8 {} \;
|
||||
|
||||
pip install --upgrade -r requirements.txt > /dev/null
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
python manage.py runserver 0.0.0.0:9595
|
||||
|
@ -6,7 +6,9 @@
|
||||
|
||||
<!-- CSS only -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
|
||||
{% load static %}
|
||||
<link href="{% static 'fontawesomefree/css/fontawesome.css' %}" rel="stylesheet" type="text/css">
|
||||
<link href="{% static 'fontawesomefree/css/solid.css' %}" rel="stylesheet" type="text/css">
|
||||
<!-- JS, Popper.js, and jQuery -->
|
||||
<!-- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js" -->
|
||||
<!-- integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy" -->
|
||||
@ -15,14 +17,14 @@
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
||||
{% block script %}{% endblock %}
|
||||
<style>
|
||||
{% block style %}{% endblock %}
|
||||
body {
|
||||
margin-top: 10px;
|
||||
}
|
||||
label {
|
||||
margin-right: 10px;
|
||||
}
|
||||
<style>
|
||||
{% block style %}{% endblock %}
|
||||
body {
|
||||
margin-top: 10px;
|
||||
}
|
||||
label {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.table-borderless > tbody > tr > td,
|
||||
.table-borderless > tbody > tr > th,
|
||||
.table-borderless > tfoot > tr > td,
|
||||
@ -32,14 +34,14 @@
|
||||
border: none;
|
||||
background-color: rgba(0,0,0, 0.0) !important;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">Damn Yankee Brewing</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
@ -85,9 +87,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main role="main">
|
||||
<main role="main">
|
||||
<div class="container my-5">
|
||||
<div class="p-5 text-left bg-body-tertiary rounded-3">
|
||||
<h1 class="text-body-emphasis">{% block jumbotron %}{% endblock %}</h1>
|
||||
@ -96,8 +98,8 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
</body>
|
||||
{% block endscript %}{% endblock %}
|
||||
|
98
templates/no_jumbo_base.html
Normal file
98
templates/no_jumbo_base.html
Normal file
@ -0,0 +1,98 @@
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
|
||||
<!-- CSS only -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
{% load static %}
|
||||
<link href="{% static 'fontawesomefree/css/fontawesome.css' %}" rel="stylesheet" type="text/css">
|
||||
<link href="{% static 'fontawesomefree/css/solid.css' %}" rel="stylesheet" type="text/css">
|
||||
<!-- JS, Popper.js, and jQuery -->
|
||||
<!-- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js" -->
|
||||
<!-- integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy" -->
|
||||
<!-- crossorigin="anonymous"></script> -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
||||
{% block script %}{% endblock %}
|
||||
<style>
|
||||
{% block style %}{% endblock %}
|
||||
body {
|
||||
margin-top: 10px;
|
||||
}
|
||||
label {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.table-borderless > tbody > tr > td,
|
||||
.table-borderless > tbody > tr > th,
|
||||
.table-borderless > tfoot > tr > td,
|
||||
.table-borderless > tfoot > tr > th,
|
||||
.table-borderless > thead > tr > td,
|
||||
.table-borderless > thead > tr > th {
|
||||
border: none;
|
||||
background-color: rgba(0,0,0, 0.0) !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">Damn Yankee Brewing</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'yeast:batches' %}">Batches</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="dropdown01" data-bs-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="false">Brewery Operations</a>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdown01">
|
||||
<a class="dropdown-item" href="/yeast/">Yeast</a>
|
||||
<a class="dropdown-item" href="/equipment/">Equipment</a>
|
||||
<a class="dropdown-item" href="/beer/">Beer</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<div class="dropdown ms-auto">
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
{{ user.username }}
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#" onclick="document.getElementById('LougoutForm').submit();">Log Out</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'password_change' %}">Change Password</a></li>
|
||||
{% if user.is_superuser %}
|
||||
<div class="dropdown-divider"></div>
|
||||
<li><a class="dropdown-item" href="{% url 'admin:index' %}">Admin</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
<a href="{% url 'login' %}">Log In</a>
|
||||
{% endif %}
|
||||
<form id="LougoutForm" action="{% url 'logout' %}" method="POST" class="form-inline my-2 my-lg-0">
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<main role="main">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
</body>
|
||||
{% block endscript %}{% endblock %}
|
||||
</html>
|
@ -1,36 +1,20 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block style %}
|
||||
form { display: table; }
|
||||
p { display: table-row; }
|
||||
label { display: table-cell; }
|
||||
input { display: table-cell; }
|
||||
{% endblock %}
|
||||
|
||||
{% block jumbotron %}
|
||||
Add Batch
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
|
||||
<!-- <form action="addbatch/" method="post">
|
||||
{% csrf_token %}
|
||||
production_date:<br>
|
||||
<input name="production_date">
|
||||
<br><br>
|
||||
strain:<br>
|
||||
<input name="strain">
|
||||
<br><br>
|
||||
source:<br>
|
||||
<input name="source">
|
||||
<br><br>
|
||||
data_web:<br>
|
||||
<input name="data_web">
|
||||
<br><br>
|
||||
<input type="submit" value="Submit">
|
||||
</form> -->
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% extends 'base.html' %}
|
||||
{% block style %}
|
||||
form { display: table; }
|
||||
p { display: table-row; }
|
||||
label { display: table-cell; }
|
||||
input { display: table-cell; }
|
||||
{% endblock %}
|
||||
|
||||
{% block jumbotron %}
|
||||
Add Batch
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
Loading…
Reference in New Issue
Block a user