Commit 68af0f8094802e278a34d1fc1595bb7a9904ba9f
- Diff rendering mode:
- inline
- side by side
swtr/server.py
(78 / 69)
  | |||
1 | 1 | # -*- coding: utf-8 -*- | |
2 | 2 | ||
3 | import flask | ||
4 | from flask import session | ||
3 | from flask import Flask, session, request, make_response, url_for, redirect,\ | ||
4 | render_template, jsonify, abort | ||
5 | 5 | import lxml.html | |
6 | 6 | import requests | |
7 | 7 | import json | |
… | … | ||
12 | 12 | import config | |
13 | 13 | ||
14 | 14 | ||
15 | app = flask.Flask(__name__) | ||
15 | app = Flask(__name__) | ||
16 | 16 | app.config['SECRET_KEY'] = config.secret_key | |
17 | 17 | ||
18 | 18 | ||
19 | 19 | @app.route('/', methods=['GET']) | |
20 | 20 | def 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 | ||
39 | 21 | ||
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.. | ||
49 | 23 | if 'auth_tok' in session: | |
50 | 24 | auth_tok = session['auth_tok'] | |
25 | |||
51 | 26 | # check if it has expired | |
52 | 27 | oauth_token_expires_in_endpoint = config.swtstoreURL +\ | |
53 | 28 | '/oauth/token-expires-in' | |
54 | 29 | resp = requests.get(oauth_token_expires_in_endpoint) | |
55 | 30 | expires_in = json.loads(resp.text)['expires_in'] | |
31 | |||
56 | 32 | # added for backwared compatibility. previous session stores did not | |
57 | 33 | # have issued key | |
58 | 34 | try: | |
… | … | ||
50 | 50 | ||
51 | 51 | #print 'existing tokens' | |
52 | 52 | #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')) | ||
58 | 58 | ||
59 | 59 | ||
60 | @app.route('/authenticate', methods=['GET']) | ||
61 | def 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 | |||
60 | 92 | # endpoint to search the Open Cuultur Data APIs | |
61 | 93 | # takes in `query`, `size`, and `from` parameters in query string | |
62 | 94 | # returns a JSON response | |
63 | 95 | @app.route('/search/ocd', methods=['GET']) | |
64 | 96 | def searchOCD(): | |
65 | query = flask.request.args.get('query') | ||
97 | query = request.args.get('query') | ||
66 | 98 | #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 | ||
69 | 101 | ||
70 | 102 | # if query parameter is not passed, return bad request. | |
71 | 103 | if not query: | |
72 | flask.abort(400) | ||
104 | abort(400) | ||
73 | 105 | ||
74 | 106 | payload = { | |
75 | 107 | 'query': query, | |
… | … | ||
118 | 118 | resp = requests.post('http://api.opencultuurdata.nl/v0/search', | |
119 | 119 | data=json.dumps(payload)) | |
120 | 120 | ||
121 | response = flask.make_response() | ||
121 | response = make_response() | ||
122 | 122 | response.data = json.dumps(resp.json()) | |
123 | 123 | response.headers['Content-type'] = 'application/json' | |
124 | 124 | return response | |
… | … | ||
128 | 128 | @app.route('/resolve-ocd-media', methods=['GET']) | |
129 | 129 | def resolveOCDMediaURLs(): | |
130 | 130 | ||
131 | media_hash = flask.request.args.get('hash') or None | ||
131 | media_hash = request.args.get('hash') or None | ||
132 | 132 | ||
133 | 133 | if not media_hash: | |
134 | flask.abort(400) | ||
134 | abort(400) | ||
135 | 135 | ||
136 | 136 | resp = requests.get('http://api.opencultuurdata.nl/v0/resolve/' + | |
137 | 137 | media_hash) | |
138 | 138 | ||
139 | return flask.jsonify(url=resp.url) | ||
139 | return jsonify(url=resp.url) | ||
140 | 140 | ||
141 | 141 | ||
142 | 142 | @app.route('/media-type', methods=['GET']) | |
143 | 143 | def getMediaType(): | |
144 | 144 | ||
145 | where = flask.request.args.get('where') or None | ||
145 | where = request.args.get('where') or None | ||
146 | 146 | ||
147 | 147 | if not where: | |
148 | flask.abort(400) | ||
148 | abort(400) | ||
149 | 149 | ||
150 | 150 | resp = requests.get(where) | |
151 | 151 | content = resp.text | |
152 | 152 | ||
153 | 153 | if imghdr.what('ignore', content) is None: | |
154 | return flask.jsonify({'type': 'html'}) | ||
154 | return jsonify({'type': 'html'}) | ||
155 | 155 | else: | |
156 | return flask.jsonify({'type': 'image'}) | ||
156 | return jsonify({'type': 'image'}) | ||
157 | 157 | ||
158 | 158 | ||
159 | 159 | @app.route('/annotate', methods=['GET']) | |
160 | 160 | def annotate(): | |
161 | 161 | # 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 | ||
164 | 165 | if imghdr.what('ignore', content) is None: | |
165 | 166 | root = lxml.html.parse(StringIO.StringIO(content)).getroot() | |
166 | root.make_links_absolute(flask.request.args['where'], | ||
167 | root.make_links_absolute(where, | ||
167 | 168 | resolve_base_href=True) | |
168 | 169 | ||
169 | 170 | 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"), | ||
171 | 172 | root) | |
172 | 173 | ||
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"), | ||
178 | 179 | 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"), | ||
181 | 182 | 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"), | ||
185 | 186 | root) | |
186 | 187 | ||
187 | 188 | if 'auth_tok' in session: | |
… | … | ||
213 | 213 | config.redirect_uri) | |
214 | 214 | configScript.set("type", "text/javascript") | |
215 | 215 | ||
216 | addScript(flask.url_for('static', filename="js/oauth.js"), root) | ||
216 | addScript(url_for('static', filename="js/oauth.js"), root) | ||
217 | 217 | ||
218 | addScript(flask.url_for('static', filename="js/app.js"), root) | ||
218 | addScript(url_for('static', filename="js/app.js"), root) | ||
219 | 219 | ||
220 | response = flask.make_response() | ||
220 | response = make_response() | ||
221 | 221 | response.data = lxml.html.tostring(root) | |
222 | 222 | return response | |
223 | 223 | ||
… | … | ||
227 | 227 | else: | |
228 | 228 | auth_tok = {'access_token': '', 'refresh_token': ''} | |
229 | 229 | ||
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')) | ||
235 | 235 | ||
236 | 236 | ||
237 | 237 | def addScript(src, el): |
swtr/static/css/swtmaker.css
(5 / 3)
  | |||
