Create basic recipe stuff.
This commit is contained in:
parent
d31d53ca9b
commit
e0ac1f96db
@ -3,8 +3,8 @@ from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from django.apps import apps
|
||||
|
||||
from beer.models import Batch, BatchRecipe, BatchRecipe
|
||||
from yeast.models import Yeast
|
||||
from beer.models import Batch, BatchRecipe, Mash, MashStep, RecipeFermentable, RecipeHop, RecipeMisc, RecipeYeast
|
||||
from yeast.models import Yeast, Strain
|
||||
|
||||
from config.extras import BREWFATHER_APP_ROOT
|
||||
|
||||
@ -12,9 +12,31 @@ class SampleInline(admin.TabularInline):
|
||||
model = Yeast
|
||||
extra = 0
|
||||
|
||||
class FermentableInline(admin.TabularInline):
|
||||
model = RecipeFermentable
|
||||
extra = 1
|
||||
|
||||
class HopInline(admin.TabularInline):
|
||||
model = RecipeHop
|
||||
extra = 1
|
||||
|
||||
class MiscInline(admin.TabularInline):
|
||||
model = RecipeMisc
|
||||
extra = 1
|
||||
|
||||
class StrainInline(admin.TabularInline):
|
||||
model = RecipeYeast
|
||||
extra = 1
|
||||
|
||||
@admin.register(BatchRecipe)
|
||||
class BatchRecipeAdmin(admin.ModelAdmin):
|
||||
list_display = ['name']
|
||||
inlines = [
|
||||
FermentableInline,
|
||||
HopInline,
|
||||
MiscInline,
|
||||
StrainInline
|
||||
]
|
||||
|
||||
@admin.register(Batch)
|
||||
class BeerBatchAdmin(admin.ModelAdmin):
|
||||
@ -30,6 +52,17 @@ class BeerBatchAdmin(admin.ModelAdmin):
|
||||
return format_html("<a href='{root}/tabs/batches/batch/{batch_id}'>Brewfather App: {batch_id}</a>", batch_id=bf_id, root=BREWFATHER_APP_ROOT)
|
||||
|
||||
|
||||
class MashStepInline(admin.TabularInline):
|
||||
model = MashStep
|
||||
extra = 1
|
||||
|
||||
@admin.register(Mash)
|
||||
class MashAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', ]
|
||||
inlines = [
|
||||
MashStepInline,
|
||||
]
|
||||
|
||||
app = apps.get_app_config('beer')
|
||||
for model_name, model in app.models.items():
|
||||
|
||||
|
@ -9,6 +9,42 @@ 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',
|
||||
'unit_cost': 'costPerAmount',
|
||||
'supplier': 'supplier',
|
||||
'notes': 'notes',
|
||||
'user_notes': 'userNotes',
|
||||
},
|
||||
'fermentable': {
|
||||
'grain_category': 'grainCategory',
|
||||
'fermentable_type': 'type',
|
||||
'diastatic_power': 'diastaticPower',
|
||||
'potential': 'potential',
|
||||
'protein': 'protein',
|
||||
'attenuation': 'attenuation',
|
||||
'lovibond': 'lovibond',
|
||||
'max_in_batch': 'maxInBatch',
|
||||
'moisture': 'moisture',
|
||||
'non_fermentable': 'notFermentable',
|
||||
'ibu_per_unit': 'ibuPerAmount',
|
||||
},
|
||||
'hop': {
|
||||
'ibu': 'ibu',
|
||||
'use': 'use',
|
||||
'hop_type': 'type',
|
||||
'alpha': 'alpha',
|
||||
|
||||
},
|
||||
'misc': {
|
||||
'use': 'use',
|
||||
'misc_type': 'type',
|
||||
'water_adjustment': 'waterAdjustment',
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def get_batches(api_user, api_key, batch=''):
|
||||
auth_string = api_user + ':' + api_key
|
||||
|
||||
|
@ -0,0 +1,217 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-17 17:13
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0003_delete_recipe_batchrecipe_batch_recipe_and_more'),
|
||||
('yeast', '0004_alter_propogation_options'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Fermentable',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('name', models.CharField(max_length=50)),
|
||||
('unit_cost', models.DecimalField(blank=True, decimal_places=2, max_digits=6, null=True)),
|
||||
('notes', models.TextField(blank=True, max_length=500, null=True)),
|
||||
('user_notes', models.TextField(blank=True, max_length=500, null=True)),
|
||||
('grain_category', models.IntegerField(choices=[(1, 'Base'), (2, 'Wheat/Oat'), (3, 'Crystal'), (4, 'Roasted'), (5, 'Acid')], default=1)),
|
||||
('fermentable_type', models.IntegerField(choices=[(1, 'Grain'), (2, 'Adjunct')], default=1)),
|
||||
('diastatic_power', models.DecimalField(blank=True, decimal_places=4, max_digits=6, null=True)),
|
||||
('potential', models.DecimalField(decimal_places=4, max_digits=6)),
|
||||
('protein', models.DecimalField(blank=True, decimal_places=4, max_digits=6, null=True)),
|
||||
('attenuation', models.DecimalField(blank=True, decimal_places=4, max_digits=6, null=True)),
|
||||
('lovibond', models.DecimalField(blank=True, decimal_places=4, max_digits=6, null=True)),
|
||||
('max_in_batch', models.DecimalField(blank=True, decimal_places=4, max_digits=6, null=True)),
|
||||
('moisture', models.DecimalField(blank=True, decimal_places=4, max_digits=6, null=True)),
|
||||
('non_fermentable', models.BooleanField(blank=True, null=True)),
|
||||
('ibu_per_unit', models.DecimalField(decimal_places=4, default=0, max_digits=6)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Hop',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('name', models.CharField(max_length=50)),
|
||||
('unit_cost', models.DecimalField(blank=True, decimal_places=2, max_digits=6, null=True)),
|
||||
('notes', models.TextField(blank=True, max_length=500, null=True)),
|
||||
('user_notes', models.TextField(blank=True, max_length=500, null=True)),
|
||||
('ibu', models.DecimalField(decimal_places=4, default=0, max_digits=6)),
|
||||
('use', models.IntegerField(choices=[(1, 'Bittering'), (2, 'Aroma'), (3, 'Both')], default=1)),
|
||||
('hop_type', models.IntegerField(choices=[(1, 'Pellet'), (2, 'Leaf'), (3, 'Cryo'), (4, 'CO2 Extract')], default=1)),
|
||||
('alpha', models.DecimalField(decimal_places=2, default=0, max_digits=6)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Mash',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('name', models.CharField(max_length=50)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Misc',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('name', models.CharField(max_length=50)),
|
||||
('unit_cost', models.DecimalField(blank=True, decimal_places=2, max_digits=6, null=True)),
|
||||
('notes', models.TextField(blank=True, max_length=500, null=True)),
|
||||
('user_notes', models.TextField(blank=True, max_length=500, null=True)),
|
||||
('use', models.IntegerField(choices=[(1, 'Mash'), (2, 'Sparge'), (3, 'Boil'), (4, 'Flamout'), (5, 'Primary'), (6, 'Secondary'), (7, 'Cold Crash'), (8, 'Bottling')], default=1)),
|
||||
('misc_type', models.IntegerField(choices=[(1, 'Spice'), (2, 'Fining'), (3, 'Water Agent'), (4, 'Herb'), (5, 'Flavor'), (6, 'Other')], default=1)),
|
||||
('water_adjustment', models.BooleanField(blank=True, null=True)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Supplier',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('name', models.CharField(max_length=50)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Unit',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('name', models.CharField(max_length=50)),
|
||||
('unit_type', models.CharField(choices=[('WT', 'Weight'), ('VL', 'Volume')], default='WT', max_length=3)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='batchrecipe',
|
||||
name='efficiency',
|
||||
field=models.DecimalField(blank=True, decimal_places=4, max_digits=6, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='batchrecipe',
|
||||
name='mash',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='beer.mash'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MashStep',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('name', models.CharField(max_length=50)),
|
||||
('step_temp', models.DecimalField(decimal_places=2, max_digits=6)),
|
||||
('ramp_time', models.DecimalField(decimal_places=2, max_digits=6)),
|
||||
('step_time', models.DecimalField(decimal_places=2, max_digits=6)),
|
||||
('step_type', models.IntegerField(choices=[(1, 'infusion'), (2, 'temperature'), (3, 'decoction')], default=1)),
|
||||
('parent_mash', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='beer.mash')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RecipeFermentable',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('quantity', models.DecimalField(decimal_places=4, max_digits=6)),
|
||||
('fermentable', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='beer.fermentable')),
|
||||
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='beer.batchrecipe')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RecipeHop',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('quantity', models.DecimalField(decimal_places=4, max_digits=6)),
|
||||
('hop', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='beer.hop')),
|
||||
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='beer.batchrecipe')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RecipeMisc',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('quantity', models.DecimalField(decimal_places=4, max_digits=6)),
|
||||
('misc', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='beer.misc')),
|
||||
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='beer.batchrecipe')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RecipeYeast',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='beer.batchrecipe')),
|
||||
('yeast', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='yeast.strain')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='misc',
|
||||
name='supplier',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='beer.supplier'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='hop',
|
||||
name='supplier',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='beer.supplier'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='fermentable',
|
||||
name='supplier',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='beer.supplier'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='misc',
|
||||
name='units',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='beer.unit'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='hop',
|
||||
name='units',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='beer.unit'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='fermentable',
|
||||
name='units',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='beer.unit'),
|
||||
),
|
||||
]
|
153
beer/models.py
153
beer/models.py
@ -37,9 +37,162 @@ class Batch(CustomModel):
|
||||
# Return a string that represents the instance
|
||||
return 'BF #{num}: {name}'.format(name=self.brewfather_name, num=self.brewfather_num)
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# Recipe Stuff
|
||||
#-----------------------------------------------------------------------
|
||||
class Unit(CustomModel):
|
||||
unit_types = {
|
||||
'WT': 'Weight',
|
||||
'VL': 'Volume',
|
||||
}
|
||||
|
||||
""" Recipe to be stored with a batch."""
|
||||
name = models.CharField(max_length=50)
|
||||
unit_type = models.CharField(max_length=3, choices=unit_types, default='WT')
|
||||
|
||||
class Supplier(CustomModel):
|
||||
name = models.CharField(max_length=50)
|
||||
|
||||
class CustomIngredient(CustomModel):
|
||||
""" Custom model class with default fields to use. """
|
||||
created_date = models.DateTimeField(default=timezone.now)
|
||||
name = models.CharField(max_length=50)
|
||||
units = models.ForeignKey(Unit, on_delete=models.PROTECT)
|
||||
unit_cost = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True)
|
||||
supplier = models.ForeignKey(Supplier, on_delete=models.PROTECT, null=True, blank=True)
|
||||
notes = models.TextField(max_length=500, blank=True, null=True)
|
||||
user_notes = models.TextField(max_length=500, blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
class BatchRecipe(CustomModel):
|
||||
""" Recipe to be stored with a batch."""
|
||||
name = models.CharField(max_length=50)
|
||||
batch_recipe = models.BooleanField(null=True)
|
||||
recipe_json = models.TextField(null=True, blank=True)
|
||||
mash = models.ForeignKey('Mash' , on_delete=models.PROTECT, null=True, blank=True)
|
||||
efficiency = models.DecimalField(max_digits=6, decimal_places=4, null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Fermentable(CustomIngredient):
|
||||
categories = {
|
||||
1: 'Base',
|
||||
2: 'Wheat/Oat',
|
||||
3: 'Crystal',
|
||||
4: 'Roasted',
|
||||
5: 'Acid',
|
||||
}
|
||||
|
||||
types = {
|
||||
1: 'Grain',
|
||||
2: 'Adjunct',
|
||||
}
|
||||
|
||||
grain_category = models.IntegerField(choices=categories, default=1)
|
||||
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)
|
||||
protein = models.DecimalField(max_digits=6, decimal_places=4, null=True, blank=True)
|
||||
attenuation = models.DecimalField(max_digits=6, decimal_places=4, null=True, blank=True)
|
||||
lovibond = models.DecimalField(max_digits=6, decimal_places=4, null=True, blank=True)
|
||||
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)
|
||||
non_fermentable = models.BooleanField(null=True, blank=True)
|
||||
ibu_per_unit = models.DecimalField(max_digits=6, decimal_places=4, default=0)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class RecipeFermentable(CustomModel):
|
||||
recipe = models.ForeignKey(BatchRecipe, on_delete=models.CASCADE)
|
||||
fermentable = models.ForeignKey(Fermentable, on_delete=models.CASCADE)
|
||||
quantity = models.DecimalField(max_digits=6, decimal_places=4)
|
||||
|
||||
class Hop(CustomIngredient):
|
||||
uses = {
|
||||
1: 'Bittering',
|
||||
2: 'Aroma',
|
||||
3: 'Both',
|
||||
}
|
||||
|
||||
types = {
|
||||
1: 'Pellet',
|
||||
2: 'Leaf',
|
||||
3: 'Cryo',
|
||||
4: 'CO2 Extract',
|
||||
}
|
||||
|
||||
ibu = models.DecimalField(max_digits=6, decimal_places=4, default=0)
|
||||
use = models.IntegerField(choices=uses, default=1)
|
||||
hop_type = models.IntegerField(choices=types, default=1)
|
||||
alpha = models.DecimalField(max_digits=6, decimal_places=2, default=0)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class RecipeHop(CustomModel):
|
||||
recipe = models.ForeignKey(BatchRecipe, on_delete=models.CASCADE)
|
||||
hop = models.ForeignKey(Hop, on_delete=models.CASCADE)
|
||||
quantity = models.DecimalField(max_digits=6, decimal_places=4)
|
||||
|
||||
class Misc(CustomIngredient):
|
||||
uses = {
|
||||
1: 'Mash',
|
||||
2: 'Sparge',
|
||||
3: 'Boil',
|
||||
4: 'Flamout',
|
||||
5: 'Primary',
|
||||
6: 'Secondary',
|
||||
7: 'Cold Crash',
|
||||
8: 'Bottling',
|
||||
}
|
||||
|
||||
types = {
|
||||
1: 'Spice',
|
||||
2: 'Fining',
|
||||
3: 'Water Agent',
|
||||
4: 'Herb',
|
||||
5: 'Flavor',
|
||||
6: 'Other',
|
||||
}
|
||||
|
||||
use = models.IntegerField(choices=uses, default=1)
|
||||
misc_type = models.IntegerField(choices=types, default=1)
|
||||
water_adjustment = models.BooleanField(null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class RecipeMisc(CustomModel):
|
||||
recipe = models.ForeignKey(BatchRecipe, on_delete=models.CASCADE)
|
||||
misc = models.ForeignKey(Misc, on_delete=models.CASCADE)
|
||||
quantity = models.DecimalField(max_digits=6, decimal_places=4)
|
||||
|
||||
class RecipeYeast(CustomModel):
|
||||
recipe = models.ForeignKey(BatchRecipe, on_delete=models.CASCADE)
|
||||
yeast = models.ForeignKey('yeast.Strain', on_delete=models.CASCADE)
|
||||
|
||||
class Mash(CustomModel):
|
||||
name = models.CharField(max_length=50)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class MashStep(CustomModel):
|
||||
step_types = {
|
||||
1: 'infusion',
|
||||
2: 'temperature',
|
||||
3: 'decoction',
|
||||
}
|
||||
name = models.CharField(max_length=50)
|
||||
step_temp = models.DecimalField(max_digits=6, decimal_places=2)
|
||||
ramp_time = models.DecimalField(max_digits=6, decimal_places=2)
|
||||
step_time = models.DecimalField(max_digits=6, decimal_places=2)
|
||||
step_type = models.IntegerField(choices=step_types, default=1)
|
||||
parent_mash = models.ForeignKey(Mash, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
Loading…
Reference in New Issue
Block a user