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 %}
+
+
+{% 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)