1
0
Fork 0
mirror of https://github.com/shouptech/humulus.git synced 2026-02-03 17:19:42 +00:00

Add page to view recipes matching a style

This commit is contained in:
Emma 2019-07-09 12:32:20 -06:00
parent 5968623b76
commit a647bc8151
10 changed files with 76 additions and 29 deletions

View file

@ -13,6 +13,9 @@
}, },
"by-type": { "by-type": {
"map": "function (doc) {\n if (doc.$type == \"recipe\" && doc.type && doc.name) {\n emit(doc.type, doc.name)\n }\n}" "map": "function (doc) {\n if (doc.$type == \"recipe\" && doc.type && doc.name) {\n emit(doc.type, doc.name)\n }\n}"
},
"by-style": {
"map": "function (doc) {\n if (doc.$type == \"recipe\" && doc.style && doc.name) {\n emit(doc.style, doc.name)\n }\n}"
} }
}, },
"lists": {}, "lists": {},

View file

@ -3,7 +3,7 @@
"language": "javascript", "language": "javascript",
"views": { "views": {
"by-category": { "by-category": {
"map": "function (doc) {\n if (doc.$type == \"style\") {\n category = doc.id.match(/[0-9]+|[a-zA-Z]+/g)\n category[0] = parseInt(category[0])\n emit(category, doc.name)\n }\n}" "map": "function (doc) {\n if (doc.$type == \"style\") {\n category = doc._id.match(/[0-9]+|[a-zA-Z]+/g)\n category[0] = parseInt(category[0])\n emit(category, doc.name)\n }\n}"
}, },
"by-name": { "by-name": {
"map": "function (doc) {\n if (doc.$type == \"style\") {\n emit(doc.name, doc.name)\n }\n}" "map": "function (doc) {\n if (doc.$type == \"style\") {\n emit(doc.name, doc.name)\n }\n}"

View file

@ -34,9 +34,8 @@ def sub_to_doc(sub):
The returned dictionary can be placed right into CouchDB if you want. The returned dictionary can be placed right into CouchDB if you want.
""" """
doc = { doc = {
'_id': 'style_{}'.format(sub.attrib['id']), '_id': '{}'.format(sub.attrib['id']),
'$type': 'style', '$type': 'style',
'id': sub.attrib['id'],
'name': sub.find('name').text, 'name': sub.find('name').text,
'aroma': sub.find('aroma').text, 'aroma': sub.find('aroma').text,
'appearance': sub.find('appearance').text, 'appearance': sub.find('appearance').text,
@ -91,7 +90,7 @@ def import_styles(url):
`url` defaults to the official BJCP XML styleguide. `url` defaults to the official BJCP XML styleguide.
Each subcategory is converted to JSON and then put to a couchdb. The _id of Each subcategory is converted to JSON and then put to a couchdb. The _id of
the subsequent document will be `style_<number><letter>`, i.e., style_1A. the subsequent document will be `<number><letter>`, i.e., 1A.
If the style already exists in the database, it will be skipped. If the style already exists in the database, it will be skipped.
""" """
db = get_db() db = get_db()
@ -167,3 +166,15 @@ def index():
@login_required @login_required
def info(id): 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>/recipes')
def recipes(id):
style = get_doc_or_404(id)
view = get_view('_design/recipes', 'by-style')
try:
rows = view(include_docs=True, descending=True, key=id)['rows']
except requests.exceptions.HTTPError:
abort(400)
return render_template('styles/recipes.html', style=style, rows=rows)

View file

@ -22,11 +22,7 @@
<div class="row"><h1>{{ recipe.name }}</h1></div> <div class="row"><h1>{{ recipe.name }}</h1></div>
{% if style %} {% if style %}
<div class="row"> <div class="row">
{% if session.logged_in %} <h2><a href="{{ url_for('styles.recipes', id=style._id) }}">{{ style._id }} {{ style.name }}</a></h2>
<h2><a href="{{ url_for('styles.info', id=style._id) }}">{{ style.id }} {{ style.name }}</a></h2>
{% else %}
<h2>{{ style.id }} {{ style.name }}</h2>
{% endif %}
</div> </div>
{% endif %} {% endif %}
{#- {#-

View file

@ -45,7 +45,7 @@
</thead> </thead>
{% for row in rows %} {% for row in rows %}
<tr> <tr>
<td>{{ row.doc.id }}</td> <td>{{ row.doc._id }}</td>
<td><a href="{{ url_for('styles.info', id=row.doc._id) }}">{{ row.doc.name }}</a></td> <td><a href="{{ url_for('styles.info', id=row.doc._id) }}">{{ row.doc.name }}</a></td>
</tr> </tr>
{% endfor %} {% endfor %}

View file

@ -26,10 +26,11 @@
{% extends '_base.html' %} {% extends '_base.html' %}
{% block title %}{{ style.id }} {{ style.name }}{% endblock %} {% block title %}{{ style._id }} {{ style.name }}{% endblock %}
{% block body %} {% block body %}
<div class="row mb-3"><h1>{{ style.id }} {{ style.name }}</h1></div> <div class="row mb-3"><h1>{{ style._id }} {{ style.name }}</h1></div>
<p><a href="{{ url_for('styles.recipes', id=style._id) }}">View recipes using this style</a></p>
{{ render_detail(style.impression, 'Overall Impression') }} {{ render_detail(style.impression, 'Overall Impression') }}
{{ render_detail(style.appearance, 'Appearance') }} {{ render_detail(style.appearance, 'Appearance') }}
{{ render_detail(style.aroma, 'Aroma') }} {{ render_detail(style.aroma, 'Aroma') }}

View file

@ -0,0 +1,39 @@
{#-
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.
-#}
{% extends '_base.html' %}
{% block title %}Recipes for {{ style._id }} {{ style.name }}{% endblock %}
{% block body %}
{% if session.logged_in %}
<div class="row mb-3"><h1>Recipes for <a href="{{ url_for('styles.info', id=style._id) }}">{{ style._id }} {{ style.name }}</a></h1></div>
{% else %}
<div class="row mb-3"><h1>Recipes for {{ style._id }} {{ style.name }}</h1></div>
{% endif %}
<div class="row">
<table class="table table-sm">
<thead>
<th>Name</th>
</thead>
{% for row in rows %}
<tr>
<td><a href="{{ url_for('recipes.info', id=row.doc._id) }}">{{ row.doc.name }}</a></td>
</tr>
{% endfor %}
</table>
</div>
{% endblock %}

View file

@ -78,7 +78,7 @@ def app():
'name': 'Awesome Beer', 'name': 'Awesome Beer',
'notes': 'This is a test beer that contains most possible fields.', 'notes': 'This is a test beer that contains most possible fields.',
'volume': '2.5', 'volume': '2.5',
'style': 'style_1A', 'style': '1A',
'fermentables': [ 'fermentables': [
{ {
'name': '2row', 'name': '2row',
@ -127,7 +127,7 @@ def app():
# Add a test style # Add a test style
put_doc({'$type': 'style', put_doc({'$type': 'style',
'_id': 'style_1A', '_id': '1A',
'abv': {'high': '100', 'low': '0'}, 'abv': {'high': '100', 'low': '0'},
'appearance': 'Good looking', 'appearance': 'Good looking',
'aroma': 'Smelly', 'aroma': 'Smelly',

View file

@ -136,7 +136,7 @@ def test_create(client, app, auth):
'name': 'Test', 'name': 'Test',
'notes': 'Test', 'notes': 'Test',
'volume': '5.5', 'volume': '5.5',
'style': 'style_1A' 'style': '1A'
} }
response = client.post('/recipes/create', data=data) response = client.post('/recipes/create', data=data)
assert response.status_code == 302 assert response.status_code == 302
@ -148,7 +148,7 @@ def test_create(client, app, auth):
assert doc['notes'] == 'Test' assert doc['notes'] == 'Test'
assert doc['volume'] == '5.5' assert doc['volume'] == '5.5'
assert doc['efficiency'] == '65' assert doc['efficiency'] == '65'
assert doc['style'] == 'style_1A' assert doc['style'] == '1A'
def test_update(client, app, auth): def test_update(client, app, auth):
@ -296,7 +296,7 @@ def test_recipe_form_doc(app):
recipe.volume.data = Decimal('5.5') recipe.volume.data = Decimal('5.5')
recipe.notes.data = 'This is a test' recipe.notes.data = 'This is a test'
recipe.type.data = 'All-Grain' recipe.type.data = 'All-Grain'
recipe.style.data = 'style_1A' recipe.style.data = '1A'
assert recipe.doc == { assert recipe.doc == {
'name': 'Test', 'name': 'Test',
@ -307,7 +307,7 @@ def test_recipe_form_doc(app):
'fermentables': [], 'fermentables': [],
'hops': [], 'hops': [],
'$type': 'recipe', '$type': 'recipe',
'style': 'style_1A' 'style': '1A'
} }
ferm = FermentableForm() ferm = FermentableForm()
@ -340,7 +340,7 @@ def test_recipe_form_doc(app):
'volume': '5.5', 'volume': '5.5',
'notes': 'This is a test', 'notes': 'This is a test',
'$type': 'recipe', '$type': 'recipe',
'style': 'style_1A', 'style': '1A',
'fermentables': [{ 'fermentables': [{
'name': 'Test', 'name': 'Test',
'type': 'Grain', 'type': 'Grain',

View file

@ -84,9 +84,8 @@ TEST_XML = '''<?xml version="1.0" encoding="UTF-8"?>
def test_sub_to_doc(): def test_sub_to_doc():
assert sub_to_doc(ET.fromstring(COMPLETE_STYLE)) == { assert sub_to_doc(ET.fromstring(COMPLETE_STYLE)) == {
'_id': 'style_1A', '_id': '1A',
'$type': 'style', '$type': 'style',
'id': '1A',
'name': 'Test Style', 'name': 'Test Style',
'aroma': 'Smelly', 'aroma': 'Smelly',
'appearance': 'Good looking', 'appearance': 'Good looking',
@ -107,9 +106,8 @@ def test_sub_to_doc():
} }
assert sub_to_doc(ET.fromstring(INCOMPLETE_STYLE)) == { assert sub_to_doc(ET.fromstring(INCOMPLETE_STYLE)) == {
'_id': 'style_2B', '_id': '2B',
'$type': 'style', '$type': 'style',
'id': '2B',
'name': 'Test Style', 'name': 'Test Style',
'aroma': 'Smelly', 'aroma': 'Smelly',
'appearance': 'Good looking', 'appearance': 'Good looking',
@ -148,14 +146,13 @@ def test_import_styles(monkeypatch):
import_styles(None) import_styles(None)
assert PutRecorder.doc == {'$type': 'style', assert PutRecorder.doc == {'$type': 'style',
'_id': 'style_1A', '_id': '1A',
'abv': {'high': '100', 'low': '0'}, 'abv': {'high': '100', 'low': '0'},
'appearance': 'Good looking', 'appearance': 'Good looking',
'aroma': 'Smelly', 'aroma': 'Smelly',
'fg': {'high': '1.2', 'low': '1.0'}, 'fg': {'high': '1.2', 'low': '1.0'},
'flavor': 'Good tasting', 'flavor': 'Good tasting',
'ibu': {'high': '100', 'low': '0'}, 'ibu': {'high': '100', 'low': '0'},
'id': '1A',
'impression': 'Refreshing', 'impression': 'Refreshing',
'mouthfeel': 'Good feeling', 'mouthfeel': 'Good feeling',
'name': 'Test Style', 'name': 'Test Style',
@ -163,7 +160,7 @@ def test_import_styles(monkeypatch):
'srm': {'high': '100', 'low': '0'} 'srm': {'high': '100', 'low': '0'}
} }
MockDB.db = {'style_1A': ''} MockDB.db = {'1A': ''}
PutRecorder.doc = None PutRecorder.doc = None
import_styles(None) import_styles(None)
assert PutRecorder.doc is None assert PutRecorder.doc is None
@ -188,7 +185,7 @@ def test_get_styles_choices(app):
styles = get_styles_list() styles = get_styles_list()
assert styles == [ assert styles == [
['', ''], ['', ''],
['style_1A', '1A Test Style'] ['1A', '1A Test Style']
] ]
@ -213,12 +210,12 @@ def test_index(auth, client):
def test_info(auth, client): def test_info(auth, client):
"""Test success in retrieving a style's info page""" """Test success in retrieving a style's info page"""
# Test not logged in # Test not logged in
response = client.get('/styles/info/style_1A') response = client.get('/styles/info/1A')
assert response.status_code == 302 assert response.status_code == 302
# Login and test # Login and test
auth.login() auth.login()
response = client.get('/styles/info/style_1A') response = client.get('/styles/info/1A')
assert response.status_code == 200 assert response.status_code == 200
assert b'1A' in response.data assert b'1A' in response.data
assert b'Test Style' in response.data assert b'Test Style' in response.data