file upload to gridftp
authorMaciej Tronowski <mtro@man.poznan.pl>
Fri, 3 Apr 2015 14:50:46 +0000 (16:50 +0200)
committerMaciej Tronowski <mtro@man.poznan.pl>
Fri, 3 Apr 2015 14:50:46 +0000 (16:50 +0200)
filex/templates/filex/upload.css.html [new file with mode: 0644]
filex/templates/filex/upload.html [new file with mode: 0644]
filex/templates/filex/upload.js.html [new file with mode: 0644]
filex/uploadhandler.py
filex/urls.py
filex/views.py
qcg/templates/qcg/base.html
qcg/templates/qcg/gridftp.html
qcg/templates/qcg/gridftp_upload.html [new file with mode: 0644]
qcg/urls.py
qcg/views.py

diff --git a/filex/templates/filex/upload.css.html b/filex/templates/filex/upload.css.html
new file mode 100644 (file)
index 0000000..3490714
--- /dev/null
@@ -0,0 +1,30 @@
+<style>
+    #path {
+        font-size: 16px;
+    }
+
+    #elements {
+        position: absolute;
+        left: 0;
+        right: 0;
+        top: 90px;
+        bottom: 51px;
+        overflow-y: auto;
+    }
+
+    #elements .item .text {
+        margin: 10px 0;
+    }
+
+    #elements .item .status {
+        float: right;
+    }
+
+    #btn-open {
+        float: left;
+    }
+
+    #btn-close {
+        margin-right: 15px;
+    }
+</style>
diff --git a/filex/templates/filex/upload.html b/filex/templates/filex/upload.html
new file mode 100644 (file)
index 0000000..ef1ddf0
--- /dev/null
@@ -0,0 +1,23 @@
+<header class="container-fluid">
+    <h3>{% block title %}Wgrywanie plików{% endblock %}</h3>
+    <p id="path"><span class="text-muted">Lokalizacja:</span> {{ host }}{{ path }}</p>
+</header>
+
+<form enctype="multipart/form-data" method="post" action="{{ url }}" hidden>
+    {% csrf_token %}
+    <input id="files" type="file" name="files" multiple>
+</form>
+
+<div id="elements" class="container-fluid">
+</div>
+
+<footer class="navbar navbar-default navbar-fixed-bottom">
+    <div class="container-fluid">
+        <button id="btn-open" class="btn btn-default navbar-btn" onclick="$('#files').click()">Wybierz pliki</button>
+        <p class="navbar-text">lub przeciągnij je w obszar tego okna</p>
+        <div class="navbar-right">
+            <p id="status" class="navbar-text"></p>
+            <button id="btn-close" class="btn btn-default navbar-btn" onclick="window.opener.filex.reloadFiles(); window.close()">Zamknij</button>
+        </div>
+    </div>
+</footer>
diff --git a/filex/templates/filex/upload.js.html b/filex/templates/filex/upload.js.html
new file mode 100644 (file)
index 0000000..1ad049f
--- /dev/null
@@ -0,0 +1,100 @@
+{% load staticfiles %}
+
+<script src="{% static 'filex/fileupload/jquery.ui.widget.js' %}"></script>
+<script src="{% static 'filex/fileupload/jquery.iframe-transport.js' %}"></script>
+<script src="{% static 'filex/fileupload/jquery.fileupload.js' %}"></script>
+<script src="{% static 'filex/underscore/underscore-min.js' %}"></script>
+<script src="{% static 'filex/humanize/humanize-duration.js' %}"></script>
+
+<script>
+    $(function () {
+        'use strict';
+
+        var template = _.template($('#template').html());
+
+        function humanBytes(bytes, no_unit) {
+            function format(val, unit) {
+                if(no_unit)
+                    return val;
+
+                return val + ' ' + unit;
+            }
+
+            if(bytes == 0)
+                return format('0', 'B');
+
+            var k = 1024,
+                sizes = ['B', 'KB', 'MB', 'GB', 'TB'],
+                i = Math.floor(Math.log(bytes) / Math.log(k));
+
+            return format((bytes / Math.pow(k, i)).toFixed(1), sizes[i]);
+        }
+
+        $('#files').fileupload({
+            sequentialUploads: true,
+            add: function (e, data) {
+                $.each(data.files, function (index, file) {
+                    data.context = $(template(file));
+                    $('#elements').append(data.context);
+                });
+
+                data.submit();
+            },
+            progress: function (e, data) {
+                var progress = parseInt(data.loaded / data.total * 100, 10);
+
+                data.context.find('.progress-bar').css('width', progress+'%').attr('aria-valuenow', data.loaded);
+                data.context.find('.progress-bar span').text(progress+'%');
+
+                if (data.loaded < data.total) {
+                    data.context.find('.bit-rate').text(humanBytes(data.bitrate / 8) + '/s');
+                    data.context.find('.progress-info').text(humanBytes(data.loaded, true) + ' / ' + humanBytes(data.total));
+                }
+            },
+            progressall: function (e, data) {
+                var remaining = (data.total - data.loaded) / (data.bitrate / 8);
+
+                if (remaining) {
+                    $('#btn-close').hide();
+                    $('#status').text('Pozostało ' + (humanizeDuration(remaining * 1000, {language: 'pl', round: true}) || 'kilka sekund'));
+                }
+                else {
+                    $('#btn-close').show();
+                    $('#status').text('');
+                }
+            },
+            done: function (e, data) {
+                data.context.find('.bit-rate').text('');
+                data.context.find('.progress-info').text('Zakończono');
+            },
+            fail: function (e, data) {
+                data.context.find('.progress-info').text('Błąd');
+                console.log(data.errorThrown);
+                console.log(data.textStatus);
+            }
+        });
+
+        $(window).on('beforeunload', function() {
+            if ($('#files').fileupload('active'))
+                return 'Nie zakończono przesyłania wszystkich plików, czy chcesz kontynuować?';
+        });
+    });
+</script>
+
+<script type="text/template" id="template">
+    <div class="item">
+        <div class="text clearfix">
+            <span class="name"><%= name %></span>
+            <span class="status pull-right">
+                <em class="small bit-rate" style="margin-right: 15px"></em>
+                <span class="text-muted progress-info">Oczekiwanie</span>
+            </span>
+        </div>
+
+        <div class="progress">
+            <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="<%= size %>" style="width: 0;">
+                <span class="sr-only">0%</span>
+            </div>
+        </div>
+    </div>
+</script>
index 20a1339..0f5bfb3 100644 (file)
@@ -1,5 +1,8 @@
+from functools import wraps
+
 from django.core.files.uploadedfile import UploadedFile
 from django.core.files.uploadhandler import FileUploadHandler, StopUpload, StopFutureHandlers