22 | 22 | margin 0 20px; | |
23 | 23 | padding: 10px; | |
24 | 24 | text-align: center; | |
25 | z-index: 100001; | ||
25 | 26 | } | |
26 | 27 | ||
27 | 28 | #img-annotation-wrapper, #ocd-results { | |
… | … | ||
78 | 78 | font-size: 1.3em; | |
79 | 79 | min-height: 50px; | |
80 | 80 | text-align: center; | |
81 | z-index: 100001; | ||
81 | 82 | } | |
82 | 83 | #app-overlay { | |
83 | position: absolute; | ||
84 | top: 60px; | ||
84 | position: fixed; | ||
85 | top: 0; | ||
85 | 86 | left: 0; | |
86 | 87 | display: none; | |
87 | 88 | width: 100%; | |
88 | height: 800px; | ||
89 | height: 100%; | ||
89 | 90 | z-index: 10000; | |
90 | 91 | background-color: rgba(255, 255, 255, 0.7); | |
91 | 92 | } |
swtr/static/js/swtmaker.js
(88 / 17)
  | |||
66 | 66 | throw Error('"where" option must be passed to get sweets of a URI'); | |
67 | 67 | return false; | |
68 | 68 | } | |
69 | /*if(!swtr.access_token) { | ||
70 | throw new Error('Access Token required to get query that API'); | ||
71 | }*/ | ||
72 | 69 | // setting up params | |
73 | 70 | var where = options.where, | |
74 | 71 | who = options.who || null; | |
… | … | ||
230 | 230 | cleanUp: function() { | |
231 | 231 | //console.log('cleaning up'); | |
232 | 232 | $(this.el).hide(); | |
233 | if(swtr.appView.source === 'ocd') { | ||
234 | $('#img-annotation-wrapper').hide(); | ||
235 | $('#ocd-results').show(); | ||
236 | } | ||
237 | 233 | } | |
238 | 234 | }); | |
239 | 235 | ||
… | … | ||
299 | 299 | this.loadURL(input); | |
300 | 300 | } | |
301 | 301 | }, | |
302 | // load a URL for annotation (can be of image or html resource for now) | ||
302 | 303 | loadURL: function(url, type) { | |
303 | console.log('loadURL()'); | ||
304 | //console.log('loadURL()'); | ||
304 | 305 | if(this.source !== 'ocd') { | |
305 | 306 | $('#ocd-results').hide(); | |
306 | 307 | } | |
307 | 308 | $('#img-annotation-wrapper').show(); | |
308 | if(!url) { | ||
309 | if(!url || !url.match(/http/)) { | ||
310 | this.helpview.step(13); | ||
309 | 311 | return false; | |
310 | 312 | } | |
311 | 313 | // if type is given explicitly; we load it as such. | |
… | … | ||
605 | 605 | var self = this; | |
606 | 606 | $('#img-annotation-wrapper').hide(); | |
607 | 607 | $('#ocd-results').show(); | |
608 | $('#ocd-results').append('Loading..'); | ||
608 | $('#ocd-results').html('<h4 style="text-align: center;">Loading..</h4>'); | ||
609 | 609 | $.ajax({ | |
610 | 610 | type: 'GET', | |
611 | 611 | url: '/search/ocd', | |
612 | 612 | data: {query: input}, | |
613 | 613 | 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 | }); | ||
615 | 619 | } | |
616 | 620 | }); | |
617 | 621 | } | |
618 | 622 | }); | |
619 | 623 | ||
620 | 624 | var OCDView = Backbone.View.extend({ | |
621 | el: $('#ocd-results'), | ||
625 | el: $('#ocd-view'), | ||
622 | 626 | events: { | |
623 | 'click .ocd-item a': 'onClickImg' | ||
627 | 'click .ocd-item a': 'onImgClick', | ||
628 | 'click .pager li': 'onPagerClick' | ||
624 | 629 | }, | |
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; | ||
626 | 635 | this.item_template = _.template($('#ocd-item-template').html()); | |
636 | this.base_template = _.template($('#ocd-view-base-template').html()); | ||
627 | 637 | this.render(); | |
628 | 638 | }, | |
629 | 639 | render: function() { | |
630 | 640 | var $row_el; | |
631 | 641 | 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'); | ||
632 | 648 | _.each(this.model, function(item, idx) { | |
633 | 649 | // put every 3 items in a row | |
634 | 650 | if(idx % 3 === 0) { | |
635 | 651 | $row_el = $('<div class="row"></div>'); | |
636 | this.$el.append($row_el); | ||
652 | $el.append($row_el); | ||
637 | 653 | } | |
638 | 654 | $row_el.append(this.item_template({ | |
639 | 655 | title: item._source.title, | |
… | … | ||
658 | 658 | })); | |
659 | 659 | }, this); | |
660 | 660 | this.resolveOCDURLs(); | |
661 | this.appendTotal(); | ||
661 | 662 | }, | |
663 | appendTotal: function() { | ||
664 | $('#ocd-total-results').html(this.data.hits.total + ' results found.'); | ||
665 | }, | ||
662 | 666 | // resolve the OCD media URLs | |
663 | 667 | resolveOCDURLs: function() { | |
664 | 668 | var self = this; | |
… | … | ||
674 | 674 | }); | |
675 | 675 | }); | |
676 | 676 | }, | |
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) { | ||
678 | 683 | 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(); | ||
679 | 720 | // TODO: init the image anno | |
680 | 721 | var url = $(event.currentTarget).find('img').attr('src'); | |
681 | 722 | swtr.appView.loadURL(url, 'image'); | |
682 | 723 | 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 | }); | ||
683 | 737 | } | |
684 | 738 | }); | |
685 | 739 | ||
… | … | ||
752 | 752 | switch (n) { | |
753 | 753 | case 0 : text = 'Getting annotations..'; | |
754 | 754 | 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!'; | ||
756 | 756 | break; | |
757 | 757 | case 2: text = 'Annotate the image, or see other annotations'; | |
758 | 758 | break; | |
… | … | ||
775 | 775 | case 11: text = 'Search in <a href="http://www.opencultuurdata.nl/">Open Cuultur Data API</a>'; | |
776 | 776 | break; | |
777 | 777 | 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.'; | ||
778 | 780 | break; | |
779 | 781 | } | |
780 | 782 | $(this.el).html(text); |
swtr/templates/index.html
(11 / 1)
  | |||
76 | 76 | <img src="" id="annotatable-img" class="img-responsive" alt="Annotation Workbench"> | |
77 | 77 | {% endif %} | |
78 | 78 | </div> | |
79 | <div id="ocd-results"></div> | ||
79 | <div id="ocd-view"></div> | ||
80 | 80 | </div> <!-- end fourth row --> | |
81 | 81 | ||
82 | 82 | <div id="sweet-list-wrapper"> | |
… | … | ||
142 | 142 | <li class="custom-popup-line"><span>Related Links: </span><%= Links %></li> | |
143 | 143 | </ul> | |
144 | 144 | </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="#">« Previous </a></li> | ||
153 | <li class="next"> <a href="#"> Next » </a></li> | ||
154 | </ul> | ||
145 | 155 | </script> | |
146 | 156 | <script type="text/template" id="ocd-item-template"> | |
147 | 157 | <div class="col-sm-6 col-md-4 ocd-item"> |