Commit 68af0f8094802e278a34d1fc1595bb7a9904ba9f

Add pagination to OCD search results, a different URL for oauth, and fixes

  - Add pagination to OCD search results.
  - Change server code to have a different endpoint (redirect URI of swtstore)
    to process the OAuth authentication.
  - Fix bug when entering input which is not a URL, the app would throw 500;
    now it handles that error.
  - Fix overlay not showing for the entire page.
  
11# -*- coding: utf-8 -*-
22
3import flask
4from flask import session
3from flask import Flask, session, request, make_response, url_for, redirect,\
4 render_template, jsonify, abort
55import lxml.html
66import requests
77import json
1212import config
1313
1414
15app = flask.Flask(__name__)
15app = Flask(__name__)
1616app.config['SECRET_KEY'] = config.secret_key
1717
1818
1919@app.route('/', methods=['GET'])
2020def index():
21 auth_tok = None
22 # check if ?code param is there
23 if flask.request.args.get('code'):
24 # prepare the payload
25 payload = {
26 'scopes': 'email sweet',
27 'client_secret': config.app_secret,
28 'code': flask.request.args.get('code'),
29 'redirect_uri': config.redirect_uri,
30 'grant_type': 'authorization_code',
31 'client_id': config.app_id
32 }
33 # token exchange endpoint
34 oauth_token_x_endpoint = config.swtstoreURL + '/oauth/token'
35 resp = requests.post(oauth_token_x_endpoint, data=payload)
36 auth_tok = json.loads(resp.text)
37 print 'recvd auth token from swtstore'
38 print auth_tok
3921
40 if 'error' in auth_tok:
41 print auth_tok['error']
42 return flask.make_response(auth_tok['error'], 200)
43
44 # set sessions et al
45 session['auth_tok'] = auth_tok
46 session['auth_tok']['issued'] = datetime.utcnow()
47
48 # else if session is already existing..
22 # if auth_tok is in session already..
4923 if 'auth_tok' in session:
5024 auth_tok = session['auth_tok']
25
5126 # check if it has expired
5227 oauth_token_expires_in_endpoint = config.swtstoreURL +\
5328 '/oauth/token-expires-in'
5429 resp = requests.get(oauth_token_expires_in_endpoint)
5530 expires_in = json.loads(resp.text)['expires_in']
31
5632 # added for backwared compatibility. previous session stores did not
5733 # have issued key
5834 try:
5050
5151 #print 'existing tokens'
5252 #print auth_tok
53 return flask.render_template('index.html',
54 access_token=auth_tok['access_token'],
55 refresh_token=auth_tok['refresh_token'],
56 config=config,
57 url=flask.request.args.get('where'))
53 return render_template('index.html',
54 access_token=auth_tok['access_token'],
55 refresh_token=auth_tok['refresh_token'],
56 config=config,
57 url=request.args.get('where'))
5858
5959
60@app.route('/authenticate', methods=['GET'])
61def authenticateWithOAuth():
62 auth_tok = None
63 code = request.args.get('code')
64
65 # prepare the payload
66 payload = {
67 'scopes': 'email sweet',
68 'client_secret': config.app_secret,
69 'code': code,
70 'redirect_uri': config.redirect_uri,
71 'grant_type': 'authorization_code',
72 'client_id': config.app_id
73 }
74
75 # token exchange endpoint
76 oauth_token_x_endpoint = config.swtstoreURL + '/oauth/token'
77 resp = requests.post(oauth_token_x_endpoint, data=payload)
78 auth_tok = json.loads(resp.text)
79 print 'recvd auth token from swtstore'
80 print auth_tok
81
82 if 'error' in auth_tok:
83 print auth_tok['error']
84 return make_response(auth_tok['error'], 200)
85
86 # set sessions et al
87 session['auth_tok'] = auth_tok
88 session['auth_tok']['issued'] = datetime.utcnow()
89 return redirect(url_for('index'))
90
91
6092# endpoint to search the Open Cuultur Data APIs
6193# takes in `query`, `size`, and `from` parameters in query string
6294# returns a JSON response
6395@app.route('/search/ocd', methods=['GET'])
6496def searchOCD():
65 query = flask.request.args.get('query')
97 query = request.args.get('query')
6698 #collection = flask.request.args.get('collection')
67 size = flask.request.args.get('size') or 10
68 offset = flask.request.args.get('from') or 0
99 size = request.args.get('size') or 9
100 offset = request.args.get('from') or 0
69101
70102 # if query parameter is not passed, return bad request.
71103 if not query:
72 flask.abort(400)
104 abort(400)
73105
74106 payload = {
75107 'query': query,
118118 resp = requests.post('http://api.opencultuurdata.nl/v0/search',
119119 data=json.dumps(payload))
120120
121 response = flask.make_response()
121 response = make_response()
122122 response.data = json.dumps(resp.json())
123123 response.headers['Content-type'] = 'application/json'
124124 return response
128128@app.route('/resolve-ocd-media', methods=['GET'])
129129def resolveOCDMediaURLs():
130130
131 media_hash = flask.request.args.get('hash') or None
131 media_hash = request.args.get('hash') or None
132132
133133 if not media_hash:
134 flask.abort(400)
134 abort(400)
135135
136136 resp = requests.get('http://api.opencultuurdata.nl/v0/resolve/' +
137137 media_hash)
138138
139 return flask.jsonify(url=resp.url)
139 return jsonify(url=resp.url)
140140
141141
142142@app.route('/media-type', methods=['GET'])
143143def getMediaType():
144144
145 where = flask.request.args.get('where') or None
145 where = request.args.get('where') or None
146146
147147 if not where:
148 flask.abort(400)
148 abort(400)
149149
150150 resp = requests.get(where)
151151 content = resp.text
152152
153153 if imghdr.what('ignore', content) is None:
154 return flask.jsonify({'type': 'html'})
154 return jsonify({'type': 'html'})
155155 else:
156 return flask.jsonify({'type': 'image'})
156 return jsonify({'type': 'image'})
157157
158158
159159@app.route('/annotate', methods=['GET'])
160160def annotate():
161161 # img = urllib2.urlopen(flask.request.args['where']).read()
162 request = requests.get(flask.request.args['where'])
163 content = request.text
162 where = request.args.get('where')
163 response = requests.get(where)
164 content = response.text
164165 if imghdr.what('ignore', content) is None:
165166 root = lxml.html.parse(StringIO.StringIO(content)).getroot()
166 root.make_links_absolute(flask.request.args['where'],
167 root.make_links_absolute(where,
167168 resolve_base_href=True)
168169
169170 addScript("//code.jquery.com/jquery-1.11.0.min.js", root)
170 addScript(flask.url_for('static', filename="js/annotator-full.min.js"),
171 addScript(url_for('static', filename="js/annotator-full.min.js"),
171172 root)
172173
173 addCSS(flask.url_for('static', filename='css/annotator.min.css'), root)
174 addCSS(flask.url_for('static', filename='css/swtmaker.css'), root)
175 addCSS(flask.url_for('static', filename='css/bootstrap.min.css'), root)
176 addScript(flask.url_for('static',
177 filename="js/lib/underscore-1.5.2.min.js"),
174 addCSS(url_for('static', filename='css/annotator.min.css'), root)
175 addCSS(url_for('static', filename='css/swtmaker.css'), root)
176 addCSS(url_for('static', filename='css/bootstrap.min.css'), root)
177 addScript(url_for('static',
178 filename="js/lib/underscore-1.5.2.min.js"),
178179 root)
179 addScript(flask.url_for('static',
180 filename="js/lib/backbone-1.0.0.min.js"),
180 addScript(url_for('static',
181 filename="js/lib/backbone-1.0.0.min.js"),
181182 root)
182 addCSS(flask.url_for('static', filename='css/annotorious.css'), root)
183 addScript(flask.url_for('static',
184 filename="js/annotorious.okfn.0.3.js"),
183 addCSS(url_for('static', filename='css/annotorious.css'), root)
184 addScript(url_for('static',
185 filename="js/annotorious.okfn.0.3.js"),
185186 root)
186187
187188 if 'auth_tok' in session:
213213 config.redirect_uri)
214214 configScript.set("type", "text/javascript")
215215
216 addScript(flask.url_for('static', filename="js/oauth.js"), root)
216 addScript(url_for('static', filename="js/oauth.js"), root)
217217
218 addScript(flask.url_for('static', filename="js/app.js"), root)
218 addScript(url_for('static', filename="js/app.js"), root)
219219
220 response = flask.make_response()
220 response = make_response()
221221 response.data = lxml.html.tostring(root)
222222 return response
223223
227227 else:
228228 auth_tok = {'access_token': '', 'refresh_token': ''}
229229
230 return flask.render_template('index.html',
231 access_token=auth_tok['access_token'],
232 refresh_token=auth_tok['refresh_token'],
233 config=config,
234 url=flask.request.args.get('where'))
230 return render_template('index.html',
231 access_token=auth_tok['access_token'],
232 refresh_token=auth_tok['refresh_token'],
233 config=config,
234 url=request.args.get('where'))
235235
236236
237237def addScript(src, el):
  
2222 margin 0 20px;
2323 padding: 10px;
2424 text-align: center;
25 z-index: 100001;
2526}
2627
2728#img-annotation-wrapper, #ocd-results {
7878 font-size: 1.3em;
7979 min-height: 50px;
8080 text-align: center;
81 z-index: 100001;
8182}
8283#app-overlay {
83 position: absolute;
84 top: 60px;
84 position: fixed;
85 top: 0;
8586 left: 0;
8687 display: none;
8788 width: 100%;
88 height: 800px;
89 height: 100%;
8990 z-index: 10000;
9091 background-color: rgba(255, 255, 255, 0.7);
9192}
  
6666 throw Error('"where" option must be passed to get sweets of a URI');
6767 return false;
6868 }
69 /*if(!swtr.access_token) {
70 throw new Error('Access Token required to get query that API');
71 }*/
7269 // setting up params
7370 var where = options.where,
7471 who = options.who || null;
230230 cleanUp: function() {
231231 //console.log('cleaning up');
232232 $(this.el).hide();
233 if(swtr.appView.source === 'ocd') {
234 $('#img-annotation-wrapper').hide();
235 $('#ocd-results').show();
236 }
237233 }
238234 });
239235
299299 this.loadURL(input);
300300 }
301301 },
302 // load a URL for annotation (can be of image or html resource for now)
302303 loadURL: function(url, type) {
303 console.log('loadURL()');
304 //console.log('loadURL()');
304305 if(this.source !== 'ocd') {
305306 $('#ocd-results').hide();
306307 }
307308 $('#img-annotation-wrapper').show();
308 if(!url) {
309 if(!url || !url.match(/http/)) {
310 this.helpview.step(13);
309311 return false;
310312 }
311313 // if type is given explicitly; we load it as such.
605605 var self = this;
606606 $('#img-annotation-wrapper').hide();
607607 $('#ocd-results').show();
608 $('#ocd-results').append('Loading..');
608 $('#ocd-results').html('<h4 style="text-align: center;">Loading..</h4>');
609609 $.ajax({
610610 type: 'GET',
611611 url: '/search/ocd',
612612 data: {query: input},
613613 success: function(data) {
614 self.ocdView = new OCDView({model: data.hits.hits});
614 self.ocdView = new OCDView({
615 query: input,
616 data: data,
617 model: data.hits.hits
618 });
615619 }
616620 });
617621 }
618622 });
619623
620624 var OCDView = Backbone.View.extend({
621 el: $('#ocd-results'),
625 el: $('#ocd-view'),
622626 events: {
623 'click .ocd-item a': 'onClickImg'
627 'click .ocd-item a': 'onImgClick',
628 'click .pager li': 'onPagerClick'
624629 },
625 initialize: function() {
630 initialize: function(opts) {
631 this.data = opts.data || {};
632 this.query = opts.query || '';
633 this.size = 9; // num of items per page
634 this.page = 0;
626635 this.item_template = _.template($('#ocd-item-template').html());
636 this.base_template = _.template($('#ocd-view-base-template').html());
627637 this.render();
628638 },
629639 render: function() {
630640 var $row_el;
631641 this.$el.html('');
642 if(!this.model.length) {
643 this.$el.html('No results could be found from your query.');
644 return;
645 }
646 this.$el.html(this.base_template());
647 var $el = $('#ocd-results');
632648 _.each(this.model, function(item, idx) {
633649 // put every 3 items in a row
634650 if(idx % 3 === 0) {
635651 $row_el = $('<div class="row"></div>');
636 this.$el.append($row_el);
652 $el.append($row_el);
637653 }
638654 $row_el.append(this.item_template({
639655 title: item._source.title,
658658 }));
659659 }, this);
660660 this.resolveOCDURLs();
661 this.appendTotal();
661662 },
663 appendTotal: function() {
664 $('#ocd-total-results').html(this.data.hits.total + ' results found.');
665 },
662666 // resolve the OCD media URLs
663667 resolveOCDURLs: function() {
664668 var self = this;
674674 });
675675 });
676676 },
677 onClickImg: function(event) {
677 rerender: function(data) {
678 this.data = data;
679 this.model = data.hits.hits;
680 this.render();
681 },
682 onPagerClick: function(event) {
678683 event.preventDefault();
684 var elem = $(event.currentTarget);
685 var self = this;
686 if(elem.hasClass('next')) {
687 if((this.page + 1) * this.size >= this.data.hits.total) {
688 console.log('no next page to go to');
689 return false;
690 }
691 console.log('clicked next');
692 this.search({
693 query: this.query,
694 from: (this.page + 1) * this.size
695 }, function(resp) {
696 console.log('reached next page');
697 self.page = self.page + 1;
698 self.rerender(resp);
699 });
700 }
701 else if (elem.hasClass('previous')) {
702 if(this.page <= 0) {
703 console.log('no prev page to go to');
704 return false;
705 }
706 console.log('clicked prev');
707 this.search({
708 query: this.query,
709 from: (this.page - 1) * this.size
710 }, function(resp) {
711 console.log('reached prev page');
712 self.page = self.page - 1;
713 self.rerender(resp);
714 });
715 }
716 return false;
717 },
718 onImgClick: function(event) {
719 event.preventDefault();
679720 // TODO: init the image anno
680721 var url = $(event.currentTarget).find('img').attr('src');
681722 swtr.appView.loadURL(url, 'image');
682723 return false;
724 },
725 search: function(data, cb) {
726 swtr.appView.$overlay.show();
727 var self = this;
728 $.ajax({
729 type: 'GET',
730 url: '/search/ocd',
731 data: data,
732 success: function(resp) {
733 swtr.appView.$overlay.hide();
734 cb(resp);
735 }
736 });
683737 }
684738 });
685739
752752 switch (n) {
753753 case 0 : text = 'Getting annotations..';
754754 break;
755 case 1: text = 'Enter the URL of an image or web page below, and start annotating!';
755 case 1: text = 'Enter URL of an image or web page below, and start annotating!';
756756 break;
757757 case 2: text = 'Annotate the image, or see other annotations';
758758 break;
775775 case 11: text = 'Search in <a href="http://www.opencultuurdata.nl/">Open Cuultur Data API</a>';
776776 break;
777777 case 12: text = 'Analyzing the resource type..';
778 break;
779 case 13: text = 'This does not seem to be a URL. Please enter a valid URL.';
778780 break;
779781 }
780782 $(this.el).html(text);
  
7676 <img src="" id="annotatable-img" class="img-responsive" alt="Annotation Workbench">
7777 {% endif %}
7878 </div>
79 <div id="ocd-results"></div>
79 <div id="ocd-view"></div>
8080 </div> <!-- end fourth row -->
8181
8282 <div id="sweet-list-wrapper">
142142 <li class="custom-popup-line"><span>Related Links: </span><%= Links %></li>
143143 </ul>
144144 </span>
145 </script>
146 <script type="text/template" id="ocd-view-base-template">
147 <div id="ocd-hud">
148 <span id="ocd-total-results"></span>
149 </div>
150 <div id="ocd-results"></div>
151 <ul class="pager">
152 <li class="previous"> <a href="#">&laquo; Previous </a></li>
153 <li class="next"> <a href="#"> Next &raquo; </a></li>
154 </ul>
145155 </script>
146156 <script type="text/template" id="ocd-item-template">
147157 <div class="col-sm-6 col-md-4 ocd-item">