diff --git a/combo/apps/assets/forms.py b/combo/apps/assets/forms.py index aba7a548..414e0c02 100644 --- a/combo/apps/assets/forms.py +++ b/combo/apps/assets/forms.py @@ -20,3 +20,8 @@ from django.utils.translation import ugettext_lazy as _ class AssetUploadForm(forms.Form): upload = forms.FileField(label=_('File')) + + +class AssetsImportForm(forms.Form): + assets_file = forms.FileField(label=_('Assets File')) + overwrite = forms.BooleanField(label=_('Overwrite Existing Files'), required=False) diff --git a/combo/apps/assets/templates/combo/manager_assets.html b/combo/apps/assets/templates/combo/manager_assets.html index 423d0d2d..1c8d9de6 100644 --- a/combo/apps/assets/templates/combo/manager_assets.html +++ b/combo/apps/assets/templates/combo/manager_assets.html @@ -4,7 +4,12 @@ {% block appbar %}

{% trans 'Assets' %}

+ {% trans 'Upload' %} + {% endblock %} diff --git a/combo/apps/assets/templates/combo/manager_assets_import.html b/combo/apps/assets/templates/combo/manager_assets_import.html new file mode 100644 index 00000000..74fe8ff2 --- /dev/null +++ b/combo/apps/assets/templates/combo/manager_assets_import.html @@ -0,0 +1,18 @@ +{% extends "combo/manager_base.html" %} +{% load i18n %} + +{% block appbar %} +

{% trans "Assets Import" %}

