mirror of
https://github.com/shouptech/humulus.git
synced 2026-02-03 16:09:44 +00:00
Add page to view recipes matching a style
This commit is contained in:
parent
5968623b76
commit
a647bc8151
10 changed files with 76 additions and 29 deletions
|
|
@ -13,6 +13,9 @@
|
|||
},
|
||||
"by-type": {
|
||||
"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": {},
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"language": "javascript",
|
||||
"views": {
|
||||
"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": {
|
||||
"map": "function (doc) {\n if (doc.$type == \"style\") {\n emit(doc.name, doc.name)\n }\n}"
|
||||
|
|
|
|||
|
|
@ -34,9 +34,8 @@ def sub_to_doc(sub):
|
|||
The returned dictionary can be placed right into CouchDB if you want.
|
||||
"""
|
||||
doc = {
|
||||
'_id': 'style_{}'.format(sub.attrib['id']),
|
||||
'_id': '{}'.format(sub.attrib['id']),
|
||||
'$type': 'style',
|
||||
'id': sub.attrib['id'],
|
||||
'name': sub.find('name').text,
|
||||
'aroma': sub.find('aroma').text,
|
||||
'appearance': sub.find('appearance').text,
|
||||
|
|
@ -91,7 +90,7 @@ def import_styles(url):
|
|||
`url` defaults to the official BJCP XML styleguide.
|
||||
|
||||
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.
|
||||
"""
|
||||
db = get_db()
|
||||
|
|
@ -167,3 +166,15 @@ def index():
|
|||
@login_required
|
||||
def info(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)
|
||||
|
|
|
|||
|
|
@ -22,11 +22,7 @@
|
|||
<div class="row"><h1>{{ recipe.name }}</h1></div>
|
||||
{% if style %}
|
||||
<div class="row">
|
||||
{% if session.logged_in %}
|
||||
<h2><a href="{{ url_for('styles.info', id=style._id) }}">{{ style.id }} {{ style.name }}</a></h2>
|
||||
{% else %}
|
||||
<h2>{{ style.id }} {{ style.name }}</h2>
|
||||
{% endif %}
|
||||
<h2><a href="{{ url_for('styles.recipes', id=style._id) }}">{{ style._id }} {{ style.name }}</a></h2>
|
||||
</div>
|
||||
{% endif %}
|
||||
{#-
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
</thead>
|
||||
{% for row in rows %}
|
||||
<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>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
|||
|
|
@ -26,10 +26,11 @@
|
|||
|
||||
|
||||
{% extends '_base.html' %}
|
||||
{% block title %}{{ style.id }} {{ style.name }}{% endblock %}
|
||||
{% block title %}{{ style._id }} {{ style.name }}{% endblock %}
|
||||
|
||||
{% 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.appearance, 'Appearance') }}
|
||||
{{ render_detail(style.aroma, 'Aroma') }}
|
||||
|
|
|
|||
39
src/humulus/templates/styles/recipes.html
Normal file
39
src/humulus/templates/styles/recipes.html
Normal 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 %}
|
||||
|
|
@ -78,7 +78,7 @@ def app():
|
|||
'name': 'Awesome Beer',
|
||||
'notes': 'This is a test beer that contains most possible fields.',
|
||||
'volume': '2.5',
|
||||
'style': 'style_1A',
|
||||
'style': '1A',
|
||||
'fermentables': [
|
||||
{
|
||||
'name': '2row',
|
||||
|
|
@ -127,7 +127,7 @@ def app():
|
|||
|
||||
# Add a test style
|
||||
put_doc({'$type': 'style',
|
||||
'_id': 'style_1A',
|
||||
'_id': '1A',
|
||||
'abv': {'high': '100', 'low': '0'},
|
||||
'appearance': 'Good looking',
|
||||
'aroma': 'Smelly',
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ def test_create(client, app, auth):
|
|||
'name': 'Test',
|
||||
'notes': 'Test',
|
||||
'volume': '5.5',
|
||||
'style': 'style_1A'
|
||||
'style': '1A'
|
||||
}
|
||||
response = client.post('/recipes/create', data=data)
|
||||
assert response.status_code == 302
|
||||
|
|
@ -148,7 +148,7 @@ def test_create(client, app, auth):
|
|||
assert doc['notes'] == 'Test'
|
||||
assert doc['volume'] == '5.5'
|
||||
assert doc['efficiency'] == '65'
|
||||
assert doc['style'] == 'style_1A'
|
||||
assert doc['style'] == '1A'
|
||||
|
||||
|
||||
def test_update(client, app, auth):
|
||||
|
|
@ -296,7 +296,7 @@ def test_recipe_form_doc(app):
|
|||
recipe.volume.data = Decimal('5.5')
|
||||
recipe.notes.data = 'This is a test'
|
||||
recipe.type.data = 'All-Grain'
|
||||
recipe.style.data = 'style_1A'
|
||||
recipe.style.data = '1A'
|
||||
|
||||
assert recipe.doc == {
|
||||
'name': 'Test',
|
||||
|
|
@ -307,7 +307,7 @@ def test_recipe_form_doc(app):
|
|||
'fermentables': [],
|
||||
'hops': [],
|
||||
'$type': 'recipe',
|
||||
'style': 'style_1A'
|
||||
'style': '1A'
|
||||
}
|
||||
|
||||
ferm = FermentableForm()
|
||||
|
|
@ -340,7 +340,7 @@ def test_recipe_form_doc(app):
|
|||
'volume': '5.5',
|
||||
'notes': 'This is a test',
|
||||
'$type': 'recipe',
|
||||
'style': 'style_1A',
|
||||
'style': '1A',
|
||||
'fermentables': [{
|
||||
'name': 'Test',
|
||||
'type': 'Grain',
|
||||
|
|
|
|||
|
|
@ -84,9 +84,8 @@ TEST_XML = '''<?xml version="1.0" encoding="UTF-8"?>
|
|||
|
||||
def test_sub_to_doc():
|
||||
assert sub_to_doc(ET.fromstring(COMPLETE_STYLE)) == {
|
||||
'_id': 'style_1A',
|
||||
'_id': '1A',
|
||||
'$type': 'style',
|
||||
'id': '1A',
|
||||
'name': 'Test Style',
|
||||
'aroma': 'Smelly',
|
||||
'appearance': 'Good looking',
|
||||
|
|
@ -107,9 +106,8 @@ def test_sub_to_doc():
|
|||
}
|
||||
|
||||
assert sub_to_doc(ET.fromstring(INCOMPLETE_STYLE)) == {
|
||||
'_id': 'style_2B',
|
||||
'_id': '2B',
|
||||
'$type': 'style',
|
||||
'id': '2B',
|
||||
'name': 'Test Style',
|
||||
'aroma': 'Smelly',
|
||||
'appearance': 'Good looking',
|
||||
|
|
@ -148,14 +146,13 @@ def test_import_styles(monkeypatch):
|
|||
|
||||
import_styles(None)
|
||||
assert PutRecorder.doc == {'$type': 'style',
|
||||
'_id': 'style_1A',
|
||||
'_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',
|
||||
|
|
@ -163,7 +160,7 @@ def test_import_styles(monkeypatch):
|
|||
'srm': {'high': '100', 'low': '0'}
|
||||
}
|
||||
|
||||
MockDB.db = {'style_1A': ''}
|
||||
MockDB.db = {'1A': ''}
|
||||
PutRecorder.doc = None
|
||||
import_styles(None)
|
||||
assert PutRecorder.doc is None
|
||||
|
|
@ -188,7 +185,7 @@ def test_get_styles_choices(app):
|
|||
styles = get_styles_list()
|
||||
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):
|
||||
"""Test success in retrieving a style's info page"""
|
||||
# Test not logged in
|
||||
response = client.get('/styles/info/style_1A')
|
||||
response = client.get('/styles/info/1A')
|
||||
assert response.status_code == 302
|
||||
|
||||
# Login and test
|
||||
auth.login()
|
||||
response = client.get('/styles/info/style_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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue