From 68af0f8094802e278a34d1fc1595bb7a9904ba9f Mon Sep 17 00:00:00 2001 From: Anon Ray Date: Wed, 27 Aug 2014 19:11:08 +0530 Subject: [PATCH] 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. --- swtr/server.py | 149 ++++++++++++++++++++++-------------------- swtr/static/css/swtmaker.css | 8 ++- swtr/static/js/swtmaker.js | 107 +++++++++++++++++++++++++----- swtr/templates/index.html | 12 +++- 4 files changed, 184 insertions(+), 92 deletions(-) diff --git a/swtr/server.py b/swtr/server.py index 069b9e3..bf7baa3 100644 --- a/swtr/server.py +++ b/swtr/server.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -import flask -from flask import session +from flask import Flask, session, request, make_response, url_for, redirect,\ + render_template, jsonify, abort import lxml.html import requests import json @@ -12,47 +12,23 @@ from datetime import datetime, timedelta import config -app = flask.Flask(__name__) +app = Flask(__name__) app.config['SECRET_KEY'] = config.secret_key @app.route('/', methods=['GET']) def index(): - auth_tok = None - # check if ?code param is there - if flask.request.args.get('code'): - # prepare the payload - payload = { - 'scopes': 'email sweet', - 'client_secret': config.app_secret, - 'code': flask.request.args.get('code'), - 'redirect_uri': config.redirect_uri, - 'grant_type': 'authorization_code', - 'client_id': config.app_id - } - # token exchange endpoint - oauth_token_x_endpoint = config.swtstoreURL + '/oauth/token' - resp = requests.post(oauth_token_x_endpoint, data=payload) - auth_tok = json.loads(resp.text) - print 'recvd auth token from swtstore' - print auth_tok - - if 'error' in auth_tok: - print auth_tok['error'] - return flask.make_response(auth_tok['error'], 200) - - # set sessions et al - session['auth_tok'] = auth_tok - session['auth_tok']['issued'] = datetime.utcnow() - - # else if session is already existing.. + + # if auth_tok is in session already.. if 'auth_tok' in session: auth_tok = session['auth_tok'] + # check if it has expired oauth_token_expires_in_endpoint = config.swtstoreURL +\ '/oauth/token-expires-in' resp = requests.get(oauth_token_expires_in_endpoint) expires_in = json.loads(resp.text)['expires_in'] + # added for backwared compatibility. previous session stores did not # have issued key try: @@ -74,11 +50,43 @@ def index(): #print 'existing tokens' #print auth_tok - return flask.render_template('index.html', - access_token=auth_tok['access_token'], - refresh_token=auth_tok['refresh_token'], - config=config, - url=flask.request.args.get('where')) + return render_template('index.html', + access_token=auth_tok['access_token'], + refresh_token=auth_tok['refresh_token'], + config=config, + url=request.args.get('where')) + + +@app.route('/authenticate', methods=['GET']) +def authenticateWithOAuth(): + auth_tok = None + code = request.args.get('code') + + # prepare the payload + payload = { + 'scopes': 'email sweet', + 'client_secret': config.app_secret, + 'code': code, + 'redirect_uri': config.redirect_uri, + 'grant_type': 'authorization_code', + 'client_id': config.app_id + } + + # token exchange endpoint + oauth_token_x_endpoint = config.swtstoreURL + '/oauth/token' + resp = requests.post(oauth_token_x_endpoint, data=payload) + auth_tok = json.loads(resp.text) + print 'recvd auth token from swtstore' + print auth_tok + + if 'error' in auth_tok: + print auth_tok['error'] + return make_response(auth_tok['error'], 200) + + # set sessions et al + session['auth_tok'] = auth_tok + session['auth_tok']['issued'] = datetime.utcnow() + return redirect(url_for('index')) # endpoint to search the Open Cuultur Data APIs @@ -86,14 +94,14 @@ def index(): # returns a JSON response @app.route('/search/ocd', methods=['GET']) def searchOCD(): - query = flask.request.args.get('query') + query = request.args.get('query') #collection = flask.request.args.get('collection') - size = flask.request.args.get('size') or 10 - offset = flask.request.args.get('from') or 0 + size = request.args.get('size') or 9 + offset = request.args.get('from') or 0 # if query parameter is not passed, return bad request. if not query: - flask.abort(400) + abort(400) payload = { 'query': query, @@ -110,7 +118,7 @@ def searchOCD(): resp = requests.post('http://api.opencultuurdata.nl/v0/search', data=json.dumps(payload)) - response = flask.make_response() + response = make_response() response.data = json.dumps(resp.json()) response.headers['Content-type'] = 'application/json' return response @@ -120,60 +128,61 @@ def searchOCD(): @app.route('/resolve-ocd-media', methods=['GET']) def resolveOCDMediaURLs(): - media_hash = flask.request.args.get('hash') or None + media_hash = request.args.get('hash') or None if not media_hash: - flask.abort(400) + abort(400) resp = requests.get('http://api.opencultuurdata.nl/v0/resolve/' + media_hash) - return flask.jsonify(url=resp.url) + return jsonify(url=resp.url) @app.route('/media-type', methods=['GET']) def getMediaType(): - where = flask.request.args.get('where') or None + where = request.args.get('where') or None if not where: - flask.abort(400) + abort(400) resp = requests.get(where) content = resp.text if imghdr.what('ignore', content) is None: - return flask.jsonify({'type': 'html'}) + return jsonify({'type': 'html'}) else: - return flask.jsonify({'type': 'image'}) + return jsonify({'type': 'image'}) @app.route('/annotate', methods=['GET']) def annotate(): # img = urllib2.urlopen(flask.request.args['where']).read() - request = requests.get(flask.request.args['where']) - content = request.text + where = request.args.get('where') + response = requests.get(where) + content = response.text if imghdr.what('ignore', content) is None: root = lxml.html.parse(StringIO.StringIO(content)).getroot() - root.make_links_absolute(flask.request.args['where'], + root.make_links_absolute(where, resolve_base_href=True) addScript("//code.jquery.com/jquery-1.11.0.min.js", root) - addScript(flask.url_for('static', filename="js/annotator-full.min.js"), + addScript(url_for('static', filename="js/annotator-full.min.js"), root) - addCSS(flask.url_for('static', filename='css/annotator.min.css'), root) - addCSS(flask.url_for('static', filename='css/swtmaker.css'), root) - addCSS(flask.url_for('static', filename='css/bootstrap.min.css'), root) - addScript(flask.url_for('static', - filename="js/lib/underscore-1.5.2.min.js"), + addCSS(url_for('static', filename='css/annotator.min.css'), root) + addCSS(url_for('static', filename='css/swtmaker.css'), root) + addCSS(url_for('static', filename='css/bootstrap.min.css'), root) + addScript(url_for('static', + filename="js/lib/underscore-1.5.2.min.js"), root) - addScript(flask.url_for('static', - filename="js/lib/backbone-1.0.0.min.js"), + addScript(url_for('static', + filename="js/lib/backbone-1.0.0.min.js"), root) - addCSS(flask.url_for('static', filename='css/annotorious.css'), root) - addScript(flask.url_for('static', - filename="js/annotorious.okfn.0.3.js"), + addCSS(url_for('static', filename='css/annotorious.css'), root) + addScript(url_for('static', + filename="js/annotorious.okfn.0.3.js"), root) if 'auth_tok' in session: @@ -204,11 +213,11 @@ def annotate(): config.redirect_uri) configScript.set("type", "text/javascript") - addScript(flask.url_for('static', filename="js/oauth.js"), root) + addScript(url_for('static', filename="js/oauth.js"), root) - addScript(flask.url_for('static', filename="js/app.js"), root) + addScript(url_for('static', filename="js/app.js"), root) - response = flask.make_response() + response = make_response() response.data = lxml.html.tostring(root) return response @@ -218,11 +227,11 @@ def annotate(): else: auth_tok = {'access_token': '', 'refresh_token': ''} - return flask.render_template('index.html', - access_token=auth_tok['access_token'], - refresh_token=auth_tok['refresh_token'], - config=config, - url=flask.request.args.get('where')) + return render_template('index.html', + access_token=auth_tok['access_token'], + refresh_token=auth_tok['refresh_token'], + config=config, + url=request.args.get('where')) def addScript(src, el): diff --git a/swtr/static/css/swtmaker.css b/swtr/static/css/swtmaker.css index d21fb01..df1645f 100644 --- a/swtr/static/css/swtmaker.css +++ b/swtr/static/css/swtmaker.css @@ -22,6 +22,7 @@ body { margin 0 20px; padding: 10px; text-align: center; + z-index: 100001; } #img-annotation-wrapper, #ocd-results { @@ -77,14 +78,15 @@ body { font-size: 1.3em; min-height: 50px; text-align: center; + z-index: 100001; } #app-overlay { - position: absolute; - top: 60px; + position: fixed; + top: 0; left: 0; display: none; width: 100%; - height: 800px; + height: 100%; z-index: 10000; background-color: rgba(255, 255, 255, 0.7); } diff --git a/swtr/static/js/swtmaker.js b/swtr/static/js/swtmaker.js index c86498e..aba56b9 100644 --- a/swtr/static/js/swtmaker.js +++ b/swtr/static/js/swtmaker.js @@ -66,9 +66,6 @@ throw Error('"where" option must be passed to get sweets of a URI'); return false; } - /*if(!swtr.access_token) { - throw new Error('Access Token required to get query that API'); - }*/ // setting up params var where = options.where, who = options.who || null; @@ -233,10 +230,6 @@ cleanUp: function() { //console.log('cleaning up'); $(this.el).hide(); - if(swtr.appView.source === 'ocd') { - $('#img-annotation-wrapper').hide(); - $('#ocd-results').show(); - } } }); @@ -306,13 +299,15 @@ this.loadURL(input); } }, + // load a URL for annotation (can be of image or html resource for now) loadURL: function(url, type) { - console.log('loadURL()'); + //console.log('loadURL()'); if(this.source !== 'ocd') { $('#ocd-results').hide(); } $('#img-annotation-wrapper').show(); - if(!url) { + if(!url || !url.match(/http/)) { + this.helpview.step(13); return false; } // if type is given explicitly; we load it as such. @@ -610,35 +605,51 @@ var self = this; $('#img-annotation-wrapper').hide(); $('#ocd-results').show(); - $('#ocd-results').append('Loading..'); + $('#ocd-results').html('