+from django.views.decorators.csrf import csrf_exempt, csrf_protect
 
 from filex.ftp import FTPOperation
 
@@ -35,3 +38,13 @@ class FtpUploadHandler(FileUploadHandler):
 
         return UploadedFile(name=self.file_name, size=file_size, charset=self.charset,
                             content_type=self.content_type, content_type_extra=self.content_type_extra)
+
+
+def with_ftp_upload_handler(view_func):
+    @wraps(view_func)
+    def wrapped_view(request, *args, **kwargs):
+        request.upload_handlers = [FtpUploadHandler(request)]
+
+        return csrf_protect(view_func)(request, *args, **kwargs)
+
+    return csrf_exempt(wrapped_view)
index 382d720..3a1945d 100644 (file)
@@ -5,4 +5,5 @@ from filex import views
 urlpatterns = patterns('',
     url(r'^list/$', views.list_content, name='list'),
     url(r'^download/$', views.download, name='download'),
+    url(r'^upload/$', views.upload, name='upload'),
 )
index 2bda5c8..57932d7 100644 (file)
@@ -1,13 +1,14 @@
 from datetime import datetime
+import mimetypes
 
 from django.core.exceptions import PermissionDenied, SuspiciousOperation
 from django.http import JsonResponse, StreamingHttpResponse
 from django.template.defaultfilters import filesizeformat
 from django.utils.formats import date_format
 from django.utils.timezone import UTC, localtime
