mirror of
https://github.com/shouptech/humulus.git
synced 2026-02-03 19:39:43 +00:00
Create a recipe
This commit is contained in:
parent
02b15956d9
commit
5af167e394
8 changed files with 298 additions and 51 deletions
|
|
@ -12,32 +12,4 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import os
|
from humulus.app import create_app
|
||||||
|
|
||||||
from flask import Flask
|
|
||||||
|
|
||||||
|
|
||||||
def create_app(test_config=None):
|
|
||||||
# create and configure the app
|
|
||||||
app = Flask(__name__, instance_relative_config=True)
|
|
||||||
|
|
||||||
if test_config is not None:
|
|
||||||
# Load the test config if provided
|
|
||||||
app.config.from_mapping(test_config)
|
|
||||||
else:
|
|
||||||
# Load config from configuration provided via ENV
|
|
||||||
app.config.from_envvar('HUMULUS_SETTINGS')
|
|
||||||
|
|
||||||
from . import couch
|
|
||||||
couch.init_app(app)
|
|
||||||
|
|
||||||
# Register blueprint for index page
|
|
||||||
from . import home
|
|
||||||
app.register_blueprint(home.bp)
|
|
||||||
app.add_url_rule('/', endpoint='index')
|
|
||||||
|
|
||||||
# Register blueprint for recipes
|
|
||||||
from . import recipes
|
|
||||||
app.register_blueprint(recipes.bp)
|
|
||||||
|
|
||||||
return app
|
|
||||||
|
|
|
||||||
43
src/humulus/app.py
Normal file
43
src/humulus/app.py
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Copyright 2019 Mike Shoup
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
|
||||||
|
|
||||||
|
def create_app(test_config=None):
|
||||||
|
# create and configure the app
|
||||||
|
app = Flask(__name__, instance_relative_config=True)
|
||||||
|
|
||||||
|
if test_config is not None:
|
||||||
|
# Load the test config if provided
|
||||||
|
app.config.from_mapping(test_config)
|
||||||
|
else:
|
||||||
|
# Load config from configuration provided via ENV
|
||||||
|
app.config.from_envvar('HUMULUS_SETTINGS')
|
||||||
|
|
||||||
|
from . import couch
|
||||||
|
couch.init_app(app)
|
||||||
|
|
||||||
|
# Register blueprint for index page
|
||||||
|
from . import home
|
||||||
|
app.register_blueprint(home.bp)
|
||||||
|
app.add_url_rule('/', endpoint='index')
|
||||||
|
|
||||||
|
# Register blueprint for recipes
|
||||||
|
from . import recipes
|
||||||
|
app.register_blueprint(recipes.bp)
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
@ -18,19 +18,58 @@ from decimal import Decimal
|
||||||
|
|
||||||
from flask import Blueprint, flash, redirect, render_template, url_for
|
from flask import Blueprint, flash, redirect, render_template, url_for
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, DecimalField, TextAreaField
|
from wtforms import (Form, StringField, DecimalField, TextAreaField, FieldList,
|
||||||
|
FormField, SelectField)
|
||||||
from wtforms.validators import DataRequired
|
from wtforms.validators import DataRequired
|
||||||
|
|
||||||
from humulus.couch import get_doc_or_404, put_doc
|
from humulus.couch import get_doc_or_404, put_doc
|
||||||
|
|
||||||
bp = Blueprint('recipes', __name__, url_prefix='/recipes')
|
bp = Blueprint('recipes', __name__, url_prefix='/recipes')
|
||||||
|
|
||||||
class RecipeForm(FlaskForm):
|
|
||||||
|
class FermentableForm(Form):
|
||||||
|
"""Form for fermentables.
|
||||||
|
|
||||||
|
CSRF is disabled for this subform (using `Form as parent class) because it
|
||||||
|
is never used by itself.
|
||||||
|
"""
|
||||||
|
|
||||||
name = StringField('Name', validators=[DataRequired()])
|
name = StringField('Name', validators=[DataRequired()])
|
||||||
efficiency = DecimalField('Batch Efficiency', validators=[DataRequired()])
|
type = SelectField('Type', validators=[DataRequired()],
|
||||||
volume = DecimalField('Batch Volume', validators=[DataRequired()])
|
choices=[(c, c) for c in ['Grain', 'LME', 'DME', 'Sugar',
|
||||||
|
'Non-fermentable', 'Other']])
|
||||||
|
amount = DecimalField('Amount (lb)', validators=[DataRequired()])
|
||||||
|
ppg = DecimalField('PPG', validators=[DataRequired()])
|
||||||
|
color = DecimalField('Color (°L)', validators=[DataRequired()])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def doc(self):
|
||||||
|
"""Returns a dictionary that can be deserialized into JSON.
|
||||||
|
|
||||||
|
Used for putting into CouchDB.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'name': self.name.data,
|
||||||
|
'type': self.type.data,
|
||||||
|
'amount': str(self.amount.data),
|
||||||
|
'ppg': str(self.ppg.data),
|
||||||
|
'color': str(self.color.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RecipeForm(FlaskForm):
|
||||||
|
"""Form for recipes."""
|
||||||
|
name = StringField('Name', validators=[DataRequired()])
|
||||||
|
efficiency = DecimalField('Batch Efficiency (%)', validators=[DataRequired()])
|
||||||
|
volume = DecimalField('Batch Volume (gal)', validators=[DataRequired()])
|
||||||
notes = TextAreaField('Notes')
|
notes = TextAreaField('Notes')
|
||||||
|
|
||||||
|
fermentables = FieldList(
|
||||||
|
FormField(FermentableForm),
|
||||||
|
min_entries=0,
|
||||||
|
max_entries=20
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def doc(self):
|
def doc(self):
|
||||||
"""Returns a dictionary that can be deserialized into JSON.
|
"""Returns a dictionary that can be deserialized into JSON.
|
||||||
|
|
@ -41,7 +80,8 @@ class RecipeForm(FlaskForm):
|
||||||
'name': self.name.data,
|
'name': self.name.data,
|
||||||
'efficiency': str(self.efficiency.data),
|
'efficiency': str(self.efficiency.data),
|
||||||
'volume': str(self.volume.data),
|
'volume': str(self.volume.data),
|
||||||
'notes': self.notes.data
|
'notes': self.notes.data,
|
||||||
|
'fermentables': [f.doc for f in self.fermentables]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
.bd-placeholder-img {
|
.placeholder-img {
|
||||||
font-size: 1.125rem;
|
font-size: 1.125rem;
|
||||||
text-anchor: middle;
|
text-anchor: middle;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.bd-placeholder-img-lg {
|
.placeholder-img-lg {
|
||||||
font-size: 3.5rem;
|
font-size: 3.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ limitations under the License.
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
||||||
<main role="main" class="container">
|
<main role="main" class="container">
|
||||||
{% block alert %}
|
{% block alert %}
|
||||||
{% for category, message in get_flashed_messages(with_categories=true) %}
|
{% for category, message in get_flashed_messages(with_categories=true) %}
|
||||||
|
|
@ -54,7 +54,6 @@ limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
<!-- Bootstrap JS -->
|
<!-- Bootstrap JS -->
|
||||||
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
|
||||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
-#}
|
-#}
|
||||||
{% macro render_field_with_errors(field) %}
|
{% macro render_field_with_errors(field, size='') %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{{ field.label }} {{ field(class_='form-control', **kwargs)|safe }}
|
{{ field.label }} {{ field(class_='form-control ' + size, **kwargs)|safe }}
|
||||||
{% if field.errors %}
|
{% if field.errors %}
|
||||||
{% for error in field.errors %}
|
{% for error in field.errors %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,165 @@ limitations under the License.
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="row"><h1>Create a new recipe</h1></div>
|
<div class="row"><h1>Create a new recipe</h1></div>
|
||||||
<div class="row">
|
<form method="POST" action="{{ url_for('recipes.create') }}">
|
||||||
<form method="POST" action="{{ url_for('recipes.create') }}">
|
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
{{ render_field_with_errors(form.name) }}
|
<div class="row">
|
||||||
{{ render_field_with_errors(form.efficiency) }}
|
<div class="col-sm-6">{{ render_field_with_errors(form.name) }}</div>
|
||||||
{{ render_field_with_errors(form.volume) }}
|
<div class="col-sm-3">{{ render_field_with_errors(form.efficiency) }}</div>
|
||||||
{{ render_field_with_errors(form.notes) }}
|
<div class="col-sm-3">{{ render_field_with_errors(form.volume) }}</div>
|
||||||
<button type="submit" class="btn btn-primary">Submit</button>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
<div class="row">
|
||||||
|
<div class="col"><h3>Fermentables</h3></div>
|
||||||
|
</div>
|
||||||
|
<div id="ferms" data-length="{{ form.fermentables|length }}">
|
||||||
|
{% for fermentable in form.fermentables %}
|
||||||
|
<div class="border pl-2 pr-2 pt-1 pb-1 ferm-form" data-index="{{ loop.index0 }}">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
{{ render_field_with_errors(fermentable.form.name, 'form-control-sm') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm">{{ render_field_with_errors(fermentable.form.type, 'form-control-sm') }}</div>
|
||||||
|
<div class="col-sm">{{ render_field_with_errors(fermentable.form.amount, 'form-control-sm') }}</div>
|
||||||
|
<div class="col-sm">{{ render_field_with_errors(fermentable.form.ppg, 'form-control-sm') }}</div>
|
||||||
|
<div class="col-sm">{{ render_field_with_errors(fermentable.form.color, 'form-control-sm') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<button type="button" class="float-right btn btn-sm btn-outline-danger rem-ferm">Remove fermentable</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="row pt-1 pb-1">
|
||||||
|
<div class="col">
|
||||||
|
<button type="button" id="add-ferm" class="btn btn-secondary btn-sm">Add a fermentable</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">{{ render_field_with_errors(form.notes) }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col"><button type="submit" class="btn btn-primary">Create Recipe</button></div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Remove a fermentable
|
||||||
|
function removeFerm() {
|
||||||
|
var $removedForm = $(this).closest('.ferm-form');
|
||||||
|
var removedIndex = parseInt($removedForm.data('index'));
|
||||||
|
$removedForm.remove();
|
||||||
|
var $fermsDiv = $('#ferms');
|
||||||
|
$fermsDiv.data('length', $fermsDiv.data('length') - 1);
|
||||||
|
console.log('calling adjust');
|
||||||
|
adjustFermIndices(removedIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a fermentable
|
||||||
|
function addFerm() {
|
||||||
|
var $fermsDiv = $('#ferms');
|
||||||
|
var fermsLength = $fermsDiv.data('length');
|
||||||
|
if (fermsLength == 20) {
|
||||||
|
window.alert("Can't have more than 20 fermentables.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var newFerm = `<div class="border pl-2 pr-2 pt-1 pb-1 ferm-form" data-index="${fermsLength}">` +
|
||||||
|
// Name field
|
||||||
|
'<div class="row"><div class="col"><div class="form-group">' +
|
||||||
|
`<label for="fermentables-${fermsLength}-name">Name</label>` +
|
||||||
|
`<input class="form-control form-control-sm" id="fermentables-${fermsLength}-name"` +
|
||||||
|
` name="fermentables-${fermsLength}-name" required type="text" value="">` +
|
||||||
|
'</div></div></div>' + // End name field
|
||||||
|
'<div class="row">' +
|
||||||
|
// Type field
|
||||||
|
'<div class="col-sm"><div class="form-group">' +
|
||||||
|
`<label for="fermentables-${fermsLength}-type">Type</label>` +
|
||||||
|
`<select class="form-control form-control-sm" id="fermentables-${fermsLength}-type"` +
|
||||||
|
` name="fermentables-${fermsLength}-type" required>` +
|
||||||
|
'<option value="Grain">Grain</option>' +
|
||||||
|
'<option value="LME">LME</option>' +
|
||||||
|
'<option value="DME">DME</option>' +
|
||||||
|
'<option value="Sugar">Sugar</option>' +
|
||||||
|
'<option value="Non-fermentable">Non-fermentable</option>' +
|
||||||
|
'<option value="Other">Other</option></select>' +
|
||||||
|
'</div></div>' + // End type field
|
||||||
|
// Amount field
|
||||||
|
'<div class="col-sm"><div class="form-group">' +
|
||||||
|
`<label for="fermentables-${fermsLength}-amount">Amount (lb)</label>` +
|
||||||
|
`<input class="form-control form-control-sm" id="fermentables-${fermsLength}-amount"` +
|
||||||
|
` name="fermentables-${fermsLength}-amount" required type="text" value="">` +
|
||||||
|
'</div></div>' + // End amount field
|
||||||
|
// PPG field
|
||||||
|
'<div class="col-sm"><div class="form-group">' +
|
||||||
|
`<label for="fermentables-${fermsLength}-ppg">PPG</label>` +
|
||||||
|
`<input class="form-control form-control-sm" id="fermentables-${fermsLength}-ppg"` +
|
||||||
|
` name="fermentables-${fermsLength}-ppg" required type="text" value="">` +
|
||||||
|
'</div></div>' + // End PPG field
|
||||||
|
// Color field
|
||||||
|
'<div class="col-sm"><div class="form-group">' +
|
||||||
|
`<label for="fermentables-${fermsLength}-color">Color (°L)</label>` +
|
||||||
|
`<input class="form-control form-control-sm" id="fermentables-${fermsLength}-color"` +
|
||||||
|
` name="fermentables-${fermsLength}-color" required type="text" value="">` +
|
||||||
|
'</div></div>' + // End PPG field
|
||||||
|
'</div>' +
|
||||||
|
// Remove button
|
||||||
|
'<div class="row"><div class="col">' +
|
||||||
|
'<button type="button" class="float-right btn btn-sm btn-outline-danger rem-ferm">' +
|
||||||
|
'Remove fermentable</button>' +
|
||||||
|
'</div></div>' + // End remove button
|
||||||
|
'</div>';
|
||||||
|
$fermsDiv.append(newFerm);
|
||||||
|
$fermsDiv.data('length', fermsLength + 1);
|
||||||
|
$('.rem-ferm').unbind('click');
|
||||||
|
$('.rem-ferm').click(removeFerm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Correct all the indexes for the removed form
|
||||||
|
function adjustFermIndices(removedIndex) {
|
||||||
|
var $forms = $('.ferm-form')
|
||||||
|
console.log($forms)
|
||||||
|
$forms.each(function(i) {
|
||||||
|
var $form = $(this);
|
||||||
|
var index = parseInt($form.data('index'));
|
||||||
|
var newIndex = index - 1;
|
||||||
|
console.log('index: ' + index)
|
||||||
|
console.log('newIndex:' + newIndex);
|
||||||
|
console.log('removedIndex: ' + removedIndex);
|
||||||
|
if (index <= removedIndex) {
|
||||||
|
// Skip this one
|
||||||
|
console.log('index < removedIndex is true');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
console.log("I didn't quit");
|
||||||
|
|
||||||
|
// Change form index
|
||||||
|
$form.data('index', newIndex);
|
||||||
|
|
||||||
|
// Change ID in form input, select, and labels
|
||||||
|
$form.find('input').each(function(j) {
|
||||||
|
var $item = $(this)
|
||||||
|
$item.attr('id', $item.attr('id').replace(index, newIndex));
|
||||||
|
$item.attr('name', $item.attr('name').replace(index, newIndex));
|
||||||
|
});
|
||||||
|
$form.find('select').each(function(j) {
|
||||||
|
var $item = $(this)
|
||||||
|
$item.attr('id', $item.attr('id').replace(index, newIndex));
|
||||||
|
$item.attr('name', $item.attr('name').replace(index, newIndex));
|
||||||
|
});
|
||||||
|
//$form.find('label').each(function(j) {
|
||||||
|
// var $item = $(this)
|
||||||
|
// $item.attr('for', $item.attr('id').replace(index, newIndex));
|
||||||
|
//});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#add-ferm').click(addFerm);
|
||||||
|
$('.rem-ferm').click(removeFerm);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,14 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
from humulus.couch import get_db
|
from humulus.couch import get_db
|
||||||
|
from humulus.recipes import FermentableForm, RecipeForm
|
||||||
|
|
||||||
|
|
||||||
def test_create(client, app):
|
def test_create(client, app):
|
||||||
|
"""Test success in creating a recipe document."""
|
||||||
# Test GET
|
# Test GET
|
||||||
response = client.get('/recipes/create')
|
response = client.get('/recipes/create')
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
@ -40,6 +44,7 @@ def test_create(client, app):
|
||||||
|
|
||||||
|
|
||||||
def test_info(client):
|
def test_info(client):
|
||||||
|
"""Test success in retrieving a recipe document."""
|
||||||
# Validate 404
|
# Validate 404
|
||||||
response = client.get('/recipes/info/thisdoesnotexist')
|
response = client.get('/recipes/info/thisdoesnotexist')
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
@ -48,3 +53,40 @@ def test_info(client):
|
||||||
response = client.get('/recipes/info/awesome-lager')
|
response = client.get('/recipes/info/awesome-lager')
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert b'Awesome Lager' in response.data
|
assert b'Awesome Lager' in response.data
|
||||||
|
|
||||||
|
|
||||||
|
def test_recipe_form_doc(app):
|
||||||
|
"""Test if a recipeform can be turned into a document.
|
||||||
|
|
||||||
|
This test also tests that subforms can be turned into a document. Subforms
|
||||||
|
are not tested individually since they will never be used individually.
|
||||||
|
"""
|
||||||
|
with app.app_context():
|
||||||
|
recipe = RecipeForm()
|
||||||
|
|
||||||
|
ferm = FermentableForm()
|
||||||
|
ferm.name.data = 'Test'
|
||||||
|
ferm.type.data = 'Grain'
|
||||||
|
ferm.amount.data = Decimal('5.5')
|
||||||
|
ferm.ppg.data = Decimal('37')
|
||||||
|
ferm.color.data = Decimal('1.8')
|
||||||
|
|
||||||
|
recipe.name.data = 'Test'
|
||||||
|
recipe.efficiency.data = Decimal('65')
|
||||||
|
recipe.volume.data = Decimal('5.5')
|
||||||
|
recipe.notes.data = 'This is a test'
|
||||||
|
recipe.fermentables = [ferm]
|
||||||
|
|
||||||
|
assert recipe.doc == {
|
||||||
|
'name': 'Test',
|
||||||
|
'efficiency': '65',
|
||||||
|
'volume': '5.5',
|
||||||
|
'notes': 'This is a test',
|
||||||
|
'fermentables': [{
|
||||||
|
'name': 'Test',
|
||||||
|
'type': 'Grain',
|
||||||
|
'amount': '5.5',
|
||||||
|
'ppg': '37',
|
||||||
|
'color': '1.8',
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue