Add parent field to all recipe relations.
Need to make a copy of master item when attaching to a recipe. Also add some more calculations.
This commit is contained in:
parent
34470cbea7
commit
ea4340c8ea
@ -0,0 +1,77 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-18 23:23
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0006_batchrecipe_batch_size_alter_batchrecipe_efficiency'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='fermentable',
|
||||
name='parent',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='beer.fermentable'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='hop',
|
||||
name='parent',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='beer.hop'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mash',
|
||||
name='parent',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='beer.mash'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='misc',
|
||||
name='parent',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='beer.misc'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='fermentable',
|
||||
name='fermentable_type',
|
||||
field=models.IntegerField(choices=[(1, 'Grain'), (2, 'Adjunct'), (3, 'Sugar')], default=1),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EquipmentProfile',
|
||||
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)),
|
||||
('hlt_deadspace', models.DecimalField(decimal_places=2, default=0.25, max_digits=6)),
|
||||
('mt_deadspace', models.DecimalField(decimal_places=2, default=0.25, max_digits=6)),
|
||||
('mt_capacity', models.DecimalField(decimal_places=2, default=10, max_digits=6)),
|
||||
('grain_absorption', models.DecimalField(decimal_places=2, default=0.12, max_digits=6)),
|
||||
('kettle_deadspace', models.DecimalField(decimal_places=2, default=0.25, max_digits=6)),
|
||||
('kettle_plumbing_loss', models.DecimalField(decimal_places=2, default=0.25, max_digits=6)),
|
||||
('kettle_boil_rate', models.DecimalField(decimal_places=2, default=0.5, max_digits=6)),
|
||||
('batch_volume', models.DecimalField(decimal_places=2, default=5.5, max_digits=6)),
|
||||
('leaf_hop_trub', models.DecimalField(decimal_places=2, default=0.0625, max_digits=6)),
|
||||
('pellet_hop_trub', models.DecimalField(decimal_places=2, default=0.025, max_digits=6)),
|
||||
('hops_remain_kettle', models.BooleanField(default=True)),
|
||||
('mt_initial_hear', models.DecimalField(decimal_places=2, default=0.74, max_digits=6)),
|
||||
('mt_heat_loss_hour', models.DecimalField(decimal_places=2, default=2.0, max_digits=6)),
|
||||
('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='beer.equipmentprofile')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RecipeEquipment',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('equipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='beer.equipmentprofile')),
|
||||
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='beer.batchrecipe')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
@ -0,0 +1,39 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-18 23:37
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0007_fermentable_parent_hop_parent_mash_parent_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='parent',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='beer.equipmentprofile'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='fermentable',
|
||||
name='parent',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='beer.fermentable'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hop',
|
||||
name='parent',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='beer.hop'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mash',
|
||||
name='parent',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='beer.mash'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='misc',
|
||||
name='parent',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='beer.misc'),
|
||||
),
|
||||
]
|
42
beer/migrations/0009_batchrecipe_equipment_and_more.py
Normal file
42
beer/migrations/0009_batchrecipe_equipment_and_more.py
Normal file
@ -0,0 +1,42 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-19 00:13
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('beer', '0008_alter_equipmentprofile_parent_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='batchrecipe',
|
||||
name='equipment',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='beer.equipmentprofile'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='leaf_hop_trub',
|
||||
field=models.DecimalField(decimal_places=4, default=0.0625, max_digits=6),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='mt_heat_loss_hour',
|
||||
field=models.DecimalField(decimal_places=4, default=2.0, max_digits=6),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='mt_initial_hear',
|
||||
field=models.DecimalField(decimal_places=4, default=0.74, max_digits=6),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentprofile',
|
||||
name='pellet_hop_trub',
|
||||
field=models.DecimalField(decimal_places=4, default=0.025, max_digits=6),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='RecipeEquipment',
|
||||
),
|
||||
]
|
@ -69,6 +69,7 @@ class CustomIngredient(CustomModel):
|
||||
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)
|
||||
parent = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
@ -78,7 +79,8 @@ class BatchRecipe(CustomModel):
|
||||
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)
|
||||
mash = models.ForeignKey('Mash', on_delete=models.PROTECT, null=True, blank=True)
|
||||
equipment = models.ForeignKey('EquipmentProfile', on_delete=models.PROTECT, null=True, blank=True)
|
||||
efficiency = models.DecimalField(max_digits=6, decimal_places=2, default=75)
|
||||
batch_size = models.DecimalField(max_digits=6, decimal_places=2, default=11)
|
||||
|
||||
@ -86,6 +88,10 @@ class BatchRecipe(CustomModel):
|
||||
def fermentables(self):
|
||||
return [x for x in list(self.recipefermentable_set.all())]
|
||||
|
||||
@property
|
||||
def fermentable_weight(self):
|
||||
return sum([x.quantity for x in self.fermentables])
|
||||
|
||||
@property
|
||||
def hops(self):
|
||||
return [x for x in list(self.recipehop_set.all())]
|
||||
@ -122,7 +128,7 @@ class BatchRecipe(CustomModel):
|
||||
|
||||
@property
|
||||
def pre_boil_sg(self):
|
||||
return self.ferm_yield / (self.final_volume + self.boil_off_gph)
|
||||
return round(1 + self.ferm_yield / (self.final_volume + self.boil_off_gph) / 1000, 3)
|
||||
|
||||
@property
|
||||
def hop_water_loss(self):
|
||||
@ -135,20 +141,20 @@ class BatchRecipe(CustomModel):
|
||||
result = self.kettle_dead_space - self.hop_water_loss
|
||||
return float(max(0, result)) # No deadspace if its all filled with hop trub)
|
||||
|
||||
# Else hops in bag or removed
|
||||
#TODO: Else hops in bag or removed
|
||||
return 0
|
||||
|
||||
@property
|
||||
def kettle_hose_loss(self):
|
||||
return .25 # TODO
|
||||
return float(self.equipment.kettle_plumbing_loss)
|
||||
|
||||
@property
|
||||
def kettle_dead_space(self):
|
||||
return .25 # TODO
|
||||
return float(self.equipment.kettle_deadspace)
|
||||
|
||||
@property
|
||||
def boil_off_gph(self):
|
||||
return .8 # TODO
|
||||
return float(self.equipment.kettle_boil_rate)
|
||||
|
||||
@property
|
||||
def ibu_tinseth(self):
|
||||
@ -172,6 +178,14 @@ class BatchRecipe(CustomModel):
|
||||
|
||||
return '#{}'.format(SRM_HEX[int(self.srm)])
|
||||
|
||||
@property
|
||||
def bu_gu(self):
|
||||
gu = (self.original_sg - 1) * 1000
|
||||
return self.ibu_tinseth / gu
|
||||
|
||||
@property
|
||||
def rbr(self):
|
||||
return self.bu_gu * (1 + (.75 - 0.7655)) # .75 needs to be calculated number...
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@ -211,6 +225,10 @@ class RecipeFermentable(CustomModel):
|
||||
fermentable = models.ForeignKey(Fermentable, on_delete=models.CASCADE)
|
||||
quantity = models.DecimalField(max_digits=6, decimal_places=4)
|
||||
|
||||
@property
|
||||
def percent(self):
|
||||
return 100 * float(self.quantity) / float(self.recipe.fermentable_weight)
|
||||
|
||||
@property
|
||||
def srm(self):
|
||||
return round(float(self.fermentable.lovibond) * float(self.quantity) / self.recipe.final_volume, 1)
|
||||
@ -262,7 +280,8 @@ class RecipeHop(CustomModel):
|
||||
|
||||
ibu = 0
|
||||
|
||||
average_wort_sg = (self.recipe.pre_boil_sg/1000 + (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)
|
||||
@ -312,6 +331,7 @@ class RecipeYeast(CustomModel):
|
||||
|
||||
class Mash(CustomModel):
|
||||
name = models.CharField(max_length=50)
|
||||
parent = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@ -331,3 +351,24 @@ class MashStep(CustomModel):
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class EquipmentProfile(CustomModel):
|
||||
name = models.CharField(max_length=50)
|
||||
parent = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
||||
# Water managment stuff
|
||||
hlt_deadspace = models.DecimalField(max_digits=6, decimal_places=2, default=0.25)
|
||||
mt_deadspace = models.DecimalField(max_digits=6, decimal_places=2, default=0.25)
|
||||
mt_capacity = models.DecimalField(max_digits=6, decimal_places=2, default=10)
|
||||
grain_absorption = models.DecimalField(max_digits=6, decimal_places=2, default=0.12) # gal/lb
|
||||
kettle_deadspace = models.DecimalField(max_digits=6, decimal_places=2, default=0.25)
|
||||
kettle_plumbing_loss = models.DecimalField(max_digits=6, decimal_places=2, default=0.25)
|
||||
kettle_boil_rate = models.DecimalField(max_digits=6, decimal_places=2, default=0.5) # gal/hr
|
||||
batch_volume = models.DecimalField(max_digits=6, decimal_places=2, default=5.5)
|
||||
leaf_hop_trub = models.DecimalField(max_digits=6, decimal_places=4, default=0.0625)
|
||||
pellet_hop_trub = models.DecimalField(max_digits=6, decimal_places=4, default=0.025)
|
||||
hops_remain_kettle = models.BooleanField(default=True)
|
||||
|
||||
# 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)
|
||||
|
@ -32,12 +32,23 @@ input, label {
|
||||
<dd>All Grain</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-lg-3" style="border:1px solid #cecece; margin-right:.5em; margin-left:.5em">
|
||||
<div>Equipment Selection</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>
|
||||
<!--
|
||||
<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>
|
||||
<dt>Mash Efficiency</dt>
|
||||
<dd>{{ recipe.efficiency|floatformat:2 }} %</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-lg-3" style="border:1px solid #cecece; margin-right:.5em; margin-left:.5em">
|
||||
<div class="col-xl-3" style="border:1px solid #cecece; margin-right:.5em; margin-left:.5em">
|
||||
Style Data
|
||||
</div>
|
||||
</div>
|
||||
@ -57,14 +68,20 @@ input, label {
|
||||
<table class="table table-sm ">
|
||||
<tbody>
|
||||
{% for f in recipe.recipefermentable_set.all %}
|
||||
<tr><td>{{ f.quantity|floatformat:2 }} lb</td><td>{{ f.fermentable.name }}<br>{{ f.srm }}</td></tr>
|
||||
<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>
|
||||
<p class="text-end small">
|
||||
Pre-Boil Gravity: <b>1.XXX</b><br>
|
||||
Pre-Boil Gravity: <b>{{ recipe.pre_boil_sg }}</b><br>
|
||||
Original Gravity: <b>{{ recipe.original_sg }}</b><br>
|
||||
Color: <b>XXX SRM</b>
|
||||
Color: <b>{{ recipe.srm|floatformat:0 }} SRM</b>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -77,14 +94,19 @@ input, label {
|
||||
<table class="table table-sm ">
|
||||
<tbody>
|
||||
{% for h in recipe.recipehop_set.all %}
|
||||
<tr><td>{{ h.quantity|floatformat:2 }} oz</td><td>{{ h.hop.name }} {{ h.hop.alpha|floatformat:1 }} %<br>{{ h.ibu_tinseth|floatformat:1 }} IBU</td><td>{{ h.time }} min</td></tr>
|
||||
<tr>
|
||||
<td>{{ h.quantity|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>
|
||||
<td>{{ h.time }} min</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="text-end small">
|
||||
Total IBU: <b>{{ recipe.ibu_tinseth|floatformat:1 }}</b><br>
|
||||
BU/GU: <b>0.XX</b><br>
|
||||
RBR: <b>0.XX</b>
|
||||
BU/GU: <b>{{ recipe.bu_gu|floatformat:2 }}</b><br>
|
||||
RBR: <b>{{ recipe.rbr|floatformat:2 }}</b>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user