Commit 2431ed7933f1ccc58cca2210b879873dcd59e924

Initial implementation of external API source (Open Cuultur Data)

  - An intial version to search in the Open Cuultur Data APIs and annotate
    images from search results. Right now fetching, and displaying of search
results is implemented along with first attempt at annotating search result
images.
  • Diff rendering mode:
  • inline
  • side by side

setup.py

77
8requires = [8requires = [
9 'Flask',9 'Flask',
10 'requests'
10 'requests',
11 'lxml'
11]12]
1213
13setup(name='Swtr',14setup(name='Swtr',

swtr/server.py

3import flask3import flask
4from flask import session4from flask import session
5import lxml.html5import lxml.html
6import config
7import requests6import requests
8import json7import json
9import StringIO8import StringIO
10import imghdr9import imghdr
11from datetime import datetime, timedelta10from datetime import datetime, timedelta
1211
12import config
1313
14
14app = flask.Flask(__name__)15app = flask.Flask(__name__)
15app.config['SECRET_KEY'] = config.secret_key16app.config['SECRET_KEY'] = config.secret_key
1617
81 url=flask.request.args.get('where'))81 url=flask.request.args.get('where'))
8282
8383
84# endpoint to search the Open Cuultur Data APIs
85# takes in `query`, `size`, and `from` parameters in query string
86# returns a JSON response
87@app.route('/search/ocd', methods=['GET'])
88def search_ocd():
89 query = flask.request.args.get('query')
90 #collection = flask.request.args.get('collection')
91 size = flask.request.args.get('size') or 10
92 offset = flask.request.args.get('from') or 0
93
94 # if query parameter is not passed, return bad request.
95 if not query:
96 flask.abort(400)
97
98 payload = {
99 'query': query,
100 'facets': {
101 'collection': {},
102 'date': {'interval': 'day'}
103 },
104 'filters': {
105 'media_content_type': {'terms': ['image/jpeg', 'image/png']}
106 },
107 'size': size,
108 'from': offset
109 }
110 resp = requests.post('http://api.opencultuurdata.nl/v0/search',
111 data=json.dumps(payload))
112
113 response = flask.make_response()
114 response.data = json.dumps(resp.json())
115 response.headers['Content-type'] = 'application/json'
116 return response
117
118
119# resolve OCD Media URLs: http://docs.opencultuurdata.nl/user/api.html#resolver
120@app.route('/resolve-ocd-media/<media_hash>', methods=['GET'])
121def resolve_ocd_media_urls(media_hash):
122
123 resp = requests.get('http://api.opencultuurdata.nl/v0/resolve/' +
124 media_hash)
125
126 response = flask.make_response()
127 response.data = resp.url
128 return response
129
130
84@app.route('/annotate', methods=['GET'])131@app.route('/annotate', methods=['GET'])
85def annotate():132def annotate():
86 print flask.request.args['where']
87 # img = urllib2.urlopen(flask.request.args['where']).read()133 # img = urllib2.urlopen(flask.request.args['where']).read()
88 request = requests.get(flask.request.args['where'])134 request = requests.get(flask.request.args['where'])
89 content = request.text135 content = request.text
188 "js/lib/backbone-1.0.0.min.js"))188 "js/lib/backbone-1.0.0.min.js"))
189 backboneJS.set("type", "text/javascript")189 backboneJS.set("type", "text/javascript")
190190
191 # annotorious plugin
192 annotoriousCSS = root.makeelement('link')
193 root.body.append(annotoriousCSS)
194 annotoriousCSS.set("href", flask.url_for('static',
195 filename='css/annotorious.css'))
196 annotoriousCSS.set('rel', 'stylesheet')
197
198 annotoriousJS = root.makeelement('script')
199 root.body.append(annotoriousJS)
200 annotoriousJS.set('src', flask.url_for('static',
201 filename='js/annotorious.okfn.0.3.js'))
202
191 if 'auth_tok' in session:203 if 'auth_tok' in session:
192 auth_tok = session['auth_tok']204 auth_tok = session['auth_tok']
193 else:205 else:
261 refresh_token=auth_tok['refresh_token'],261 refresh_token=auth_tok['refresh_token'],
262 config=config,262 config=config,
263 url=flask.request.args.get('where'))263 url=flask.request.args.get('where'))
264
265
266@app.route("/search")
267def search():
268 if 'size' not in flask.request.args:
269 size = 10
270 else:
271 size = flask.request.args['size']
272 if 'from' not in flask.request.args:
273 fr = 0
274 else:
275 fr = flask.request.args['from']
276
277 results = requests.post("http://api.opencultuurdata.nl/v0/search",
278 data=json.dumps({
279 "query": flask.request.args['term'],
280 "facets": {"collection": {},
281 "date": {"interval": "day"}},
282 "filters": {"media_content_type":
283 {"terms": ["image/jpeg",
284 "image/png"]}},
285 "size": size,
286 "from": fr}))
287 return flask.jsonify(results.json())
288264
289265
290# if the app is run directly from command-line266# if the app is run directly from command-line

