Commit 1d593744cace6fa44757c77b06803b5738707889
Add uploads support
Add basic uploads support. This should improve both in terms of UI and in
terms of robustness, and functionality.
| | | | 34 | | 34 | |
---|
35 | PLUGIN_UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)) | 35 | PLUGIN_UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)) |
---|
36 | + '/static/user_plugins') | 36 | + '/static/user_plugins') |
---|
37 | PLUGIN_ALLOWED_EXTENSIONS = set(['js', 'css']) | | PLUGIN_ALLOWED_EXTENSIONS = set(['js', 'css']) |
---|
38 | | 37 | |
---|
| | 38 | ALLOWED_EXTENSIONS = set(['js', 'css', 'jpg', 'JPG', 'png', 'gif', 'PNG', |
---|
| | 39 | 'svg', 'pdf']) |
---|
| | 40 | #ALLOWED_EXTENSIONS = set(['js', 'css']) |
---|
| | 41 | |
---|
39 | FILE_UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)) + | 42 | FILE_UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)) + |
---|
40 | '/static/uploads') | 43 | '/static/uploads') |
---|
41 | | 44 | |
---|
… | | … | |
---|
95 | | 95 | |
---|
96 | def allowed_file(filename): | 96 | def allowed_file(filename): |
---|
97 | return '.' in filename and \ | 97 | return '.' in filename and \ |
---|
98 | filename.rsplit('.', 1)[1] in PLUGIN_ALLOWED_EXTENSIONS | | filename.rsplit('.', 1)[1] in PLUGIN_ALLOWED_EXTENSIONS |
---|
| | 98 | filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS | 99 | | 99 | |
---|
100 | | 100 | |
---|
101 | @app.errorhandler(404) | 101 | @app.errorhandler(404) |
---|
… | | … | |
---|
302 | #return flask.redirect(flask.url_for('uploaded_file', | 302 | #return flask.redirect(flask.url_for('uploaded_file', |
---|
303 | # filename=filename)) | 303 | # filename=filename)) |
---|
304 | return flask.jsonify(uploaded = True, | 304 | return flask.jsonify(uploaded = True, |
---|
305 | path=flask.url_for('static', filename = | | path=flask.url_for('static', filename = |
---|
| | 305 | path=flask.url_for('static', filename =\ | 306 | 'user_plugins/'+ filename)) | 306 | 'user_plugins/'+ filename)) |
---|
307 | | 307 | |
---|
| | 308 | @app.route('/upload', methods=['GET', 'POST']) |
---|
| | 309 | def upload(): |
---|
| | 310 | if flask.request.method == 'POST': |
---|
| | 311 | print flask.request.files |
---|
| | 312 | file = flask.request.files['upload-file'] |
---|
| | 313 | if file and allowed_file(file.filename): |
---|
| | 314 | print 'file ok' |
---|
| | 315 | filename = secure_filename(file.filename) |
---|
| | 316 | file.save(os.path.join(app.config['FILE_UPLOAD_FOLDER'], filename)) |
---|
| | 317 | |
---|
| | 318 | return flask.jsonify(uploaded = True, path =\ |
---|
| | 319 | flask.url_for('static', filename =\ |
---|
| | 320 | 'uploads/' + filename)) |
---|
| | 321 | |
---|
| | 322 | else: |
---|
| | 323 | resp = flask.make_response() |
---|
| | 324 | print 'file not ok' |
---|
| | 325 | resp.status_code = 400 |
---|
| | 326 | return resp |
---|
| | 327 | |
---|
| | 328 | if flask.request.method == 'GET': |
---|
| | 329 | uploaded_files = os.listdir(app.config['FILE_UPLOAD_FOLDER']) |
---|
| | 330 | print uploaded_files |
---|
| | 331 | return flask.jsonify({'uploaded_files': uploaded_files}) |
---|
| | 332 | |
---|
| | 333 | @app.route('/upload/<filename>', methods=['DELETE']) |
---|
| | 334 | def removeFile(filename): |
---|
| | 335 | filepath = os.path.join(app.config['FILE_UPLOAD_FOLDER'], filename) |
---|
| | 336 | print filepath |
---|
| | 337 | res = os.remove(filepath) |
---|
| | 338 | print res |
---|
| | 339 | return '200 OK' |
---|
| | 340 | |
---|
| | 341 | |
---|
308 | @app.route('/robots.txt') | 342 | @app.route('/robots.txt') |
---|
309 | @app.route('/crossdomain.xml') | 343 | @app.route('/crossdomain.xml') |
---|
310 | def static_from_root(): | 344 | def static_from_root(): |
---|
… | | … | |
---|
347 | | 347 | |
---|
348 | app.config.from_object(conf) | 348 | app.config.from_object(conf) |
---|
349 | app.config['PLUGIN_UPLOAD_FOLDER'] = PLUGIN_UPLOAD_FOLDER | 349 | app.config['PLUGIN_UPLOAD_FOLDER'] = PLUGIN_UPLOAD_FOLDER |
---|
| | 350 | app.config['FILE_UPLOAD_FOLDER'] = FILE_UPLOAD_FOLDER |
---|
350 | | 351 | |
---|
351 | import logging,os | 352 | import logging,os |
---|
352 | from logging import FileHandler | 353 | from logging import FileHandler |
---|
| | | | 11 | 'click .pagename .remove': 'removePage', | 11 | 'click .pagename .remove': 'removePage', |
---|
12 | 'click #menu-config': 'showMenu', | 12 | 'click #menu-config': 'showMenu', |
---|
13 | 'click #footer-config': 'showFooterConfig', | 13 | 'click #footer-config': 'showFooterConfig', |
---|
14 | 'click #header-config': 'showHeaderConfig' | | 'click #header-config': 'showHeaderConfig' |
---|
| | 14 | 'click #header-config': 'showHeaderConfig', | | | 15 | 'click #uploads': 'uploads' |
---|
15 | }, | 16 | }, |
---|
16 | initialize: function() { | 17 | initialize: function() { |
---|
17 | _.bindAll.apply(_, [this].concat(_.functions(this))); | 18 | _.bindAll.apply(_, [this].concat(_.functions(this))); |
---|
… | | … | |
---|
31 | this.footerconfigview = new FooterConfigView({model: this.footerconfig}); | 31 | this.footerconfigview = new FooterConfigView({model: this.footerconfig}); |
---|
32 | this.headerconfig = new M.types.model.header(M.site_content.header); | 32 | this.headerconfig = new M.types.model.header(M.site_content.header); |
---|
33 | this.headerconfigview = new HeaderConfigView({model: this.headerconfig}); | 33 | this.headerconfigview = new HeaderConfigView({model: this.headerconfig}); |
---|
| | 34 | this.uploadview = new UploadView(); |
---|
34 | }, | 35 | }, |
---|
35 | render: function() { | 36 | render: function() { |
---|
36 | // append the page list | 37 | // append the page list |
---|
… | | … | |
---|
104 | this.headerconfigview.render(); | 104 | this.headerconfigview.render(); |
---|
105 | return false; | 105 | return false; |
---|
106 | }, | 106 | }, |
---|
| | 107 | uploads: function(event) { |
---|
| | 108 | event.preventDefault(); |
---|
| | 109 | this.uploadview.render(); |
---|
| | 110 | return false; |
---|
| | 111 | }, |
---|
107 | // validate the page list with menu order list | 112 | // validate the page list with menu order list |
---|
108 | validate: function() { | 113 | validate: function() { |
---|
109 | //TODO: validate if the menu order list matches with the list of pages | 114 | //TODO: validate if the menu order list matches with the list of pages |
---|
… | | … | |
---|
676 | ' will be rendered'+ | 676 | ' will be rendered'+ |
---|
677 | '\n@delay: (optional) a delay time, after which the notification'+ | 677 | '\n@delay: (optional) a delay time, after which the notification'+ |
---|
678 | ' will be hidden'; | 678 | ' will be hidden'; |
---|
| | 679 | } |
---|
| | 680 | }); |
---|
| | 681 | |
---|
| | 682 | /* Upload View */ |
---|
| | 683 | var UploadView = Backbone.View.extend({ |
---|
| | 684 | tagName: 'div', |
---|
| | 685 | className: 'prettybox-lg', |
---|
| | 686 | id: 'page', |
---|
| | 687 | events: { |
---|
| | 688 | 'click #upload-new-file': 'uploadFile', |
---|
| | 689 | 'click .uploaded-item .remove': 'removeFile' |
---|
| | 690 | }, |
---|
| | 691 | initialize: function() { |
---|
| | 692 | _.bindAll.apply(_, [this].concat(_.functions(this))); |
---|
| | 693 | this.template = _.template($('#uploads-template').html()); |
---|
| | 694 | }, |
---|
| | 695 | render: function() { |
---|
| | 696 | $('#page').remove(); |
---|
| | 697 | $('#content-container').append(this.$el); |
---|
| | 698 | //console.log('rendering..', this.$el); |
---|
| | 699 | var uploaded_files, self = this; |
---|
| | 700 | M.editor.showOverlay(); |
---|
| | 701 | $.ajax({ |
---|
| | 702 | url: '/upload', |
---|
| | 703 | type: 'GET', |
---|
| | 704 | success: function(data) { |
---|
| | 705 | M.editor.hideOverlay(); |
---|
| | 706 | self.$el.html(self.template({ |
---|
| | 707 | })); |
---|
| | 708 | self.appendFileListTemplate(data.uploaded_files); |
---|
| | 709 | self.delegateEvents(); |
---|
| | 710 | } |
---|
| | 711 | }); |
---|
| | 712 | }, |
---|
| | 713 | appendFileListTemplate: function(files) { |
---|
| | 714 | var template = _.template($('#uploaded-item-template').html()); |
---|
| | 715 | if(files.length) { |
---|
| | 716 | _.each(files, function(file) { |
---|
| | 717 | $('#uploads-list').append(template({ |
---|
| | 718 | filename: file |
---|
| | 719 | })); |
---|
| | 720 | }); |
---|
| | 721 | } |
---|
| | 722 | else { |
---|
| | 723 | $('#uploads-list').html('<b> No files uploaded yet </b>'); |
---|
| | 724 | } |
---|
| | 725 | }, |
---|
| | 726 | uploadFile: function() { |
---|
| | 727 | //console.log('upload file'); |
---|
| | 728 | var self = this; |
---|
| | 729 | M.editor.showOverlay(); |
---|
| | 730 | var $form = $('#file-upload-form')[0]; |
---|
| | 731 | var formdata = new FormData($form); |
---|
| | 732 | $.ajax({ |
---|
| | 733 | type: 'POST', |
---|
| | 734 | url: '/upload', |
---|
| | 735 | data: formdata, |
---|
| | 736 | processData: false, |
---|
| | 737 | contentType: false, |
---|
| | 738 | success: function(response) { |
---|
| | 739 | M.editor.hideOverlay(); |
---|
| | 740 | M.editor.notifs.show('success', 'Success', 'File uploaded'); |
---|
| | 741 | self.render(); |
---|
| | 742 | }, |
---|
| | 743 | error: function(jqxhr, status, error) { |
---|
| | 744 | M.editor.hideOverlay(); |
---|
| | 745 | if(error === 'BAD REQUEST') { |
---|
| | 746 | var msg = 'File format not allowed. Please contact your administrator to allow this kind of file.' |
---|
| | 747 | } else { |
---|
| | 748 | var msg = 'Something went wrong. Please try again!'; |
---|
| | 749 | } |
---|
| | 750 | M.editor.notifs.show('fail', 'Error!', msg); |
---|
| | 751 | } |
---|
| | 752 | }); |
---|
| | 753 | }, |
---|
| | 754 | removeFile: function(event) { |
---|
| | 755 | M.editor.showOverlay(); |
---|
| | 756 | //console.log('remove file'); |
---|
| | 757 | var self = this; |
---|
| | 758 | var filename = $(event.currentTarget).attr('for'); |
---|
| | 759 | $.ajax({ |
---|
| | 760 | type: 'DELETE', |
---|
| | 761 | url: '/upload/' + filename, |
---|
| | 762 | success: function(data) { |
---|
| | 763 | M.editor.hideOverlay(); |
---|
| | 764 | self.render(); |
---|
| | 765 | }, |
---|
| | 766 | error: function(jqxhr, status, error) { |
---|
| | 767 | console.log(arguments); |
---|
| | 768 | } |
---|
| | 769 | }); |
---|
679 | } | 770 | } |
---|
680 | }); | 771 | }); |
---|
681 | | 772 | |
---|
| | | | 26 | M.HeaderURL = function() { return "{{ url_for('insertHeader') }}"; }; | 26 | M.HeaderURL = function() { return "{{ url_for('insertHeader') }}"; }; |
---|
27 | M.PageURL = function() { return "{{ url_for('insertPage') }}"; }; | 27 | M.PageURL = function() { return "{{ url_for('insertPage') }}"; }; |
---|
28 | M.PluginUploadURL = function() { return "{{ url_for('uploadPlugin') }}"; }; | 28 | M.PluginUploadURL = function() { return "{{ url_for('uploadPlugin') }}"; }; |
---|
| | 29 | M.UploadsURL = function() { return "{{ url_for('static', filename='uploads/') }}"; }; |
---|
29 | M.site_content = {{ content|tojson|safe }}; | 30 | M.site_content = {{ content|tojson|safe }}; |
---|
30 | window.onload = function() { | 31 | window.onload = function() { |
---|
31 | M.editor.init(); | 32 | M.editor.init(); |
---|
… | | … | |
---|
144 | <p> <a href="#" id="header-config"> Header </a> </p> | 144 | <p> <a href="#" id="header-config"> Header </a> </p> |
---|
145 | <p> <a href="#" id="footer-config"> Footer </a> </p> | 145 | <p> <a href="#" id="footer-config"> Footer </a> </p> |
---|
146 | <p> <a href="#" id="menu-config"> Navigation Menu </a> </p> | 146 | <p> <a href="#" id="menu-config"> Navigation Menu </a> </p> |
---|
| | 147 | <p> <a href="#" id="uploads"> Uploads </a> </p> |
---|
147 | <p><a href="{{ url_for('index') }}"> Go to site </a></p> | 148 | <p><a href="{{ url_for('index') }}"> Go to site </a></p> |
---|
148 | </div> | 149 | </div> |
---|
149 | </script> | 150 | </script> |
---|
… | | … | |
---|
196 | <small>title of the page</small> | 196 | <small>title of the page</small> |
---|
197 | </span> | 197 | </span> |
---|
198 | </div> | 198 | </div> |
---|
199 | <div class="form-group"> | | <div class="form-group"> |
---|
| | 199 | <!--div class="form-group"> | 200 | <div class="input-group"> | 200 | <div class="input-group"> |
---|
201 | <span class="input-group-addon"> <strong>Children</strong> </span> | 201 | <span class="input-group-addon"> <strong>Children</strong> </span> |
---|
202 | <input class="form-control" id="children" type="text" placeholder="csv of child pages" | 202 | <input class="form-control" id="children" type="text" placeholder="csv of child pages" |
---|
… | | … | |
---|
205 | <span class="help-block"> | 205 | <span class="help-block"> |
---|
206 | <small> leave this blank for now</small> | 206 | <small> leave this blank for now</small> |
---|
207 | </span> | 207 | </span> |
---|
208 | </div> | | </div> |
---|
| | 208 | </div--> | 209 | <div class="form-group"> | 209 | <div class="form-group"> |
---|
210 | <div class="input-group"> | 210 | <div class="input-group"> |
---|
211 | <span class="input-group-addon"> | 211 | <span class="input-group-addon"> |
---|
… | | … | |
---|
336 | <div class="row"> | 336 | <div class="row"> |
---|
337 | <div class="form-group col-lg-6"> | 337 | <div class="form-group col-lg-6"> |
---|
338 | <form id="plugin-upload-form" action="" method="post" enctype="multipart/form-data"> | 338 | <form id="plugin-upload-form" action="" method="post" enctype="multipart/form-data"> |
---|
339 | <input type="file" name="plugin-file" id="select-plugin"> | | <input type="file" name="plugin-file" id="select-plugin"> |
---|
| | 339 | <input type="file" name="plugin-file" id="select-plugin"> | 340 | <input type="button" id="upload-plugin" class="btn btn-info" value="Upload Plugin"> | 340 | <input type="button" id="upload-plugin" class="btn btn-info" value="Upload Plugin"> |
---|
341 | </form> | 341 | </form> |
---|
342 | </div> | 342 | </div> |
---|
… | | … | |
---|
404 | <button type="button" class="close" data-dismiss="alert">×</button> | 404 | <button type="button" class="close" data-dismiss="alert">×</button> |
---|
405 | <h4> <%= title %> </h4> | 405 | <h4> <%= title %> </h4> |
---|
406 | <%= msg %> | 406 | <%= msg %> |
---|
| | 407 | </div> |
---|
| | 408 | </script> |
---|
| | 409 | |
---|
| | 410 | <script type="text/template" id="uploads-template"> |
---|
| | 411 | <div class="page"> |
---|
| | 412 | <div><h4> All your uploads </h4></div> |
---|
| | 413 | <div id="uploads-list" class="well"> |
---|
| | 414 | </div> |
---|
| | 415 | <hr> |
---|
| | 416 | <div class="row"> |
---|
| | 417 | <div> <h4> New Upload </h4> </div> |
---|
| | 418 | <form id="file-upload-form" action="" method="post" enctype="multipart/form-data"> |
---|
| | 419 | <input type="file" name="upload-file" id="select-file"> |
---|
| | 420 | <input type="button" id="upload-new-file" class="btn btn-sm btn-primary update-btn" value="Upload File"> |
---|
| | 421 | </form> |
---|
| | 422 | </div> |
---|
| | 423 | <div class="clearfix"></div> |
---|
| | 424 | </div> |
---|
| | 425 | </script> |
---|
| | 426 | |
---|
| | 427 | <script type="text/template" id="uploaded-item-template"> |
---|
| | 428 | <div class="uploaded-item row"> |
---|
| | 429 | <span> <icon here> </span> |
---|
| | 430 | <!--span class="uploaded-item-name"> <%= filename %> </span--> |
---|
| | 431 | <span class="uploaded-item-path"> |
---|
| | 432 | <a href="<%= M.UploadsURL() + filename %>"> |
---|
| | 433 | <%= M.UploadsURL() + filename %> |
---|
| | 434 | </a> |
---|
| | 435 | </span> |
---|
| | 436 | <span class="pull-right"> |
---|
| | 437 | <a href="javascript:void(0);" class="remove" for="<%= filename %>"> |
---|
| | 438 | <span class="glyphicon glyphicon-trash"></span> |
---|
| | 439 | </a> |
---|
| | 440 | </span> |
---|
| | 441 | <span class="clearfix"></span> |
---|
407 | </div> | 442 | </div> |
---|
408 | </script> | 443 | </script> |
---|
409 | | 444 | |
---|