Coverage for portality / view / openurl.py: 92%
48 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-04 09:41 +0100
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-04 09:41 +0100
1import re
2from flask import Blueprint, request, redirect, url_for, render_template, abort
3from portality.models import OpenURLRequest
4from portality.lib import plausible
5from portality.core import app
6from urllib.parse import unquote
7from portality.ui import templates
9blueprint = Blueprint('openurl', __name__)
12@blueprint.route("/openurl", methods=["GET", "POST"])
13def openurl():
14 # check that we've been given some arguments at all
15 if len(request.values) == 0:
16 abort(404)
18 # Decode and unquote the query string, which comes in as bytes.
19 qs = unquote(request.query_string.decode('utf-8'))
21 # Validate the query syntax version and build an object representing it
22 parser_response = parse_query(query=qs, req=request)
24 # theoretically this can return None, so catch it
25 if parser_response is None:
26 abort(404)
28 # If it's not parsed to an OpenURLRequest, it's a redirect URL to try again.
29 if type(parser_response) != OpenURLRequest:
30 return redirect(parser_response, 301)
32 # Log this request to analytics
33 plausible.send_event(app.config.get('ANALYTICS_CATEGORY_OPENURL', 'OpenURL'),
34 action=parser_response.genre,
35 label=qs)
37 # Get the OpenURLRequest object to issue a query and supply a url for the result
38 result_url = parser_response.get_result_url()
39 if result_url:
40 return redirect(result_url)
41 else:
42 abort(404)
45def parse_query(query, req):
46 """
47 Create the model which holds the query
48 :param query: The query string from the request URL (separated for the sake of analytics)
49 :param req: an incoming OpenURL request
50 :return: an object representing the query, or a redirect to the reissued query, or None if failed.
51 """
52 # Check if this is new or old syntax, translate if necessary
53 if "url_ver=Z39.88-2004" not in query:
54 app.logger.info("Legacy OpenURL 0.1 request: " + unquote(req.url))
55 return old_to_new(req)
57 app.logger.info("OpenURL 1.0 request: " + unquote(req.url))
59 # Wee function to strip of the referent namespace prefix from parameters
60 rem_ns = lambda x: re.sub('rft.', '', x)
62 # Pack the list of parameters into a dictionary, while un-escaping the string.
63 dict_params = {rem_ns(key): value for (key, value) in req.values.items()}
65 # Create an object to represent this OpenURL request.
66 try:
67 query_object = OpenURLRequest(**dict_params)
68 except:
69 query_object = None
70 app.logger.info("Failed to create OpenURLRequest object")
72 return query_object
75def old_to_new(req):
76 """
77 Translate the OpenURL 0.1 syntax to 1.0, to provide a redirect.
78 :param req: An incoming OpenURL request
79 :return: An OpenURL 1.0 query string
80 """
82 # The meta parameters in the preamble.
83 params = {'url_ver': 'Z39.88-2004', 'url_ctx_fmt': 'info:ofi/fmt:kev:mtx:ctx', 'rft_val_fmt': 'info:ofi/fmt:kev:mtx:journal'}
85 # In OpenURL 0.1, jtitle is just title. This function substitutes them.
86 sub_title = lambda x: re.sub('^title', 'jtitle', x)
88 # Add referent tags to each parameter, and change title tag using above function
89 rewritten_params = {"rft." + sub_title(key): value for (key, value) in req.values.items()}
91 # Add the rewritten parameters to the meta params
92 params.update(rewritten_params)
94 return url_for('.openurl', **params)
97@blueprint.route("/openurl/help")
98def help():
99 return render_template(templates.OPENURL_HELP)
102@blueprint.errorhandler(404)
103def bad_request(e):
104 return render_template(templates.OPENURL_404), 404