swtr/static/css/swtmaker.css

10 min-height: 35px;10 min-height: 35px;
11}11}
1212
13
14#swt-maker {13#swt-maker {
15}14}
15
16#control-panel {
17 margin-top: 10px;
18 margin-left: 0;
19}
20
16#signinview {21#signinview {
17 margin 0 20px;22 margin 0 20px;
18 padding: 10px;23 padding: 10px;
19 text-align: center;24 text-align: center;
20}25}
21#img-annotation-wrapper {
26
27#img-annotation-wrapper, #ocd-results {
22 margin: 30px auto 0 auto;28 margin: 30px auto 0 auto;
23}29}
30
24#annotatable-img {31#annotatable-img {
25 margin: 0 auto;32 margin: 0 auto;
26 max-width: 1125px;33 max-width: 1125px;
27}34}
35
28#sweet-list-wrapper {36#sweet-list-wrapper {
29 width: 500px;37 width: 500px;
30 min-height: 300px;38 min-height: 300px;
46 padding: 30px;46 padding: 30px;
47 display: none;47 display: none;
48}48}
49
49#sweet-list {50#sweet-list {
50 margin-bottom: 10px;51 margin-bottom: 10px;
51}52}
52.btn-grp {
53 position: relative;
54 bottom: -10%;
55 right: -10%;
53
54.ocd-item {
55 padding-bottom: 4px;
56 border: 1px solid #ddd;
57 border-radius: 4px;
58 margin-bottom: 20px;
59 text-align: center;
56}60}
61
62.thumb-image {
63 background-position: center center;
64 background-repeat: no-repeat;
65 background-size: cover;
66 display: block;
67 height: 400px;
68 margin: 0 auto;
69}
70
57#sweet {71#sweet {
58 display: none;72 display: none;
59}73}
74
60#helpview {75#helpview {
61 font-weight: bold;76 font-weight: bold;
62 font-size: 1.3em;77 font-size: 1.3em;

swtr/static/js/app.js

130 this.annotator = new Annotator(document.body);130 this.annotator = new Annotator(document.body);
131 swtr.anno = this.annotator;131 swtr.anno = this.annotator;
132 this.annotator.addPlugin("Tags");132 this.annotator.addPlugin("Tags");
133 this.annotator.addPlugin("AnnotoriousImagePlugin");
133 this.listenTo(this.collection, "add", this.loadAnno);134 this.listenTo(this.collection, "add", this.loadAnno);
134 this.annotator.subscribe("annotationCreated", this.storeAnno);135 this.annotator.subscribe("annotationCreated", this.storeAnno);
135 },136 },

swtr/static/js/swtmaker.js

