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

Compare commits

..

No commits in common. "868d12fcedf1ee594732becd95c5df21effb266a" and "3005c6e44d0220765c111c5a408a8e86d6cf378a" have entirely different histories.

7 changed files with 5 additions and 268 deletions

View file

@ -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)

View file

@ -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))

View file

@ -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">

View file

@ -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 &darr;</a>
{% elif sort_by == 'category' %}
<a class="text-dark" href="{{ url_for('styles.index', descending='true', sort_by='category', limit=limit) }}">Category &uarr;</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 &darr;</a>
{% elif sort_by == 'name' %}
<a class="text-dark" href="{{ url_for('styles.index', descending='true', sort_by='name', limit=limit) }}">Name &uarr;</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 %}

View file

@ -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 %}

View file

@ -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():

View file

@ -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