From 98404e38ef7df41f2ff038ff7278f0b2627be47f Mon Sep 17 00:00:00 2001 From: Mike Shoup Date: Sat, 22 Jun 2019 21:51:39 -0600 Subject: [PATCH] Add yeast --- src/humulus/recipes.py | 74 +++++++++++++++++++++-- src/humulus/templates/recipes/create.html | 22 +++++++ tests/test_recipes.py | 71 +++++++++++++++++++--- 3 files changed, 154 insertions(+), 13 deletions(-) diff --git a/src/humulus/recipes.py b/src/humulus/recipes.py index 2ca92f1..d79c78a 100644 --- a/src/humulus/recipes.py +++ b/src/humulus/recipes.py @@ -20,7 +20,7 @@ from flask import Blueprint, flash, redirect, render_template, url_for from flask_wtf import FlaskForm from wtforms import (Form, StringField, DecimalField, TextAreaField, FieldList, FormField, SelectField) -from wtforms.validators import DataRequired +from wtforms.validators import DataRequired, Optional from humulus.couch import get_doc_or_404, put_doc @@ -85,6 +85,62 @@ class HopForm(Form): 'amount': str(self.amount.data) } + +class YeastForm(Form): + """Form for yeast. + + CSRF is disabled for this subform (using `Form as parent class) because it + is never used by itself. + """ + name = StringField('Name', validators=[DataRequired()]) + type = SelectField('Type', default='', + choices=[(c, c) for c in ['', 'Liquid', 'Dry']], + validators=[Optional()]) + lab = StringField('Lab') + code = StringField('Lab Code') + flocculation = SelectField('Flocculation', default='', + choices=[(c, c) for c in ['', 'Low', + 'Medium', 'High']], + validators=[Optional()]) + low_attenuation = DecimalField('Low Attenuation', + validators=[DataRequired()]) + high_attenuation = DecimalField('High Attenuation', + validators=[DataRequired()]) + min_temperature = DecimalField('Min Temp (°F)', + validators=[Optional()]) + max_temperature = DecimalField('Max Temp (°F)', + validators=[Optional()]) + abv_tolerance = DecimalField('ABV % tolerance', + validators=[Optional()]) + + @property + def doc(self): + """Returns a dictionary that can be deserialized into JSON. + + Used for putting into CouchDB. + """ + yeast = { + 'name': self.name.data, + 'low_attenuation': str(self.low_attenuation.data), + 'high_attenuation': str(self.high_attenuation.data) + } + if self.type.data: + yeast['type'] = self.type.data + if self.lab.data: + yeast['lab'] = self.lab.data + if self.code.data: + yeast['code'] = self.code.data + if self.flocculation.data: + yeast['flocculation'] = self.flocculation.data + if self.min_temperature.data: + yeast['min_temperature'] = str(self.min_temperature.data) + if self.max_temperature.data: + yeast['max_temperature'] = str(self.max_temperature.data) + if self.abv_tolerance.data: + yeast['abv_tolerance'] = str(self.abv_tolerance.data) + return yeast + + class RecipeForm(FlaskForm): """Form for recipes.""" name = StringField('Name', validators=[DataRequired()]) @@ -102,6 +158,7 @@ class RecipeForm(FlaskForm): min_entries=0, max_entries=20 ) + yeast = FormField(YeastForm) @property def doc(self): @@ -109,15 +166,21 @@ class RecipeForm(FlaskForm): Used for putting into CouchDB. """ - return { + recipe = { 'name': self.name.data, 'efficiency': str(self.efficiency.data), 'volume': str(self.volume.data), - 'notes': self.notes.data, - 'fermentables': [f.doc for f in self.fermentables], - 'hops': [h.doc for h in self.hops], + 'notes': self.notes.data } + if len(self.fermentables) > 0: + recipe['fermentables'] = [f.doc for f in self.fermentables] + if len(self.hops) > 0: + recipe['hops'] = [h.doc for h in self.hops] + if self.yeast.doc['name']: + recipe['yeast'] = self.yeast.doc + return recipe + @bp.route('/create', methods=('GET', 'POST')) def create(): @@ -127,7 +190,6 @@ def create(): response = put_doc(form.doc) flash('Created recipe: {}'.format(form.name.data), 'success') return redirect(url_for('recipes.info', id=response['_id'])) - return render_template('recipes/create.html', form=form) diff --git a/src/humulus/templates/recipes/create.html b/src/humulus/templates/recipes/create.html index 37da459..1485cec 100644 --- a/src/humulus/templates/recipes/create.html +++ b/src/humulus/templates/recipes/create.html @@ -92,6 +92,28 @@ + {#- + Recipe Yeast + -#} +

Yeast

+
+
+
+
{{ render_field_with_errors(form.yeast.form.name, 'form-control-sm') }}
+
{{ render_field_with_errors(form.yeast.form.type, 'form-control-sm') }}
+
{{ render_field_with_errors(form.yeast.form.lab, 'form-control-sm') }}
+
{{ render_field_with_errors(form.yeast.form.code, 'form-control-sm') }}
+
+
+
{{ render_field_with_errors(form.yeast.form.low_attenuation, 'form-control-sm') }}
+
{{ render_field_with_errors(form.yeast.form.high_attenuation, 'form-control-sm') }}
+
{{ render_field_with_errors(form.yeast.form.flocculation, 'form-control-sm') }}
+
{{ render_field_with_errors(form.yeast.form.min_temperature, 'form-control-sm') }}
+
{{ render_field_with_errors(form.yeast.form.max_temperature, 'form-control-sm') }}
+
{{ render_field_with_errors(form.yeast.form.abv_tolerance, 'form-control-sm') }}
+
+
+
{#- Recipe Notes -#} diff --git a/tests/test_recipes.py b/tests/test_recipes.py index 0d63236..1d4be02 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -15,7 +15,7 @@ from decimal import Decimal from humulus.couch import get_db -from humulus.recipes import FermentableForm, HopForm, RecipeForm +from humulus.recipes import FermentableForm, HopForm, RecipeForm, YeastForm def test_create(client, app): @@ -29,7 +29,10 @@ def test_create(client, app): 'efficiency': '65', 'name': 'Test', 'notes': 'Test', - 'volume': '5.5' + 'volume': '5.5', + 'yeast-name': 'Test', + 'yeast-low_attenuation': '60', + 'yeast-high_attenuation': '75', } response = client.post('/recipes/create', data=data) assert response.status_code == 302 @@ -55,6 +58,41 @@ def test_info(client): assert b'Awesome Lager' in response.data +def test_yeast_form_doc(app): + """Evaluates conditionals in generation of doc from a yeast form.""" + yeast = YeastForm() + yeast.name.data = 'Test' + yeast.low_attenuation.data = Decimal('60') + yeast.high_attenuation.data = Decimal('75') + + assert yeast.doc == { + 'name': 'Test', + 'low_attenuation': '60', + 'high_attenuation': '75' + } + + yeast.type.data = 'Dry' + yeast.code.data = 'INIS-001' + yeast.lab.data = 'Inland Island' + yeast.flocculation.data = 'Low' + yeast.min_temperature.data = Decimal('40') + yeast.max_temperature.data = Decimal('50') + yeast.abv_tolerance.data = Decimal('15') + + assert yeast.doc == { + 'name': 'Test', + 'low_attenuation': '60', + 'high_attenuation': '75', + 'flocculation': 'Low', + 'type': 'Dry', + 'code': 'INIS-001', + 'lab': 'Inland Island', + 'min_temperature': '40', + 'max_temperature': '50', + 'abv_tolerance': '15' + } + + def test_recipe_form_doc(app): """Test if a recipeform can be turned into a document. @@ -64,6 +102,18 @@ def test_recipe_form_doc(app): with app.app_context(): recipe = RecipeForm() + recipe.name.data = 'Test' + recipe.efficiency.data = Decimal('65') + recipe.volume.data = Decimal('5.5') + recipe.notes.data = 'This is a test' + + assert recipe.doc == { + 'name': 'Test', + 'efficiency': '65', + 'volume': '5.5', + 'notes': 'This is a test', + } + ferm = FermentableForm() ferm.name.data = 'Test' ferm.type.data = 'Grain' @@ -78,12 +128,14 @@ def test_recipe_form_doc(app): hop.duration.data = Decimal('60') hop.amount.data = Decimal('0.5') - recipe.name.data = 'Test' - recipe.efficiency.data = Decimal('65') - recipe.volume.data = Decimal('5.5') - recipe.notes.data = 'This is a test' + yeast = YeastForm() + yeast.name.data = 'Test' + yeast.low_attenuation.data = '70' + yeast.high_attenuation.data = '75' + recipe.fermentables = [ferm] recipe.hops = [hop] + recipe.yeast = yeast assert recipe.doc == { 'name': 'Test', @@ -103,5 +155,10 @@ def test_recipe_form_doc(app): 'alpha': '12.5', 'duration': '60', 'amount': '0.5' - }] + }], + 'yeast': { + 'name': 'Test', + 'low_attenuation': '70', + 'high_attenuation': '75' + } }