+{% endblock %} + +{% block content %} + +
+ {% csrf_token %} + {{ form.as_p }} +
+ + {% trans 'Cancel' %} +
+
+{% endblock %} diff --git a/combo/apps/assets/urls.py b/combo/apps/assets/urls.py index 087716b4..38770b91 100644 --- a/combo/apps/assets/urls.py +++ b/combo/apps/assets/urls.py @@ -27,6 +27,8 @@ assets_manager_urls = [ url(r'^upload/$', views.asset_upload, name='combo-manager-asset-upload'), url(r'^upload/(?P[\w_:-]+)/$', views.slot_asset_upload, name='combo-manager-slot-asset-upload'), url(r'^delete/(?P[\w_:-]+)/$', views.slot_asset_delete, name='combo-manager-slot-asset-delete'), + url(r'^export/$', views.assets_export, name='combo-manager-assets-export'), + url(r'^import/$', views.assets_import, name='combo-manager-assets-import'), ] urlpatterns = [ diff --git a/combo/apps/assets/views.py b/combo/apps/assets/views.py index 7d39214d..3270dc6e 100644 --- a/combo/apps/assets/views.py +++ b/combo/apps/assets/views.py @@ -14,14 +14,18 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import tarfile import os from django.conf import settings +from django.contrib import messages from django.core.exceptions import PermissionDenied from django.core.files.storage import default_storage from django.core.urlresolvers import reverse, reverse_lazy -from django.http import Http404 +from django.http import Http404, HttpResponse from django.shortcuts import redirect +from django.utils.six import BytesIO +from django.utils.translation import ugettext_lazy as _ from django.views.generic import TemplateView, ListView, FormView import ckeditor @@ -29,7 +33,7 @@ from sorl.thumbnail.shortcuts import get_thumbnail from combo.data.models import CellBase -from .forms import AssetUploadForm +from .forms import AssetUploadForm, AssetsImportForm from .models import Asset @@ -240,6 +244,43 @@ class SlotAssetDelete(TemplateView): slot_asset_delete = SlotAssetDelete.as_view() +class AssetsImport(FormView): + form_class = AssetsImportForm + template_name = 'combo/manager_assets_import.html' + success_url = reverse_lazy('combo-manager-assets') + + def form_valid(self, form): + overwrite = form.cleaned_data.get('overwrite') + try: + assets = tarfile.open(fileobj=form.cleaned_data['assets_file']) + except tarfile.TarError: + messages.error(self.request, _('The assets file is not valid.')) + return super(AssetsImport, self).form_valid(form) + media_prefix = default_storage.path('') + for tarinfo in assets.getmembers(): + filepath = default_storage.path(tarinfo.name) + if not overwrite and os.path.exists(filepath): + continue + assets.extract(tarinfo, path=media_prefix) + messages.success(self.request, _('The assets file has been imported.')) + return super(AssetsImport, self).form_valid(form) + +assets_import = AssetsImport.as_view() + + +def assets_export(request, *args, **kwargs): + fd = BytesIO() + assets_file = tarfile.open('assets.tar', 'w', fileobj=fd) + media_prefix = default_storage.path('') + for basedir, dirnames, filenames in os.walk(media_prefix): + for filename in filenames: + assets_file.add( + os.path.join(basedir, filename), + os.path.join(basedir, filename)[len(media_prefix):]) + assets_file.close() + return HttpResponse(fd.getvalue(), content_type='application/x-tar') + + def serve_asset(request, key): try: asset = Asset.objects.get(key=key) diff --git a/tests/test_manager.py b/tests/test_manager.py index 3f4185d3..8392f421 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -2,6 +2,7 @@ import base64 import json import os import re +import shutil import mock @@ -823,6 +824,80 @@ def test_asset_slots_management(app, admin_user): assert '>Delete<' in resp.text assert Asset.objects.filter(key='collectivity:cgu').count() == 1 + +def test_asset_export_import(app, admin_user): + for path in ('uploads', 'assets', 'cache'): + if os.path.exists(default_storage.path(path)): + shutil.rmtree(default_storage.path(path)) + + app = login(app) + + # upload a file + resp = app.get('/manage/assets/') + resp = resp.click('Upload') + resp.form['upload'] = Upload('test.png', + base64.decodestring(b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='), + 'image/png') + resp = resp.form.submit() + + resp = app.get('/manage/assets/') + resp = resp.click('Export') + assert resp.content_type == 'application/x-tar' + content = resp.content + + for path in ('uploads', 'assets'): + if os.path.exists(default_storage.path(path)): + shutil.rmtree(default_storage.path(path)) + + resp = app.get('/manage/assets/') + assert 'have any asset yet.' in resp.text + resp = resp.click('Import') + resp.form['assets_file'] = Upload('test.tar', content) + resp = resp.form.submit() + assert sum([len(x[2]) for x in os.walk(default_storage.path(''))]) == 2 + resp = resp.follow() + assert 'The assets file has been imported.' in resp.text + + # test no overwrite + filename = re.findall('data-href="(.*?)"', resp.text)[0][7:] # strip /media/ + with open(default_storage.path(filename), 'w') as fd: + fd.write('test') # 4 bytes + assert os.stat(default_storage.path(filename)).st_size == 4 + + resp = app.get('/manage/assets/') + resp = resp.click('Import') + resp.form['assets_file'] = Upload('test.tar', content) + resp = resp.form.submit() + resp = resp.follow() + assert 'The assets file has been imported.' in resp.text + + assert os.stat(default_storage.path(filename)).st_size == 4 + + # test overwrite + resp = app.get('/manage/assets/') + resp = resp.click('Import') + resp.form['overwrite'] = True + resp.form['assets_file'] = Upload('test.tar', content) + resp = resp.form.submit() + resp = resp.follow() + assert 'The assets file has been imported.' in resp.text + + assert os.stat(default_storage.path(filename)).st_size == 67 + + # test uploading garbage + for path in ('uploads', 'assets'): + if os.path.exists(default_storage.path(path)): + shutil.rmtree(default_storage.path(path)) + + resp = app.get('/manage/assets/') + resp = resp.click('Import') + resp.form['assets_file'] = Upload('test.tar', b'garbage') + resp = resp.form.submit() + assert sum([len(x[2]) for x in os.walk(default_storage.path(''))]) == 0 + resp = resp.follow() + assert 'The assets file is not valid.' in resp.text + + def test_menu_json(app, admin_user): app.get('/manage/menu.json', status=302)