diff --git a/src/humulus/__init__.py b/src/humulus/__init__.py
index eb554ee..c88f342 100644
--- a/src/humulus/__init__.py
+++ b/src/humulus/__init__.py
@@ -12,32 +12,4 @@
# 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
+from humulus.app import create_app
diff --git a/src/humulus/app.py b/src/humulus/app.py
new file mode 100644
index 0000000..eb554ee
--- /dev/null
+++ b/src/humulus/app.py
@@ -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
diff --git a/src/humulus/recipes.py b/src/humulus/recipes.py
index b078461..fbb8b36 100644
--- a/src/humulus/recipes.py
+++ b/src/humulus/recipes.py
@@ -18,19 +18,58 @@ from decimal import Decimal
from flask import Blueprint, flash, redirect, render_template, url_for
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 humulus.couch import get_doc_or_404, put_doc
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()])
- efficiency = DecimalField('Batch Efficiency', validators=[DataRequired()])
- volume = DecimalField('Batch Volume', validators=[DataRequired()])
+ type = SelectField('Type', 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')
+ fermentables = FieldList(
+ FormField(FermentableForm),
+ min_entries=0,
+ max_entries=20
+ )
+
@property
def doc(self):
"""Returns a dictionary that can be deserialized into JSON.
@@ -41,7 +80,8 @@ class RecipeForm(FlaskForm):
'name': self.name.data,
'efficiency': str(self.efficiency.data),
'volume': str(self.volume.data),
- 'notes': self.notes.data
+ 'notes': self.notes.data,
+ 'fermentables': [f.doc for f in self.fermentables]
}
diff --git a/src/humulus/static/style.css b/src/humulus/static/style.css
index 9fd33b6..995df0e 100644
--- a/src/humulus/static/style.css
+++ b/src/humulus/static/style.css
@@ -1,4 +1,4 @@
-.bd-placeholder-img {
+.placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
@@ -8,7 +8,7 @@
}
@media (min-width: 768px) {
- .bd-placeholder-img-lg {
+ .placeholder-img-lg {
font-size: 3.5rem;
}
}
diff --git a/src/humulus/templates/_base.html b/src/humulus/templates/_base.html
index 70be0d7..d1ae2ec 100644
--- a/src/humulus/templates/_base.html
+++ b/src/humulus/templates/_base.html
@@ -40,7 +40,7 @@ limitations under the License.
-
+
{% block alert %}
{% for category, message in get_flashed_messages(with_categories=true) %}
@@ -54,7 +54,6 @@ limitations under the License.
-