From 7be88cd934964a52d2407e1d66be2b0d10dcae76 Mon Sep 17 00:00:00 2001 From: Mike Shoup Date: Mon, 8 Jul 2019 12:06:40 -0600 Subject: [PATCH] Add style list view --- src/humulus/app.py | 8 ++- src/humulus/styles.py | 37 ++++++++++- src/humulus/templates/styles/index.html | 84 +++++++++++++++++++++++++ tests/conftest.py | 17 +++++ tests/test_styles.py | 18 ++++++ 5 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 src/humulus/templates/styles/index.html diff --git a/src/humulus/app.py b/src/humulus/app.py index 3feb6b3..df149fb 100644 --- a/src/humulus/app.py +++ b/src/humulus/app.py @@ -33,9 +33,6 @@ def create_app(test_config=None): from . import couch couch.init_app(app) - from . import styles - styles.init_app(app) - # Register blueprint for index page from . import home app.register_blueprint(home.bp) @@ -49,6 +46,11 @@ def create_app(test_config=None): from . import auth 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 from . import filters filters.create_filters(app) diff --git a/src/humulus/styles.py b/src/humulus/styles.py index 41d573e..7ef9ab1 100644 --- a/src/humulus/styles.py +++ b/src/humulus/styles.py @@ -14,14 +14,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +import math import xml.etree.ElementTree as ET import click import requests -from flask import current_app +from flask import Blueprint, abort, current_app, render_template, request from flask.cli import with_appcontext -from humulus.couch import get_db, put_doc +from humulus.auth import login_required +from humulus.couch import get_db, put_doc, get_view + +bp = Blueprint('styles', __name__, url_prefix='/styles') + def sub_to_doc(sub): """Coverts sub (XML) to a dictionary document. @@ -114,3 +119,31 @@ def import_command(): def init_app(app): """Register the CLI command with the app.""" 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 + ) diff --git a/src/humulus/templates/styles/index.html b/src/humulus/templates/styles/index.html new file mode 100644 index 0000000..c230703 --- /dev/null +++ b/src/humulus/templates/styles/index.html @@ -0,0 +1,84 @@ +{#- + 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 %} +

Styles

+
+ + + + + + + + {% for row in rows %} + + + + + {% endfor %} +
+ {% if sort_by == 'category' and descending %} + Category ↓ + {% elif sort_by == 'category' %} + Category ↑ + {% else %} + Category + {% endif %} + + {% if sort_by == 'name' and descending %} + Name ↓ + {% elif sort_by == 'name' %} + Name ↑ + {% else %} + Name + {% endif %} +
{{ row.doc.id }}{{ row.doc.name }}
+
+
+ + +
+{% endblock %} diff --git a/tests/conftest.py b/tests/conftest.py index d997ccb..9404281 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -122,6 +122,23 @@ 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 with app.app_context(): diff --git a/tests/test_styles.py b/tests/test_styles.py index 14f2e7f..45ac4ac 100644 --- a/tests/test_styles.py +++ b/tests/test_styles.py @@ -180,3 +180,21 @@ def test_import_command(runner, monkeypatch): result = runner.invoke(args=['import-styles']) assert Recorder.called 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