From e11fd79debd3401397f10f5189767d65645c898b Mon Sep 17 00:00:00 2001 From: Mike Shoup Date: Sun, 7 Jul 2019 08:31:52 -0600 Subject: [PATCH] Allow Recipe Types (#16) * Adds a recipe type field for recipe form. (Closes #7) * Abort w/ 400 is view is not found --- src/humulus/designs/recipes.json | 3 +++ src/humulus/recipes.py | 24 ++++++++++++------- src/humulus/templates/recipes/_macros.html | 5 ++-- src/humulus/templates/recipes/index.html | 10 ++++++++ src/humulus/templates/recipes/info.html | 2 ++ tests/conftest.py | 5 ++++ tests/test_recipes.py | 28 ++++++++++++++++++++++ 7 files changed, 67 insertions(+), 10 deletions(-) diff --git a/src/humulus/designs/recipes.json b/src/humulus/designs/recipes.json index 7ea1b59..28aca01 100644 --- a/src/humulus/designs/recipes.json +++ b/src/humulus/designs/recipes.json @@ -10,6 +10,9 @@ }, "by-volume": { "map": "function (doc) {\n if (doc.$type == \"recipe\" && doc.volume && doc.name) {\n emit(doc.volume, doc.name)\n }\n}" + }, + "by-type": { + "map": "function (doc) {\n if (doc.$type == \"recipe\" && doc.type && doc.name) {\n emit(doc.type, doc.name)\n }\n}" } }, "lists": {}, diff --git a/src/humulus/recipes.py b/src/humulus/recipes.py index 4c1bf65..d147532 100644 --- a/src/humulus/recipes.py +++ b/src/humulus/recipes.py @@ -17,7 +17,8 @@ import json from decimal import Decimal -from flask import (Blueprint, flash, redirect, render_template, jsonify, +import requests +from flask import (abort, Blueprint, flash, redirect, render_template, jsonify, request, url_for) from flask_wtf import FlaskForm from flask_wtf.file import FileField, FileRequired @@ -148,6 +149,11 @@ class YeastForm(Form): class RecipeForm(FlaskForm): """Form for recipes.""" name = StringField('Name', validators=[DataRequired()]) + type = SelectField('Type', default='', + choices=[(c, c) for c in ['All-Grain', + 'Partial Extract', + 'Extract']], + validators=[Optional()]) efficiency = DecimalField('Batch Efficiency (%)', validators=[DataRequired()]) volume = DecimalField('Batch Volume (gal)', validators=[DataRequired()]) @@ -176,6 +182,7 @@ class RecipeForm(FlaskForm): 'volume': str(self.volume.data), 'notes': self.notes.data, '$type': 'recipe', + 'type': self.type.data } recipe['fermentables'] = [f.doc for f in self.fermentables] @@ -191,6 +198,7 @@ class RecipeForm(FlaskForm): def copyfrom(self, data): """Copies from a dictionary (data) into the current object""" self.name.data = data['name'] + self.type.data = data['type'] self.efficiency.data = Decimal(data['efficiency']) self.volume.data = Decimal(data['volume']) self.notes.data = data['notes'] @@ -255,16 +263,16 @@ def index(): ['true', 'yes'] ) sort_by = request.args.get('sort_by', default='name', type=str) - if sort_by == 'date': - view = get_view('_design/recipes', 'by-date') - elif sort_by == 'volume': - view = get_view('_design/recipes', 'by-volume') - else: - view = get_view('_design/recipes', 'by-name') + + view = get_view('_design/recipes', 'by-{}'.format(sort_by)) + try: + rows = view(include_docs=True, descending=descending)['rows'] + except requests.exceptions.HTTPError: + abort(400) return render_template( 'recipes/index.html', - rows=view(include_docs=True, descending=descending)['rows'], + rows=rows, descending=descending, sort_by=sort_by ) diff --git a/src/humulus/templates/recipes/_macros.html b/src/humulus/templates/recipes/_macros.html index ae48f16..6ac50ac 100644 --- a/src/humulus/templates/recipes/_macros.html +++ b/src/humulus/templates/recipes/_macros.html @@ -26,8 +26,9 @@ -#}
{{ render_field_with_errors(form.name) }}
-
{{ render_field_with_errors(form.efficiency, 'ingredient-field') }}
-
{{ render_field_with_errors(form.volume, 'ingredient-field') }}
+
{{ render_field_with_errors(form.type) }}
+
{{ render_field_with_errors(form.efficiency, 'ingredient-field') }}
+
{{ render_field_with_errors(form.volume, 'ingredient-field') }}
{#- Fermentable Ingredients diff --git a/src/humulus/templates/recipes/index.html b/src/humulus/templates/recipes/index.html index 495e0b9..23647f8 100644 --- a/src/humulus/templates/recipes/index.html +++ b/src/humulus/templates/recipes/index.html @@ -49,6 +49,15 @@ Batch Size {% endif %} + + {% if sort_by == 'type' and descending %} + Type ↓ + {% elif sort_by == 'type' %} + Type ↑ + {% else %} + Type + {% endif %} + {% if sort_by == 'date' and descending %} Created On ↓ @@ -64,6 +73,7 @@ {{ row.doc.name }} {{ row.doc.volume }} gal. + {{ row.doc.type }} {{ moment(row.doc.created) }} {% endfor %} diff --git a/src/humulus/templates/recipes/info.html b/src/humulus/templates/recipes/info.html index 13252ba..0a8695c 100644 --- a/src/humulus/templates/recipes/info.html +++ b/src/humulus/templates/recipes/info.html @@ -28,6 +28,8 @@
+
Recipe Type
+
{{ recipe.type }}
Batch Efficiency
{{ recipe.efficiency|int }}%
Batch Volume
diff --git a/tests/conftest.py b/tests/conftest.py index 917f4fd..d997ccb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -44,6 +44,7 @@ def app(): put_doc({ '_id': 'awesome-lager', '$type': 'recipe', + 'type': 'All-Grain', 'efficiency': '65', 'name': 'Awesome Lager', 'notes': 'Test', @@ -56,6 +57,7 @@ def app(): '$type': 'recipe', 'efficiency': '75', 'name': 'Partial Beer', + 'type': 'Extract', 'notes': 'Contains only required fields for yeast.', 'volume': '3.5', 'fermentables': [], @@ -70,6 +72,7 @@ def app(): '_id': 'full-recipe', '$type': 'recipe', 'efficiency': '78', + 'type': 'All-Grain', 'name': 'Awesome Beer', 'notes': 'This is a test beer that contains most possible fields.', 'volume': '2.5', @@ -160,6 +163,7 @@ def sample_recipes(): return { 'lager': { 'efficiency': '72', + 'type': 'All-Grain', 'fermentables': [ { 'amount': '9.5', @@ -217,6 +221,7 @@ def sample_recipes(): }, 'sweetstout': { 'efficiency': '72', + 'type': 'All-Grain', 'fermentables': [ { 'amount': '2.75', diff --git a/tests/test_recipes.py b/tests/test_recipes.py index d8ca6a4..43f2c2f 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -22,6 +22,10 @@ from humulus.recipes import FermentableForm, HopForm, RecipeForm, YeastForm def test_index(client): """Test success in retrieving index.""" + # Test for bad request + response = client.get('/recipes/?sort_by=foobar') + assert response.status_code == 400 + # Verify defaults response = client.get('/recipes/') assert response.status_code == 200 @@ -93,6 +97,27 @@ def test_index(client): response.data ) + # Test sort by type ascending + response = client.get('/recipes/?descending=false&sort_by=type') + assert ( + b'"/recipes/?descending=false&sort_by=name">Name' in + response.data + ) + assert ( + b'"/recipes/?descending=true&sort_by=type">Type ↑' in + response.data + ) + + # Test sort by type descending + response = client.get('/recipes/?descending=true&sort_by=type') + assert ( + b'"/recipes/?descending=false&sort_by=name">Name' in + response.data + ) + assert ( + b'"/recipes/?descending=false&sort_by=type">Type ↓' in + response.data + ) def test_create(client, app, auth): """Test success in creating a recipe document.""" @@ -253,10 +278,12 @@ def test_recipe_form_doc(app): recipe.efficiency.data = Decimal('65') recipe.volume.data = Decimal('5.5') recipe.notes.data = 'This is a test' + recipe.type.data = 'All-Grain' assert recipe.doc == { 'name': 'Test', 'efficiency': '65', + 'type': 'All-Grain', 'volume': '5.5', 'notes': 'This is a test', 'fermentables': [], @@ -290,6 +317,7 @@ def test_recipe_form_doc(app): assert recipe.doc == { 'name': 'Test', 'efficiency': '65', + 'type': 'All-Grain', 'volume': '5.5', 'notes': 'This is a test', '$type': 'recipe',