0001-manager-use-ajax-to-display-tar-format-site-export-s.patch
combo/apps/assets/urls.py | ||
---|---|---|
35 | 35 | |
36 | 36 |
urlpatterns = [ |
37 | 37 |
url(r'^assets/(?P<key>[\w_:-]+)$', views.serve_asset), |
38 | 38 |
url(r'^manage/assets/', decorated_includes(manager_required, |
39 | 39 |
include(assets_manager_urls))), |
40 | 40 | |
41 | 41 |
url(r'^api/assets/set/(?P<key>[\w_:-]+)/$', api_views.view_set, |
42 | 42 |
name='api-assets-set'), |
43 |
url(r'^ajax/asset-disk-space-usage/$', |
|
44 |
views.ajax_disk_space_usage, name='combo-assets-ajax-disk-space-usage'), |
|
43 | 45 |
] |
combo/apps/assets/views.py | ||
---|---|---|
12 | 12 |
# GNU Affero General Public License for more details. |
13 | 13 |
# |
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 |
import json |
18 | 18 |
import tarfile |
19 | 19 |
import os |
20 |
import time |
|
20 | 21 | |
21 | 22 |
from django.conf import settings |
22 | 23 |
from django.contrib import messages |
23 | 24 |
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied |
24 | 25 |
from django.core.files.storage import default_storage |
25 | 26 |
from django.urls import reverse, reverse_lazy |
26 |
from django.http import Http404, HttpResponse |
|
27 |
from django.http import Http404, HttpResponse, StreamingHttpResponse
|
|
27 | 28 |
from django.shortcuts import get_object_or_404 |
28 | 29 |
from django.shortcuts import redirect |
29 | 30 |
from django.utils.six import BytesIO |
30 | 31 |
from django.utils.translation import ugettext_lazy as _ |
31 | 32 |
from django.views.generic import TemplateView, ListView, FormView |
32 | 33 | |
33 | 34 |
import ckeditor |
34 | 35 |
from sorl.thumbnail.shortcuts import get_thumbnail |
... | ... | |
372 | 373 |
geometry_string += 'x%s' % height |
373 | 374 | |
374 | 375 |
# no thumbnail whithout geometry_string or for a svg file |
375 | 376 |
if not geometry_string or asset.asset.name.endswith('svg'): |
376 | 377 |
return redirect(asset.asset.url) |
377 | 378 | |
378 | 379 |
# get or create thumbnail and return url |
379 | 380 |
return redirect(get_thumbnail(asset.asset, geometry_string, **thumb_options).url) |
381 | ||
382 | ||
383 |
def ajax_disk_space_usage(request): |
|
384 |
response = StreamingHttpResponse(ajax_disk_space_usage_generator()) |
|
385 |
response['X-Accel-Buffering'] = 'no' |
|
386 |
return response |
|
387 | ||
388 | ||
389 |
def ajax_disk_space_usage_generator(): |
|
390 |
media_prefix = default_storage.path('') |
|
391 |
start = time.time() |
|
392 |
total = 0 |
|
393 |
units = ['Bytes', 'Ko', 'Mo', 'Go'] |
|
394 | ||
395 |
def get_message(size): |
|
396 |
unit = 0 |
|
397 |
while unit < 3 and size > 1024: |
|
398 |
size = int(size / 1024) |
|
399 |
unit += 1 |
|
400 |
return "<message>(%s %s)</message>" % (size, units[unit]) |
|
401 | ||
402 |
for basedir, dirnames, filenames in os.walk(media_prefix): |
|
403 |
for filename in filenames: |
|
404 |
time.sleep(0.5) # to test progression (to remove) |
|
405 |
total += os.stat(os.path.join(basedir, filename)).st_size |
|
406 |
if time.time() - start > 0.5: |
|
407 |
yield get_message(total) |
|
408 |
yield get_message(total) |
combo/manager/forms.py | ||
---|---|---|
188 | 188 |
self.instance.restricted_to_unlogged = True |
189 | 189 |
self.instance.groups.set(self.cleaned_data['groups']) |
190 | 190 |
self.instance.save() |
191 | 191 |
return self.instance |
192 | 192 | |
193 | 193 | |
194 | 194 |
class SiteImportForm(forms.Form): |
195 | 195 |
site_file = forms.FileField(label=_('Site Export File')) |
196 | ||
197 | ||
198 |
class SiteExportForm(forms.Form): |
|
199 |
include_asset = forms.BooleanField(label=_('Include assets into the export'), required=False) |
combo/manager/templates/combo/site_export.html | ||
---|---|---|
3 | 3 | |
4 | 4 |
{% block appbar %} |
5 | 5 |
<h2>{% trans "Site Export" %}</h2> |
6 | 6 |
{% endblock %} |
7 | 7 | |
8 | 8 |
{% block content %} |
9 | 9 |
<form method="post"> |
10 | 10 |
{% csrf_token %} |
11 |
{{ form.as_p }} |
|
11 |
<p> |
|
12 |
<input type="checkbox" name="include_asset" id="id_include_asset"> |
|
13 |
{% trans 'Include assets into the export' %} |
|
14 |
<span id="asset-disk-space-usage-message"></span> |
|
15 |
</input> |
|
16 |
</p> |
|
12 | 17 |
<div class="buttons"> |
13 | 18 |
<button>{% trans 'Export' %}</button> |
14 | 19 |
<a class="cancel" href="{% url 'combo-manager-homepage' %}">{% trans 'Cancel' %}</a> |
15 | 20 |
</div> |
21 |
<script> |
|
22 |
function renderMessage(text) { |
|
23 |
document.getElementById("asset-disk-space-usage-message").innerHTML = text; |
|
24 |
} |
|
25 | ||
26 |
$(document).ready(function() { |
|
27 |
var xhReq = new XMLHttpRequest(); |
|
28 |
var nextReadPos = 0; |
|
29 | ||
30 |
function pollLatestResponse() { |
|
31 |
var allMessages = xhReq.responseText; |
|
32 |
do { |
|
33 |
var unprocessed = allMessages.substring(nextReadPos); |
|
34 |
var messageXMLEndIndex = unprocessed.indexOf("</message>"); |
|
35 |
if (messageXMLEndIndex!=-1) { |
|
36 |
var endOfFirstMessageIndex = messageXMLEndIndex + "</message>".length; |
|
37 |
var anUpdate = unprocessed.substring(0, endOfFirstMessageIndex); |
|
38 |
renderMessage(anUpdate); |
|
39 |
nextReadPos += endOfFirstMessageIndex; |
|
40 |
} |
|
41 |
} while (messageXMLEndIndex != -1); |
|
42 |
} |
|
43 | ||
44 |
xhReq.open("GET", "{% url 'combo-assets-ajax-disk-space-usage' %}", true); |
|
45 |
xhReq.send(null); |
|
46 |
pollTimer = setInterval(pollLatestResponse, 500); |
|
47 |
}) |
|
48 |
</script> |
|
16 | 49 |
</form> |
17 | 50 |
{% endblock %} |
combo/manager/views.py | ||
---|---|---|
39 | 39 |
CreateView, UpdateView, ListView, DeleteView, FormView, TemplateView) |
40 | 40 | |
41 | 41 |
from combo.data.models import Page, CellBase, ParentContentCell, PageSnapshot, LinkListCell |
42 | 42 |
from combo.data.library import get_cell_class |
43 | 43 |
from combo.data.utils import (export_site, export_site_tar, import_site, import_site_tar, |
44 | 44 |
ImportSiteError, MissingGroups) |
45 | 45 |
from combo import plugins |
46 | 46 | |
47 |
from .forms import (PageEditTitleForm, PageVisibilityForm, SiteImportForm, SiteExportForm,
|
|
47 |
from .forms import (PageEditTitleForm, PageVisibilityForm, SiteImportForm, |
|
48 | 48 |
PageEditRedirectionForm, PageSelectTemplateForm, PageEditSlugForm, |
49 | 49 |
PageEditPictureForm, PageEditIncludeInNavigationForm, |
50 | 50 |
PageEditDescriptionForm, CellVisibilityForm, PageDuplicateForm) |
51 | 51 | |
52 | 52 | |
53 | 53 |
class HomepageView(ListView): |
54 | 54 |
model = Page |
55 | 55 |
template_name = 'combo/manager_home.html' |
... | ... | |
60 | 60 |
context['extra_actions'] = plugins.get_extra_manager_actions() |
61 | 61 |
context['collapse_pages'] = settings.COMBO_MANAGE_HOME_COLLAPSE_PAGES |
62 | 62 |
return context |
63 | 63 | |
64 | 64 | |
65 | 65 |
homepage = HomepageView.as_view() |
66 | 66 | |
67 | 67 | |
68 |
class SiteExportView(FormView): |
|
69 |
form_class = SiteExportForm |
|
68 |
class SiteExportView(TemplateView): |
|
70 | 69 |
template_name = 'combo/site_export.html' |
71 | 70 | |
72 | 71 |
def post(self, request, *args, **kwargs): |
73 | 72 |
if request.POST.get('include_asset'): |
74 | 73 |
fd = BytesIO() |
75 | 74 |
export_site_tar(fd) |
76 | 75 |
response = HttpResponse(content=fd.getvalue(), content_type='application/x-tar') |
77 | 76 |
response['Content-Disposition'] = 'attachment; filename="site-export.tar"' |
78 |
- |