Loading..

'); $.ajax({ type: 'GET', url: '/search/ocd', data: {query: input}, success: function(data) { - self.ocdView = new OCDView({model: data.hits.hits}); + self.ocdView = new OCDView({ + query: input, + data: data, + model: data.hits.hits + }); } }); } }); var OCDView = Backbone.View.extend({ - el: $('#ocd-results'), + el: $('#ocd-view'), events: { - 'click .ocd-item a': 'onClickImg' - }, - initialize: function() { + 'click .ocd-item a': 'onImgClick', + 'click .pager li': 'onPagerClick' + }, + initialize: function(opts) { + this.data = opts.data || {}; + this.query = opts.query || ''; + this.size = 9; // num of items per page + this.page = 0; this.item_template = _.template($('#ocd-item-template').html()); + this.base_template = _.template($('#ocd-view-base-template').html()); this.render(); }, render: function() { var $row_el; this.$el.html(''); + if(!this.model.length) { + this.$el.html('No results could be found from your query.'); + return; + } + this.$el.html(this.base_template()); + var $el = $('#ocd-results'); _.each(this.model, function(item, idx) { // put every 3 items in a row if(idx % 3 === 0) { $row_el = $('
'); - this.$el.append($row_el); + $el.append($row_el); } $row_el.append(this.item_template({ title: item._source.title, @@ -647,6 +658,10 @@ })); }, this); this.resolveOCDURLs(); + this.appendTotal(); + }, + appendTotal: function() { + $('#ocd-total-results').html(this.data.hits.total + ' results found.'); }, // resolve the OCD media URLs resolveOCDURLs: function() { @@ -659,12 +674,66 @@ }); }); }, - onClickImg: function(event) { + rerender: function(data) { + this.data = data; + this.model = data.hits.hits; + this.render(); + }, + onPagerClick: function(event) { + event.preventDefault(); + var elem = $(event.currentTarget); + var self = this; + if(elem.hasClass('next')) { + if((this.page + 1) * this.size >= this.data.hits.total) { + console.log('no next page to go to'); + return false; + } + console.log('clicked next'); + this.search({ + query: this.query, + from: (this.page + 1) * this.size + }, function(resp) { + console.log('reached next page'); + self.page = self.page + 1; + self.rerender(resp); + }); + } + else if (elem.hasClass('previous')) { + if(this.page <= 0) { + console.log('no prev page to go to'); + return false; + } + console.log('clicked prev'); + this.search({ + query: this.query, + from: (this.page - 1) * this.size + }, function(resp) { + console.log('reached prev page'); + self.page = self.page - 1; + self.rerender(resp); + }); + } + return false; + }, + onImgClick: function(event) { event.preventDefault(); // TODO: init the image anno var url = $(event.currentTarget).find('img').attr('src'); swtr.appView.loadURL(url, 'image'); return false; + }, + search: function(data, cb) { + swtr.appView.$overlay.show(); + var self = this; + $.ajax({ + type: 'GET', + url: '/search/ocd', + data: data, + success: function(resp) { + swtr.appView.$overlay.hide(); + cb(resp); + } + }); } }); @@ -683,7 +752,7 @@ switch (n) { case 0 : text = 'Getting annotations..'; break; - case 1: text = 'Enter the URL of an image or web page below, and start annotating!'; + case 1: text = 'Enter URL of an image or web page below, and start annotating!'; break; case 2: text = 'Annotate the image, or see other annotations'; break; @@ -707,6 +776,8 @@ break; case 12: text = 'Analyzing the resource type..'; break; + case 13: text = 'This does not seem to be a URL. Please enter a valid URL.'; + break; } $(this.el).html(text); $(window).scrollTop(0, 0); diff --git a/swtr/templates/index.html b/swtr/templates/index.html index dbb2e8d..04e5e19 100644 --- a/swtr/templates/index.html +++ b/swtr/templates/index.html @@ -76,7 +76,7 @@ Annotation Workbench {% endif %} -
+
@@ -143,6 +143,16 @@ +