mirror of
https://github.com/shouptech/humulus.git
synced 2026-02-03 18:19:42 +00:00
Compare commits
2 commits
868d12fced
...
5968623b76
| Author | SHA1 | Date | |
|---|---|---|---|
| 5968623b76 | |||
| 40fe65cd69 |
7 changed files with 88 additions and 11 deletions
|
|
@ -27,7 +27,9 @@ from wtforms import (Form, StringField, DecimalField, TextAreaField, FieldList,
|
||||||
from wtforms.validators import DataRequired, Optional
|
from wtforms.validators import DataRequired, Optional
|
||||||
|
|
||||||
from humulus.auth import login_required
|
from humulus.auth import login_required
|
||||||
from humulus.couch import get_doc_or_404, put_doc, update_doc, get_view
|
from humulus.couch import (get_doc, get_doc_or_404, put_doc, update_doc,
|
||||||
|
get_view)
|
||||||
|
from humulus.styles import get_styles_list
|
||||||
|
|
||||||
bp = Blueprint('recipes', __name__, url_prefix='/recipes')
|
bp = Blueprint('recipes', __name__, url_prefix='/recipes')
|
||||||
|
|
||||||
|
|
@ -169,6 +171,7 @@ class RecipeForm(FlaskForm):
|
||||||
max_entries=20
|
max_entries=20
|
||||||
)
|
)
|
||||||
yeast = FormField(YeastForm)
|
yeast = FormField(YeastForm)
|
||||||
|
style = SelectField('Style', choices=[], validators=[Optional()])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def doc(self):
|
def doc(self):
|
||||||
|
|
@ -182,7 +185,8 @@ class RecipeForm(FlaskForm):
|
||||||
'volume': str(self.volume.data),
|
'volume': str(self.volume.data),
|
||||||
'notes': self.notes.data,
|
'notes': self.notes.data,
|
||||||
'$type': 'recipe',
|
'$type': 'recipe',
|
||||||
'type': self.type.data
|
'type': self.type.data,
|
||||||
|
'style': self.style.data
|
||||||
}
|
}
|
||||||
|
|
||||||
recipe['fermentables'] = [f.doc for f in self.fermentables]
|
recipe['fermentables'] = [f.doc for f in self.fermentables]
|
||||||
|
|
@ -202,6 +206,7 @@ class RecipeForm(FlaskForm):
|
||||||
self.efficiency.data = Decimal(data['efficiency'])
|
self.efficiency.data = Decimal(data['efficiency'])
|
||||||
self.volume.data = Decimal(data['volume'])
|
self.volume.data = Decimal(data['volume'])
|
||||||
self.notes.data = data['notes']
|
self.notes.data = data['notes']
|
||||||
|
self.style.data = data['style']
|
||||||
|
|
||||||
for fermentable in data['fermentables']:
|
for fermentable in data['fermentables']:
|
||||||
self.fermentables.append_entry({
|
self.fermentables.append_entry({
|
||||||
|
|
@ -282,6 +287,7 @@ def index():
|
||||||
@login_required
|
@login_required
|
||||||
def create():
|
def create():
|
||||||
form = RecipeForm()
|
form = RecipeForm()
|
||||||
|
form.style.choices = get_styles_list()
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
response = put_doc(form.doc)
|
response = put_doc(form.doc)
|
||||||
|
|
@ -308,7 +314,17 @@ def create_json():
|
||||||
|
|
||||||
@bp.route('/info/<id>')
|
@bp.route('/info/<id>')
|
||||||
def info(id):
|
def info(id):
|
||||||
return render_template('recipes/info.html', recipe=get_doc_or_404(id))
|
recipe = get_doc_or_404(id)
|
||||||
|
|
||||||
|
style = None
|
||||||
|
if recipe['style'] != '':
|
||||||
|
try:
|
||||||
|
style = get_doc(recipe['style'])
|
||||||
|
except KeyError:
|
||||||
|
flash('Could not find style `{}`.'.format(recipe['style']),
|
||||||
|
'warning')
|
||||||
|
|
||||||
|
return render_template('recipes/info.html', recipe=recipe, style=style)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/info/<id>/json')
|
@bp.route('/info/<id>/json')
|
||||||
|
|
@ -334,6 +350,7 @@ def delete(id):
|
||||||
def update(id):
|
def update(id):
|
||||||
# Get the recipe from the database and validate it is the same revision
|
# Get the recipe from the database and validate it is the same revision
|
||||||
form = RecipeForm()
|
form = RecipeForm()
|
||||||
|
form.style.choices = get_styles_list()
|
||||||
recipe = get_doc_or_404(id)
|
recipe = get_doc_or_404(id)
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
if recipe['_rev'] != request.args.get('rev', None):
|
if recipe['_rev'] != request.args.get('rev', None):
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,20 @@ def import_styles(url):
|
||||||
put_doc(doc)
|
put_doc(doc)
|
||||||
|
|
||||||
|
|
||||||
|
def get_styles_list():
|
||||||
|
"""Returns a list containing id and names of all styles."""
|
||||||
|
view = get_view('_design/styles', 'by-category')
|
||||||
|
styles = [['', '']]
|
||||||
|
for row in view(include_docs=False)['rows']:
|
||||||
|
print(row)
|
||||||
|
styles.append([row['id'], '{}{} {}'.format(
|
||||||
|
row['key'][0],
|
||||||
|
row['key'][1],
|
||||||
|
row['value']
|
||||||
|
)])
|
||||||
|
return styles
|
||||||
|
|
||||||
|
|
||||||
@click.command('import-styles')
|
@click.command('import-styles')
|
||||||
@with_appcontext
|
@with_appcontext
|
||||||
def import_command():
|
def import_command():
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,12 @@
|
||||||
-#}
|
-#}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-6">{{ render_field_with_errors(form.name) }}</div>
|
<div class="col-sm-6">{{ render_field_with_errors(form.name) }}</div>
|
||||||
<div class="col-sm-2">{{ render_field_with_errors(form.type) }}</div>
|
<div class="col-sm-3">{{ render_field_with_errors(form.efficiency, 'ingredient-field') }}</div>
|
||||||
<div class="col-sm-2">{{ render_field_with_errors(form.efficiency, 'ingredient-field') }}</div>
|
<div class="col-sm-3">{{ render_field_with_errors(form.volume, 'ingredient-field') }}</div>
|
||||||
<div class="col-sm-2">{{ render_field_with_errors(form.volume, 'ingredient-field') }}</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm">{{ render_field_with_errors(form.style) }}</div>
|
||||||
|
<div class="col-sm">{{ render_field_with_errors(form.type) }}</div>
|
||||||
</div>
|
</div>
|
||||||
{#-
|
{#-
|
||||||
Fermentable Ingredients
|
Fermentable Ingredients
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,15 @@
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="row"><h1>{{ recipe.name }}</h1></div>
|
<div class="row"><h1>{{ recipe.name }}</h1></div>
|
||||||
|
{% if style %}
|
||||||
|
<div class="row">
|
||||||
|
{% if session.logged_in %}
|
||||||
|
<h2><a href="{{ url_for('styles.info', id=style._id) }}">{{ style.id }} {{ style.name }}</a></h2>
|
||||||
|
{% else %}
|
||||||
|
<h2>{{ style.id }} {{ style.name }}</h2>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{#-
|
{#-
|
||||||
Recipe Details
|
Recipe Details
|
||||||
-#}
|
-#}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,8 @@ def app():
|
||||||
'notes': 'Test',
|
'notes': 'Test',
|
||||||
'volume': '5.5',
|
'volume': '5.5',
|
||||||
'fermentables': [],
|
'fermentables': [],
|
||||||
'hops': []
|
'hops': [],
|
||||||
|
'style': ''
|
||||||
})
|
})
|
||||||
put_doc({
|
put_doc({
|
||||||
'_id': 'partial-yeast-recipe',
|
'_id': 'partial-yeast-recipe',
|
||||||
|
|
@ -66,7 +67,8 @@ def app():
|
||||||
'name': 'US-05',
|
'name': 'US-05',
|
||||||
'low_attenuation': '60',
|
'low_attenuation': '60',
|
||||||
'high_attenuation': '72',
|
'high_attenuation': '72',
|
||||||
}
|
},
|
||||||
|
'style': ''
|
||||||
})
|
})
|
||||||
put_doc({
|
put_doc({
|
||||||
'_id': 'full-recipe',
|
'_id': 'full-recipe',
|
||||||
|
|
@ -76,6 +78,7 @@ def app():
|
||||||
'name': 'Awesome Beer',
|
'name': 'Awesome Beer',
|
||||||
'notes': 'This is a test beer that contains most possible fields.',
|
'notes': 'This is a test beer that contains most possible fields.',
|
||||||
'volume': '2.5',
|
'volume': '2.5',
|
||||||
|
'style': 'style_1A',
|
||||||
'fermentables': [
|
'fermentables': [
|
||||||
{
|
{
|
||||||
'name': '2row',
|
'name': '2row',
|
||||||
|
|
@ -181,6 +184,7 @@ def sample_recipes():
|
||||||
'lager': {
|
'lager': {
|
||||||
'efficiency': '72',
|
'efficiency': '72',
|
||||||
'type': 'All-Grain',
|
'type': 'All-Grain',
|
||||||
|
'style': '',
|
||||||
'fermentables': [
|
'fermentables': [
|
||||||
{
|
{
|
||||||
'amount': '9.5',
|
'amount': '9.5',
|
||||||
|
|
@ -239,6 +243,7 @@ def sample_recipes():
|
||||||
'sweetstout': {
|
'sweetstout': {
|
||||||
'efficiency': '72',
|
'efficiency': '72',
|
||||||
'type': 'All-Grain',
|
'type': 'All-Grain',
|
||||||
|
'style': '',
|
||||||
'fermentables': [
|
'fermentables': [
|
||||||
{
|
{
|
||||||
'amount': '2.75',
|
'amount': '2.75',
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,7 @@ def test_create(client, app, auth):
|
||||||
'name': 'Test',
|
'name': 'Test',
|
||||||
'notes': 'Test',
|
'notes': 'Test',
|
||||||
'volume': '5.5',
|
'volume': '5.5',
|
||||||
|
'style': 'style_1A'
|
||||||
}
|
}
|
||||||
response = client.post('/recipes/create', data=data)
|
response = client.post('/recipes/create', data=data)
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
|
|
@ -147,6 +148,7 @@ def test_create(client, app, auth):
|
||||||
assert doc['notes'] == 'Test'
|
assert doc['notes'] == 'Test'
|
||||||
assert doc['volume'] == '5.5'
|
assert doc['volume'] == '5.5'
|
||||||
assert doc['efficiency'] == '65'
|
assert doc['efficiency'] == '65'
|
||||||
|
assert doc['style'] == 'style_1A'
|
||||||
|
|
||||||
|
|
||||||
def test_update(client, app, auth):
|
def test_update(client, app, auth):
|
||||||
|
|
@ -205,8 +207,12 @@ def test_update(client, app, auth):
|
||||||
assert 'Update conflict' in flash_message
|
assert 'Update conflict' in flash_message
|
||||||
|
|
||||||
|
|
||||||
def test_info(client):
|
def test_info(client, monkeypatch):
|
||||||
"""Test success in retrieving a recipe document."""
|
"""Test success in retrieving a recipe document."""
|
||||||
|
def mock_get_doc(id):
|
||||||
|
# This function always raises KeyError
|
||||||
|
raise KeyError(id)
|
||||||
|
|
||||||
# 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
|
||||||
|
|
@ -216,6 +222,17 @@ def test_info(client):
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert b'Awesome Lager' in response.data
|
assert b'Awesome Lager' in response.data
|
||||||
|
|
||||||
|
# Validate response for recipe with style
|
||||||
|
response = client.get('/recipes/info/full-recipe')
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b'Awesome Beer' in response.data
|
||||||
|
assert b'Test Style' in response.data
|
||||||
|
|
||||||
|
# Validate warning is flashed when style cannot be found
|
||||||
|
monkeypatch.setattr('humulus.recipes.get_doc', mock_get_doc)
|
||||||
|
response = client.get('/recipes/info/full-recipe')
|
||||||
|
assert b'Could not find style' in response.data
|
||||||
|
|
||||||
|
|
||||||
def test_info_json(client):
|
def test_info_json(client):
|
||||||
"""Test success in retrieving a JSON recipe."""
|
"""Test success in retrieving a JSON recipe."""
|
||||||
|
|
@ -279,6 +296,7 @@ def test_recipe_form_doc(app):
|
||||||
recipe.volume.data = Decimal('5.5')
|
recipe.volume.data = Decimal('5.5')
|
||||||
recipe.notes.data = 'This is a test'
|
recipe.notes.data = 'This is a test'
|
||||||
recipe.type.data = 'All-Grain'
|
recipe.type.data = 'All-Grain'
|
||||||
|
recipe.style.data = 'style_1A'
|
||||||
|
|
||||||
assert recipe.doc == {
|
assert recipe.doc == {
|
||||||
'name': 'Test',
|
'name': 'Test',
|
||||||
|
|
@ -289,6 +307,7 @@ def test_recipe_form_doc(app):
|
||||||
'fermentables': [],
|
'fermentables': [],
|
||||||
'hops': [],
|
'hops': [],
|
||||||
'$type': 'recipe',
|
'$type': 'recipe',
|
||||||
|
'style': 'style_1A'
|
||||||
}
|
}
|
||||||
|
|
||||||
ferm = FermentableForm()
|
ferm = FermentableForm()
|
||||||
|
|
@ -321,6 +340,7 @@ def test_recipe_form_doc(app):
|
||||||
'volume': '5.5',
|
'volume': '5.5',
|
||||||
'notes': 'This is a test',
|
'notes': 'This is a test',
|
||||||
'$type': 'recipe',
|
'$type': 'recipe',
|
||||||
|
'style': 'style_1A',
|
||||||
'fermentables': [{
|
'fermentables': [{
|
||||||
'name': 'Test',
|
'name': 'Test',
|
||||||
'type': 'Grain',
|
'type': 'Grain',
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
from humulus.styles import import_styles, sub_to_doc
|
from humulus.styles import import_styles, sub_to_doc, get_styles_list
|
||||||
|
|
||||||
COMPLETE_STYLE = '''<subcategory id="1A">
|
COMPLETE_STYLE = '''<subcategory id="1A">
|
||||||
<name>Test Style</name>
|
<name>Test Style</name>
|
||||||
|
|
@ -182,6 +182,16 @@ def test_import_command(runner, monkeypatch):
|
||||||
assert 'Imported BJCP styles.' in result.output
|
assert 'Imported BJCP styles.' in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_styles_choices(app):
|
||||||
|
"""Test success in getting list of styles."""
|
||||||
|
with app.app_context():
|
||||||
|
styles = get_styles_list()
|
||||||
|
assert styles == [
|
||||||
|
['', ''],
|
||||||
|
['style_1A', '1A Test Style']
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_index(auth, client):
|
def test_index(auth, client):
|
||||||
"""Test success in retrieving index."""
|
"""Test success in retrieving index."""
|
||||||
# Test not logged in
|
# Test not logged in
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue