mirror of
https://github.com/shouptech/humulus.git
synced 2026-02-03 16:09:44 +00:00
Formatting fixes for black
This commit is contained in:
parent
9cc60bd8f2
commit
346b3a5ea8
18 changed files with 1298 additions and 1146 deletions
|
|
@ -17,8 +17,7 @@ steps:
|
|||
from_secret: CODECOV_TOKEN
|
||||
commands:
|
||||
# Install pre-requisites
|
||||
- pip install coverage pytest
|
||||
- pip install -e .
|
||||
- pip install -r requirements-dev.txt
|
||||
# Wait for couch
|
||||
- until curl "$COUCH_URL" ; do sleep 1 ; done
|
||||
# Run tests
|
||||
|
|
@ -31,7 +30,7 @@ steps:
|
|||
- name: linting
|
||||
image: python:3.6
|
||||
commands:
|
||||
- pip install flake8
|
||||
- black --check src tests
|
||||
- flake8
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
[tool.black]
|
||||
line-length = 80
|
||||
line-length = 79
|
||||
target_version = ['py35','py36','py37']
|
||||
|
|
|
|||
|
|
@ -11,5 +11,8 @@ source = humulus
|
|||
[flake8]
|
||||
exclude = .svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg,instance
|
||||
show-source = True
|
||||
ignore = W504
|
||||
count = True
|
||||
max-line-length = 79
|
||||
max-complexity = 18
|
||||
ignore = W503
|
||||
select = B,C,E,F,W,T4,B9
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@
|
|||
|
||||
from humulus.app import create_app
|
||||
|
||||
__all__ = ['create_app', ]
|
||||
__all__ = ["create_app"]
|
||||
|
|
|
|||
|
|
@ -26,31 +26,37 @@ def create_app(test_config=None):
|
|||
app.config.from_mapping(test_config)
|
||||
else:
|
||||
# Load config from configuration provided via ENV
|
||||
app.config.from_envvar('HUMULUS_SETTINGS')
|
||||
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')
|
||||
app.add_url_rule("/", endpoint="index")
|
||||
|
||||
# Register blueprint for recipes
|
||||
from . import recipes
|
||||
|
||||
app.register_blueprint(recipes.bp)
|
||||
|
||||
# Register auth blueprint
|
||||
from . import auth
|
||||
|
||||
app.register_blueprint(auth.bp)
|
||||
|
||||
# Register styles blueprint and cli commands
|
||||
from . import styles
|
||||
|
||||
styles.init_app(app)
|
||||
app.register_blueprint(styles.bp)
|
||||
|
||||
# Register custom filters
|
||||
from . import filters
|
||||
|
||||
filters.create_filters(app)
|
||||
|
||||
# Register custom error handlers
|
||||
|
|
@ -62,13 +68,13 @@ def create_app(test_config=None):
|
|||
|
||||
def bad_request(e):
|
||||
return (
|
||||
render_template('_error.html', code=400, message='400 Bad Request'),
|
||||
400
|
||||
render_template("_error.html", code=400, message="400 Bad Request"),
|
||||
400,
|
||||
)
|
||||
|
||||
|
||||
def page_not_found(e):
|
||||
return (
|
||||
render_template('_error.html', code=404, message='404 Not Found'),
|
||||
404
|
||||
render_template("_error.html", code=404, message="404 Not Found"),
|
||||
404,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,50 +16,59 @@
|
|||
|
||||
import functools
|
||||
|
||||
from flask import (Blueprint, current_app, flash, redirect, render_template,
|
||||
session, url_for)
|
||||
from flask import (
|
||||
Blueprint,
|
||||
current_app,
|
||||
flash,
|
||||
redirect,
|
||||
render_template,
|
||||
session,
|
||||
url_for,
|
||||
)
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import PasswordField, BooleanField
|
||||
from wtforms.validators import DataRequired
|
||||
|
||||
|
||||
bp = Blueprint('auth', __name__)
|
||||
bp = Blueprint("auth", __name__)
|
||||
|
||||
|
||||
class LoginForm(FlaskForm):
|
||||
"""Form for login."""
|
||||
password = PasswordField('Password', validators=[DataRequired()])
|
||||
permanent = BooleanField('Stay logged in')
|
||||
|
||||
password = PasswordField("Password", validators=[DataRequired()])
|
||||
permanent = BooleanField("Stay logged in")
|
||||
|
||||
|
||||
def login_required(view):
|
||||
"""View decorator that redirects anonymous users to the login page."""
|
||||
|
||||
@functools.wraps(view)
|
||||
def wrapped_view(**kwargs):
|
||||
logged_in = session.get('logged_in', False)
|
||||
logged_in = session.get("logged_in", False)
|
||||
if not logged_in:
|
||||
return redirect(url_for('auth.login'))
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
return view(**kwargs)
|
||||
|
||||
return wrapped_view
|
||||
|
||||
|
||||
@bp.route('/login', methods=('GET', 'POST'))
|
||||
@bp.route("/login", methods=("GET", "POST"))
|
||||
def login():
|
||||
form = LoginForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
if form.password.data == current_app.config['HUMULUS_PASSWORD']:
|
||||
if form.password.data == current_app.config["HUMULUS_PASSWORD"]:
|
||||
session.clear()
|
||||
session.permanent = form.permanent.data
|
||||
session['logged_in'] = True
|
||||
return redirect(url_for('index'))
|
||||
flash('Password is invalid.', category='warning')
|
||||
return render_template('auth/login.html', form=form)
|
||||
session["logged_in"] = True
|
||||
return redirect(url_for("index"))
|
||||
flash("Password is invalid.", category="warning")
|
||||
return render_template("auth/login.html", form=form)
|
||||
|
||||
|
||||
@bp.route('/logout')
|
||||
@bp.route("/logout")
|
||||
def logout():
|
||||
session.clear()
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for("index"))
|
||||
|
|
|
|||
|
|
@ -29,25 +29,25 @@ from slugify import slugify
|
|||
|
||||
def get_couch():
|
||||
"""Connect to the configured CouchDB."""
|
||||
if 'couch' not in g:
|
||||
if "couch" not in g:
|
||||
g.couch = CouchDB(
|
||||
current_app.config['COUCH_USERNAME'],
|
||||
current_app.config['COUCH_PASSWORD'],
|
||||
url=current_app.config['COUCH_URL'],
|
||||
current_app.config["COUCH_USERNAME"],
|
||||
current_app.config["COUCH_PASSWORD"],
|
||||
url=current_app.config["COUCH_URL"],
|
||||
connect=True,
|
||||
auto_renew=True
|
||||
auto_renew=True,
|
||||
)
|
||||
return g.couch
|
||||
|
||||
|
||||
def get_db():
|
||||
"""Returns a database to interact with."""
|
||||
return get_couch()[current_app.config['COUCH_DATABASE']]
|
||||
return get_couch()[current_app.config["COUCH_DATABASE"]]
|
||||
|
||||
|
||||
def close_couch(e=None):
|
||||
"""Disconnect from CouchDB."""
|
||||
couch = g.pop('couch', None)
|
||||
couch = g.pop("couch", None)
|
||||
if couch is not None:
|
||||
couch.disconnect()
|
||||
|
||||
|
|
@ -55,17 +55,17 @@ def close_couch(e=None):
|
|||
def build_couch():
|
||||
"""Create any necessary databases and design documents."""
|
||||
couch = get_couch()
|
||||
dbname = current_app.config['COUCH_DATABASE']
|
||||
dbname = current_app.config["COUCH_DATABASE"]
|
||||
couch.create_database(dbname, throw_on_exists=False)
|
||||
put_designs()
|
||||
|
||||
|
||||
@click.command('build-couch')
|
||||
@click.command("build-couch")
|
||||
@with_appcontext
|
||||
def build_couch_command():
|
||||
"""Builds the couch for easy relaxing."""
|
||||
build_couch()
|
||||
click.echo('Built a couch. Please have a seat.')
|
||||
click.echo("Built a couch. Please have a seat.")
|
||||
|
||||
|
||||
def init_app(app):
|
||||
|
|
@ -82,22 +82,22 @@ def put_doc(doc):
|
|||
"""
|
||||
db = get_db()
|
||||
|
||||
if 'name' in doc and '_id' not in doc:
|
||||
if "name" in doc and "_id" not in doc:
|
||||
# Slugify the name, use that for id
|
||||
slug = slugify(doc['name'])
|
||||
doc['_id'] = slug
|
||||
slug = slugify(doc["name"])
|
||||
doc["_id"] = slug
|
||||
i = 1
|
||||
# Check if id exists and append/increment a number until it doesn't.
|
||||
while doc['_id'] in db:
|
||||
doc['_id'] = slug + '-{}'.format(i)
|
||||
while doc["_id"] in db:
|
||||
doc["_id"] = slug + "-{}".format(i)
|
||||
i += 1
|
||||
elif '_id' not in doc:
|
||||
elif "_id" not in doc:
|
||||
# Use a UUID for name
|
||||
doc['_id'] = str(uuid.uuid4())
|
||||
doc["_id"] = str(uuid.uuid4())
|
||||
|
||||
# Add a created timestamp
|
||||
# Timestamps are written to couchdb in ISO-8601 format
|
||||
doc['created'] = datetime.utcnow().isoformat(timespec='seconds')
|
||||
doc["created"] = datetime.utcnow().isoformat(timespec="seconds")
|
||||
|
||||
return db.create_document(doc, throw_on_exists=True)
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ def update_doc(doc):
|
|||
|
||||
Adds an 'updated' field representing the current time the doc was updated.
|
||||
"""
|
||||
doc['updated'] = datetime.utcnow().isoformat(timespec='seconds')
|
||||
doc["updated"] = datetime.utcnow().isoformat(timespec="seconds")
|
||||
doc.save()
|
||||
|
||||
|
||||
|
|
@ -133,23 +133,23 @@ def put_designs():
|
|||
"""
|
||||
here = Path(__file__).parent
|
||||
|
||||
for filename in here.glob('designs/*.json'):
|
||||
with open(filename, 'r') as fp:
|
||||
for filename in here.glob("designs/*.json"):
|
||||
with open(filename, "r") as fp:
|
||||
data = json.load(fp)
|
||||
|
||||
# See if document already exists
|
||||
if data['_id'] in get_db():
|
||||
doc = get_doc(data['_id'])
|
||||
if data["_id"] in get_db():
|
||||
doc = get_doc(data["_id"])
|
||||
# Popping off the revision and storing it. Then compare
|
||||
rev = doc.pop('_rev')
|
||||
doc.pop('created', None)
|
||||
rev = doc.pop("_rev")
|
||||
doc.pop("created", None)
|
||||
if data == doc:
|
||||
get_db().clear()
|
||||
return
|
||||
# Copy the values of data to doc.
|
||||
for k in data:
|
||||
doc[k] = data[k]
|
||||
doc['_rev'] = rev # Add the revision back
|
||||
doc["_rev"] = rev # Add the revision back
|
||||
doc.save()
|
||||
else:
|
||||
put_doc(data)
|
||||
|
|
|
|||
|
|
@ -19,120 +19,122 @@ import math
|
|||
|
||||
def recipe_og(recipe):
|
||||
"""Returns a recipe's Original Gravity"""
|
||||
if 'fermentables' not in recipe:
|
||||
return '0.000'
|
||||
if "fermentables" not in recipe:
|
||||
return "0.000"
|
||||
points = 0
|
||||
grain_points = 0
|
||||
# Loop through fermentables, adding up points
|
||||
for fermentable in recipe['fermentables']:
|
||||
if fermentable['type'] == 'Grain':
|
||||
grain_points += (
|
||||
float(fermentable['amount']) * float(fermentable['ppg'])
|
||||
for fermentable in recipe["fermentables"]:
|
||||
if fermentable["type"] == "Grain":
|
||||
grain_points += float(fermentable["amount"]) * float(
|
||||
fermentable["ppg"]
|
||||
)
|
||||
else:
|
||||
points += (
|
||||
float(fermentable['amount']) * float(fermentable['ppg'])
|
||||
)
|
||||
points += grain_points * float(recipe['efficiency']) / 100
|
||||
return '{:.3f}'.format(
|
||||
round(1 + points / (1000 * float(recipe['volume'])), 3)
|
||||
points += float(fermentable["amount"]) * float(fermentable["ppg"])
|
||||
points += grain_points * float(recipe["efficiency"]) / 100
|
||||
return "{:.3f}".format(
|
||||
round(1 + points / (1000 * float(recipe["volume"])), 3)
|
||||
)
|
||||
|
||||
|
||||
def recipe_fg(recipe):
|
||||
"""Returns a recipe's final gravity"""
|
||||
if 'yeast' not in recipe or 'fermentables' not in recipe:
|
||||
return '0.000'
|
||||
if "yeast" not in recipe or "fermentables" not in recipe:
|
||||
return "0.000"
|
||||
og = float(recipe_og(recipe))
|
||||
og_delta = 0.0
|
||||
# Adjust original gravity by removing nonfermentables (i.e., Lactose)
|
||||
for fermentable in recipe['fermentables']:
|
||||
if fermentable['type'] == 'Non-fermentable':
|
||||
for fermentable in recipe["fermentables"]:
|
||||
if fermentable["type"] == "Non-fermentable":
|
||||
og_delta += (
|
||||
float(fermentable['amount']) * float(fermentable['ppg']) /
|
||||
(1000 * float(recipe['volume']))
|
||||
float(fermentable["amount"])
|
||||
* float(fermentable["ppg"])
|
||||
/ (1000 * float(recipe["volume"]))
|
||||
)
|
||||
attenuation = (
|
||||
(
|
||||
float(recipe['yeast']['low_attenuation']) +
|
||||
float(recipe['yeast']['high_attenuation'])
|
||||
float(recipe["yeast"]["low_attenuation"])
|
||||
+ float(recipe["yeast"]["high_attenuation"])
|
||||
) / 200
|
||||
)
|
||||
return '{:.3f}'.format(
|
||||
return "{:.3f}".format(
|
||||
round(1 + (og - 1 - og_delta) * (1 - attenuation) + og_delta, 3)
|
||||
)
|
||||
|
||||
|
||||
def recipe_ibu(recipe):
|
||||
"""Return a recipe's IBU"""
|
||||
if 'hops' not in recipe:
|
||||
return '0'
|
||||
if "hops" not in recipe:
|
||||
return "0"
|
||||
bigness = 1.65 * 0.000125 ** (float(recipe_og(recipe)) - 1)
|
||||
ibu = 0.0
|
||||
for h in recipe['hops']:
|
||||
if h['use'] != 'Boil' and h['use'] != 'FWH':
|
||||
for h in recipe["hops"]:
|
||||
if h["use"] != "Boil" and h["use"] != "FWH":
|
||||
continue
|
||||
mgl = (
|
||||
float(h['alpha']) * float(h['amount']) * 7490.0 /
|
||||
(float(recipe['volume']) * 100.0)
|
||||
float(h["alpha"])
|
||||
* float(h["amount"])
|
||||
* 7490.0
|
||||
/ (float(recipe["volume"]) * 100.0)
|
||||
)
|
||||
btf = (1 - math.exp(-0.04 * float(h['duration']))) / 4.15
|
||||
btf = (1 - math.exp(-0.04 * float(h["duration"]))) / 4.15
|
||||
ibu += bigness * btf * mgl
|
||||
return '{:.0f}'.format(ibu)
|
||||
return "{:.0f}".format(ibu)
|
||||
|
||||
|
||||
def recipe_ibu_ratio(recipe):
|
||||
"""Return a recipe's IBU ratio"""
|
||||
if 'fermentables' not in recipe or 'hops' not in recipe:
|
||||
return '0'
|
||||
if len(recipe['fermentables']) == 0:
|
||||
return '0' # Otherwise a divide by zero error will occur
|
||||
if "fermentables" not in recipe or "hops" not in recipe:
|
||||
return "0"
|
||||
if len(recipe["fermentables"]) == 0:
|
||||
return "0" # Otherwise a divide by zero error will occur
|
||||
og = float(recipe_og(recipe))
|
||||
ibu = float(recipe_ibu(recipe))
|
||||
return '{:.2f}'.format(round(0.001 * ibu / (og - 1), 2))
|
||||
return "{:.2f}".format(round(0.001 * ibu / (og - 1), 2))
|
||||
|
||||
|
||||
def recipe_abv(recipe):
|
||||
"""Return a recipe's finished ABV"""
|
||||
if 'fermentables' not in recipe or 'yeast' not in recipe:
|
||||
return '0'
|
||||
if "fermentables" not in recipe or "yeast" not in recipe:
|
||||
return "0"
|
||||
og = float(recipe_og(recipe))
|
||||
fg = float(recipe_fg(recipe))
|
||||
return '{:.1f}'.format(round((og - fg) * 131.25, 1))
|
||||
return "{:.1f}".format(round((og - fg) * 131.25, 1))
|
||||
|
||||
|
||||
def recipe_srm(recipe):
|
||||
"""Return a recipe's SRM"""
|
||||
if 'fermentables' not in recipe:
|
||||
return '0'
|
||||
if "fermentables" not in recipe:
|
||||
return "0"
|
||||
mcu = 0
|
||||
for f in recipe['fermentables']:
|
||||
mcu += float(f['amount']) * float(f['color']) / float(recipe['volume'])
|
||||
return '{:.0f}'.format(1.4922 * (mcu**0.6859))
|
||||
for f in recipe["fermentables"]:
|
||||
mcu += float(f["amount"]) * float(f["color"]) / float(recipe["volume"])
|
||||
return "{:.0f}".format(1.4922 * (mcu ** 0.6859))
|
||||
|
||||
|
||||
def sort_hops(hops, form=False):
|
||||
"""Sorts a list of hops by use in recipe."""
|
||||
by_use = {'FWH': [], 'Boil': [], 'Whirlpool': [], 'Dry-Hop': []}
|
||||
by_use = {"FWH": [], "Boil": [], "Whirlpool": [], "Dry-Hop": []}
|
||||
|
||||
# Split hops into each use type.
|
||||
for hop in hops:
|
||||
if form:
|
||||
by_use[hop.use.data].append(hop)
|
||||
else:
|
||||
by_use[hop['use']].append(hop)
|
||||
by_use[hop["use"]].append(hop)
|
||||
|
||||
if form:
|
||||
|
||||
def key(hop):
|
||||
return float(hop.duration.data)
|
||||
else:
|
||||
def key(hop):
|
||||
return float(hop['duration'])
|
||||
|
||||
hops_sorted = sorted(by_use['FWH'], key=key, reverse=True)
|
||||
hops_sorted.extend(sorted(by_use['Boil'], key=key, reverse=True))
|
||||
hops_sorted.extend(sorted(by_use['Whirlpool'], key=key, reverse=True))
|
||||
hops_sorted.extend(sorted(by_use['Dry-Hop'], key=key, reverse=True))
|
||||
else:
|
||||
|
||||
def key(hop):
|
||||
return float(hop["duration"])
|
||||
|
||||
hops_sorted = sorted(by_use["FWH"], key=key, reverse=True)
|
||||
hops_sorted.extend(sorted(by_use["Boil"], key=key, reverse=True))
|
||||
hops_sorted.extend(sorted(by_use["Whirlpool"], key=key, reverse=True))
|
||||
hops_sorted.extend(sorted(by_use["Dry-Hop"], key=key, reverse=True))
|
||||
return hops_sorted
|
||||
|
||||
|
||||
|
|
@ -144,10 +146,10 @@ def ferm_pct(fermentables):
|
|||
total = 0
|
||||
# Calculate total
|
||||
for ferm in fermentables:
|
||||
total += float(ferm['amount'])
|
||||
total += float(ferm["amount"])
|
||||
# Add a pct to each ferm
|
||||
for ferm in fermentables:
|
||||
ferm['pct'] = 100 * float(ferm['amount']) / total
|
||||
ferm["pct"] = 100 * float(ferm["amount"]) / total
|
||||
return fermentables
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -18,20 +18,20 @@ from flask import Blueprint, redirect, url_for, request, jsonify
|
|||
|
||||
from humulus.couch import get_db
|
||||
|
||||
bp = Blueprint('home', __name__)
|
||||
bp = Blueprint("home", __name__)
|
||||
|
||||
|
||||
@bp.route('/')
|
||||
@bp.route("/")
|
||||
def index():
|
||||
"""Renders the homepage template"""
|
||||
return redirect(url_for('recipes.index'))
|
||||
return redirect(url_for("recipes.index"))
|
||||
|
||||
|
||||
@bp.route('/status')
|
||||
@bp.route("/status")
|
||||
def status():
|
||||
if request.args.get('couch', default=False):
|
||||
if request.args.get("couch", default=False):
|
||||
if get_db().exists():
|
||||
return jsonify({'ping': 'ok', 'couch': 'ok'}), 200
|
||||
return jsonify({"ping": "ok", "couch": "ok"}), 200
|
||||
else:
|
||||
return jsonify({'ping': 'ok', 'couch': 'not_exist'}), 500
|
||||
return jsonify({'ping': 'ok'}), 200
|
||||
return jsonify({"ping": "ok", "couch": "not_exist"}), 500
|
||||
return jsonify({"ping": "ok"}), 200
|
||||
|
|
|
|||
|
|
@ -18,20 +18,40 @@ import json
|
|||
from decimal import Decimal
|
||||
|
||||
import requests
|
||||
from flask import (abort, Blueprint, flash, redirect, render_template, jsonify,
|
||||
request, url_for)
|
||||
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
|
||||
from wtforms import (Form, StringField, DecimalField, TextAreaField, FieldList,
|
||||
FormField, SelectField)
|
||||
from wtforms import (
|
||||
Form,
|
||||
StringField,
|
||||
DecimalField,
|
||||
TextAreaField,
|
||||
FieldList,
|
||||
FormField,
|
||||
SelectField,
|
||||
)
|
||||
from wtforms.validators import DataRequired, Optional
|
||||
|
||||
from humulus.auth import login_required
|
||||
from humulus.couch import (get_doc, 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")
|
||||
|
||||
|
||||
class FermentableForm(Form):
|
||||
|
|
@ -40,14 +60,26 @@ class FermentableForm(Form):
|
|||
CSRF is disabled for this form (using `Form as parent class)
|
||||
because it is never used by itself.
|
||||
"""
|
||||
name = StringField('Name', 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()])
|
||||
|
||||
name = StringField("Name", 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):
|
||||
|
|
@ -56,11 +88,11 @@ class FermentableForm(Form):
|
|||
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)
|
||||
"name": self.name.data,
|
||||
"type": self.type.data,
|
||||
"amount": str(self.amount.data),
|
||||
"ppg": str(self.ppg.data),
|
||||
"color": str(self.color.data),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -70,13 +102,16 @@ class HopForm(Form):
|
|||
CSRF is disabled for this form (using `Form as parent class)
|
||||
because it is never used by itself.
|
||||
"""
|
||||
name = StringField('Name', validators=[DataRequired()])
|
||||
use = SelectField('Usage', validators=[DataRequired()],
|
||||
choices=[(c, c) for c in ['Boil', 'FWH', 'Whirlpool',
|
||||
'Dry-Hop']])
|
||||
alpha = DecimalField('Alpha Acid %', validators=[DataRequired()])
|
||||
duration = DecimalField('Duration (min/day)', validators=[DataRequired()])
|
||||
amount = DecimalField('Amount (oz)', validators=[DataRequired()])
|
||||
|
||||
name = StringField("Name", validators=[DataRequired()])
|
||||
use = SelectField(
|
||||
"Usage",
|
||||
validators=[DataRequired()],
|
||||
choices=[(c, c) for c in ["Boil", "FWH", "Whirlpool", "Dry-Hop"]],
|
||||
)
|
||||
alpha = DecimalField("Alpha Acid %", validators=[DataRequired()])
|
||||
duration = DecimalField("Duration (min/day)", validators=[DataRequired()])
|
||||
amount = DecimalField("Amount (oz)", validators=[DataRequired()])
|
||||
|
||||
@property
|
||||
def doc(self):
|
||||
|
|
@ -85,11 +120,11 @@ class HopForm(Form):
|
|||
Used for putting into CouchDB.
|
||||
"""
|
||||
return {
|
||||
'name': self.name.data,
|
||||
'use': self.use.data,
|
||||
'alpha': str(self.alpha.data),
|
||||
'duration': str(self.duration.data),
|
||||
'amount': str(self.amount.data)
|
||||
"name": self.name.data,
|
||||
"use": self.use.data,
|
||||
"alpha": str(self.alpha.data),
|
||||
"duration": str(self.duration.data),
|
||||
"amount": str(self.amount.data),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -99,26 +134,29 @@ class YeastForm(Form):
|
|||
CSRF is disabled for this form (using `Form as parent class)
|
||||
because it is never used by itself.
|
||||
"""
|
||||
name = StringField('Name', validators=[Optional()])
|
||||
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=[Optional()])
|
||||
high_attenuation = DecimalField('High Attenuation',
|
||||
validators=[Optional()])
|
||||
min_temperature = DecimalField('Min Temp (°F)',
|
||||
validators=[Optional()])
|
||||
max_temperature = DecimalField('Max Temp (°F)',
|
||||
validators=[Optional()])
|
||||
abv_tolerance = DecimalField('ABV % tolerance',
|
||||
validators=[Optional()])
|
||||
|
||||
name = StringField("Name", validators=[Optional()])
|
||||
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=[Optional()])
|
||||
high_attenuation = DecimalField(
|
||||
"High Attenuation", validators=[Optional()]
|
||||
)
|
||||
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):
|
||||
|
|
@ -127,24 +165,24 @@ class YeastForm(Form):
|
|||
Used for putting into CouchDB.
|
||||
"""
|
||||
yeast = {
|
||||
'name': self.name.data,
|
||||
'low_attenuation': str(self.low_attenuation.data),
|
||||
'high_attenuation': str(self.high_attenuation.data)
|
||||
"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
|
||||
yeast["type"] = self.type.data
|
||||
if self.lab.data:
|
||||
yeast['lab'] = self.lab.data
|
||||
yeast["lab"] = self.lab.data
|
||||
if self.code.data:
|
||||
yeast['code'] = self.code.data
|
||||
yeast["code"] = self.code.data
|
||||
if self.flocculation.data:
|
||||
yeast['flocculation'] = self.flocculation.data
|
||||
yeast["flocculation"] = self.flocculation.data
|
||||
if self.min_temperature.data:
|
||||
yeast['min_temperature'] = str(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)
|
||||
yeast["max_temperature"] = str(self.max_temperature.data)
|
||||
if self.abv_tolerance.data:
|
||||
yeast['abv_tolerance'] = str(self.abv_tolerance.data)
|
||||
yeast["abv_tolerance"] = str(self.abv_tolerance.data)
|
||||
return yeast
|
||||
|
||||
|
||||
|
|
@ -154,14 +192,15 @@ class MashStepForm(Form):
|
|||
CSRF is disabled for this form (using `Form as parent class)
|
||||
because it is never used by itself.
|
||||
"""
|
||||
name = StringField('Step Name', validators=[DataRequired()])
|
||||
type = SelectField('Type',
|
||||
choices=[(c, c) for c in ['Infusion',
|
||||
'Temperature',
|
||||
'Decoction']])
|
||||
temp = DecimalField('Temperature (°F)', validators=[DataRequired()])
|
||||
time = DecimalField('Time (min)', validators=[DataRequired()])
|
||||
amount = DecimalField('Water Amount (gal)')
|
||||
|
||||
name = StringField("Step Name", validators=[DataRequired()])
|
||||
type = SelectField(
|
||||
"Type",
|
||||
choices=[(c, c) for c in ["Infusion", "Temperature", "Decoction"]],
|
||||
)
|
||||
temp = DecimalField("Temperature (°F)", validators=[DataRequired()])
|
||||
time = DecimalField("Time (min)", validators=[DataRequired()])
|
||||
amount = DecimalField("Water Amount (gal)")
|
||||
|
||||
@property
|
||||
def doc(self):
|
||||
|
|
@ -170,13 +209,13 @@ class MashStepForm(Form):
|
|||
Used for putting into CouchDB.
|
||||
"""
|
||||
step = {
|
||||
'name': self.name.data,
|
||||
'type': self.type.data,
|
||||
'temp': str(self.temp.data),
|
||||
'time': str(self.time.data),
|
||||
"name": self.name.data,
|
||||
"type": self.type.data,
|
||||
"temp": str(self.temp.data),
|
||||
"time": str(self.time.data),
|
||||
}
|
||||
if self.amount.data:
|
||||
step['amount'] = str(self.amount.data)
|
||||
step["amount"] = str(self.amount.data)
|
||||
return step
|
||||
|
||||
|
||||
|
|
@ -186,12 +225,9 @@ class MashForm(Form):
|
|||
CSRF is disabled for this form (using `Form as parent class)
|
||||
because it is never used by itself.
|
||||
"""
|
||||
name = StringField('Mash Name', validators=[Optional()])
|
||||
steps = FieldList(
|
||||
FormField(MashStepForm),
|
||||
min_entries=0,
|
||||
max_entries=20
|
||||
)
|
||||
|
||||
name = StringField("Mash Name", validators=[Optional()])
|
||||
steps = FieldList(FormField(MashStepForm), min_entries=0, max_entries=20)
|
||||
|
||||
@property
|
||||
def doc(self):
|
||||
|
|
@ -199,37 +235,31 @@ class MashForm(Form):
|
|||
|
||||
Used for putting into CouchDB.
|
||||
"""
|
||||
return {
|
||||
'name': self.name.data,
|
||||
'steps': [s.doc for s in self.steps]
|
||||
}
|
||||
return {"name": self.name.data, "steps": [s.doc for s in self.steps]}
|
||||
|
||||
|
||||
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()])
|
||||
notes = TextAreaField('Notes')
|
||||
|
||||
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()])
|
||||
notes = TextAreaField("Notes")
|
||||
fermentables = FieldList(
|
||||
FormField(FermentableForm),
|
||||
min_entries=0,
|
||||
max_entries=20
|
||||
)
|
||||
hops = FieldList(
|
||||
FormField(HopForm),
|
||||
min_entries=0,
|
||||
max_entries=20
|
||||
FormField(FermentableForm), min_entries=0, max_entries=20
|
||||
)
|
||||
hops = FieldList(FormField(HopForm), min_entries=0, max_entries=20)
|
||||
yeast = FormField(YeastForm)
|
||||
mash = FormField(MashForm)
|
||||
style = SelectField('Style', choices=[], validators=[Optional()])
|
||||
style = SelectField("Style", choices=[], validators=[Optional()])
|
||||
|
||||
@property
|
||||
def doc(self):
|
||||
|
|
@ -238,98 +268,102 @@ class RecipeForm(FlaskForm):
|
|||
Used for putting into CouchDB.
|
||||
"""
|
||||
recipe = {
|
||||
'name': self.name.data,
|
||||
'efficiency': str(self.efficiency.data),
|
||||
'volume': str(self.volume.data),
|
||||
'notes': self.notes.data,
|
||||
'$type': 'recipe',
|
||||
'type': self.type.data,
|
||||
'style': self.style.data
|
||||
"name": self.name.data,
|
||||
"efficiency": str(self.efficiency.data),
|
||||
"volume": str(self.volume.data),
|
||||
"notes": self.notes.data,
|
||||
"$type": "recipe",
|
||||
"type": self.type.data,
|
||||
"style": self.style.data,
|
||||
}
|
||||
|
||||
recipe['fermentables'] = [f.doc for f in self.fermentables]
|
||||
recipe['hops'] = [h.doc for h in self.hops]
|
||||
recipe["fermentables"] = [f.doc for f in self.fermentables]
|
||||
recipe["hops"] = [h.doc for h in self.hops]
|
||||
if (
|
||||
self.yeast.doc['name'] and
|
||||
self.yeast.doc['low_attenuation'] != "None" and
|
||||
self.yeast.doc['high_attenuation'] != "None"
|
||||
self.yeast.doc["name"]
|
||||
and self.yeast.doc["low_attenuation"] != "None"
|
||||
and self.yeast.doc["high_attenuation"] != "None"
|
||||
):
|
||||
recipe['yeast'] = self.yeast.doc
|
||||
if self.mash.doc['name']:
|
||||
recipe['mash'] = self.mash.doc
|
||||
recipe["yeast"] = self.yeast.doc
|
||||
if self.mash.doc["name"]:
|
||||
recipe["mash"] = self.mash.doc
|
||||
return recipe
|
||||
|
||||
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']
|
||||
self.style.data = data['style']
|
||||
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"]
|
||||
self.style.data = data["style"]
|
||||
|
||||
for fermentable in data['fermentables']:
|
||||
self.fermentables.append_entry({
|
||||
'name': fermentable['name'],
|
||||
'type': fermentable['type'],
|
||||
'amount': Decimal(fermentable['amount']),
|
||||
'ppg': Decimal(fermentable['ppg']),
|
||||
'color': Decimal(fermentable['color'])
|
||||
})
|
||||
|
||||
for hop in data['hops']:
|
||||
self.hops.append_entry({
|
||||
'name': hop['name'],
|
||||
'use': hop['use'],
|
||||
'alpha': Decimal(hop['alpha']),
|
||||
'duration': Decimal(hop['duration']),
|
||||
'amount': Decimal(hop['amount']),
|
||||
})
|
||||
|
||||
if 'yeast' in data:
|
||||
self.yeast.form.name.data = data['yeast']['name']
|
||||
self.yeast.form.low_attenuation.data = (
|
||||
Decimal(data['yeast']['low_attenuation'])
|
||||
)
|
||||
self.yeast.form.high_attenuation.data = (
|
||||
Decimal(data['yeast']['high_attenuation'])
|
||||
)
|
||||
if 'type' in data['yeast']:
|
||||
self.yeast.form.type.data = data['yeast']['type']
|
||||
if 'lab' in data['yeast']:
|
||||
self.yeast.form.lab.data = data['yeast']['lab']
|
||||
if 'code' in data['yeast']:
|
||||
self.yeast.form.code.data = data['yeast']['code']
|
||||
if 'flocculation' in data['yeast']:
|
||||
self.yeast.form.flocculation.data = (
|
||||
data['yeast']['flocculation']
|
||||
)
|
||||
if 'min_temperature' in data['yeast']:
|
||||
self.yeast.form.min_temperature.data = (
|
||||
Decimal(data['yeast']['min_temperature'])
|
||||
)
|
||||
if 'max_temperature' in data['yeast']:
|
||||
self.yeast.form.max_temperature.data = (
|
||||
Decimal(data['yeast']['max_temperature'])
|
||||
)
|
||||
if 'abv_tolerance' in data['yeast']:
|
||||
self.yeast.form.abv_tolerance.data = (
|
||||
Decimal(data['yeast']['abv_tolerance'])
|
||||
)
|
||||
|
||||
if 'mash' in data:
|
||||
if 'name' in data['mash']:
|
||||
self.mash.form.name.data = data['mash']['name']
|
||||
if 'steps' in data['mash']:
|
||||
for step in data['mash']['steps']:
|
||||
new_step = {
|
||||
'name': step['name'],
|
||||
'type': step['type'],
|
||||
'temp': Decimal(step['temp']),
|
||||
'time': Decimal(step['time'])
|
||||
for fermentable in data["fermentables"]:
|
||||
self.fermentables.append_entry(
|
||||
{
|
||||
"name": fermentable["name"],
|
||||
"type": fermentable["type"],
|
||||
"amount": Decimal(fermentable["amount"]),
|
||||
"ppg": Decimal(fermentable["ppg"]),
|
||||
"color": Decimal(fermentable["color"]),
|
||||
}
|
||||
if 'amount' in step:
|
||||
new_step['amount'] = Decimal(step['amount'])
|
||||
)
|
||||
|
||||
for hop in data["hops"]:
|
||||
self.hops.append_entry(
|
||||
{
|
||||
"name": hop["name"],
|
||||
"use": hop["use"],
|
||||
"alpha": Decimal(hop["alpha"]),
|
||||
"duration": Decimal(hop["duration"]),
|
||||
"amount": Decimal(hop["amount"]),
|
||||
}
|
||||
)
|
||||
|
||||
if "yeast" in data:
|
||||
self.yeast.form.name.data = data["yeast"]["name"]
|
||||
self.yeast.form.low_attenuation.data = Decimal(
|
||||
data["yeast"]["low_attenuation"]
|
||||
)
|
||||
self.yeast.form.high_attenuation.data = Decimal(
|
||||
data["yeast"]["high_attenuation"]
|
||||
)
|
||||
if "type" in data["yeast"]:
|
||||
self.yeast.form.type.data = data["yeast"]["type"]
|
||||
if "lab" in data["yeast"]:
|
||||
self.yeast.form.lab.data = data["yeast"]["lab"]
|
||||
if "code" in data["yeast"]:
|
||||
self.yeast.form.code.data = data["yeast"]["code"]
|
||||
if "flocculation" in data["yeast"]:
|
||||
self.yeast.form.flocculation.data = data["yeast"][
|
||||
"flocculation"
|
||||
]
|
||||
if "min_temperature" in data["yeast"]:
|
||||
self.yeast.form.min_temperature.data = Decimal(
|
||||
data["yeast"]["min_temperature"]
|
||||
)
|
||||
if "max_temperature" in data["yeast"]:
|
||||
self.yeast.form.max_temperature.data = Decimal(
|
||||
data["yeast"]["max_temperature"]
|
||||
)
|
||||
if "abv_tolerance" in data["yeast"]:
|
||||
self.yeast.form.abv_tolerance.data = Decimal(
|
||||
data["yeast"]["abv_tolerance"]
|
||||
)
|
||||
|
||||
if "mash" in data:
|
||||
if "name" in data["mash"]:
|
||||
self.mash.form.name.data = data["mash"]["name"]
|
||||
if "steps" in data["mash"]:
|
||||
for step in data["mash"]["steps"]:
|
||||
new_step = {
|
||||
"name": step["name"],
|
||||
"type": step["type"],
|
||||
"temp": Decimal(step["temp"]),
|
||||
"time": Decimal(step["time"]),
|
||||
}
|
||||
if "amount" in step:
|
||||
new_step["amount"] = Decimal(step["amount"])
|
||||
print(new_step)
|
||||
self.mash.steps.append_entry(new_step)
|
||||
|
||||
|
|
@ -338,29 +372,25 @@ class ImportForm(FlaskForm):
|
|||
upload = FileField(validators=[FileRequired()])
|
||||
|
||||
|
||||
@bp.route('/')
|
||||
@bp.route("/")
|
||||
def index():
|
||||
descending = (
|
||||
request.args.get('descending', default='false', type=str).lower() in
|
||||
['true', 'yes']
|
||||
)
|
||||
sort_by = request.args.get('sort_by', default='name', type=str)
|
||||
descending = request.args.get(
|
||||
"descending", default="false", type=str
|
||||
).lower() in ["true", "yes"]
|
||||
sort_by = request.args.get("sort_by", default="name", type=str)
|
||||
|
||||
view = get_view('_design/recipes', 'by-{}'.format(sort_by))
|
||||
view = get_view("_design/recipes", "by-{}".format(sort_by))
|
||||
try:
|
||||
rows = view(include_docs=True, descending=descending)['rows']
|
||||
rows = view(include_docs=True, descending=descending)["rows"]
|
||||
except requests.exceptions.HTTPError:
|
||||
abort(400)
|
||||
|
||||
return render_template(
|
||||
'recipes/index.html',
|
||||
rows=rows,
|
||||
descending=descending,
|
||||
sort_by=sort_by
|
||||
"recipes/index.html", rows=rows, descending=descending, sort_by=sort_by
|
||||
)
|
||||
|
||||
|
||||
@bp.route('/create', methods=('GET', 'POST'))
|
||||
@bp.route("/create", methods=("GET", "POST"))
|
||||
@login_required
|
||||
def create():
|
||||
form = RecipeForm()
|
||||
|
|
@ -368,12 +398,12 @@ def create():
|
|||
|
||||
if form.validate_on_submit():
|
||||
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)
|
||||
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)
|
||||
|
||||
|
||||
@bp.route('/create/json', methods=('GET', 'POST'))
|
||||
@bp.route("/create/json", methods=("GET", "POST"))
|
||||
@login_required
|
||||
def create_json():
|
||||
form = ImportForm()
|
||||
|
|
@ -382,47 +412,48 @@ def create_json():
|
|||
try:
|
||||
recipe.copyfrom(json.load(form.upload.data))
|
||||
except Exception as e:
|
||||
flash('Error converting data from JSON: {}'.format(e), 'warning')
|
||||
return render_template('recipes/create_json.html', form=form)
|
||||
flash("Error converting data from JSON: {}".format(e), "warning")
|
||||
return render_template("recipes/create_json.html", form=form)
|
||||
response = put_doc(recipe.doc)
|
||||
return redirect(url_for('recipes.info', id=response['_id']))
|
||||
return render_template('recipes/create_json.html', form=form)
|
||||
return redirect(url_for("recipes.info", id=response["_id"]))
|
||||
return render_template("recipes/create_json.html", form=form)
|
||||
|
||||
|
||||
@bp.route('/info/<id>')
|
||||
@bp.route("/info/<id>")
|
||||
def info(id):
|
||||
recipe = get_doc_or_404(id)
|
||||
|
||||
style = None
|
||||
if recipe['style'] != '':
|
||||
if recipe["style"] != "":
|
||||
try:
|
||||
style = get_doc(recipe['style'])
|
||||
style = get_doc(recipe["style"])
|
||||
except KeyError:
|
||||
flash('Could not find style `{}`.'.format(recipe['style']),
|
||||
'warning')
|
||||
flash(
|
||||
"Could not find style `{}`.".format(recipe["style"]), "warning"
|
||||
)
|
||||
|
||||
return render_template('recipes/info.html', recipe=recipe, style=style)
|
||||
return render_template("recipes/info.html", recipe=recipe, style=style)
|
||||
|
||||
|
||||
@bp.route('/info/<id>/json')
|
||||
@bp.route("/info/<id>/json")
|
||||
def info_json(id):
|
||||
recipe = get_doc_or_404(id)
|
||||
# Remove fields specific not intended for export
|
||||
recipe.pop('_id')
|
||||
recipe.pop('_rev')
|
||||
recipe.pop('$type')
|
||||
recipe.pop("_id")
|
||||
recipe.pop("_rev")
|
||||
recipe.pop("$type")
|
||||
return jsonify(recipe)
|
||||
|
||||
|
||||
@bp.route('/delete/<id>', methods=('POST',))
|
||||
@bp.route("/delete/<id>", methods=("POST",))
|
||||
@login_required
|
||||
def delete(id):
|
||||
recipe = get_doc_or_404(id)
|
||||
recipe.delete()
|
||||
return redirect(url_for('home.index'))
|
||||
return redirect(url_for("home.index"))
|
||||
|
||||
|
||||
@bp.route('/update/<id>', methods=('GET', 'POST'))
|
||||
@bp.route("/update/<id>", methods=("GET", "POST"))
|
||||
@login_required
|
||||
def update(id):
|
||||
# Get the recipe from the database and validate it is the same revision
|
||||
|
|
@ -430,24 +461,25 @@ def update(id):
|
|||
form.style.choices = get_styles_list()
|
||||
recipe = get_doc_or_404(id)
|
||||
if form.validate_on_submit():
|
||||
if recipe['_rev'] != request.args.get('rev', None):
|
||||
if recipe["_rev"] != request.args.get("rev", None):
|
||||
flash(
|
||||
(
|
||||
'Update conflict for recipe: {}. '
|
||||
'Your changes have been lost.'.format(recipe['name'])
|
||||
"Update conflict for recipe: {}. "
|
||||
"Your changes have been lost.".format(recipe["name"])
|
||||
),
|
||||
'danger'
|
||||
"danger",
|
||||
)
|
||||
return redirect(url_for('recipes.info', id=id))
|
||||
return redirect(url_for("recipes.info", id=id))
|
||||
# Copy values from submitted form to the existing recipe and save
|
||||
for key, value in form.doc.items():
|
||||
recipe[key] = value
|
||||
update_doc(recipe)
|
||||
|
||||
flash('Updated recipe: {}'.format(form.name.data), 'success')
|
||||
return redirect(url_for('recipes.info', id=id))
|
||||
flash("Updated recipe: {}".format(form.name.data), "success")
|
||||
return redirect(url_for("recipes.info", id=id))
|
||||
else:
|
||||
form.copyfrom(recipe)
|
||||
|
||||
return render_template('recipes/update.html', form=form,
|
||||
id=id, rev=recipe['_rev'])
|
||||
return render_template(
|
||||
"recipes/update.html", form=form, id=id, rev=recipe["_rev"]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -19,14 +19,20 @@ import xml.etree.ElementTree as ET
|
|||
|
||||
import click
|
||||
import requests
|
||||
from flask import (Blueprint, abort, current_app, render_template, request,
|
||||
jsonify)
|
||||
from flask import (
|
||||
Blueprint,
|
||||
abort,
|
||||
current_app,
|
||||
render_template,
|
||||
request,
|
||||
jsonify,
|
||||
)
|
||||
from flask.cli import with_appcontext
|
||||
|
||||
from humulus.auth import login_required
|
||||
from humulus.couch import get_db, put_doc, get_view, get_doc_or_404
|
||||
|
||||
bp = Blueprint('styles', __name__, url_prefix='/styles')
|
||||
bp = Blueprint("styles", __name__, url_prefix="/styles")
|
||||
|
||||
|
||||
def sub_to_doc(sub):
|
||||
|
|
@ -35,56 +41,83 @@ def sub_to_doc(sub):
|
|||
The returned dictionary can be placed right into CouchDB if you want.
|
||||
"""
|
||||
doc = {
|
||||
'_id': '{}'.format(sub.attrib['id']),
|
||||
'$type': 'style',
|
||||
'name': sub.find('name').text,
|
||||
'aroma': sub.find('aroma').text,
|
||||
'appearance': sub.find('appearance').text,
|
||||
'flavor': sub.find('flavor').text,
|
||||
'mouthfeel': sub.find('mouthfeel').text,
|
||||
'impression': sub.find('impression').text,
|
||||
'ibu': {},
|
||||
'og': {},
|
||||
'fg': {},
|
||||
'srm': {},
|
||||
'abv': {}
|
||||
"_id": "{}".format(sub.attrib["id"]),
|
||||
"$type": "style",
|
||||
"name": sub.find("name").text,
|
||||
"aroma": sub.find("aroma").text,
|
||||
"appearance": sub.find("appearance").text,
|
||||
"flavor": sub.find("flavor").text,
|
||||
"mouthfeel": sub.find("mouthfeel").text,
|
||||
"impression": sub.find("impression").text,
|
||||
"ibu": {},
|
||||
"og": {},
|
||||
"fg": {},
|
||||
"srm": {},
|
||||
"abv": {},
|
||||
}
|
||||
if sub.find('comments') is not None:
|
||||
doc['comments'] = sub.find('comments').text
|
||||
if sub.find('history') is not None:
|
||||
doc['history'] = sub.find('history').text
|
||||
if sub.find('ingredients') is not None:
|
||||
doc['ingredients'] = sub.find('ingredients').text
|
||||
if sub.find('comparison') is not None:
|
||||
doc['comparison'] = sub.find('comparison').text
|
||||
if sub.find('examples') is not None:
|
||||
doc['examples'] = sub.find('examples').text
|
||||
if sub.find('tags') is not None:
|
||||
doc['tags'] = sub.find('tags').text.split(', ')
|
||||
if sub.find("comments") is not None:
|
||||
doc["comments"] = sub.find("comments").text
|
||||
if sub.find("history") is not None:
|
||||
doc["history"] = sub.find("history").text
|
||||
if sub.find("ingredients") is not None:
|
||||
doc["ingredients"] = sub.find("ingredients").text
|
||||
if sub.find("comparison") is not None:
|
||||
doc["comparison"] = sub.find("comparison").text
|
||||
if sub.find("examples") is not None:
|
||||
doc["examples"] = sub.find("examples").text
|
||||
if sub.find("tags") is not None:
|
||||
doc["tags"] = sub.find("tags").text.split(", ")
|
||||
|
||||
doc['ibu']['low'] = (sub.find('./stats/ibu/low').text
|
||||
if sub.find('./stats/ibu/low') is not None else '0')
|
||||
doc['ibu']['high'] = (sub.find('./stats/ibu/high').text
|
||||
if sub.find('./stats/ibu/high') is not None
|
||||
else '100')
|
||||
doc['og']['low'] = (sub.find('./stats/og/low').text
|
||||
if sub.find('./stats/og/low') is not None else '1.0')
|
||||
doc['og']['high'] = (sub.find('./stats/og/high').text
|
||||
if sub.find('./stats/og/high') is not None else '1.2')
|
||||
doc['fg']['low'] = (sub.find('./stats/fg/low').text
|
||||
if sub.find('./stats/fg/low') is not None else '1.0')
|
||||
doc['fg']['high'] = (sub.find('./stats/fg/high').text
|
||||
if sub.find('./stats/fg/high') is not None else '1.2')
|
||||
doc['srm']['low'] = (sub.find('./stats/srm/low').text
|
||||
if sub.find('./stats/srm/low') is not None else '0')
|
||||
doc['srm']['high'] = (sub.find('./stats/srm/high').text
|
||||
if sub.find('./stats/srm/high') is not None
|
||||
else '100')
|
||||
doc['abv']['low'] = (sub.find('./stats/abv/low').text
|
||||
if sub.find('./stats/abv/low') is not None else '0')
|
||||
doc['abv']['high'] = (sub.find('./stats/abv/high').text
|
||||
if sub.find('./stats/abv/high') is not None
|
||||
else '100')
|
||||
doc["ibu"]["low"] = (
|
||||
sub.find("./stats/ibu/low").text
|
||||
if sub.find("./stats/ibu/low") is not None
|
||||
else "0"
|
||||
)
|
||||
doc["ibu"]["high"] = (
|
||||
sub.find("./stats/ibu/high").text
|
||||
if sub.find("./stats/ibu/high") is not None
|
||||
else "100"
|
||||
)
|
||||
doc["og"]["low"] = (
|
||||
sub.find("./stats/og/low").text
|
||||
if sub.find("./stats/og/low") is not None
|
||||
else "1.0"
|
||||
)
|
||||
doc["og"]["high"] = (
|
||||
sub.find("./stats/og/high").text
|
||||
if sub.find("./stats/og/high") is not None
|
||||
else "1.2"
|
||||
)
|
||||
doc["fg"]["low"] = (
|
||||
sub.find("./stats/fg/low").text
|
||||
if sub.find("./stats/fg/low") is not None
|
||||
else "1.0"
|
||||
)
|
||||
doc["fg"]["high"] = (
|
||||
sub.find("./stats/fg/high").text
|
||||
if sub.find("./stats/fg/high") is not None
|
||||
else "1.2"
|
||||
)
|
||||
doc["srm"]["low"] = (
|
||||
sub.find("./stats/srm/low").text
|
||||
if sub.find("./stats/srm/low") is not None
|
||||
else "0"
|
||||
)
|
||||
doc["srm"]["high"] = (
|
||||
sub.find("./stats/srm/high").text
|
||||
if sub.find("./stats/srm/high") is not None
|
||||
else "100"
|
||||
)
|
||||
doc["abv"]["low"] = (
|
||||
sub.find("./stats/abv/low").text
|
||||
if sub.find("./stats/abv/low") is not None
|
||||
else "0"
|
||||
)
|
||||
doc["abv"]["high"] = (
|
||||
sub.find("./stats/abv/high").text
|
||||
if sub.find("./stats/abv/high") is not None
|
||||
else "100"
|
||||
)
|
||||
return doc
|
||||
|
||||
|
||||
|
|
@ -102,31 +135,34 @@ def import_styles(url):
|
|||
subs = root.findall('./class[@type="beer"]/category/subcategory')
|
||||
for sub in subs:
|
||||
doc = sub_to_doc(sub)
|
||||
if doc['_id'] not in db:
|
||||
if doc["_id"] not in db:
|
||||
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']:
|
||||
styles.append([row['id'], '{}{} {}'.format(
|
||||
row['key'][0],
|
||||
row['key'][1],
|
||||
row['value']
|
||||
)])
|
||||
view = get_view("_design/styles", "by-category")
|
||||
styles = [["", ""]]
|
||||
for row in view(include_docs=False)["rows"]:
|
||||
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
|
||||
def import_command():
|
||||
"""CLI command to import BJCP styles."""
|
||||
url = current_app.config.get(
|
||||
'BJCP_STYLES_URL',
|
||||
('https://raw.githubusercontent.com/meanphil'
|
||||
'/bjcp-guidelines-2015/master/styleguide.xml')
|
||||
"BJCP_STYLES_URL",
|
||||
(
|
||||
"https://raw.githubusercontent.com/meanphil"
|
||||
"/bjcp-guidelines-2015/master/styleguide.xml"
|
||||
),
|
||||
)
|
||||
import_styles(url)
|
||||
click.echo("Imported BJCP styles.")
|
||||
|
|
@ -137,41 +173,40 @@ def init_app(app):
|
|||
app.cli.add_command(import_command)
|
||||
|
||||
|
||||
@bp.route('/')
|
||||
@bp.route("/")
|
||||
@login_required
|
||||
def index():
|
||||
descending = (
|
||||
request.args.get('descending', default='false', type=str).lower() in
|
||||
['true', 'yes']
|
||||
)
|
||||
sort_by = request.args.get('sort_by', default='category', type=str)
|
||||
page = request.args.get('page', default=1, type=int)
|
||||
limit = request.args.get('limit', default=20, type=int)
|
||||
descending = request.args.get(
|
||||
"descending", default="false", type=str
|
||||
).lower() in ["true", "yes"]
|
||||
sort_by = request.args.get("sort_by", default="category", type=str)
|
||||
page = request.args.get("page", default=1, type=int)
|
||||
limit = request.args.get("limit", default=20, type=int)
|
||||
|
||||
view = get_view('_design/styles', 'by-{}'.format(sort_by))
|
||||
view = get_view("_design/styles", "by-{}".format(sort_by))
|
||||
try:
|
||||
rows = view(include_docs=True, descending=descending)['rows']
|
||||
rows = view(include_docs=True, descending=descending)["rows"]
|
||||
except requests.exceptions.HTTPError:
|
||||
abort(400)
|
||||
|
||||
return render_template(
|
||||
'styles/index.html',
|
||||
rows=rows[(page - 1) * limit:page * limit],
|
||||
"styles/index.html",
|
||||
rows=rows[(page - 1) * limit : page * limit], # noqa
|
||||
descending=descending,
|
||||
sort_by=sort_by,
|
||||
page=page,
|
||||
num_pages=math.ceil(len(rows) / limit),
|
||||
limit=limit
|
||||
limit=limit,
|
||||
)
|
||||
|
||||
|
||||
@bp.route('/info/<id>')
|
||||
@bp.route("/info/<id>")
|
||||
@login_required
|
||||
def info(id):
|
||||
return render_template('styles/info.html', style=get_doc_or_404(id))
|
||||
return render_template("styles/info.html", style=get_doc_or_404(id))
|
||||
|
||||
|
||||
@bp.route('/info/<id>/json')
|
||||
@bp.route("/info/<id>/json")
|
||||
@login_required
|
||||
def info_json(id):
|
||||
"""Returns JSON for the style.
|
||||
|
|
@ -181,24 +216,26 @@ def info_json(id):
|
|||
"""
|
||||
style = get_doc_or_404(id)
|
||||
# Remove fields not needed for specs
|
||||
if request.args.get('specs', None) is not None:
|
||||
return jsonify({
|
||||
'ibu': style['ibu'],
|
||||
'og': style['og'],
|
||||
'fg': style['fg'],
|
||||
'abv': style['abv'],
|
||||
'srm': style['srm']
|
||||
})
|
||||
if request.args.get("specs", None) is not None:
|
||||
return jsonify(
|
||||
{
|
||||
"ibu": style["ibu"],
|
||||
"og": style["og"],
|
||||
"fg": style["fg"],
|
||||
"abv": style["abv"],
|
||||
"srm": style["srm"],
|
||||
}
|
||||
)
|
||||
# Remove fields not needed for export
|
||||
style.pop('_id')
|
||||
style.pop('_rev')
|
||||
style.pop('$type')
|
||||
style.pop("_id")
|
||||
style.pop("_rev")
|
||||
style.pop("$type")
|
||||
return jsonify(style)
|
||||
|
||||
|
||||
@bp.route('/info/<id>/recipes')
|
||||
@bp.route("/info/<id>/recipes")
|
||||
def recipes(id):
|
||||
style = get_doc_or_404(id)
|
||||
view = get_view('_design/recipes', 'by-style')
|
||||
rows = view(include_docs=True, descending=True, key=id)['rows']
|
||||
return render_template('styles/recipes.html', style=style, rows=rows)
|
||||
view = get_view("_design/recipes", "by-style")
|
||||
rows = view(include_docs=True, descending=True, key=id)["rows"]
|
||||
return render_template("styles/recipes.html", style=style, rows=rows)
|
||||
|
|
|
|||
|
|
@ -23,136 +23,150 @@ from humulus.couch import build_couch, get_couch, put_doc
|
|||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
dbname = 'test_{}'.format(str(uuid.uuid4()))
|
||||
couchurl = os.environ.get('COUCH_URL', 'http://127.0.0.1:5984')
|
||||
app = create_app({
|
||||
'COUCH_URL': couchurl,
|
||||
'COUCH_USERNAME': 'admin',
|
||||
'COUCH_PASSWORD': 'password',
|
||||
'COUCH_DATABASE': dbname,
|
||||
'WTF_CSRF_ENABLED': False,
|
||||
'SECRET_KEY': 'testing',
|
||||
'HUMULUS_PASSWORD': 'password'
|
||||
})
|
||||
dbname = "test_{}".format(str(uuid.uuid4()))
|
||||
couchurl = os.environ.get("COUCH_URL", "http://127.0.0.1:5984")
|
||||
app = create_app(
|
||||
{
|
||||
"COUCH_URL": couchurl,
|
||||
"COUCH_USERNAME": "admin",
|
||||
"COUCH_PASSWORD": "password",
|
||||
"COUCH_DATABASE": dbname,
|
||||
"WTF_CSRF_ENABLED": False,
|
||||
"SECRET_KEY": "testing",
|
||||
"HUMULUS_PASSWORD": "password",
|
||||
}
|
||||
)
|
||||
|
||||
with app.app_context():
|
||||
# Create the database
|
||||
build_couch()
|
||||
# Add a test doc
|
||||
put_doc({'data': 'test', '_id': 'foobar'})
|
||||
put_doc({"data": "test", "_id": "foobar"})
|
||||
|
||||
# Add a couple test recipe
|
||||
put_doc({
|
||||
'_id': 'awesome-lager',
|
||||
'$type': 'recipe',
|
||||
'type': 'All-Grain',
|
||||
'efficiency': '65',
|
||||
'name': 'Awesome Lager',
|
||||
'notes': 'Test',
|
||||
'volume': '5.5',
|
||||
'fermentables': [],
|
||||
'hops': [],
|
||||
'style': ''
|
||||
})
|
||||
put_doc({
|
||||
'_id': 'partial-yeast-recipe',
|
||||
'$type': 'recipe',
|
||||
'efficiency': '75',
|
||||
'name': 'Partial Beer',
|
||||
'type': 'Extract',
|
||||
'notes': 'Contains only required fields for yeast.',
|
||||
'volume': '3.5',
|
||||
'fermentables': [],
|
||||
'hops': [],
|
||||
'yeast': {
|
||||
'name': 'US-05',
|
||||
'low_attenuation': '60',
|
||||
'high_attenuation': '72',
|
||||
},
|
||||
'style': ''
|
||||
})
|
||||
put_doc({
|
||||
'_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',
|
||||
'style': '1A',
|
||||
'fermentables': [
|
||||
put_doc(
|
||||
{
|
||||
'name': '2row',
|
||||
'type': 'Grain',
|
||||
'amount': '5',
|
||||
'ppg': '37',
|
||||
'color': '2'
|
||||
"_id": "awesome-lager",
|
||||
"$type": "recipe",
|
||||
"type": "All-Grain",
|
||||
"efficiency": "65",
|
||||
"name": "Awesome Lager",
|
||||
"notes": "Test",
|
||||
"volume": "5.5",
|
||||
"fermentables": [],
|
||||
"hops": [],
|
||||
"style": "",
|
||||
}
|
||||
)
|
||||
put_doc(
|
||||
{
|
||||
"_id": "partial-yeast-recipe",
|
||||
"$type": "recipe",
|
||||
"efficiency": "75",
|
||||
"name": "Partial Beer",
|
||||
"type": "Extract",
|
||||
"notes": "Contains only required fields for yeast.",
|
||||
"volume": "3.5",
|
||||
"fermentables": [],
|
||||
"hops": [],
|
||||
"yeast": {
|
||||
"name": "US-05",
|
||||
"low_attenuation": "60",
|
||||
"high_attenuation": "72",
|
||||
},
|
||||
"style": "",
|
||||
}
|
||||
)
|
||||
put_doc(
|
||||
{
|
||||
"_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",
|
||||
"style": "1A",
|
||||
"fermentables": [
|
||||
{
|
||||
"name": "2row",
|
||||
"type": "Grain",
|
||||
"amount": "5",
|
||||
"ppg": "37",
|
||||
"color": "2",
|
||||
},
|
||||
{
|
||||
'name': 'Dextrose',
|
||||
'type': 'Sugar',
|
||||
'amount': '1',
|
||||
'ppg': '46',
|
||||
'color': '1'
|
||||
"name": "Dextrose",
|
||||
"type": "Sugar",
|
||||
"amount": "1",
|
||||
"ppg": "46",
|
||||
"color": "1",
|
||||
},
|
||||
],
|
||||
"hops": [
|
||||
{
|
||||
"name": "Nugget (US)",
|
||||
"use": "Boil",
|
||||
"alpha": "12.5",
|
||||
"duration": "60",
|
||||
"amount": "1",
|
||||
},
|
||||
{
|
||||
"name": "CTZ (US)",
|
||||
"use": "Dry-Hop",
|
||||
"alpha": "16",
|
||||
"duration": "5",
|
||||
"amount": "0.5",
|
||||
},
|
||||
],
|
||||
"yeast": {
|
||||
"name": "Northern California Ale",
|
||||
"type": "Liquid",
|
||||
"lab": "Inland Island",
|
||||
"code": "INIS-001",
|
||||
"flocculation": "Medium",
|
||||
"low_attenuation": "73",
|
||||
"high_attenuation": "77",
|
||||
"min_temperature": "60",
|
||||
"max_temperature": "72",
|
||||
"abv_tolerance": "10",
|
||||
},
|
||||
"mash": {
|
||||
"name": "Single Infusion",
|
||||
"steps": [
|
||||
{
|
||||
"name": "Infusion",
|
||||
"type": "Infusion",
|
||||
"temp": "152",
|
||||
"time": "60",
|
||||
"amount": "3.5",
|
||||
}
|
||||
],
|
||||
'hops': [
|
||||
{
|
||||
'name': 'Nugget (US)',
|
||||
'use': 'Boil',
|
||||
'alpha': '12.5',
|
||||
'duration': '60',
|
||||
'amount': '1'
|
||||
},
|
||||
{
|
||||
'name': 'CTZ (US)',
|
||||
'use': 'Dry-Hop',
|
||||
'alpha': '16',
|
||||
'duration': '5',
|
||||
'amount': '0.5'
|
||||
}
|
||||
],
|
||||
'yeast': {
|
||||
'name': 'Northern California Ale',
|
||||
'type': 'Liquid',
|
||||
'lab': 'Inland Island',
|
||||
'code': 'INIS-001',
|
||||
'flocculation': 'Medium',
|
||||
'low_attenuation': '73',
|
||||
'high_attenuation': '77',
|
||||
'min_temperature': '60',
|
||||
'max_temperature': '72',
|
||||
'abv_tolerance': '10'
|
||||
},
|
||||
'mash': {
|
||||
'name': 'Single Infusion',
|
||||
'steps': [{
|
||||
'name': 'Infusion',
|
||||
'type': 'Infusion',
|
||||
'temp': '152',
|
||||
'time': '60',
|
||||
'amount': '3.5'
|
||||
}]
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
# Add a test style
|
||||
put_doc({
|
||||
'$type': 'style',
|
||||
'_id': '1A',
|
||||
'abv': {'high': '100', 'low': '0'},
|
||||
'appearance': 'Good looking',
|
||||
'aroma': 'Smelly',
|
||||
'fg': {'high': '1.2', 'low': '1.0'},
|
||||
'flavor': 'Good tasting',
|
||||
'ibu': {'high': '100', 'low': '0'},
|
||||
'id': '1A',
|
||||
'impression': 'Refreshing',
|
||||
'mouthfeel': 'Good feeling',
|
||||
'name': 'Test Style',
|
||||
'og': {'high': '1.2', 'low': '1.0'},
|
||||
'srm': {'high': '100', 'low': '0'}
|
||||
})
|
||||
put_doc(
|
||||
{
|
||||
"$type": "style",
|
||||
"_id": "1A",
|
||||
"abv": {"high": "100", "low": "0"},
|
||||
"appearance": "Good looking",
|
||||
"aroma": "Smelly",
|
||||
"fg": {"high": "1.2", "low": "1.0"},
|
||||
"flavor": "Good tasting",
|
||||
"ibu": {"high": "100", "low": "0"},
|
||||
"id": "1A",
|
||||
"impression": "Refreshing",
|
||||
"mouthfeel": "Good feeling",
|
||||
"name": "Test Style",
|
||||
"og": {"high": "1.2", "low": "1.0"},
|
||||
"srm": {"high": "100", "low": "0"},
|
||||
}
|
||||
)
|
||||
|
||||
yield app
|
||||
|
||||
|
|
@ -174,14 +188,11 @@ class AuthActions(object):
|
|||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
def login(self, password='password'):
|
||||
return self._client.post(
|
||||
'/login',
|
||||
data={'password': password}
|
||||
)
|
||||
def login(self, password="password"):
|
||||
return self._client.post("/login", data={"password": password})
|
||||
|
||||
def logout(self):
|
||||
return self._client.get('/logout')
|
||||
return self._client.get("/logout")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -193,122 +204,122 @@ def auth(client):
|
|||
def sample_recipes():
|
||||
"""These sample recipes are useful for testing filters."""
|
||||
return {
|
||||
'lager': {
|
||||
'efficiency': '72',
|
||||
'type': 'All-Grain',
|
||||
'style': '',
|
||||
'fermentables': [
|
||||
"lager": {
|
||||
"efficiency": "72",
|
||||
"type": "All-Grain",
|
||||
"style": "",
|
||||
"fermentables": [
|
||||
{
|
||||
'amount': '9.5',
|
||||
'color': '1.80',
|
||||
'name': 'Pale Malt, 2-row (Rahr) (US)',
|
||||
'ppg': '37.00',
|
||||
'type': 'Grain'
|
||||
"amount": "9.5",
|
||||
"color": "1.80",
|
||||
"name": "Pale Malt, 2-row (Rahr) (US)",
|
||||
"ppg": "37.00",
|
||||
"type": "Grain",
|
||||
},
|
||||
{
|
||||
'amount': '1',
|
||||
'color': '0',
|
||||
'name': 'Corn Sugar (Dextrose)',
|
||||
'ppg': '46.00',
|
||||
'type': 'Sugar'
|
||||
}
|
||||
"amount": "1",
|
||||
"color": "0",
|
||||
"name": "Corn Sugar (Dextrose)",
|
||||
"ppg": "46.00",
|
||||
"type": "Sugar",
|
||||
},
|
||||
],
|
||||
'hops': [
|
||||
"hops": [
|
||||
{
|
||||
'alpha': '7.0',
|
||||
'amount': '1',
|
||||
'duration': '60',
|
||||
'name': 'Cluster (US)',
|
||||
'use': 'Boil'
|
||||
"alpha": "7.0",
|
||||
"amount": "1",
|
||||
"duration": "60",
|
||||
"name": "Cluster (US)",
|
||||
"use": "Boil",
|
||||
},
|
||||
{
|
||||
'alpha': '2.8',
|
||||
'amount': '1',
|
||||
'duration': '10.00',
|
||||
'name': 'Saaz (CZ)',
|
||||
'use': 'Boil'
|
||||
"alpha": "2.8",
|
||||
"amount": "1",
|
||||
"duration": "10.00",
|
||||
"name": "Saaz (CZ)",
|
||||
"use": "Boil",
|
||||
},
|
||||
{
|
||||
'alpha': '2.8',
|
||||
'amount': '1.0',
|
||||
'duration': '5',
|
||||
'name': 'Saaz (CZ)',
|
||||
'use': 'Dry-Hop'
|
||||
}
|
||||
"alpha": "2.8",
|
||||
"amount": "1.0",
|
||||
"duration": "5",
|
||||
"name": "Saaz (CZ)",
|
||||
"use": "Dry-Hop",
|
||||
},
|
||||
],
|
||||
'name': 'Lager',
|
||||
'notes': 'Test simple dry-hopped lager w/ sugar',
|
||||
'volume': '5.50',
|
||||
'yeast': {
|
||||
'abv_tolerance': '15.00',
|
||||
'code': 'WLP940',
|
||||
'flocculation': 'Medium',
|
||||
'high_attenuation': '78.00',
|
||||
'lab': 'White Labs',
|
||||
'low_attenuation': '70.00',
|
||||
'max_temperature': '55.00',
|
||||
'min_temperature': '50.00',
|
||||
'name': 'Mexican Lager',
|
||||
'type': 'Liquid'
|
||||
}
|
||||
"name": "Lager",
|
||||
"notes": "Test simple dry-hopped lager w/ sugar",
|
||||
"volume": "5.50",
|
||||
"yeast": {
|
||||
"abv_tolerance": "15.00",
|
||||
"code": "WLP940",
|
||||
"flocculation": "Medium",
|
||||
"high_attenuation": "78.00",
|
||||
"lab": "White Labs",
|
||||
"low_attenuation": "70.00",
|
||||
"max_temperature": "55.00",
|
||||
"min_temperature": "50.00",
|
||||
"name": "Mexican Lager",
|
||||
"type": "Liquid",
|
||||
},
|
||||
'sweetstout': {
|
||||
'efficiency': '72',
|
||||
'type': 'All-Grain',
|
||||
'style': '',
|
||||
'fermentables': [
|
||||
},
|
||||
"sweetstout": {
|
||||
"efficiency": "72",
|
||||
"type": "All-Grain",
|
||||
"style": "",
|
||||
"fermentables": [
|
||||
{
|
||||
'amount': '2.75',
|
||||
'color': '3',
|
||||
'name': 'Pale Malt, 2-row (UK)',
|
||||
'ppg': '36.00',
|
||||
'type': 'Grain'
|
||||
"amount": "2.75",
|
||||
"color": "3",
|
||||
"name": "Pale Malt, 2-row (UK)",
|
||||
"ppg": "36.00",
|
||||
"type": "Grain",
|
||||
},
|
||||
{
|
||||
'amount': '0.25',
|
||||
'color': '450',
|
||||
'name': 'Chocolate Malt (UK)',
|
||||
'ppg': '34.00',
|
||||
'type': 'Grain'
|
||||
"amount": "0.25",
|
||||
"color": "450",
|
||||
"name": "Chocolate Malt (UK)",
|
||||
"ppg": "34.00",
|
||||
"type": "Grain",
|
||||
},
|
||||
{
|
||||
'amount': '0.5',
|
||||
'color': '0',
|
||||
'name': 'Lactose',
|
||||
'ppg': '35.00',
|
||||
'type': 'Non-fermentable'
|
||||
}
|
||||
"amount": "0.5",
|
||||
"color": "0",
|
||||
"name": "Lactose",
|
||||
"ppg": "35.00",
|
||||
"type": "Non-fermentable",
|
||||
},
|
||||
],
|
||||
'hops': [
|
||||
"hops": [
|
||||
{
|
||||
'alpha': '5.0',
|
||||
'amount': '0.5',
|
||||
'duration': '60',
|
||||
'name': 'East Kent Goldings (UK)',
|
||||
'use': 'Boil'
|
||||
"alpha": "5.0",
|
||||
"amount": "0.5",
|
||||
"duration": "60",
|
||||
"name": "East Kent Goldings (UK)",
|
||||
"use": "Boil",
|
||||
},
|
||||
{
|
||||
'alpha': '5.0',
|
||||
'amount': '0.5',
|
||||
'duration': '30',
|
||||
'name': 'East Kent Goldings (UK)',
|
||||
'use': 'Boil'
|
||||
}
|
||||
"alpha": "5.0",
|
||||
"amount": "0.5",
|
||||
"duration": "30",
|
||||
"name": "East Kent Goldings (UK)",
|
||||
"use": "Boil",
|
||||
},
|
||||
],
|
||||
'name': 'Sweet Stout',
|
||||
'notes': 'Test stout w/ Lactose',
|
||||
'volume': '2.5',
|
||||
'yeast': {
|
||||
'abv_tolerance': '12.00',
|
||||
'code': '',
|
||||
'flocculation': 'High',
|
||||
'high_attenuation': '77.00',
|
||||
'lab': 'Danstar',
|
||||
'low_attenuation': '73.00',
|
||||
'max_temperature': '70.00',
|
||||
'min_temperature': '57.00',
|
||||
'name': 'Nottingham',
|
||||
'type': 'Dry'
|
||||
}
|
||||
}
|
||||
"name": "Sweet Stout",
|
||||
"notes": "Test stout w/ Lactose",
|
||||
"volume": "2.5",
|
||||
"yeast": {
|
||||
"abv_tolerance": "12.00",
|
||||
"code": "",
|
||||
"flocculation": "High",
|
||||
"high_attenuation": "77.00",
|
||||
"lab": "Danstar",
|
||||
"low_attenuation": "73.00",
|
||||
"max_temperature": "70.00",
|
||||
"min_temperature": "57.00",
|
||||
"name": "Nottingham",
|
||||
"type": "Dry",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,30 +15,30 @@
|
|||
|
||||
def test_login(client, auth):
|
||||
# Test GET
|
||||
response = client.get('/login')
|
||||
response = client.get("/login")
|
||||
assert response.status_code == 200
|
||||
|
||||
# Test failed login
|
||||
data = {'password': 'invalid'}
|
||||
response = client.post('/login', data=data)
|
||||
data = {"password": "invalid"}
|
||||
response = client.post("/login", data=data)
|
||||
assert response.status_code == 200
|
||||
assert b'Password is invalid' in response.data
|
||||
assert b"Password is invalid" in response.data
|
||||
|
||||
# Test successful login
|
||||
data = {'password': 'password'}
|
||||
response = client.post('/login', data=data)
|
||||
data = {"password": "password"}
|
||||
response = client.post("/login", data=data)
|
||||
assert response.status_code == 302
|
||||
with client.session_transaction() as session:
|
||||
assert session['logged_in']
|
||||
assert session["logged_in"]
|
||||
assert not session.permanent
|
||||
session.clear()
|
||||
|
||||
# Test permanent login
|
||||
data = {'password': 'password', 'permanent': 'y'}
|
||||
response = client.post('/login', data=data)
|
||||
data = {"password": "password", "permanent": "y"}
|
||||
response = client.post("/login", data=data)
|
||||
assert response.status_code == 302
|
||||
with client.session_transaction() as session:
|
||||
assert session['logged_in']
|
||||
assert session["logged_in"]
|
||||
assert session.permanent
|
||||
|
||||
|
||||
|
|
@ -46,9 +46,9 @@ def test_logout(client, auth):
|
|||
# Login
|
||||
auth.login()
|
||||
with client.session_transaction() as session:
|
||||
assert session['logged_in']
|
||||
assert session["logged_in"]
|
||||
|
||||
response = client.get('/logout')
|
||||
response = client.get("/logout")
|
||||
assert response.status_code == 302
|
||||
with client.session_transaction() as session:
|
||||
assert not session.get('logged_in', False)
|
||||
assert not session.get("logged_in", False)
|
||||
|
|
|
|||
|
|
@ -19,33 +19,33 @@ from humulus.couch import put_doc, get_doc, update_doc, put_designs, get_view
|
|||
|
||||
def test_put_doc(app):
|
||||
with app.app_context():
|
||||
data = {'foo': 'bar'}
|
||||
data = {"foo": "bar"}
|
||||
response = put_doc(data)
|
||||
assert '_id' in response
|
||||
assert 'created' in response
|
||||
assert "_id" in response
|
||||
assert "created" in response
|
||||
|
||||
response = put_doc({'name': 'test'})
|
||||
assert response['_id'] == 'test'
|
||||
response = put_doc({"name": "test"})
|
||||
assert response["_id"] == "test"
|
||||
|
||||
response = put_doc({'name': 'test'})
|
||||
assert response['_id'] == 'test-1'
|
||||
response = put_doc({"name": "test"})
|
||||
assert response["_id"] == "test-1"
|
||||
|
||||
response = put_doc({'name': 'test'})
|
||||
assert response['_id'] == 'test-2'
|
||||
response = put_doc({"name": "test"})
|
||||
assert response["_id"] == "test-2"
|
||||
|
||||
|
||||
def test_update_doc(app):
|
||||
with app.app_context():
|
||||
doc = get_doc('awesome-lager')
|
||||
rev = doc['_rev']
|
||||
doc['test'] = 'update'
|
||||
doc = get_doc("awesome-lager")
|
||||
rev = doc["_rev"]
|
||||
doc["test"] = "update"
|
||||
update_doc(doc)
|
||||
|
||||
updated_doc = get_doc('awesome-lager')
|
||||
assert doc['_id'] == updated_doc['_id']
|
||||
assert rev < updated_doc['_rev']
|
||||
assert updated_doc['test'] == 'update'
|
||||
assert 'updated' in updated_doc
|
||||
updated_doc = get_doc("awesome-lager")
|
||||
assert doc["_id"] == updated_doc["_id"]
|
||||
assert rev < updated_doc["_rev"]
|
||||
assert updated_doc["test"] == "update"
|
||||
assert "updated" in updated_doc
|
||||
|
||||
|
||||
def test_build_couch_command(runner, monkeypatch):
|
||||
|
|
@ -55,15 +55,15 @@ def test_build_couch_command(runner, monkeypatch):
|
|||
def fake_build_couch():
|
||||
Recorder.called = True
|
||||
|
||||
monkeypatch.setattr('humulus.couch.build_couch', fake_build_couch)
|
||||
result = runner.invoke(args=['build-couch'])
|
||||
assert 'Built a couch. Please have a seat.' in result.output
|
||||
monkeypatch.setattr("humulus.couch.build_couch", fake_build_couch)
|
||||
result = runner.invoke(args=["build-couch"])
|
||||
assert "Built a couch. Please have a seat." in result.output
|
||||
assert Recorder.called
|
||||
|
||||
|
||||
def test_get_doc(app):
|
||||
with app.app_context():
|
||||
assert get_doc('foobar')['data'] == 'test'
|
||||
assert get_doc("foobar")["data"] == "test"
|
||||
|
||||
|
||||
def test_put_designs(app, monkeypatch):
|
||||
|
|
@ -72,26 +72,26 @@ def test_put_designs(app, monkeypatch):
|
|||
|
||||
with app.app_context():
|
||||
# Test initial load of designs
|
||||
monkeypatch.setattr(Path, 'parent', testpath / 'assets/initial')
|
||||
monkeypatch.setattr(Path, "parent", testpath / "assets/initial")
|
||||
put_designs()
|
||||
|
||||
recipes = get_doc('_design/recipes')
|
||||
assert 'language' in recipes
|
||||
rev = recipes['_rev']
|
||||
recipes = get_doc("_design/recipes")
|
||||
assert "language" in recipes
|
||||
rev = recipes["_rev"]
|
||||
|
||||
# Try again, make sure nothing changed.
|
||||
put_designs()
|
||||
recipes = get_doc('_design/recipes')
|
||||
assert recipes['_rev'] == rev
|
||||
recipes = get_doc("_design/recipes")
|
||||
assert recipes["_rev"] == rev
|
||||
|
||||
# Test that changes can be loaded
|
||||
monkeypatch.setattr(Path, 'parent', testpath / 'assets/changed')
|
||||
monkeypatch.setattr(Path, "parent", testpath / "assets/changed")
|
||||
put_designs()
|
||||
recipes = get_doc('_design/recipes')
|
||||
assert 'by-date' in recipes['views']
|
||||
recipes = get_doc("_design/recipes")
|
||||
assert "by-date" in recipes["views"]
|
||||
|
||||
|
||||
def test_get_view(app):
|
||||
with app.app_context():
|
||||
view = get_view('_design/recipes', 'by-date')
|
||||
assert view()['total_rows'] > 0
|
||||
view = get_view("_design/recipes", "by-date")
|
||||
assert view()["total_rows"] > 0
|
||||
|
|
|
|||
|
|
@ -14,92 +14,100 @@
|
|||
|
||||
from decimal import Decimal
|
||||
|
||||
from humulus.filters import (recipe_abv, recipe_fg, recipe_ibu, sort_hops,
|
||||
recipe_ibu_ratio, recipe_og, recipe_srm, ferm_pct)
|
||||
from humulus.filters import (
|
||||
recipe_abv,
|
||||
recipe_fg,
|
||||
recipe_ibu,
|
||||
sort_hops,
|
||||
recipe_ibu_ratio,
|
||||
recipe_og,
|
||||
recipe_srm,
|
||||
ferm_pct,
|
||||
)
|
||||
from humulus.recipes import HopForm
|
||||
|
||||
|
||||
def test_recipe_og(sample_recipes):
|
||||
assert recipe_og(sample_recipes['lager']) == '1.054'
|
||||
assert recipe_og(sample_recipes['sweetstout']) == '1.038'
|
||||
assert recipe_og(sample_recipes["lager"]) == "1.054"
|
||||
assert recipe_og(sample_recipes["sweetstout"]) == "1.038"
|
||||
# Remove fermentables, verify 0 is returned
|
||||
sample_recipes['lager'].pop('fermentables')
|
||||
assert recipe_og(sample_recipes['lager']) == '0.000'
|
||||
sample_recipes["lager"].pop("fermentables")
|
||||
assert recipe_og(sample_recipes["lager"]) == "0.000"
|
||||
|
||||
|
||||
def test_recipe_fg(sample_recipes):
|
||||
assert recipe_fg(sample_recipes['lager']) == '1.014'
|
||||
assert recipe_fg(sample_recipes['sweetstout']) == '1.015'
|
||||
assert recipe_fg(sample_recipes["lager"]) == "1.014"
|
||||
assert recipe_fg(sample_recipes["sweetstout"]) == "1.015"
|
||||
# Remove fermentables, verify 0 is returned
|
||||
sample_recipes['lager'].pop('fermentables')
|
||||
assert recipe_fg(sample_recipes['lager']) == '0.000'
|
||||
sample_recipes["lager"].pop("fermentables")
|
||||
assert recipe_fg(sample_recipes["lager"]) == "0.000"
|
||||
# Remove yeast, verify 0 is returned
|
||||
sample_recipes['sweetstout'].pop('yeast')
|
||||
assert recipe_fg(sample_recipes['sweetstout']) == '0.000'
|
||||
sample_recipes["sweetstout"].pop("yeast")
|
||||
assert recipe_fg(sample_recipes["sweetstout"]) == "0.000"
|
||||
|
||||
|
||||
def test_recipe_ibu(sample_recipes):
|
||||
assert recipe_ibu(sample_recipes['lager']) == '24'
|
||||
assert recipe_ibu(sample_recipes['sweetstout']) == '34'
|
||||
assert recipe_ibu(sample_recipes["lager"]) == "24"
|
||||
assert recipe_ibu(sample_recipes["sweetstout"]) == "34"
|
||||
# Remove hops, verify 0 is returned
|
||||
sample_recipes['lager'].pop('hops')
|
||||
assert recipe_ibu(sample_recipes['lager']) == '0'
|
||||
sample_recipes["lager"].pop("hops")
|
||||
assert recipe_ibu(sample_recipes["lager"]) == "0"
|
||||
|
||||
|
||||
def test_recipe_ibu_ratio(sample_recipes):
|
||||
assert recipe_ibu_ratio(sample_recipes['lager']) == '0.44'
|
||||
assert recipe_ibu_ratio(sample_recipes['sweetstout']) == '0.89'
|
||||
assert recipe_ibu_ratio(sample_recipes["lager"]) == "0.44"
|
||||
assert recipe_ibu_ratio(sample_recipes["sweetstout"]) == "0.89"
|
||||
# Remove fermentables, verify 0 is returned
|
||||
sample_recipes['lager'].pop('fermentables')
|
||||
assert recipe_ibu_ratio(sample_recipes['lager']) == '0'
|
||||
sample_recipes["lager"].pop("fermentables")
|
||||
assert recipe_ibu_ratio(sample_recipes["lager"]) == "0"
|
||||
# Remove hops, verify 0 is returned
|
||||
sample_recipes['sweetstout'].pop('hops')
|
||||
assert recipe_ibu_ratio(sample_recipes['sweetstout']) == '0'
|
||||
sample_recipes["sweetstout"].pop("hops")
|
||||
assert recipe_ibu_ratio(sample_recipes["sweetstout"]) == "0"
|
||||
|
||||
|
||||
def test_recipe_abv(sample_recipes):
|
||||
assert recipe_abv(sample_recipes['lager']) == '5.3'
|
||||
assert recipe_abv(sample_recipes['sweetstout']) == '3.0'
|
||||
assert recipe_abv(sample_recipes["lager"]) == "5.3"
|
||||
assert recipe_abv(sample_recipes["sweetstout"]) == "3.0"
|
||||
# Remove fermentables, verify 0 is returned
|
||||
sample_recipes['lager'].pop('fermentables')
|
||||
assert recipe_abv(sample_recipes['lager']) == '0'
|
||||
sample_recipes["lager"].pop("fermentables")
|
||||
assert recipe_abv(sample_recipes["lager"]) == "0"
|
||||
# Remove yeast, verify 0 is returned
|
||||
sample_recipes['sweetstout'].pop('yeast')
|
||||
assert recipe_abv(sample_recipes['sweetstout']) == '0'
|
||||
sample_recipes["sweetstout"].pop("yeast")
|
||||
assert recipe_abv(sample_recipes["sweetstout"]) == "0"
|
||||
|
||||
|
||||
def test_recipe_srm(sample_recipes):
|
||||
assert recipe_srm(sample_recipes['lager']) == '3'
|
||||
assert recipe_srm(sample_recipes['sweetstout']) == '21'
|
||||
assert recipe_srm(sample_recipes["lager"]) == "3"
|
||||
assert recipe_srm(sample_recipes["sweetstout"]) == "21"
|
||||
# Remove fermentables, verify 0 is returned
|
||||
sample_recipes['lager'].pop('fermentables')
|
||||
assert recipe_srm(sample_recipes['lager']) == '0'
|
||||
sample_recipes["lager"].pop("fermentables")
|
||||
assert recipe_srm(sample_recipes["lager"]) == "0"
|
||||
|
||||
|
||||
def test_sort_hops():
|
||||
# Test with no form
|
||||
hops = [
|
||||
{'name': '4', 'use': 'Dry-Hop', 'duration': '5'},
|
||||
{'name': '3', 'use': 'Whirlpool', 'duration': '10'},
|
||||
{'name': '2', 'use': 'Boil', 'duration': '5'},
|
||||
{'name': '1', 'use': 'Boil', 'duration': '15'},
|
||||
{'name': '0', 'use': 'FWH', 'duration': '60'},
|
||||
{"name": "4", "use": "Dry-Hop", "duration": "5"},
|
||||
{"name": "3", "use": "Whirlpool", "duration": "10"},
|
||||
{"name": "2", "use": "Boil", "duration": "5"},
|
||||
{"name": "1", "use": "Boil", "duration": "15"},
|
||||
{"name": "0", "use": "FWH", "duration": "60"},
|
||||
]
|
||||
assert sort_hops(hops) == [
|
||||
{'name': '0', 'use': 'FWH', 'duration': '60'},
|
||||
{'name': '1', 'use': 'Boil', 'duration': '15'},
|
||||
{'name': '2', 'use': 'Boil', 'duration': '5'},
|
||||
{'name': '3', 'use': 'Whirlpool', 'duration': '10'},
|
||||
{'name': '4', 'use': 'Dry-Hop', 'duration': '5'},
|
||||
{"name": "0", "use": "FWH", "duration": "60"},
|
||||
{"name": "1", "use": "Boil", "duration": "15"},
|
||||
{"name": "2", "use": "Boil", "duration": "5"},
|
||||
{"name": "3", "use": "Whirlpool", "duration": "10"},
|
||||
{"name": "4", "use": "Dry-Hop", "duration": "5"},
|
||||
]
|
||||
|
||||
# Test with form
|
||||
hop_forms = []
|
||||
for hop in hops:
|
||||
form = HopForm()
|
||||
form.name.data = hop['name']
|
||||
form.use.data = hop['use']
|
||||
form.duration.data = Decimal(hop['duration'])
|
||||
form.name.data = hop["name"]
|
||||
form.use.data = hop["use"]
|
||||
form.duration.data = Decimal(hop["duration"])
|
||||
hop_forms.append(form)
|
||||
|
||||
for num, hop in enumerate(sort_hops(hop_forms, form=True)):
|
||||
|
|
@ -107,9 +115,9 @@ def test_sort_hops():
|
|||
|
||||
|
||||
def test_ferm_pct():
|
||||
ferms = [{'amount': '4'}, {'amount': '2'}, {'amount': '2'}]
|
||||
ferms = [{"amount": "4"}, {"amount": "2"}, {"amount": "2"}]
|
||||
assert ferm_pct(ferms) == [
|
||||
{'amount': '4', 'pct': 50.0},
|
||||
{'amount': '2', 'pct': 25.0},
|
||||
{'amount': '2', 'pct': 25.0}
|
||||
{"amount": "4", "pct": 50.0},
|
||||
{"amount": "2", "pct": 25.0},
|
||||
{"amount": "2", "pct": 25.0},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
|
||||
def test_home(client):
|
||||
response = client.get('/')
|
||||
response = client.get("/")
|
||||
assert response.status_code == 302
|
||||
|
||||
|
||||
|
|
@ -27,16 +27,16 @@ def test_status(client, monkeypatch):
|
|||
def exists(self):
|
||||
return False
|
||||
|
||||
response = client.get('/status')
|
||||
response = client.get("/status")
|
||||
assert response.status_code == 200
|
||||
assert response.get_json() == {'ping': 'ok'}
|
||||
assert response.get_json() == {"ping": "ok"}
|
||||
|
||||
monkeypatch.setattr('humulus.home.get_db', MockDBTrue)
|
||||
response = client.get('/status?couch=y')
|
||||
monkeypatch.setattr("humulus.home.get_db", MockDBTrue)
|
||||
response = client.get("/status?couch=y")
|
||||
assert response.status_code == 200
|
||||
assert response.get_json() == {'ping': 'ok', 'couch': 'ok'}
|
||||
assert response.get_json() == {"ping": "ok", "couch": "ok"}
|
||||
|
||||
monkeypatch.setattr('humulus.home.get_db', MockDBFalse)
|
||||
response = client.get('/status?couch=y')
|
||||
monkeypatch.setattr("humulus.home.get_db", MockDBFalse)
|
||||
response = client.get("/status?couch=y")
|
||||
assert response.status_code == 500
|
||||
assert response.get_json() == {'ping': 'ok', 'couch': 'not_exist'}
|
||||
assert response.get_json() == {"ping": "ok", "couch": "not_exist"}
|
||||
|
|
|
|||
|
|
@ -17,293 +17,293 @@ from decimal import Decimal
|
|||
from io import BytesIO
|
||||
|
||||
from humulus.couch import get_doc
|
||||
from humulus.recipes import (FermentableForm, HopForm, RecipeForm, YeastForm,
|
||||
MashForm, MashStepForm)
|
||||
from humulus.recipes import (
|
||||
FermentableForm,
|
||||
HopForm,
|
||||
RecipeForm,
|
||||
YeastForm,
|
||||
MashForm,
|
||||
MashStepForm,
|
||||
)
|
||||
|
||||
|
||||
def test_index(client):
|
||||
"""Test success in retrieving index."""
|
||||
# Test for bad request
|
||||
response = client.get('/recipes/?sort_by=foobar')
|
||||
response = client.get("/recipes/?sort_by=foobar")
|
||||
assert response.status_code == 400
|
||||
|
||||
# Verify defaults
|
||||
response = client.get('/recipes/')
|
||||
response = client.get("/recipes/")
|
||||
assert response.status_code == 200
|
||||
# Assert recipes are returned
|
||||
assert b'Awesome Lager' in response.data
|
||||
assert b'Awesome Beer' in response.data
|
||||
assert b"Awesome Lager" in response.data
|
||||
assert b"Awesome Beer" in response.data
|
||||
assert (
|
||||
b'"/recipes/?descending=true&sort_by=name">Name ↑' in
|
||||
response.data
|
||||
b'"/recipes/?descending=true&sort_by=name">Name ↑'
|
||||
in response.data
|
||||
)
|
||||
assert (
|
||||
b'"/recipes/?descending=false&sort_by=date">Created On' in
|
||||
response.data
|
||||
b'"/recipes/?descending=false&sort_by=date">Created On'
|
||||
in response.data
|
||||
)
|
||||
|
||||
# Test sort by name descending
|
||||
response = client.get('/recipes/?descending=true&sort_by=name')
|
||||
response = client.get("/recipes/?descending=true&sort_by=name")
|
||||
assert (
|
||||
b'"/recipes/?descending=false&sort_by=name">Name ↓' in
|
||||
response.data
|
||||
b'"/recipes/?descending=false&sort_by=name">Name ↓'
|
||||
in response.data
|
||||
)
|
||||
assert (
|
||||
b'"/recipes/?descending=false&sort_by=date">Created On' in
|
||||
response.data
|
||||
b'"/recipes/?descending=false&sort_by=date">Created On'
|
||||
in response.data
|
||||
)
|
||||
|
||||
# Test sort by date ascending
|
||||
response = client.get('/recipes/?descending=false&sort_by=date')
|
||||
response = client.get("/recipes/?descending=false&sort_by=date")
|
||||
assert (
|
||||
b'"/recipes/?descending=false&sort_by=name">Name' in
|
||||
response.data
|
||||
b'"/recipes/?descending=false&sort_by=name">Name' in response.data
|
||||
)
|
||||
assert (
|
||||
b'"/recipes/?descending=true&sort_by=date">Created On ↑' in
|
||||
response.data
|
||||
b'"/recipes/?descending=true&sort_by=date">Created On ↑'
|
||||
in response.data
|
||||
)
|
||||
|
||||
# Test sort by date descending
|
||||
response = client.get('/recipes/?descending=true&sort_by=date')
|
||||
response = client.get("/recipes/?descending=true&sort_by=date")
|
||||
assert (
|
||||
b'"/recipes/?descending=false&sort_by=name">Name' in
|
||||
response.data
|
||||
b'"/recipes/?descending=false&sort_by=name">Name' in response.data
|
||||
)
|
||||
assert (
|
||||
b'"/recipes/?descending=false&sort_by=date">Created On ↓' in
|
||||
response.data
|
||||
b'"/recipes/?descending=false&sort_by=date">Created On ↓'
|
||||
in response.data
|
||||
)
|
||||
|
||||
# Test sort by volume ascending
|
||||
response = client.get('/recipes/?descending=false&sort_by=volume')
|
||||
response = client.get("/recipes/?descending=false&sort_by=volume")
|
||||
assert (
|
||||
b'"/recipes/?descending=false&sort_by=name">Name' in
|
||||
response.data
|
||||
b'"/recipes/?descending=false&sort_by=name">Name' in response.data
|
||||
)
|
||||
assert (
|
||||
b'"/recipes/?descending=true&sort_by=volume">Batch Size ↑' in
|
||||
response.data
|
||||
b'"/recipes/?descending=true&sort_by=volume">Batch Size ↑'
|
||||
in response.data
|
||||
)
|
||||
|
||||
# Test sort by volume descending
|
||||
response = client.get('/recipes/?descending=true&sort_by=volume')
|
||||
response = client.get("/recipes/?descending=true&sort_by=volume")
|
||||
assert (
|
||||
b'"/recipes/?descending=false&sort_by=name">Name' in
|
||||
response.data
|
||||
b'"/recipes/?descending=false&sort_by=name">Name' in response.data
|
||||
)
|
||||
assert (
|
||||
b'"/recipes/?descending=false&sort_by=volume">Batch Size ↓' in
|
||||
response.data
|
||||
b'"/recipes/?descending=false&sort_by=volume">Batch Size ↓'
|
||||
in response.data
|
||||
)
|
||||
|
||||
# Test sort by type ascending
|
||||
response = client.get('/recipes/?descending=false&sort_by=type')
|
||||
response = client.get("/recipes/?descending=false&sort_by=type")
|
||||
assert (
|
||||
b'"/recipes/?descending=false&sort_by=name">Name' in
|
||||
response.data
|
||||
b'"/recipes/?descending=false&sort_by=name">Name' in response.data
|
||||
)
|
||||
assert (
|
||||
b'"/recipes/?descending=true&sort_by=type">Type ↑' in
|
||||
response.data
|
||||
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')
|
||||
response = client.get("/recipes/?descending=true&sort_by=type")
|
||||
assert (
|
||||
b'"/recipes/?descending=false&sort_by=name">Name' in
|
||||
response.data
|
||||
b'"/recipes/?descending=false&sort_by=name">Name' in response.data
|
||||
)
|
||||
assert (
|
||||
b'"/recipes/?descending=false&sort_by=type">Type ↓' in
|
||||
response.data
|
||||
b'"/recipes/?descending=false&sort_by=type">Type ↓'
|
||||
in response.data
|
||||
)
|
||||
|
||||
|
||||
def test_create(client, app, auth):
|
||||
"""Test success in creating a recipe document."""
|
||||
# Test GET without login
|
||||
response = client.get('/recipes/create')
|
||||
response = client.get("/recipes/create")
|
||||
assert response.status_code == 302
|
||||
|
||||
# Test GET with login
|
||||
auth.login()
|
||||
response = client.get('/recipes/create')
|
||||
response = client.get("/recipes/create")
|
||||
assert response.status_code == 200
|
||||
|
||||
# Test POST
|
||||
data = {
|
||||
'efficiency': '65',
|
||||
'name': 'Test',
|
||||
'notes': 'Test',
|
||||
'volume': '5.5',
|
||||
'style': '1A'
|
||||
"efficiency": "65",
|
||||
"name": "Test",
|
||||
"notes": "Test",
|
||||
"volume": "5.5",
|
||||
"style": "1A",
|
||||
}
|
||||
response = client.post('/recipes/create', data=data)
|
||||
response = client.post("/recipes/create", data=data)
|
||||
assert response.status_code == 302
|
||||
|
||||
with app.app_context():
|
||||
doc = get_doc('test')
|
||||
doc = get_doc("test")
|
||||
|
||||
assert doc['name'] == 'Test'
|
||||
assert doc['notes'] == 'Test'
|
||||
assert doc['volume'] == '5.5'
|
||||
assert doc['efficiency'] == '65'
|
||||
assert doc['style'] == '1A'
|
||||
assert doc["name"] == "Test"
|
||||
assert doc["notes"] == "Test"
|
||||
assert doc["volume"] == "5.5"
|
||||
assert doc["efficiency"] == "65"
|
||||
assert doc["style"] == "1A"
|
||||
|
||||
|
||||
def test_update(client, app, auth):
|
||||
"""Test success in updating a recipe document."""
|
||||
# Test GET without login
|
||||
response = client.get('/recipes/update/awesome-lager')
|
||||
response = client.get("/recipes/update/awesome-lager")
|
||||
assert response.status_code == 302
|
||||
|
||||
auth.login()
|
||||
# Test GET on a bare minimum recipe
|
||||
response = client.get('/recipes/update/awesome-lager')
|
||||
response = client.get("/recipes/update/awesome-lager")
|
||||
assert response.status_code == 200
|
||||
assert b'Awesome Lager' in response.data
|
||||
assert b"Awesome Lager" in response.data
|
||||
|
||||
# Test GET on a more complete recipe
|
||||
response = client.get('/recipes/update/full-recipe')
|
||||
response = client.get("/recipes/update/full-recipe")
|
||||
assert response.status_code == 200
|
||||
test_items = [
|
||||
b'Awesome Beer',
|
||||
b'2row',
|
||||
b'Dextrose',
|
||||
b'Nugget (US)',
|
||||
b'CTZ (US)',
|
||||
b'Northern California Ale'
|
||||
b"Awesome Beer",
|
||||
b"2row",
|
||||
b"Dextrose",
|
||||
b"Nugget (US)",
|
||||
b"CTZ (US)",
|
||||
b"Northern California Ale",
|
||||
]
|
||||
for item in test_items:
|
||||
assert item in response.data
|
||||
|
||||
# Test GET on a recipe missing most yeast fields
|
||||
response = client.get('/recipes/update/partial-yeast-recipe')
|
||||
response = client.get("/recipes/update/partial-yeast-recipe")
|
||||
assert response.status_code == 200
|
||||
test_items = [
|
||||
b'Partial Beer',
|
||||
b'US-05'
|
||||
]
|
||||
test_items = [b"Partial Beer", b"US-05"]
|
||||
for item in test_items:
|
||||
assert item in response.data
|
||||
|
||||
# Get a doc, make an update, and test a POST
|
||||
id = 'awesome-lager'
|
||||
id = "awesome-lager"
|
||||
with app.app_context():
|
||||
doc = get_doc(id)
|
||||
# Remove unneeded fields
|
||||
doc.pop('_id')
|
||||
rev = doc.pop('_rev')
|
||||
response = client.post('/recipes/update/awesome-lager',
|
||||
query_string={'rev': rev}, data=doc)
|
||||
doc.pop("_id")
|
||||
rev = doc.pop("_rev")
|
||||
response = client.post(
|
||||
"/recipes/update/awesome-lager", query_string={"rev": rev}, data=doc
|
||||
)
|
||||
assert response.status_code == 302
|
||||
|
||||
# Test response without valid/conflicted rev
|
||||
response = client.post('/recipes/update/awesome-lager',
|
||||
query_string={'rev': ''}, data=doc)
|
||||
response = client.post(
|
||||
"/recipes/update/awesome-lager", query_string={"rev": ""}, data=doc
|
||||
)
|
||||
assert response.status_code == 302
|
||||
with client.session_transaction() as session:
|
||||
flash_message = dict(session['_flashes']).pop('danger', None)
|
||||
assert 'Update conflict' in flash_message
|
||||
flash_message = dict(session["_flashes"]).pop("danger", None)
|
||||
assert "Update conflict" in flash_message
|
||||
|
||||
|
||||
def test_info(client, monkeypatch):
|
||||
"""Test success in retrieving a recipe document."""
|
||||
|
||||
def mock_get_doc(id):
|
||||
# This function always raises KeyError
|
||||
raise KeyError(id)
|
||||
|
||||
# Validate 404
|
||||
response = client.get('/recipes/info/thisdoesnotexist')
|
||||
response = client.get("/recipes/info/thisdoesnotexist")
|
||||
assert response.status_code == 404
|
||||
|
||||
# Validate response for existing doc
|
||||
response = client.get('/recipes/info/awesome-lager')
|
||||
response = client.get("/recipes/info/awesome-lager")
|
||||
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')
|
||||
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
|
||||
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
|
||||
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):
|
||||
"""Test success in retrieving a JSON recipe."""
|
||||
# Validate 404
|
||||
response = client.get('/recipes/info/thisdoesnotexist/json')
|
||||
response = client.get("/recipes/info/thisdoesnotexist/json")
|
||||
assert response.status_code == 404
|
||||
|
||||
# Validate response for existing doc
|
||||
response = client.get('/recipes/info/awesome-lager/json')
|
||||
response = client.get("/recipes/info/awesome-lager/json")
|
||||
assert response.status_code == 200
|
||||
assert response.is_json
|
||||
assert response.get_json()['name'] == 'Awesome Lager'
|
||||
assert response.get_json()["name"] == "Awesome Lager"
|
||||
|
||||
|
||||
def test_step_form_doc(app):
|
||||
"""Evaluates conditionals in generation of doc from a step form."""
|
||||
step = MashStepForm()
|
||||
step.name.data = 'Test Mash Step'
|
||||
step.type.data = 'Infusion'
|
||||
step.temp.data = Decimal('152')
|
||||
step.time.data = Decimal('60')
|
||||
step.name.data = "Test Mash Step"
|
||||
step.type.data = "Infusion"
|
||||
step.temp.data = Decimal("152")
|
||||
step.time.data = Decimal("60")
|
||||
assert step.doc == {
|
||||
'name': 'Test Mash Step',
|
||||
'type': 'Infusion',
|
||||
'temp': '152',
|
||||
'time': '60'
|
||||
"name": "Test Mash Step",
|
||||
"type": "Infusion",
|
||||
"temp": "152",
|
||||
"time": "60",
|
||||
}
|
||||
|
||||
step.amount.data = Decimal('3.5')
|
||||
step.amount.data = Decimal("3.5")
|
||||
assert step.doc == {
|
||||
'name': 'Test Mash Step',
|
||||
'type': 'Infusion',
|
||||
'temp': '152',
|
||||
'time': '60',
|
||||
'amount': '3.5'
|
||||
"name": "Test Mash Step",
|
||||
"type": "Infusion",
|
||||
"temp": "152",
|
||||
"time": "60",
|
||||
"amount": "3.5",
|
||||
}
|
||||
|
||||
|
||||
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')
|
||||
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'
|
||||
"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')
|
||||
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'
|
||||
"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",
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -316,52 +316,52 @@ 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'
|
||||
recipe.type.data = 'All-Grain'
|
||||
recipe.style.data = '1A'
|
||||
recipe.name.data = "Test"
|
||||
recipe.efficiency.data = Decimal("65")
|
||||
recipe.volume.data = Decimal("5.5")
|
||||
recipe.notes.data = "This is a test"
|
||||
recipe.type.data = "All-Grain"
|
||||
recipe.style.data = "1A"
|
||||
|
||||
assert recipe.doc == {
|
||||
'name': 'Test',
|
||||
'efficiency': '65',
|
||||
'type': 'All-Grain',
|
||||
'volume': '5.5',
|
||||
'notes': 'This is a test',
|
||||
'fermentables': [],
|
||||
'hops': [],
|
||||
'$type': 'recipe',
|
||||
'style': '1A'
|
||||
"name": "Test",
|
||||
"efficiency": "65",
|
||||
"type": "All-Grain",
|
||||
"volume": "5.5",
|
||||
"notes": "This is a test",
|
||||
"fermentables": [],
|
||||
"hops": [],
|
||||
"$type": "recipe",
|
||||
"style": "1A",
|
||||
}
|
||||
|
||||
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')
|
||||
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")
|
||||
|
||||
hop = HopForm()
|
||||
hop.name.data = 'Test'
|
||||
hop.use.data = 'Boil'
|
||||
hop.alpha.data = Decimal('12.5')
|
||||
hop.duration.data = Decimal('60')
|
||||
hop.amount.data = Decimal('0.5')
|
||||
hop.name.data = "Test"
|
||||
hop.use.data = "Boil"
|
||||
hop.alpha.data = Decimal("12.5")
|
||||
hop.duration.data = Decimal("60")
|
||||
hop.amount.data = Decimal("0.5")
|
||||
|
||||
yeast = YeastForm()
|
||||
yeast.name.data = 'Test'
|
||||
yeast.low_attenuation.data = '70'
|
||||
yeast.high_attenuation.data = '75'
|
||||
yeast.name.data = "Test"
|
||||
yeast.low_attenuation.data = "70"
|
||||
yeast.high_attenuation.data = "75"
|
||||
|
||||
step = MashStepForm()
|
||||
step.name.data = 'Test Mash Step'
|
||||
step.type.data = 'Infusion'
|
||||
step.temp.data = Decimal('152')
|
||||
step.time.data = Decimal('60')
|
||||
step.amount.data = Decimal('3.5')
|
||||
step.name.data = "Test Mash Step"
|
||||
step.type.data = "Infusion"
|
||||
step.temp.data = Decimal("152")
|
||||
step.time.data = Decimal("60")
|
||||
step.amount.data = Decimal("3.5")
|
||||
mash = MashForm()
|
||||
mash.name.data = 'Single Infusion'
|
||||
mash.name.data = "Single Infusion"
|
||||
mash.steps = [step]
|
||||
|
||||
recipe.fermentables = [ferm]
|
||||
|
|
@ -370,202 +370,249 @@ def test_recipe_form_doc(app):
|
|||
recipe.yeast = yeast
|
||||
|
||||
assert recipe.doc == {
|
||||
'name': 'Test',
|
||||
'efficiency': '65',
|
||||
'type': 'All-Grain',
|
||||
'volume': '5.5',
|
||||
'notes': 'This is a test',
|
||||
'$type': 'recipe',
|
||||
'style': '1A',
|
||||
'fermentables': [{
|
||||
'name': 'Test',
|
||||
'type': 'Grain',
|
||||
'amount': '5.5',
|
||||
'ppg': '37',
|
||||
'color': '1.8',
|
||||
}],
|
||||
'hops': [{
|
||||
'name': 'Test',
|
||||
'use': 'Boil',
|
||||
'alpha': '12.5',
|
||||
'duration': '60',
|
||||
'amount': '0.5'
|
||||
}],
|
||||
'yeast': {
|
||||
'name': 'Test',
|
||||
'low_attenuation': '70',
|
||||
'high_attenuation': '75'
|
||||
},
|
||||
'mash': {
|
||||
'name': 'Single Infusion',
|
||||
'steps': [{
|
||||
'name': 'Test Mash Step',
|
||||
'type': 'Infusion',
|
||||
'temp': '152',
|
||||
'time': '60',
|
||||
'amount': '3.5'
|
||||
}]
|
||||
"name": "Test",
|
||||
"efficiency": "65",
|
||||
"type": "All-Grain",
|
||||
"volume": "5.5",
|
||||
"notes": "This is a test",
|
||||
"$type": "recipe",
|
||||
"style": "1A",
|
||||
"fermentables": [
|
||||
{
|
||||
"name": "Test",
|
||||
"type": "Grain",
|
||||
"amount": "5.5",
|
||||
"ppg": "37",
|
||||
"color": "1.8",
|
||||
}
|
||||
],
|
||||
"hops": [
|
||||
{
|
||||
"name": "Test",
|
||||
"use": "Boil",
|
||||
"alpha": "12.5",
|
||||
"duration": "60",
|
||||
"amount": "0.5",
|
||||
}
|
||||
],
|
||||
"yeast": {
|
||||
"name": "Test",
|
||||
"low_attenuation": "70",
|
||||
"high_attenuation": "75",
|
||||
},
|
||||
"mash": {
|
||||
"name": "Single Infusion",
|
||||
"steps": [
|
||||
{
|
||||
"name": "Test Mash Step",
|
||||
"type": "Infusion",
|
||||
"temp": "152",
|
||||
"time": "60",
|
||||
"amount": "3.5",
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_recipe_delete(client, auth):
|
||||
"""Test success in deleting a document."""
|
||||
# Try to delete a document without logging in
|
||||
response = client.post('/recipes/delete/awesome-lager')
|
||||
response = client.get('/recipes/info/awesome-lager')
|
||||
response = client.post("/recipes/delete/awesome-lager")
|
||||
response = client.get("/recipes/info/awesome-lager")
|
||||
assert response.status_code == 200
|
||||
|
||||
# Delete document after login
|
||||
auth.login()
|
||||
# Try to delete a document without logging in
|
||||
response = client.post('/recipes/delete/awesome-lager')
|
||||
response = client.get('/recipes/info/awesome-lager')
|
||||
response = client.post("/recipes/delete/awesome-lager")
|
||||
response = client.get("/recipes/info/awesome-lager")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_recipe_create_json(client, sample_recipes, auth):
|
||||
"""Test uploading JSON recipe."""
|
||||
# Test GET without logging in
|
||||
response = client.get('/recipes/create/json')
|
||||
response = client.get("/recipes/create/json")
|
||||
assert response.status_code == 302
|
||||
|
||||
# Test GET after logging in
|
||||
auth.login()
|
||||
response = client.get('/recipes/create/json')
|
||||
response = client.get("/recipes/create/json")
|
||||
assert response.status_code == 200
|
||||
|
||||
# Test upload some good data
|
||||
data = {
|
||||
'upload': (BytesIO(json.dumps(sample_recipes['sweetstout']).encode()),
|
||||
'sweetstout.json')
|
||||
"upload": (
|
||||
BytesIO(json.dumps(sample_recipes["sweetstout"]).encode()),
|
||||
"sweetstout.json",
|
||||
)
|
||||
}
|
||||
response = client.post('/recipes/create/json', buffered=True,
|
||||
content_type='multipart/form-data', data=data)
|
||||
response = client.post(
|
||||
"/recipes/create/json",
|
||||
buffered=True,
|
||||
content_type="multipart/form-data",
|
||||
data=data,
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert 'recipes/info/sweet-stout' in response.headers['Location']
|
||||
assert "recipes/info/sweet-stout" in response.headers["Location"]
|
||||
|
||||
# Test upload with some bad data
|
||||
data = {'upload': (BytesIO(b'NOT JSON'), 'file')}
|
||||
response = client.post('/recipes/create/json', buffered=True,
|
||||
content_type='multipart/form-data', data=data)
|
||||
data = {"upload": (BytesIO(b"NOT JSON"), "file")}
|
||||
response = client.post(
|
||||
"/recipes/create/json",
|
||||
buffered=True,
|
||||
content_type="multipart/form-data",
|
||||
data=data,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_copyfrom(app, sample_recipes):
|
||||
recipe = {
|
||||
'name': 'Test',
|
||||
'type': 'All-Grain',
|
||||
'efficiency': '65',
|
||||
'volume': '5.5',
|
||||
'notes': 'Notes',
|
||||
'style': '18A',
|
||||
'fermentables': [{
|
||||
'name': 'Test',
|
||||
'type': 'Grain',
|
||||
'amount': '1',
|
||||
'ppg': '36',
|
||||
'color': '4'
|
||||
}],
|
||||
'hops': [{
|
||||
'name': 'Test',
|
||||
'use': 'Boil',
|
||||
'alpha': '5.5',
|
||||
'duration': '30',
|
||||
'amount': '1'
|
||||
}]
|
||||
"name": "Test",
|
||||
"type": "All-Grain",
|
||||
"efficiency": "65",
|
||||
"volume": "5.5",
|
||||
"notes": "Notes",
|
||||
"style": "18A",
|
||||
"fermentables": [
|
||||
{
|
||||
"name": "Test",
|
||||
"type": "Grain",
|
||||
"amount": "1",
|
||||
"ppg": "36",
|
||||
"color": "4",
|
||||
}
|
||||
],
|
||||
"hops": [
|
||||
{
|
||||
"name": "Test",
|
||||
"use": "Boil",
|
||||
"alpha": "5.5",
|
||||
"duration": "30",
|
||||
"amount": "1",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
with app.app_context():
|
||||
form = RecipeForm()
|
||||
form.copyfrom(recipe)
|
||||
assert form.name.data == recipe['name']
|
||||
assert form.type.data == recipe['type']
|
||||
assert form.efficiency.data == Decimal(recipe['efficiency'])
|
||||
assert form.volume.data == Decimal(recipe['volume'])
|
||||
assert form.notes.data == recipe['notes']
|
||||
assert len(form.fermentables) == len(recipe['fermentables'])
|
||||
assert form.fermentables[0].form.name.data == \
|
||||
recipe['fermentables'][0]['name']
|
||||
assert form.fermentables[0].form.type.data == \
|
||||
recipe['fermentables'][0]['type']
|
||||
assert form.fermentables[0].form.amount.data == \
|
||||
Decimal(recipe['fermentables'][0]['amount'])
|
||||
assert form.fermentables[0].form.ppg.data == \
|
||||
Decimal(recipe['fermentables'][0]['ppg'])
|
||||
assert form.fermentables[0].form.color.data == \
|
||||
Decimal(recipe['fermentables'][0]['color'])
|
||||
assert len(form.hops) == len(recipe['hops'])
|
||||
assert form.hops[0].form.name.data == recipe['hops'][0]['name']
|
||||
assert form.hops[0].form.use.data == recipe['hops'][0]['use']
|
||||
assert form.hops[0].form.alpha.data == Decimal(recipe['hops'][0]['alpha'])
|
||||
assert form.hops[0].form.duration.data == \
|
||||
Decimal(recipe['hops'][0]['duration'])
|
||||
assert form.hops[0].form.amount.data == \
|
||||
Decimal(recipe['hops'][0]['amount'])
|
||||
assert form.name.data == recipe["name"]
|
||||
assert form.type.data == recipe["type"]
|
||||
assert form.efficiency.data == Decimal(recipe["efficiency"])
|
||||
assert form.volume.data == Decimal(recipe["volume"])
|
||||
assert form.notes.data == recipe["notes"]
|
||||
assert len(form.fermentables) == len(recipe["fermentables"])
|
||||
assert (
|
||||
form.fermentables[0].form.name.data
|
||||
== recipe["fermentables"][0]["name"]
|
||||
)
|
||||
assert (
|
||||
form.fermentables[0].form.type.data
|
||||
== recipe["fermentables"][0]["type"]
|
||||
)
|
||||
assert form.fermentables[0].form.amount.data == Decimal(
|
||||
recipe["fermentables"][0]["amount"]
|
||||
)
|
||||
assert form.fermentables[0].form.ppg.data == Decimal(
|
||||
recipe["fermentables"][0]["ppg"]
|
||||
)
|
||||
assert form.fermentables[0].form.color.data == Decimal(
|
||||
recipe["fermentables"][0]["color"]
|
||||
)
|
||||
assert len(form.hops) == len(recipe["hops"])
|
||||
assert form.hops[0].form.name.data == recipe["hops"][0]["name"]
|
||||
assert form.hops[0].form.use.data == recipe["hops"][0]["use"]
|
||||
assert form.hops[0].form.alpha.data == Decimal(recipe["hops"][0]["alpha"])
|
||||
assert form.hops[0].form.duration.data == Decimal(
|
||||
recipe["hops"][0]["duration"]
|
||||
)
|
||||
assert form.hops[0].form.amount.data == Decimal(
|
||||
recipe["hops"][0]["amount"]
|
||||
)
|
||||
|
||||
recipe['yeast'] = {
|
||||
'name': 'Test', 'low_attenuation': '65', 'high_attenuation': '68'
|
||||
recipe["yeast"] = {
|
||||
"name": "Test",
|
||||
"low_attenuation": "65",
|
||||
"high_attenuation": "68",
|
||||
}
|
||||
recipe['mash'] = {}
|
||||
recipe["mash"] = {}
|
||||
with app.app_context():
|
||||
form = RecipeForm()
|
||||
form.copyfrom(recipe)
|
||||
assert form.yeast.form.name.data == recipe['yeast']['name']
|
||||
assert form.yeast.form.low_attenuation.data == \
|
||||
Decimal(recipe['yeast']['low_attenuation'])
|
||||
assert form.yeast.form.high_attenuation.data == \
|
||||
Decimal(recipe['yeast']['high_attenuation'])
|
||||
assert form.yeast.form.name.data == recipe["yeast"]["name"]
|
||||
assert form.yeast.form.low_attenuation.data == Decimal(
|
||||
recipe["yeast"]["low_attenuation"]
|
||||
)
|
||||
assert form.yeast.form.high_attenuation.data == Decimal(
|
||||
recipe["yeast"]["high_attenuation"]
|
||||
)
|
||||
|
||||
recipe['yeast'].update({
|
||||
'type': 'Liquid',
|
||||
'lab': 'Test',
|
||||
'code': 'Test',
|
||||
'flocculation': 'Low',
|
||||
'min_temperature': '65',
|
||||
'max_temperature': '68',
|
||||
'abv_tolerance': '15'
|
||||
})
|
||||
recipe["yeast"].update(
|
||||
{
|
||||
"type": "Liquid",
|
||||
"lab": "Test",
|
||||
"code": "Test",
|
||||
"flocculation": "Low",
|
||||
"min_temperature": "65",
|
||||
"max_temperature": "68",
|
||||
"abv_tolerance": "15",
|
||||
}
|
||||
)
|
||||
with app.app_context():
|
||||
form = RecipeForm()
|
||||
form.copyfrom(recipe)
|
||||
assert form.yeast.form.type.data == recipe['yeast']['type']
|
||||
assert form.yeast.form.lab.data == recipe['yeast']['lab']
|
||||
assert form.yeast.form.code.data == recipe['yeast']['code']
|
||||
assert form.yeast.form.flocculation.data == recipe['yeast']['flocculation']
|
||||
assert form.yeast.form.min_temperature.data == \
|
||||
Decimal(recipe['yeast']['min_temperature'])
|
||||
assert form.yeast.form.max_temperature.data == \
|
||||
Decimal(recipe['yeast']['max_temperature'])
|
||||
assert form.yeast.form.abv_tolerance.data == \
|
||||
Decimal(recipe['yeast']['abv_tolerance'])
|
||||
assert form.yeast.form.type.data == recipe["yeast"]["type"]
|
||||
assert form.yeast.form.lab.data == recipe["yeast"]["lab"]
|
||||
assert form.yeast.form.code.data == recipe["yeast"]["code"]
|
||||
assert form.yeast.form.flocculation.data == recipe["yeast"]["flocculation"]
|
||||
assert form.yeast.form.min_temperature.data == Decimal(
|
||||
recipe["yeast"]["min_temperature"]
|
||||
)
|
||||
assert form.yeast.form.max_temperature.data == Decimal(
|
||||
recipe["yeast"]["max_temperature"]
|
||||
)
|
||||
assert form.yeast.form.abv_tolerance.data == Decimal(
|
||||
recipe["yeast"]["abv_tolerance"]
|
||||
)
|
||||
|
||||
recipe['mash'] = {
|
||||
'name': 'Test',
|
||||
'steps': [{
|
||||
'name': 'Infusion',
|
||||
'type': 'Infusion',
|
||||
'temp': '152',
|
||||
'time': '60'
|
||||
}]
|
||||
recipe["mash"] = {
|
||||
"name": "Test",
|
||||
"steps": [
|
||||
{
|
||||
"name": "Infusion",
|
||||
"type": "Infusion",
|
||||
"temp": "152",
|
||||
"time": "60",
|
||||
}
|
||||
],
|
||||
}
|
||||
with app.app_context():
|
||||
form = RecipeForm()
|
||||
form.copyfrom(recipe)
|
||||
assert form.mash.form.name.data == recipe['mash']['name']
|
||||
assert len(form.mash.form.steps) == len(recipe['mash']['steps'])
|
||||
assert form.mash.form.steps[0].form.name.data == \
|
||||
recipe['mash']['steps'][0]['name']
|
||||
assert form.mash.form.steps[0].form.type.data == \
|
||||
recipe['mash']['steps'][0]['type']
|
||||
assert form.mash.form.steps[0].form.temp.data == \
|
||||
Decimal(recipe['mash']['steps'][0]['temp'])
|
||||
assert form.mash.form.steps[0].form.time.data == \
|
||||
Decimal(recipe['mash']['steps'][0]['time'])
|
||||
assert form.mash.form.name.data == recipe["mash"]["name"]
|
||||
assert len(form.mash.form.steps) == len(recipe["mash"]["steps"])
|
||||
assert (
|
||||
form.mash.form.steps[0].form.name.data
|
||||
== recipe["mash"]["steps"][0]["name"]
|
||||
)
|
||||
assert (
|
||||
form.mash.form.steps[0].form.type.data
|
||||
== recipe["mash"]["steps"][0]["type"]
|
||||
)
|
||||
assert form.mash.form.steps[0].form.temp.data == Decimal(
|
||||
recipe["mash"]["steps"][0]["temp"]
|
||||
)
|
||||
assert form.mash.form.steps[0].form.time.data == Decimal(
|
||||
recipe["mash"]["steps"][0]["time"]
|
||||
)
|
||||
|
||||
recipe['mash']['steps'][0]['amount'] = '3.5'
|
||||
recipe["mash"]["steps"][0]["amount"] = "3.5"
|
||||
with app.app_context():
|
||||
form = RecipeForm()
|
||||
form.copyfrom(recipe)
|
||||
assert form.mash.form.steps[0].form.amount.data == \
|
||||
Decimal(recipe['mash']['steps'][0]['amount'])
|
||||
assert form.mash.form.steps[0].form.amount.data == Decimal(
|
||||
recipe["mash"]["steps"][0]["amount"]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import xml.etree.ElementTree as ET
|
|||
|
||||
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>
|
||||
<aroma>Smelly</aroma>
|
||||
<appearance>Good looking</appearance>
|
||||
|
|
@ -52,9 +52,9 @@ COMPLETE_STYLE = '''<subcategory id="1A">
|
|||
</abv>
|
||||
</stats>
|
||||
</subcategory>
|
||||
'''
|
||||
"""
|
||||
|
||||
INCOMPLETE_STYLE = '''<subcategory id="2B">
|
||||
INCOMPLETE_STYLE = """<subcategory id="2B">
|
||||
<name>Test Style</name>
|
||||
<aroma>Smelly</aroma>
|
||||
<appearance>Good looking</appearance>
|
||||
|
|
@ -62,9 +62,9 @@ INCOMPLETE_STYLE = '''<subcategory id="2B">
|
|||
<mouthfeel>Good feeling</mouthfeel>
|
||||
<impression>Refreshing</impression>
|
||||
</subcategory>
|
||||
'''
|
||||
"""
|
||||
|
||||
TEST_XML = '''<?xml version="1.0" encoding="UTF-8"?>
|
||||
TEST_XML = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<styleguide>
|
||||
<class type="beer">
|
||||
<category id="1">
|
||||
|
|
@ -79,46 +79,46 @@ TEST_XML = '''<?xml version="1.0" encoding="UTF-8"?>
|
|||
</category>
|
||||
</class>
|
||||
</styleguide>
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
def test_sub_to_doc():
|
||||
assert sub_to_doc(ET.fromstring(COMPLETE_STYLE)) == {
|
||||
'_id': '1A',
|
||||
'$type': 'style',
|
||||
'name': 'Test Style',
|
||||
'aroma': 'Smelly',
|
||||
'appearance': 'Good looking',
|
||||
'flavor': 'Good tasting',
|
||||
'mouthfeel': 'Good feeling',
|
||||
'impression': 'Refreshing',
|
||||
'comments': 'Comments',
|
||||
'history': 'Old',
|
||||
'ingredients': 'Grains, Hops, and Water',
|
||||
'comparison': 'Comparison',
|
||||
'examples': 'Examples',
|
||||
'tags': ['one', 'two'],
|
||||
'ibu': {'low': '1', 'high': '2'},
|
||||
'og': {'low': '1.010', 'high': '1.020'},
|
||||
'fg': {'low': '1.000', 'high': '1.010'},
|
||||
'srm': {'low': '1', 'high': '2'},
|
||||
'abv': {'low': '1', 'high': '2'}
|
||||
"_id": "1A",
|
||||
"$type": "style",
|
||||
"name": "Test Style",
|
||||
"aroma": "Smelly",
|
||||
"appearance": "Good looking",
|
||||
"flavor": "Good tasting",
|
||||
"mouthfeel": "Good feeling",
|
||||
"impression": "Refreshing",
|
||||
"comments": "Comments",
|
||||
"history": "Old",
|
||||
"ingredients": "Grains, Hops, and Water",
|
||||
"comparison": "Comparison",
|
||||
"examples": "Examples",
|
||||
"tags": ["one", "two"],
|
||||
"ibu": {"low": "1", "high": "2"},
|
||||
"og": {"low": "1.010", "high": "1.020"},
|
||||
"fg": {"low": "1.000", "high": "1.010"},
|
||||
"srm": {"low": "1", "high": "2"},
|
||||
"abv": {"low": "1", "high": "2"},
|
||||
}
|
||||
|
||||
assert sub_to_doc(ET.fromstring(INCOMPLETE_STYLE)) == {
|
||||
'_id': '2B',
|
||||
'$type': 'style',
|
||||
'name': 'Test Style',
|
||||
'aroma': 'Smelly',
|
||||
'appearance': 'Good looking',
|
||||
'flavor': 'Good tasting',
|
||||
'mouthfeel': 'Good feeling',
|
||||
'impression': 'Refreshing',
|
||||
'ibu': {'low': '0', 'high': '100'},
|
||||
'og': {'low': '1.0', 'high': '1.2'},
|
||||
'fg': {'low': '1.0', 'high': '1.2'},
|
||||
'srm': {'low': '0', 'high': '100'},
|
||||
'abv': {'low': '0', 'high': '100'}
|
||||
"_id": "2B",
|
||||
"$type": "style",
|
||||
"name": "Test Style",
|
||||
"aroma": "Smelly",
|
||||
"appearance": "Good looking",
|
||||
"flavor": "Good tasting",
|
||||
"mouthfeel": "Good feeling",
|
||||
"impression": "Refreshing",
|
||||
"ibu": {"low": "0", "high": "100"},
|
||||
"og": {"low": "1.0", "high": "1.2"},
|
||||
"fg": {"low": "1.0", "high": "1.2"},
|
||||
"srm": {"low": "0", "high": "100"},
|
||||
"abv": {"low": "0", "high": "100"},
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -138,30 +138,31 @@ def test_import_styles(monkeypatch):
|
|||
def fake_requests_get(url):
|
||||
class TestXML:
|
||||
text = TEST_XML
|
||||
|
||||
return TestXML
|
||||
|
||||
monkeypatch.setattr('requests.get', fake_requests_get)
|
||||
monkeypatch.setattr('humulus.styles.get_db', fake_get_db)
|
||||
monkeypatch.setattr('humulus.styles.put_doc', fake_put_doc)
|
||||
monkeypatch.setattr("requests.get", fake_requests_get)
|
||||
monkeypatch.setattr("humulus.styles.get_db", fake_get_db)
|
||||
monkeypatch.setattr("humulus.styles.put_doc", fake_put_doc)
|
||||
|
||||
import_styles(None)
|
||||
assert PutRecorder.doc == {
|
||||
'$type': 'style',
|
||||
'_id': '1A',
|
||||
'abv': {'high': '100', 'low': '0'},
|
||||
'appearance': 'Good looking',
|
||||
'aroma': 'Smelly',
|
||||
'fg': {'high': '1.2', 'low': '1.0'},
|
||||
'flavor': 'Good tasting',
|
||||
'ibu': {'high': '100', 'low': '0'},
|
||||
'impression': 'Refreshing',
|
||||
'mouthfeel': 'Good feeling',
|
||||
'name': 'Test Style',
|
||||
'og': {'high': '1.2', 'low': '1.0'},
|
||||
'srm': {'high': '100', 'low': '0'}
|
||||
"$type": "style",
|
||||
"_id": "1A",
|
||||
"abv": {"high": "100", "low": "0"},
|
||||
"appearance": "Good looking",
|
||||
"aroma": "Smelly",
|
||||
"fg": {"high": "1.2", "low": "1.0"},
|
||||
"flavor": "Good tasting",
|
||||
"ibu": {"high": "100", "low": "0"},
|
||||
"impression": "Refreshing",
|
||||
"mouthfeel": "Good feeling",
|
||||
"name": "Test Style",
|
||||
"og": {"high": "1.2", "low": "1.0"},
|
||||
"srm": {"high": "100", "low": "0"},
|
||||
}
|
||||
|
||||
MockDB.db = {'1A': ''}
|
||||
MockDB.db = {"1A": ""}
|
||||
PutRecorder.doc = None
|
||||
import_styles(None)
|
||||
assert PutRecorder.doc is None
|
||||
|
|
@ -174,76 +175,73 @@ def test_import_command(runner, monkeypatch):
|
|||
def fake_import_styles(url):
|
||||
Recorder.called = True
|
||||
|
||||
monkeypatch.setattr('humulus.styles.import_styles', fake_import_styles)
|
||||
result = runner.invoke(args=['import-styles'])
|
||||
monkeypatch.setattr("humulus.styles.import_styles", fake_import_styles)
|
||||
result = runner.invoke(args=["import-styles"])
|
||||
assert Recorder.called
|
||||
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 == [
|
||||
['', ''],
|
||||
['1A', '1A Test Style']
|
||||
]
|
||||
assert styles == [["", ""], ["1A", "1A Test Style"]]
|
||||
|
||||
|
||||
def test_index(auth, client):
|
||||
"""Test success in retrieving index."""
|
||||
# Test not logged in
|
||||
response = client.get('/styles/')
|
||||
response = client.get("/styles/")
|
||||
assert response.status_code == 302
|
||||
|
||||
# Login and test get
|
||||
auth.login()
|
||||
response = client.get('/styles/')
|
||||
response = client.get("/styles/")
|
||||
assert response.status_code == 200
|
||||
assert b'1A' in response.data
|
||||
assert b'Test Style' in response.data
|
||||
assert b"1A" in response.data
|
||||
assert b"Test Style" in response.data
|
||||
|
||||
# Test for bad request
|
||||
response = client.get('/styles/?sort_by=foobar')
|
||||
response = client.get("/styles/?sort_by=foobar")
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_info(auth, client):
|
||||
"""Test success in retrieving a style's info page"""
|
||||
# Test not logged in
|
||||
response = client.get('/styles/info/1A')
|
||||
response = client.get("/styles/info/1A")
|
||||
assert response.status_code == 302
|
||||
|
||||
# Login and test
|
||||
auth.login()
|
||||
response = client.get('/styles/info/1A')
|
||||
response = client.get("/styles/info/1A")
|
||||
assert response.status_code == 200
|
||||
assert b'1A' in response.data
|
||||
assert b'Test Style' in response.data
|
||||
assert b"1A" in response.data
|
||||
assert b"Test Style" in response.data
|
||||
|
||||
|
||||
def test_recipes(client):
|
||||
"""Test success in retrieving list of recipes matching style."""
|
||||
response = client.get('/styles/info/1A/recipes')
|
||||
response = client.get("/styles/info/1A/recipes")
|
||||
assert response.status_code == 200
|
||||
assert b'Awesome Beer' in response.data
|
||||
assert b"Awesome Beer" in response.data
|
||||
|
||||
|
||||
def test_info_json(auth, client):
|
||||
"""Test success in retrieving a style's json document."""
|
||||
# Test not logged in
|
||||
response = client.get('/styles/info/1A/json')
|
||||
response = client.get("/styles/info/1A/json")
|
||||
assert response.status_code == 302
|
||||
|
||||
# Login and test
|
||||
auth.login()
|
||||
response = client.get('/styles/info/1A/json')
|
||||
response = client.get("/styles/info/1A/json")
|
||||
assert response.status_code == 200
|
||||
assert response.is_json
|
||||
assert response.get_json()['name'] == 'Test Style'
|
||||
assert response.get_json()["name"] == "Test Style"
|
||||
|
||||
# Test for specs only
|
||||
response = client.get('/styles/info/1A/json?specs=y')
|
||||
response = client.get("/styles/info/1A/json?specs=y")
|
||||
assert response.status_code == 200
|
||||
assert response.is_json
|
||||
assert 'name' not in response.get_json()
|
||||
assert "name" not in response.get_json()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue