1
0
Fork 0
mirror of https://github.com/shouptech/humulus.git synced 2026-02-03 16:09:44 +00:00

Add ability to import JSON file.

This commit is contained in:
Emma 2019-07-05 19:52:28 -06:00
parent 9c456313fc
commit 4aad7f86c5
5 changed files with 146 additions and 56 deletions

View file

@ -14,11 +14,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from decimal import Decimal
from flask import (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.validators import DataRequired, Optional
@ -186,6 +188,65 @@ class RecipeForm(FlaskForm):
recipe['yeast'] = self.yeast.doc
return recipe
def copyfrom(self, data):
"""Copies from a dictionary (data) into the current object"""
self.name.data = data['name']
self.efficiency.data = Decimal(data['efficiency'])
self.volume.data = Decimal(data['volume'])
self.notes.data = data['notes']
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:
yeast = data['yeast']
self.yeast.form.name.data = yeast['name']
self.yeast.form.low_attenuation.data = (
Decimal(yeast['low_attenuation'])
)
self.yeast.form.high_attenuation.data = (
Decimal(yeast['high_attenuation'])
)
if 'type' in yeast:
self.yeast.form.type.data = yeast['type']
if 'lab' in yeast:
self.yeast.form.lab.data = yeast['lab']
if 'code' in yeast:
self.yeast.form.code.data = yeast['code']
if 'flocculation' in yeast:
self.yeast.form.flocculation.data = yeast['flocculation']
if 'min_temperature' in yeast:
self.yeast.form.min_temperature.data = (
Decimal(yeast['min_temperature'])
)
if 'max_temperature' in yeast:
self.yeast.form.max_temperature.data = (
Decimal(yeast['max_temperature'])
)
if 'abv_tolerance' in yeast:
self.yeast.form.abv_tolerance.data = (
Decimal(yeast['abv_tolerance'])
)
class ImportForm(FlaskForm):
upload = FileField(validators=[FileRequired()])
@bp.route('/')
def index():
@ -219,6 +280,22 @@ def create():
return render_template('recipes/create.html', form=form)
@bp.route('/create/json', methods=('GET', 'POST'))
@login_required
def create_json():
form = ImportForm()
if form.validate_on_submit():
recipe = RecipeForm()
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)
response = put_doc(recipe.doc)
return redirect(url_for('recipes.info', id=response['_id']))
return render_template('recipes/create_json.html', form=form)
@bp.route('/info/<id>')
def info(id):
return render_template('recipes/info.html', recipe=get_doc_or_404(id))
@ -266,60 +343,7 @@ def update(id):
flash('Updated recipe: {}'.format(form.name.data), 'success')
return redirect(url_for('recipes.info', id=id))
else:
# Copy the recipe's data into the form.
# Is there an easier way to do this?
form.name.data = recipe['name']
form.efficiency.data = Decimal(recipe['efficiency'])
form.volume.data = Decimal(recipe['volume'])
form.notes.data = recipe['notes']
for fermentable in recipe['fermentables']:
form.fermentables.append_entry({
'name': fermentable['name'],
'type': fermentable['type'],
'amount': Decimal(fermentable['amount']),
'ppg': Decimal(fermentable['ppg']),
'color': Decimal(fermentable['color'])
})
for hop in recipe['hops']:
form.hops.append_entry({
'name': hop['name'],
'use': hop['use'],
'alpha': Decimal(hop['alpha']),
'duration': Decimal(hop['duration']),
'amount': Decimal(hop['amount']),
})
if 'yeast' in recipe:
yeast = recipe['yeast']
form.yeast.form.name.data = yeast['name']
form.yeast.form.low_attenuation.data = (
Decimal(yeast['low_attenuation'])
)
form.yeast.form.high_attenuation.data = (
Decimal(yeast['high_attenuation'])
)
if 'type' in yeast:
form.yeast.form.type.data = yeast['type']
if 'lab' in yeast:
form.yeast.form.lab.data = yeast['lab']
if 'code' in yeast:
form.yeast.form.code.data = yeast['code']
if 'flocculation' in yeast:
form.yeast.form.flocculation.data = yeast['flocculation']
if 'min_temperature' in yeast:
form.yeast.form.min_temperature.data = (
Decimal(yeast['min_temperature'])
)
if 'max_temperature' in yeast:
form.yeast.form.max_temperature.data = (
Decimal(yeast['max_temperature'])
)
if 'abv_tolerance' in yeast:
form.yeast.form.abv_tolerance.data = (
Decimal(yeast['abv_tolerance'])
)
form.copyfrom(recipe)
return render_template('recipes/update.html', form=form,
id=id, rev=recipe['_rev'])

View file

@ -18,9 +18,9 @@
Macro for rendering WTF fields.
field is a FormField, and class is an additional class that gets added onto the field.
#}
{% macro render_field_with_errors(field, class='') %}
{% macro render_field_with_errors(field, class='', base_class='form-control') %}
<div class="form-group">
{{ field.label }} {{ field(class_='form-control ' + class, **kwargs)|safe }}
{{ field.label }} {{ field(class_=base_class + ' ' + class, **kwargs)|safe }}
{% if field.errors %}
{% for error in field.errors %}
<div class="container">

View file

@ -0,0 +1,35 @@
{#-
Copyright 2019 Mike Shoup
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-#}
{% from "_macros.html" import render_field_with_errors %}
{% extends '_base.html' %}
{% block title %}Import Recipe{% endblock %}
{% block body %}
<div class="row"><h1>Import JSON Recipe</h1></div>
<form action="{{ url_for('recipes.create_json') }}" method="POST" enctype="multipart/form-data">
{{ form.hidden_tag() }}
{{ render_field_with_errors(form.upload, base_class='form-control-file') }}
<button type="submit" class="btn btn-primary">Upload</button>
</form>
<div class="row">
<div class="col">
<a href="{{ url_for('recipes.index') }}" class="mt-2 btn btn-secondary btn-sm">Back to recipe list</a>
</div>
</div>
{% endblock %}

View file

@ -24,6 +24,7 @@
{% if session.logged_in %}
<div class="row">
<a href="{{ url_for('recipes.create') }}" class="btn btn-primary btn-sm mt-1 mb-2 ml-auto">Create a recipe</a>
<a href="{{ url_for('recipes.create_json') }}" class="btn btn-secondary btn-sm mt-1 mb-2 ml-1">Import JSON recipe</a>
</div>
{% endif %}
<div class="row">

View file

@ -12,7 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from decimal import Decimal
from io import BytesIO
from humulus.couch import get_db, get_doc, put_doc
from humulus.recipes import FermentableForm, HopForm, RecipeForm, YeastForm
@ -304,3 +306,31 @@ def test_recipe_delete(client, auth):
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')
assert response.status_code == 302
# Test GET after logging in
auth.login()
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')
}
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']
# 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)
assert response.status_code == 200