aboutsummaryrefslogtreecommitdiffstats
path: root/python
diff options
context:
space:
mode:
authorbnewbold <bnewbold@archive.org>2020-07-31 00:00:30 +0000
committerbnewbold <bnewbold@archive.org>2020-07-31 00:00:30 +0000
commit7ba5cd586b652fe1d9ca384e7f8b07f6c54a8ec0 (patch)
treeaf50f6879177d48c86a280c4baef74687ef670e1 /python
parent623cc4a9691abd8a776780cebf00e45ab3daf64c (diff)
parent118e16a54bd5cc0db4b24b46a2d5db990f60ea19 (diff)
downloadfatcat-7ba5cd586b652fe1d9ca384e7f8b07f6c54a8ec0.tar.gz
fatcat-7ba5cd586b652fe1d9ca384e7f8b07f6c54a8ec0.zip
Merge branch 'bnewbold-search-improvements' into 'master'
search and error page improvements See merge request webgroup/fatcat!72
Diffstat (limited to 'python')
-rw-r--r--python/fatcat_web/editing_routes.py20
-rw-r--r--python/fatcat_web/kafka.py2
-rw-r--r--python/fatcat_web/routes.py79
-rw-r--r--python/fatcat_web/search.py23
-rw-r--r--python/fatcat_web/templates/400.html18
-rw-r--r--python/fatcat_web/templates/api_error.html20
-rw-r--r--python/fatcat_web/templates/container_search.html8
-rw-r--r--python/fatcat_web/templates/entity_macros.html64
-rw-r--r--python/fatcat_web/templates/home.html6
-rw-r--r--python/fatcat_web/templates/release_search.html7
-rw-r--r--python/fatcat_web/templates/search_macros.html68
11 files changed, 208 insertions, 107 deletions
diff --git a/python/fatcat_web/editing_routes.py b/python/fatcat_web/editing_routes.py
index 44000b1a..ffce35f3 100644
--- a/python/fatcat_web/editing_routes.py
+++ b/python/fatcat_web/editing_routes.py
@@ -25,7 +25,7 @@ def form_editgroup_get_or_create(api, edit_form):
edit_form.editgroup_id.errors.append("Editgroup does not exist")
return None
app.log.warning(ae)
- abort(ae.status)
+ raise ae
if eg.changelog_index:
edit_form.editgroup_id.errors.append("Editgroup has already been accepted")
return None
@@ -36,7 +36,7 @@ def form_editgroup_get_or_create(api, edit_form):
Editgroup(description=edit_form.editgroup_description.data or None))
except ApiException as ae:
app.log.warning(ae)
- abort(ae.status)
+ raise ae
# set this session editgroup_id
flash('Started new editgroup <a href="/editgroup/{}">{}</a>'.format(
eg.editgroup_id,
@@ -79,8 +79,7 @@ def generic_entity_edit(editgroup_id, entity_type, existing_ident, edit_template
# check that editgroup is edit-able
if editgroup.changelog_index != None:
- flash("Editgroup already merged")
- abort(400)
+ abort(400, "Editgroup already merged")
# fetch entity (if set) or 404
existing = None
@@ -123,7 +122,7 @@ def generic_entity_edit(editgroup_id, entity_type, existing_ident, edit_template
raise NotImplementedError
except ApiException as ae:
app.log.warning(ae)
- abort(ae.status)
+ raise ae
return redirect('/editgroup/{}/{}/{}'.format(editgroup.editgroup_id, entity_type, edit.ident))
else: # it's an update
# all the tricky logic is in the update method
@@ -151,7 +150,7 @@ def generic_entity_edit(editgroup_id, entity_type, existing_ident, edit_template
if ae.status == 404:
pass
else:
- abort(ae.status)
+ raise ae
try:
if entity_type == 'container':
edit = user_api.update_container(editgroup.editgroup_id, existing.ident, existing)
@@ -163,7 +162,7 @@ def generic_entity_edit(editgroup_id, entity_type, existing_ident, edit_template
raise NotImplementedError
except ApiException as ae:
app.log.warning(ae)
- abort(ae.status)
+ raise ae
return redirect('/editgroup/{}/{}/{}'.format(editgroup.editgroup_id, entity_type, edit.ident))
else:
status = 400
@@ -202,12 +201,11 @@ def generic_edit_delete(editgroup_id, entity_type, edit_id):
try:
editgroup = api.get_editgroup(editgroup_id)
except ApiException as ae:
- abort(ae.status)
+ raise ae
# check that editgroup is edit-able
if editgroup.changelog_index != None:
- flash("Editgroup already merged")
- abort(400)
+ abort(400, "Editgroup already merged")
# API on behalf of user
user_api = auth_api(session['api_token'])
@@ -223,7 +221,7 @@ def generic_edit_delete(editgroup_id, entity_type, edit_id):
else:
raise NotImplementedError
except ApiException as ae:
- abort(ae.status)
+ raise ae
return redirect("/editgroup/{}".format(editgroup_id))
diff --git a/python/fatcat_web/kafka.py b/python/fatcat_web/kafka.py
index 44aac5fb..1d7288af 100644
--- a/python/fatcat_web/kafka.py
+++ b/python/fatcat_web/kafka.py
@@ -31,4 +31,4 @@ def kafka_pixy_produce(topic, msg, key=None, sync=True, timeout=25):
timeout=timeout,
)
resp.raise_for_status()
- print(resp.json())
+ #print(resp.json())
diff --git a/python/fatcat_web/routes.py b/python/fatcat_web/routes.py
index 4a66b3c2..6f3ec21b 100644
--- a/python/fatcat_web/routes.py
+++ b/python/fatcat_web/routes.py
@@ -14,7 +14,7 @@ from fatcat_tools.normal import *
from fatcat_web import app, api, auth_api, priv_api, mwoauth, Config
from fatcat_web.auth import handle_token_login, handle_logout, load_user, handle_ia_xauth, handle_wmoauth
from fatcat_web.cors import crossdomain
-from fatcat_web.search import ReleaseQuery, GenericQuery, do_release_search, do_container_search, get_elastic_entity_stats, get_elastic_container_stats, get_elastic_container_histogram
+from fatcat_web.search import ReleaseQuery, GenericQuery, do_release_search, do_container_search, get_elastic_entity_stats, get_elastic_container_stats, get_elastic_container_histogram, FatcatSearchError
from fatcat_web.entity_helpers import *
from fatcat_web.graphics import *
from fatcat_web.kafka import *
@@ -138,7 +138,7 @@ def generic_lookup_view(entity_type, lookup_template, extid_types, lookup_lambda
ae.status)
else:
app.log.info(ae)
- abort(ae.status)
+ raise ae
return redirect('/{}/{}'.format(entity_type, resp.ident))
@app.route('/container/lookup', methods=['GET'])
@@ -503,8 +503,7 @@ def editgroup_create_annotation(ident):
try:
eg = user_api.get_editgroup(str(ident))
if eg.changelog_index:
- flash("Editgroup already accepted")
- abort(400)
+ abort(400, "Editgroup already accepted")
ega = EditgroupAnnotation(
comment_markdown=comment_markdown,
extra=None,
@@ -512,7 +511,7 @@ def editgroup_create_annotation(ident):
user_api.create_editgroup_annotation(eg.editgroup_id, ega)
except ApiException as ae:
app.log.info(ae)
- abort(ae.status)
+ raise ae
return redirect('/editgroup/{}'.format(ident))
@app.route('/editgroup/<ident>/accept', methods=['POST'])
@@ -525,8 +524,7 @@ def editgroup_accept(ident):
try:
eg = user_api.get_editgroup(str(ident))
if eg.changelog_index:
- flash("Editgroup already accepted")
- abort(400)
+ abort(400, "Editgroup already accepted")
user_api.accept_editgroup(str(ident))
except ApiException as ae:
app.log.info(ae)
@@ -543,8 +541,7 @@ def editgroup_unsubmit(ident):
try:
eg = user_api.get_editgroup(str(ident))
if eg.changelog_index:
- flash("Editgroup already accepted")
- abort(400)
+ abort(400, "Editgroup already accepted")
user_api.update_editgroup(eg.editgroup_id, eg, submit=False)
except ApiException as ae:
app.log.info(ae)
@@ -557,16 +554,13 @@ def editgroup_submit(ident):
if not app.testing:
app.csrf.protect()
# on behalf of user...
- print("submitting...")
user_api = auth_api(session['api_token'])
try:
eg = user_api.get_editgroup(str(ident))
if eg.changelog_index:
- flash("Editgroup already accepted")
- abort(400)
+ abort(400, "Editgroup already accepted")
user_api.update_editgroup(eg.editgroup_id, eg, submit=True)
except ApiException as ae:
- print(ae)
app.log.info(ae)
abort(ae.status)
return redirect('/editgroup/{}'.format(ident))
@@ -655,7 +649,6 @@ def release_save(ident):
json.dumps(msg, sort_keys=True),
)
except Exception as e:
- print(e, file=sys.stderr)
return render_template('release_save.html', entity=release, form=form, spn_status='kafka-error'), 500
return render_template('release_save.html', entity=release, form=form, spn_status='success'), 200
elif form.errors:
@@ -710,7 +703,10 @@ def release_search():
return render_template('release_search.html', query=ReleaseQuery(), found=None)
query = ReleaseQuery.from_args(request.args)
- found = do_release_search(query)
+ try:
+ found = do_release_search(query)
+ except FatcatSearchError as fse:
+ return render_template('release_search.html', query=query, es_error=fse), fse.status_code
return render_template('release_search.html', query=query, found=found)
@app.route('/container/search', methods=['GET', 'POST'])
@@ -720,7 +716,10 @@ def container_search():
return render_template('container_search.html', query=GenericQuery(), found=None)
query = GenericQuery.from_args(request.args)
- found = do_container_search(query)
+ try:
+ found = do_container_search(query)
+ except FatcatSearchError as fse:
+ return render_template('container_search.html', query=query, es_error=fse), fse.status_code
return render_template('container_search.html', query=query, found=found)
def get_changelog_stats():
@@ -759,12 +758,11 @@ def stats_json():
@crossdomain(origin='*',headers=['access-control-allow-origin','Content-Type'])
def container_issnl_stats(issnl):
if not (len(issnl) == 9 and issnl[4] == '-'):
- flash("Not a valid ISSN-L: {}".format(issnl))
- abort(400)
+ abort(400, "Not a valid ISSN-L: {}".format(issnl))
try:
container = api.lookup_container(issnl=issnl)
except ApiException as ae:
- abort(ae.status)
+ raise ae
try:
stats = get_elastic_container_stats(container.ident, issnl=container.issnl)
except Exception as ae:
@@ -820,7 +818,7 @@ def release_bibtex(ident):
try:
entity = api.get_release(ident)
except ApiException as ae:
- abort(ae.status)
+ raise ae
csl = release_to_csl(entity)
bibtex = citeproc_csl(csl, 'bibtex')
return Response(bibtex, mimetype="text/plain")
@@ -837,7 +835,7 @@ def release_citeproc(ident):
try:
entity = api.get_release(ident)
except ApiException as ae:
- abort(ae.status)
+ raise ae
csl = release_to_csl(entity)
cite = citeproc_csl(csl, style, is_html)
if is_html:
@@ -895,7 +893,7 @@ def change_username():
editor = user_api.update_editor(editor.editor_id, editor)
except ApiException as ae:
app.log.info(ae)
- abort(ae.status)
+ raise ae
# update our session
session['editor'] = editor.to_dict()
load_user(editor.editor_id)
@@ -914,8 +912,7 @@ def create_auth_token():
duration_seconds = int(duration_seconds)
assert duration_seconds >= 1
except (ValueError, AssertionError):
- flash("duration_seconds must be a positive non-zero integer")
- abort(400)
+ abort(400, "duration_seconds must be a positive non-zero integer")
# check user's auth. api_token and editor_id are signed together in session
# cookie, so if api_token is valid editor_id is assumed to match. If that
@@ -932,7 +929,7 @@ def create_auth_token():
duration_seconds=duration_seconds)
except ApiException as ae:
app.log.info(ae)
- abort(ae.status)
+ raise ae
return render_template('auth_token.html', auth_token=resp.token)
@app.route('/auth/logout')
@@ -983,7 +980,7 @@ def page_method_not_allowed(e):
@app.errorhandler(400)
def page_bad_request(e):
- return render_template('400.html'), 400
+ return render_template('400.html', err=e), 400
@app.errorhandler(409)
def page_edit_conflict(e):
@@ -999,6 +996,36 @@ def page_server_error(e):
def page_server_down(e):
return render_template('503.html'), 503
+@app.errorhandler(ApiException)
+def page_fatcat_api_error(ae):
+ """
+ Generic error handler for fatcat API problems. With this error handler,
+ don't need to explicitly catch API exceptions: they should get caught and
+ routed correctly here.
+ """
+ if ae.status == 404:
+ return page_not_found(ae)
+ elif ae.status in [401, 403]:
+ return page_not_authorized(ae)
+ elif ae.status in [405]:
+ return page_method_not_allowed(ae)
+ elif ae.status in [409]:
+ return page_edit_conflict(ae)
+ try:
+ json_body = json.loads(ae.body)
+ ae.error_name = json_body.get('error')
+ ae.message = json_body.get('message')
+ except ValueError:
+ pass
+ return render_template('api_error.html', api_error=ae), ae.status
+
+@app.errorhandler(ApiValueError)
+def page_fatcat_api_value_error(ae):
+ ae.status = 400
+ ae.error_name = "ValueError"
+ ae.message = str(ae)
+ return render_template('api_error.html', api_error=ae), 400
+
@app.errorhandler(CSRFError)
def page_csrf_error(e):
return render_template('csrf_error.html', reason=e.description), 400
diff --git a/python/fatcat_web/search.py b/python/fatcat_web/search.py
index 55caa9c5..3fd7f9dc 100644
--- a/python/fatcat_web/search.py
+++ b/python/fatcat_web/search.py
@@ -15,6 +15,15 @@ import elasticsearch_dsl.response
from fatcat_web import app
+class FatcatSearchError(Exception):
+
+ def __init__(self, status_code: int, name: str, description: str = None):
+ if status_code == "N/A":
+ status_code = 503
+ self.status_code = status_code
+ self.name = name
+ self.description = description
+
@dataclass
class ReleaseQuery:
q: Optional[str] = None
@@ -109,14 +118,19 @@ def wrap_es_execution(search: Search) -> Any:
except elasticsearch.exceptions.RequestError as e:
# this is a "user" error
print("elasticsearch 400: " + str(e.info), file=sys.stderr)
+ description = None
if e.info.get("error", {}).get("root_cause", {}):
- raise ValueError(str(e.info["error"]["root_cause"][0].get("reason")))
- else:
- raise ValueError(str(e.info))
+ description = str(e.info["error"]["root_cause"][0].get("reason"))
+ raise FatcatSearchError(e.status_code, str(e.error), description)
+ except elasticsearch.exceptions.ConnectionError as e:
+ raise FatcatSearchError(e.status_code, "ConnectionError: search engine not available")
except elasticsearch.exceptions.TransportError as e:
# all other errors
print("elasticsearch non-200 status code: {}".format(e.info), file=sys.stderr)
- raise IOError(str(e.info))
+ description = None
+ if e.info.get("error", {}).get("root_cause", {}):
+ description = str(e.info["error"]["root_cause"][0].get("reason"))
+ raise FatcatSearchError(e.status_code, str(e.error), description)
return resp
def do_container_search(
@@ -192,6 +206,7 @@ def do_release_search(
Q("bool", must_not=Q("exists", field="release_year")),
Q("bool", must_not=Q("exists", field="release_type")),
Q("bool", must_not=Q("exists", field="release_stage")),
+ Q("bool", must_not=Q("exists", field="container_id")),
],
)
diff --git a/python/fatcat_web/templates/400.html b/python/fatcat_web/templates/400.html
index f2659ca2..21f98aad 100644
--- a/python/fatcat_web/templates/400.html
+++ b/python/fatcat_web/templates/400.html
@@ -4,10 +4,20 @@
<center>
<div style="font-size: 8em;">400</div>
<div style="font-size: 3em;">Bad Request</div>
-
-<p>Wasn't able to handle the request, either due to incorrect or unexpected
-input. Usually more context should be available; if you hit this page it means
-you've discovered a new corner case!
</center>
+<div class="ui icon error message">
+ <i class="ban icon"></i>
+ <div class="content">
+ <div class="header"></div>
+ {% if err and err.description %}
+ <p>{{ err.description }}
+ {% else %}
+ <p>Wasn't able to handle the request, either due to incorrect or unexpected
+ input. Usually more context should be available; if you hit this page it means
+ you've discovered a new corner case!
+ {% endif %}
+ </div>
+</div>
+
{% endblock %}
diff --git a/python/fatcat_web/templates/api_error.html b/python/fatcat_web/templates/api_error.html
new file mode 100644
index 00000000..1a44f610
--- /dev/null
+++ b/python/fatcat_web/templates/api_error.html
@@ -0,0 +1,20 @@
+{% extends "base.html" %}
+{% block body %}
+
+<center>
+<div style="font-size: 8em;">{{ api_error.status or "" }}</div>
+<div style="font-size: 3em;">{{ api_error.reason or "" }}</div>
+</center>
+
+<div class="ui icon error message">
+ <i class="ban icon"></i>
+ <div class="content">
+ <div class="header">
+ API Error: {{ api_error.error_name or "" }}
+ </div>
+ <p>{{ api_error.message or "" }}
+ </div>
+</div>
+
+
+{% endblock %}
diff --git a/python/fatcat_web/templates/container_search.html b/python/fatcat_web/templates/container_search.html
index 2566f542..bd92dc2b 100644
--- a/python/fatcat_web/templates/container_search.html
+++ b/python/fatcat_web/templates/container_search.html
@@ -1,4 +1,4 @@
-{% import "entity_macros.html" as entity_macros %}
+{% import "search_macros.html" as search_macros %}
{% extends "base.html" %}
{% block title %}
@@ -32,7 +32,7 @@
{% if found %}
{% if found.results %}
- {{ entity_macros.top_results(query, found) }}
+ {{ search_macros.top_results(query, found) }}
{% for entity in found.results %}
<div>
@@ -55,7 +55,7 @@
{% if found.results|length > 8 %}
<div class="ui divider"></div>
<div style="text-align: center">
- {{ entity_macros.bottom_results(query, found, endpoint='container_search') }}
+ {{ search_macros.bottom_results(query, found, endpoint='container_search') }}
</div>
{% endif %}
@@ -80,6 +80,8 @@
{% endif %}
+{% elif es_error %}
+ {{ search_macros.es_error_msg(es_error) }}
{% endif %}
</div>
diff --git a/python/fatcat_web/templates/entity_macros.html b/python/fatcat_web/templates/entity_macros.html
index 0e7f135a..8e4c4f6a 100644
--- a/python/fatcat_web/templates/entity_macros.html
+++ b/python/fatcat_web/templates/entity_macros.html
@@ -162,12 +162,10 @@
{# release type suffix #}
{% if paper.release_type in ("article-journal", "paper-conference") %}
{# pass #}
- {% elif paper.release_type in ("book", "chapter", "dataset") %}
- <b style="text-transform: uppercase;">[{{ paper.release_type }}]</b>
{% elif not paper.release_type %}
- <b style="text-transform: uppercase; color: black;">[unknown-media]</b>
+ <b style="text-transform: uppercase; color: black;">[unknown]</b>
{% else %}
- <b style="text-transform: uppercase;">[{{ paper.release_type }}]</b>
+ <b style="text-transform: uppercase; color: black;">[{{ paper.release_type }}]</b>
{% endif %}
{# show original_title #}
@@ -212,17 +210,17 @@
{% endif %}
{% if paper.container_is_oa %}<i class="icon unlock orange small"></i>{% endif %}
{% endif %}
- {% if paper.withdrawn_status %}
- <b style="color: red;"><code>[{{ paper.withdrawn_status }}]</code></b>
- {% endif %}
- {% if paper.release_stage == "accepted" %}
- <b style="color: darkmagenta;"><code>[{{ paper.release_stage }} manuscript]</code></b>
- {% elif paper.release_stage == "submitted" %}
- <b style="color: magenta;"><code>[pre-print]</code></b>
+
+ {% if paper.release_stage == "submitted" %}
+ <b style="color: brown; text-transform: uppercase;">pre-print</b>
{% elif paper.release_stage and paper.release_stage != "published" %}
- <b style="color: magenta;"><code>[{{ paper.release_stage }}]</code></b>
+ <b style="color: brown; text-transform: uppercase;">{{ paper.release_stage }} version</b>
{% elif not paper.release_stage %}
- <b style="color: red;"><code>[unpublished?]</code></b>
+ <b style="color: brown; text-transform: uppercase;">unpublished</b>
+ {% endif %}
+
+ {% if paper.withdrawn_status %}
+ <b style="color: red; text-transform: uppercase;">{{ paper.withdrawn_status }}</b>
{% endif %}
{# ### IDENTIFIERS #}
@@ -262,43 +260,3 @@ yellow
{% endif %}
{%- endmacro %}
-{% macro top_results(query, found) -%}
-
-<i>Showing
- {% if found.offset == 0 %}
- first
- {% else %}
- results {{ found.offset }} &mdash;
- {% endif %}
-
- {{ found.offset + found.count_returned }}
- out of {{ found.count_found }} results
-</i>
-
-{%- endmacro %}
-
-
-{% macro bottom_results(query, found, endpoint='release_search') -%}
-
-{% if found.offset > 0 %}
- {% if found.offset - found.limit < 0 %}
- <a href="{{ url_for(endpoint, q=query.q, offset=0) }}">&#xab; Previous</a>
- {% else %}
- <a href="{{ url_for(endpoint, q=query.q, offset=found.offset - found.limit) }}">&#xab; Previous</a>
- {% endif %}
-{% else %}
- <span style="color:gray">&#xab; Previous</span>
-{% endif %}
-
-&nbsp;&nbsp;<i>Showing results {{ found.offset }} &mdash; {{ found.offset +
-found.count_returned }} out of {{ found.count_found }} results</i>&nbsp;&nbsp;
-
-{% if found.offset + found.limit < found.count_found and found.offset + found.limit < found.deep_page_limit %}
- <a href="{{ url_for(endpoint, q=query.q, offset=found.offset + found.limit) }}">Next &#xbb;</a>
- {% else %}
- <span style="color:gray">Next &#xbb;</span>
-{% endif %}
-
-</div>
-
-{%- endmacro %}
diff --git a/python/fatcat_web/templates/home.html b/python/fatcat_web/templates/home.html
index 698230d3..4557e212 100644
--- a/python/fatcat_web/templates/home.html
+++ b/python/fatcat_web/templates/home.html
@@ -35,17 +35,17 @@
<div class="row">
<div class="four wide mobile three wide center aligned column">
<a href="/stats" style="color: black;">
- <h4>106,283,000<br>Papers</h4>
+ <h4>110,814,532<br>Papers</h4>
</a>
</div>
<div class="four wide mobile three wide center aligned column">
<a href="/stats" style="color: black;">
- <h4>23,036,825<br>Fulltext</h4>
+ <h4>26,173,743<br>Fulltext</h4>
</a>
</div>
<div class="four wide mobile three wide center aligned column">
<a href="/stats" style="color: black;">
- <h4>148,757<br>Journals</h4>
+ <h4>151,707<br>Journals</h4>
</a>
</div>
</div>
diff --git a/python/fatcat_web/templates/release_search.html b/python/fatcat_web/templates/release_search.html
index 58aa35d6..7fb475e3 100644
--- a/python/fatcat_web/templates/release_search.html
+++ b/python/fatcat_web/templates/release_search.html
@@ -1,4 +1,5 @@
{% import "entity_macros.html" as entity_macros %}
+{% import "search_macros.html" as search_macros %}
{% extends "base.html" %}
{% block title %}
@@ -37,7 +38,7 @@
{% if found %}
{% if found.results %}
- {{ entity_macros.top_results(query, found) }}
+ {{ search_macros.top_results(query, found) }}
{% for paper in found.results %}
{{ entity_macros.release_search_result_row(paper) }}
@@ -46,7 +47,7 @@
{% if found.results|length > 8 %}
<div class="ui divider"></div>
<div style="text-align: center">
- {{ entity_macros.bottom_results(query, found, endpoint='release_search') }}
+ {{ search_macros.bottom_results(query, found, endpoint='release_search') }}
</div>
{% endif %}
@@ -73,6 +74,8 @@
{% endif %}
+{% elif es_error %}
+ {{ search_macros.es_error_msg(es_error) }}
{% endif %}
</div>
diff --git a/python/fatcat_web/templates/search_macros.html b/python/fatcat_web/templates/search_macros.html
new file mode 100644
index 00000000..a207bfbc
--- /dev/null
+++ b/python/fatcat_web/templates/search_macros.html
@@ -0,0 +1,68 @@
+
+{% macro top_results(query, found) -%}
+
+<i>Showing
+ {% if found.offset == 0 %}
+ first
+ {% else %}
+ results {{ found.offset }} &mdash;
+ {% endif %}
+
+ {{ found.offset + found.count_returned }}
+ out of {{ found.count_found }} results
+</i>
+
+{%- endmacro %}
+
+
+{% macro bottom_results(query, found, endpoint='release_search') -%}
+
+{% if found.offset > 0 %}
+ {% if found.offset - found.limit < 0 %}
+ <a href="{{ url_for(endpoint, q=query.q, offset=0) }}">&#xab; Previous</a>
+ {% else %}
+ <a href="{{ url_for(endpoint, q=query.q, offset=found.offset - found.limit) }}">&#xab; Previous</a>
+ {% endif %}
+{% else %}
+ <span style="color:gray">&#xab; Previous</span>
+{% endif %}
+
+&nbsp;&nbsp;<i>Showing results {{ found.offset }} &mdash; {{ found.offset +
+found.count_returned }} out of {{ found.count_found }} results</i>&nbsp;&nbsp;
+
+{% if found.offset + found.limit < found.count_found and found.offset + found.limit < found.deep_page_limit %}
+ <a href="{{ url_for(endpoint, q=query.q, offset=found.offset + found.limit) }}">Next &#xbb;</a>
+ {% else %}
+ <span style="color:gray">Next &#xbb;</span>
+{% endif %}
+
+</div>
+
+{%- endmacro %}
+
+
+{% macro es_error_msg(es_error) %}
+ <div class="ui icon error message">
+ <i class="ban icon"></i>
+ <div class="content">
+ <div class="header">
+ {% if es_error.status_code == 400 %}
+ Query Error
+ {% else %}
+ Search Index Error
+ {% if es_error.status_code %}({{ es_error.status_code }}){% endif %}
+ {% endif %}
+ </div>
+ {% if es_error.description %}
+ <p>Computer said: <code>{{ es_error.description }}</code>
+ {% elif es_error.name %}
+ <p><b>{{ es_error.name }}</b>
+ {% endif %}
+ {% if es_error.status_code == 400 %}
+ <p>Query parsing is currently very naive. Sometimes you can fix this
+ problem by adding quotes around terms or entire phrases.
+ {% endif %}
+ </div>
+ </div>
+{% endmacro %}
+