mirror of
https://github.com/shouptech/humulus.git
synced 2026-02-03 22:29:42 +00:00
Compare commits
No commits in common. "868d12fcedf1ee594732becd95c5df21effb266a" and "3005c6e44d0220765c111c5a408a8e86d6cf378a" have entirely different histories.
868d12fced
...
3005c6e44d
7 changed files with 5 additions and 268 deletions
|
|
@ -33,6 +33,9 @@ def create_app(test_config=None):
|
||||||
from . import couch
|
from . import couch
|
||||||
couch.init_app(app)
|
couch.init_app(app)
|
||||||
|
|
||||||
|
from . import styles
|
||||||
|
styles.init_app(app)
|
||||||
|
|
||||||
# Register blueprint for index page
|
# Register blueprint for index page
|
||||||
from . import home
|
from . import home
|
||||||
app.register_blueprint(home.bp)
|
app.register_blueprint(home.bp)
|
||||||
|
|
@ -46,11 +49,6 @@ def create_app(test_config=None):
|
||||||
from . import auth
|
from . import auth
|
||||||
app.register_blueprint(auth.bp)
|
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
|
# Register custom filters
|
||||||
from . import filters
|
from . import filters
|
||||||
filters.create_filters(app)
|
filters.create_filters(app)
|
||||||
|
|
|
||||||
|
|
@ -14,19 +14,14 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import math
|
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import requests
|
import requests
|
||||||
from flask import Blueprint, abort, current_app, render_template, request
|
from flask import current_app
|
||||||
from flask.cli import with_appcontext
|
from flask.cli import with_appcontext
|
||||||
|
|
||||||
from humulus.auth import login_required
|
from humulus.couch import get_db, put_doc
|
||||||
from humulus.couch import get_db, put_doc, get_view, get_doc_or_404
|
|
||||||
|
|
||||||
bp = Blueprint('styles', __name__, url_prefix='/styles')
|
|
||||||
|
|
||||||
|
|
||||||
def sub_to_doc(sub):
|
def sub_to_doc(sub):
|
||||||
"""Coverts sub (XML) to a dictionary document.
|
"""Coverts sub (XML) to a dictionary document.
|
||||||
|
|
@ -119,37 +114,3 @@ def import_command():
|
||||||
def init_app(app):
|
def init_app(app):
|
||||||
"""Register the CLI command with the app."""
|
"""Register the CLI command with the app."""
|
||||||
app.cli.add_command(import_command)
|
app.cli.add_command(import_command)
|
||||||
|
|
||||||
|
|
||||||
@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)
|
|
||||||
|
|
||||||
view = get_view('_design/styles', 'by-{}'.format(sort_by))
|
|
||||||
try:
|
|
||||||
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],
|
|
||||||
descending=descending,
|
|
||||||
sort_by=sort_by,
|
|
||||||
page=page,
|
|
||||||
num_pages=math.ceil(len(rows)/limit),
|
|
||||||
limit=limit
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/info/<id>')
|
|
||||||
@login_required
|
|
||||||
def info(id):
|
|
||||||
return render_template('styles/info.html', style=get_doc_or_404(id))
|
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,6 @@
|
||||||
<li class="nav-item {% if request.url_rule.endpoint == 'recipes.index' %}active{% endif %}">
|
<li class="nav-item {% if request.url_rule.endpoint == 'recipes.index' %}active{% endif %}">
|
||||||
<a class="nav-link" href="{{ url_for('recipes.index') }}">Recipes</a>
|
<a class="nav-link" href="{{ url_for('recipes.index') }}">Recipes</a>
|
||||||
</li>
|
</li>
|
||||||
{% if session.logged_in %}
|
|
||||||
<li class="nav-item {% if request.url_rule.endpoint == 'styles.index' %}active{% endif %}">
|
|
||||||
<a class="nav-link" href="{{ url_for('styles.index') }}">Styles</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="navbar-nav ml-auto">
|
<ul class="navbar-nav ml-auto">
|
||||||
<li class="nav-item active">
|
<li class="nav-item active">
|
||||||
|
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
{#-
|
|
||||||
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 %}Styles{% endblock %}
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
<div class="row"><h1>Styles</h1></div>
|
|
||||||
<div class="row">
|
|
||||||
<table class="table table-hover table-sm">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
{% if sort_by == 'category' and descending %}
|
|
||||||
<a class="text-dark" href="{{ url_for('styles.index', descending='false', sort_by='category', limit=limit) }}">Category ↓</a>
|
|
||||||
{% elif sort_by == 'category' %}
|
|
||||||
<a class="text-dark" href="{{ url_for('styles.index', descending='true', sort_by='category', limit=limit) }}">Category ↑</a>
|
|
||||||
{% else %}
|
|
||||||
<a class="text-dark" href="{{ url_for('styles.index', descending='false', sort_by='category', limit=limit) }}">Category</a>
|
|
||||||
{% endif %}
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
{% if sort_by == 'name' and descending %}
|
|
||||||
<a class="text-dark" href="{{ url_for('styles.index', descending='false', sort_by='name', limit=limit) }}">Name ↓</a>
|
|
||||||
{% elif sort_by == 'name' %}
|
|
||||||
<a class="text-dark" href="{{ url_for('styles.index', descending='true', sort_by='name', limit=limit) }}">Name ↑</a>
|
|
||||||
{% else %}
|
|
||||||
<a class="text-dark" href="{{ url_for('styles.index', descending='false', sort_by='name', limit=limit) }}">Name</a>
|
|
||||||
{% endif %}
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
{% for row in rows %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ row.doc.id }}</td>
|
|
||||||
<td><a href="{{ url_for('styles.info', id=row.doc._id) }}">{{ row.doc.name }}</a></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<nav>
|
|
||||||
<ul class="pagination flex-wrap">
|
|
||||||
<li class="page-item{% if page == 1 %} disabled {% endif %}">
|
|
||||||
<a class="page-link" href="{{ url_for('styles.index', descending=descending, sort_by=sort_by, page=page-1, limit=limit) }}">Previous</a>
|
|
||||||
</li>
|
|
||||||
{% for num in range(1, num_pages+1) %}
|
|
||||||
<li class="page-item{% if num == page %} active{% endif %}">
|
|
||||||
<a class="page-link" href="{{ url_for('styles.index', descending=descending, sort_by=sort_by, page=num, limit=limit) }}">{{ num }}</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
<li class="page-item{% if page == num_pages %} disabled {% endif %}">
|
|
||||||
<a class="page-link" href="{{ url_for('styles.index', descending=descending, sort_by=sort_by, page=page+1, limit=limit) }}">Next</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<div class="dropdown">
|
|
||||||
<button class="btn btn-secondary dropdown-toggle ml-2" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
||||||
Limit
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
|
||||||
<a class="dropdown-item{% if limit == 5 %} active {% endif %}" href="{{ url_for('styles.index', descending=descending, sort_by=sort_by, page=page, limit=5) }}">5</a>
|
|
||||||
<a class="dropdown-item{% if limit == 10 %} active {% endif %}" href="{{ url_for('styles.index', descending=descending, sort_by=sort_by, page=page, limit=10) }}">10</a>
|
|
||||||
<a class="dropdown-item{% if limit == 20 %} active {% endif %}" href="{{ url_for('styles.index', descending=descending, sort_by=sort_by, page=page, limit=20) }}">20</a>
|
|
||||||
<a class="dropdown-item{% if limit == 50 %} active {% endif %}" href="{{ url_for('styles.index', descending=descending, sort_by=sort_by, page=page, limit=50) }}">50</a>
|
|
||||||
<a class="dropdown-item{% if limit == 100 %} active {% endif %}" href="{{ url_for('styles.index', descending=descending, sort_by=sort_by, page=page, limit=100) }}">100</a>
|
|
||||||
<a class="dropdown-item{% if limit == 200 %} active {% endif %}" href="{{ url_for('styles.index', descending=descending, sort_by=sort_by, page=page, limit=200) }}">200</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
{#-
|
|
||||||
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 moment %}
|
|
||||||
|
|
||||||
{% macro render_detail(field, header) %}
|
|
||||||
<div class="row">
|
|
||||||
<h2>{{ header }}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<p>{{ field }}</p>
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
|
|
||||||
{% extends '_base.html' %}
|
|
||||||
{% block title %}{{ style.id }} {{ style.name }}{% endblock %}
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
<div class="row mb-3"><h1>{{ style.id }} {{ style.name }}</h1></div>
|
|
||||||
{{ render_detail(style.impression, 'Overall Impression') }}
|
|
||||||
{{ render_detail(style.appearance, 'Appearance') }}
|
|
||||||
{{ render_detail(style.aroma, 'Aroma') }}
|
|
||||||
{{ render_detail(style.flavor, 'Flavor') }}
|
|
||||||
{{ render_detail(style.mouthfeel, 'Mouthfeel') }}
|
|
||||||
|
|
||||||
{% if style.comments %}
|
|
||||||
{{ render_detail(style.comments, 'Comments') }}
|
|
||||||
{% endif %}
|
|
||||||
{% if style.history %}
|
|
||||||
{{ render_detail(style.history, 'History') }}
|
|
||||||
{% endif %}
|
|
||||||
{% if style.ingredients %}
|
|
||||||
{{ render_detail(style.ingredients, 'Characteristic Ingredients') }}
|
|
||||||
{% endif %}
|
|
||||||
{% if style.comparison %}
|
|
||||||
{{ render_detail(style.comparison, 'Style Comparisons') }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<h2>Vital Statistics</h2>
|
|
||||||
<table class="table">
|
|
||||||
<tr>
|
|
||||||
<th>IBU</th>
|
|
||||||
<td>{{ style.ibu.low }} - {{ style.ibu.high }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>OG</th>
|
|
||||||
<td>{{ '%.3f' | format(style.og.low|float) }} - {{ '%.3f' | format(style.og.high|float) }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>FG</th>
|
|
||||||
<td>{{ '%.3f' | format(style.fg.low|float) }} - {{ '%.3f' | format(style.fg.high|float) }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>SRM</th>
|
|
||||||
<td>{{ style.srm.low }} - {{ style.srm.high }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>ABV</th>
|
|
||||||
<td>{{ style.abv.low }} - {{ style.abv.high }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if style.examples %}
|
|
||||||
{{ render_detail(style.examples, 'Commercial Examples') }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="row"><a href="{{ url_for('styles.index') }}">Back to styles list</a></div>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -122,23 +122,6 @@ def app():
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
# Add a test style
|
|
||||||
put_doc({'$type': 'style',
|
|
||||||
'_id': 'style_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
|
yield app
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
|
|
|
||||||
|
|
@ -180,35 +180,3 @@ def test_import_command(runner, monkeypatch):
|
||||||
result = runner.invoke(args=['import-styles'])
|
result = runner.invoke(args=['import-styles'])
|
||||||
assert Recorder.called
|
assert Recorder.called
|
||||||
assert 'Imported BJCP styles.' in result.output
|
assert 'Imported BJCP styles.' in result.output
|
||||||
|
|
||||||
|
|
||||||
def test_index(auth, client):
|
|
||||||
"""Test success in retrieving index."""
|
|
||||||
# Test not logged in
|
|
||||||
response = client.get('/styles/')
|
|
||||||
assert response.status_code == 302
|
|
||||||
|
|
||||||
# Login and test get
|
|
||||||
auth.login()
|
|
||||||
response = client.get('/styles/')
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert b'1A' in response.data
|
|
||||||
assert b'Test Style' in response.data
|
|
||||||
|
|
||||||
# Test for bad request
|
|
||||||
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/style_1A')
|
|
||||||
assert response.status_code == 302
|
|
||||||
|
|
||||||
# Login and test
|
|
||||||
auth.login()
|
|
||||||
response = client.get('/styles/info/style_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