Coverage for portality / view / openurl.py: 92%

48 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-05 00:09 +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 

8 

9blueprint = Blueprint('openurl', __name__) 

10 

11 

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) 

17 

18 # Decode and unquote the query string, which comes in as bytes. 

19 qs = unquote(request.query_string.decode('utf-8')) 

20 

21 # Validate the query syntax version and build an object representing it 

22 parser_response = parse_query(query=qs, req=request) 

23 

24 # theoretically this can return None, so catch it 

25 if parser_response is None: 

26 abort(404) 

27 

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) 

31 

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) 

36 

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) 

43 

44 

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) 

56 

57 app.logger.info("OpenURL 1.0 request: " + unquote(req.url)) 

58 

59 # Wee function to strip of the referent namespace prefix from parameters 

60 rem_ns = lambda x: re.sub('rft.', '', x) 

61 

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()} 

64 

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

71 

72 return query_object 

73 

74 

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

81 

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'} 

84 

85 # In OpenURL 0.1, jtitle is just title. This function substitutes them. 

86 sub_title = lambda x: re.sub('^title', 'jtitle', x) 

87 

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()} 

90 

91 # Add the rewritten parameters to the meta params 

92 params.update(rewritten_params) 

93 

94 return url_for('.openurl', **params) 

95 

96 

97@blueprint.route("/openurl/help") 

98def help(): 

99 return render_template(templates.OPENURL_HELP) 

100 

101 

102@blueprint.errorhandler(404) 

103def bad_request(e): 

104 return render_template(templates.OPENURL_404), 404