233 cleanUp: function() {233 cleanUp: function() {
234 //console.log('cleaning up');234 //console.log('cleaning up');
235 $(this.el).hide();235 $(this.el).hide();
236 if(swtr.appView.source === 'ocd') {
237 $('#img-annotation-wrapper').hide();
238 $('#ocd-results').show();
239 }
236 }240 }
237 });241 });
238242
239 var AppView = Backbone.View.extend({243 var AppView = Backbone.View.extend({
240 el: $('#swt-maker'),244 el: $('#swt-maker'),
241 events: {245 events: {
242 'click #img-url-load': 'setImage',
243 'click #img-url-submit': 'setImage',
246 'click #user-input-submit': 'submitUserInput',
244 'click #sweet': 'sweet',247 'click #sweet': 'sweet',
245 'click #sign-in': 'signIn',248 'click #sign-in': 'signIn',
246 'click #setbox': 'showHide',249 'click #setbox': 'showHide',
247 'change #custom-dropdown ': 'getFormValue'
250 'change #custom-dropdown ': 'getFormValue',
251 'click #ocd-source': 'sourceChanged'
248 //'mouseup .annotorious-editor-button-save': 'addnew_anno'252 //'mouseup .annotorious-editor-button-save': 'addnew_anno'
249 },253 },
250 initialize: function() {254 initialize: function() {
251 // initialize components255 // initialize components
256 this.source = 'none';
252 this.helpview = new HelpView();257 this.helpview = new HelpView();
253 this.sweetsview = new SweetsView({collection: swtr.sweets});258 this.sweetsview = new SweetsView({collection: swtr.sweets});
254259
282 this.imgURL = this.$img.attr('src');282 this.imgURL = this.$img.attr('src');
283 if(this.imgURL) {283 if(this.imgURL) {
284 this.initImageAnno();284 this.initImageAnno();
285 $('#img-url-input').val(this.imgURL);
285 $('#user-input').val(this.imgURL);
286 }286 }
287 else {287 else {
288 this.helpview.step(1);288 this.helpview.step(1);
297 scopes: 'email,sweet'297 scopes: 'email,sweet'
298 });298 });
299 },299 },
300 setImage: function(event) {
300 submitUserInput: function(event) {
301 event.preventDefault();301 event.preventDefault();
302 var url = $('#img-url-input').val();
302 var input = $('#user-input').val();
303 if(this.source === 'ocd') {
304 this.loadOCDSearch(input);
305 }
306 else if (this.source === 'none') {
307 this.loadURL(input);
308 }
309 },
310 loadURL: function(url) {
311 $('#ocd-results').hide();
312 $('#img-annotation-wrapper').show();
313 if(!url) {
314 return false;
315 }
316 // if image url then load the image annotation
303 if(url.match(/.jpg|.jpeg|.png|.gif|.bmp|.svg/)) {317 if(url.match(/.jpg|.jpeg|.png|.gif|.bmp|.svg/)) {
304318
305 this.imgURL = $('#img-url-input').val();
306 if(!this.imgURL) {
307 return false;
308 }
319 this.imgURL = url;
320
309 if(this.$img.attr('src') === this.imgURL) {321 if(this.$img.attr('src') === this.imgURL) {
310 return;322 return;
311 }323 }
328 this.$img.attr('src', this.imgURL);328 this.$img.attr('src', this.imgURL);
329 return false;329 return false;
330 }330 }
331 // else load text annotation
331 else {332 else {
332 if(!url) {
333 return false;
334 }
335 window.location.href = '/annotate?where=' + url;333 window.location.href = '/annotate?where=' + url;
336 }334 }
337 },335 },
552 userLoggedOut: function() {552 userLoggedOut: function() {
553 swtr.who = 'Guest';553 swtr.who = 'Guest';
554 $('#signinview').html('Logged out');554 $('#signinview').html('Logged out');
555 },
556 changeURLInputPlaceholder: function(source) {
557 switch (source) {
558 case 'ocd' : $('#user-input').attr('placeholder', 'Enter search query');
559 break;
560 case 'none' : $('#user-input').attr('placeholder', 'Enter URL of image or web page');
561 break;
562 }
563 },
564 // function to change the source in the application and update the UI
565 changeSource: function(source) {
566 switch (source) {
567 case 'ocd' : this.source = 'ocd';
568 this.helpview.step(11);
569 this.changeURLInputPlaceholder('ocd');
570 break;
571 case 'none' : this.source = 'none';
572 this.helpview.step(1);
573 this.changeURLInputPlaceholder('none');
574 break;
575 }
576 },
577 // event handler to capture control panel UI change of source
578 sourceChanged: function(event) {
579 if($('#ocd-source').is(':checked')) {
580 this.changeSource('ocd');
581 }
582 else {
583 this.changeSource('none');
584 }
585 },
586 loadOCDSearch: function(input) {
587 var self = this;
588 $('#img-annotation-wrapper').hide();
589 $('#ocd-results').show();
590 $('#ocd-results').append('Loading..');
591 $.ajax({
592 type: 'GET',
593 url: '/search/ocd',
594 data: {query: input},
595 success: function(data) {
596 self.ocdView = new OCDView({model: data.hits.hits});
597 }
598 });
555 }599 }
556 });600 });
557601
602 var OCDView = Backbone.View.extend({
603 el: $('#ocd-results'),
604 events: {
605 'click .ocd-item a': 'onClickImg'
606 },
607 initialize: function() {
608 this.item_template = _.template($('#ocd-item-template').html());
609 this.render();
610 },
611 render: function() {
612 var $row_el;
613 this.$el.html('');
614 _.each(this.model, function(item, idx) {
615 if(idx % 3 === 0) {
616 $row_el = $('<div class="row"></div>');
617 this.$el.append($row_el);
618 }
619 $row_el.append(this.item_template({
620 title: item._source.title,
621 media_url: item._source.media_urls[0].url,
622 authors: item._source.authors
623 }));
624 }, this);
625 this.resolve();
626 },
627 // resolve the OCD media URLs
628 resolve: function() {
629 var self = this;
630 $('.ocd-item').each(function(idx, elem) {
631 var temp_arr = self.model[idx]._source.media_urls[0].url.split('/');
632 var media_hash = temp_arr[temp_arr.length - 1];
633 $.get('/resolve-ocd-media/'+ media_hash, function(resp) {
634 $(elem).find('img').attr('src', resp);
635 });
636 });
637 },
638 onClickImg: function(event) {
639 event.preventDefault();
640 // TODO: init the image anno
641 var url = $(event.currentTarget).find('img').attr('src');
642 swtr.appView.loadURL(url);
643 return false;
644 }
645 });
646
558 var HelpView = Backbone.View.extend({647 var HelpView = Backbone.View.extend({
559 el: $('#helpview'),648 el: $('#helpview'),
560 events: {649 events: {
678 case 9: text = 'You have to be <i>signed in</i> to sweet store to post sweets';678 case 9: text = 'You have to be <i>signed in</i> to sweet store to post sweets';
679 break;679 break;
680 case 10: text = 'Oops! Something went wrong. We couldn\'t publish the sweets. Try again.'680 case 10: text = 'Oops! Something went wrong. We couldn\'t publish the sweets. Try again.'
681 break;
682 case 11: text = 'Search in <a href="http://www.opencultuurdata.nl/">Open Cuultur Data API</a>';
681 break;683 break;
682 }684 }
683 $(this.el).html(text);685 $(this.el).html(text);

swtr/templates/index.html

3 <head>3 <head>
4 <meta charset="utf-8">4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>swtr - the default sweet web app</title>
6 <title> swtr - the canonical sweet web app </title>
77
8 <meta name="description" content="Sweet Maker, Social, Semantic, Web, Decentralized, Makes Sweet, Sweet Swagger">
8 <meta name="description" content="Sweet Maker, Social, Semantic, Web, Decentralized, Makes Sweet, Sweet Swagger, Swtr, swtr">
9 <link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">9 <link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
10 <link href="{{ url_for('static', filename='css/bootstrap-theme.min.css') }}" rel="stylesheet">10 <link href="{{ url_for('static', filename='css/bootstrap-theme.min.css') }}" rel="stylesheet">
11 <link href="{{ url_for('static', filename='css/annotorious.css') }}" rel="stylesheet">11 <link href="{{ url_for('static', filename='css/annotorious.css') }}" rel="stylesheet">
12 <link href="{{ url_for('static', filename='css/swtmaker.css') }}" rel="stylesheet">12 <link href="{{ url_for('static', filename='css/swtmaker.css') }}" rel="stylesheet">
13 <link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap-tags.css') }}" type="text/css" media="screen" />
13 <link href="{{ url_for('static', filename='css/bootstrap-tags.css') }}" rel="stylesheet">
14 </head>14 </head>
1515
16 <body>16 <body>
36 <div class="col-md-8">36 <div class="col-md-8">
37 <form class="form-inline" role="form">37 <form class="form-inline" role="form">
38 <div class="form-group col-md-11">38 <div class="form-group col-md-11">
39 <label class="sr-only" for="img-url-input">Enter URL of the image</label>
39 <label class="sr-only" for="user-input">Enter URL of an image or web page</label>
40 <input class="form-control" type="text"40 <input class="form-control" type="text"
41 placeholder="Enter URL of the image or web page" id="img-url-input">
41 placeholder="Enter URL of an image or web page" id="user-input">
42 </div>42 </div>
43 <button type="submit" class="btn btn-primary" id="img-url-load">Load</button>
43 <button type="submit" class="btn btn-primary" id="user-input-submit">Load</button>
44 </form>44 </form>
45 </div>45 </div>
46 <div class="col-md-4">46 <div class="col-md-4">
47 </div>47 </div>
48 </div> <!-- end second row -->48 </div> <!-- end second row -->
49 <!-- third row - control panel? -->49 <!-- third row - control panel? -->
50 <div class="row">
51 <div class="col-md-3">
52 <input id="setbox" type="checkbox"/>
53 <label id="setcontrol" for="setbox">Show annotated areas</label>
50 <div class="row" id="control-panel">
51 <div class="col-md-8">
52 <form class="form-inline" role="form">
53 <div class="form-group">
54 <label class="checkbox-inline">
55 <input type="checkbox" id="setbox"> Show annotated areas
56 </label>
57 </div>
58 <div class="form-group">
59 <label class="checkbox-inline">
60 <input type="checkbox" id="ocd-source"> Search in Open Cuultur Data
61 </label>
62 </div>
63 <div class="form-group">
64 <button class="btn btn-default" id="sweet">Sweet</button>
65 </div>
66 </form>
54 </div>67 </div>
55 <div class="col-md-1">
56 <button class="btn btn-default" id="sweet">Sweet</button>
57 </div>
68 <div class="col-md-4"></div>
58 </div> <!-- end third row -- >69 </div> <!-- end third row -- >
59 <!-- fourth row: the image annotation window -->70 <!-- fourth row: the image annotation window -->
60 <div class="row">71 <div class="row">
76 <img src="" id="annotatable-img" class="img-responsive">76 <img src="" id="annotatable-img" class="img-responsive">
77 {% endif %}77 {% endif %}
78 </div>78 </div>
79 <div id="ocd-results"></div>
79 </div> <!-- end fourth row -->80 </div> <!-- end fourth row -->
8081
81 <div id="sweet-list-wrapper">82 <div id="sweet-list-wrapper">
134 <option value="link">Link</option>134 <option value="link">Link</option>
135 </select>135 </select>
136 </script>136 </script>
137 <!-- script type="text/template" id="dropdown-template">
138 <form class="form-control" id="custom-dropdown">
139 <textarea class="annotorious-editor-text goog-textarea"
140 placeholder="Add Label" id="label"></textarea>
141 <textarea class="annotorious-editor-text goog-textarea"
142 placeholder="Add Tags" id="tags"></textarea>
143 <textarea class="annotorious-editor-text goog-textarea"
144 placeholder="Add Links" id="links"></textarea>
145 </form>
146 </script-->
147 <script type="text/template" id="popup-template">137 <script type="text/template" id="popup-template">
148 <span class="annotorious-popup-text">138 <span class="annotorious-popup-text">
149 <ul class="custom-style-popup">139 <ul class="custom-style-popup">
143 <li class="custom-popup-line"><span>Related Links: </span><%= Links %></li>143 <li class="custom-popup-line"><span>Related Links: </span><%= Links %></li>
144 </ul>144 </ul>
145 </span>145 </span>
146 </script>
147 <script type="text/template" id="ocd-item-template">
148 <div class="col-sm-6 col-md-4 ocd-item">
149 <h4> <%= title %> </h4>
150 <div class="media">
151 <a href="<%= media_url %>">
152 <img src="" class="responsive thumb-image">
153 </a>
154 </div>
155 <div>Authors: <%= authors %> </div>
156 </div>
146 </script>157 </script>
147 </body>158 </body>
148</html>159</html>