-import mimetypes
 
 from filex.ftp import FTPOperation, FTPException
+from filex.uploadhandler import with_ftp_upload_handler
 
 
 def check_auth(request):
@@ -78,3 +79,9 @@ def download(request):
         response['Content-Encoding'] = encoding
 
     return response
+
+
+@with_ftp_upload_handler
+def upload(request):
+    # TODO error handling
+    return JsonResponse({'success': True})
index 7030598..9f38b0c 100644 (file)
@@ -1,4 +1,4 @@
-{% load staticfiles webdesign %}
+{% load staticfiles %}
 {% load firstof from future %}
 
 <!DOCTYPE html>
@@ -14,6 +14,7 @@
     {% block extra_css %}{% endblock %}
 </head>
 <body>
+    {% block body %}
     <nav class="navbar navbar-default navbar-static-top">
         <div class="container">
             <div class="navbar-header">
             </div>
         </div>
     </footer>
+    {% endblock body %}
 
     <script src="{% static 'qcg/jquery/jquery.min.js' %}"></script>
     <script src="{% static 'qcg/bootstrap/js/bootstrap.min.js' %}"></script>
index 521992f..490c66e 100644 (file)
@@ -7,6 +7,19 @@
 
 {% block extra_js %}
     {% include 'filex/source.js.html' %}
+
+    <script>
+        $(function () {
+            $('#upload').click(function(e) {
+                e.preventDefault();
+
+                var url = this.href + '?' + $.param({host: filex.host, path: filex.path.full() + '/'});
+
+                var win = window.open(url, url, 'height=500,width=800');
+                win.focus();
+            });
+        })
+    </script>
 {% endblock extra_js %}
 
 {% block container %}
@@ -17,7 +30,7 @@
     <div class="well well-sm">
         <div class="btn-toolbar" role="toolbar">
             <div class="btn-group" role="group">
-                <button class="btn btn-default">Wgraj plik</button>
+                <a id="upload" href="{% url 'gridftp_upload' %}" class="btn btn-default" target="_blank">Wgraj plik</a>
                 <button class="btn btn-default">Utwórz katalog</button>
             </div>
             <div class="btn-group" role="group">
diff --git a/qcg/templates/qcg/gridftp_upload.html b/qcg/templates/qcg/gridftp_upload.html
new file mode 100644 (file)
index 0000000..2a11065
--- /dev/null
@@ -0,0 +1,13 @@
+{% extends 'qcg/base.html' %}
+
+{% block extra_css %}
+    {% include 'filex/upload.css.html' %}
+{% endblock extra_css %}
+
+{% block extra_js %}
+    {% include 'filex/upload.js.html' %}
+{% endblock extra_js %}
+
+{% block body %}
+    {% include 'filex/upload.html' %}
+{% endblock body %}
index fe7eab1..3bb2d7d 100644 (file)
@@ -18,4 +18,6 @@ urlpatterns = patterns('',
     url(r'^job/(?P<job_id>[\w]+)/(?P<task_id>[\w]+)/?$', views.task_details, name='task'),
 
     url(r'^gridftp/$', views.gridftp, name='gridftp'),
+
+    url(r'^gridftp/upload/$', views.gridftp_upload, name='gridftp_upload'),
 )
index 3551a43..27fdf87 100644 (file)
@@ -183,3 +183,10 @@ def job_new(request):
 @login_required
 def gridftp(request):
     return render(request, 'qcg/gridftp.html')
+
+
+def gridftp_upload(request):
+    # TODO GET data validation
+    return render(request, 'qcg/gridftp_upload.html',
+                  {'url': reverse('filex:upload') + '?' + request.GET.urlencode(safe='/'),
+                   'host': request.GET.get('host'), 'path': request.GET.get('path')})