Projet

Général

Profil

0001-assets-add-import_assets-management-command-39425.patch

Nicolas Roche, 03 février 2020 09:29

Télécharger (13,2 ko)

Voir les différences:

Subject: [PATCH] assets: add import_assets management command (#39425)

 combo/apps/assets/management/__init__.py      |  0
 .../assets/management/commands/__init__.py    |  0
 .../management/commands/export_assets.py      | 40 ++++++++
 .../management/commands/import_assets.py      | 47 +++++++++
 combo/apps/assets/utils.py                    | 38 ++++++++
 combo/apps/assets/views.py                    | 18 +---
 tests/test_assets.py                          | 96 ++++++++++++++++++-
 7 files changed, 223 insertions(+), 16 deletions(-)
 create mode 100644 combo/apps/assets/management/__init__.py
 create mode 100644 combo/apps/assets/management/commands/__init__.py
 create mode 100644 combo/apps/assets/management/commands/export_assets.py
 create mode 100644 combo/apps/assets/management/commands/import_assets.py
 create mode 100644 combo/apps/assets/utils.py
combo/apps/assets/management/commands/export_assets.py
1
# combo - content management system
2
# Copyright (C) 2020  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import sys
18
import tarfile
19

  
20
from django.core.management.base import BaseCommand, CommandError
21

  
22
from combo.apps.assets.utils import export_assets
23

  
24
class Command(BaseCommand):
25
    help = 'Export assets'
26

  
27
    def add_arguments(self, parser):
28
        parser.add_argument(
29
                '--output', metavar='FILENAME', default='assets.tar',
30
                help='name of a file to write output to')
31

  
32
    def handle(self, *args, **options):
33
        if options['output'] != '-':
34
            try:
35
                assets_file = tarfile.open(options['output'], 'w')
36
            except IOError as e:
37
                raise CommandError(e)
38
        else:
39
            assets_file = tarfile.open(mode='w|', fileobj=sys.stdout.buffer)
40
        export_assets(assets_file)
combo/apps/assets/management/commands/import_assets.py
1
# combo - content management system
2
# Copyright (C) 2020  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
from io import BytesIO
18
import sys
19
import tarfile
20

  
21
from django.core.management.base import BaseCommand, CommandError
22

  
23
from combo.apps.assets.views import import_assets
24

  
25
class Command(BaseCommand):
26
    help = 'Import an exported asset file'
27

  
28
    def add_arguments(self, parser):
29
        parser.add_argument('filename', metavar='FILENAME', type=str,
30
                help='name of file to import')
31
        parser.add_argument(
32
                '--overwrite', action='store_true', default=False,
33
                help='re-import assets already there')
34

  
35
    def handle(self, filename, *args, **options):
36
        if filename == '-':
37
            fd = BytesIO(sys.stdin.buffer.read())
38
        else:
39
            try:
40
                fd = open(filename, 'rb')
41
            except IOError as e:
42
                raise CommandError(e)
43
        try:
44
            assets = tarfile.open(fileobj=fd)
45
        except tarfile.TarError as e:
46
            raise CommandError(e)
47
        import_assets(assets, options['overwrite'])
combo/apps/assets/utils.py
1
# combo - content management system
2
# Copyright (C) 2020  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import os
18

  
19
from django.core.files.storage import default_storage
20

  
21

  
22
def import_assets(assets_tarfile, overwrite=False):
23
    media_prefix = default_storage.path('')
24
    for tarinfo in assets_tarfile.getmembers():
25
        filepath = default_storage.path(tarinfo.name)
26
        if not overwrite and os.path.exists(filepath):
27
            continue
28
        assets_tarfile.extract(tarinfo, path=media_prefix)
29

  
30

  
31
def export_assets(assets_tarfile):
32
    media_prefix = default_storage.path('')
33
    for basedir, dirnames, filenames in os.walk(media_prefix):
34
        for filename in filenames:
35
            assets_tarfile.add(
36
                    os.path.join(basedir, filename),
37
                    os.path.join(basedir, filename)[len(media_prefix):])
38
    assets_tarfile.close()
combo/apps/assets/views.py
32 32
import ckeditor
33 33
from sorl.thumbnail.shortcuts import get_thumbnail
34 34

  
35
from combo.apps.assets.utils import import_assets, export_assets
35 36
from combo.data.models import CellBase
36 37

  
37 38
from .forms import AssetUploadForm, AssetsImportForm
......
267 268
    def form_valid(self, form):
268 269
        overwrite = form.cleaned_data.get('overwrite')
269 270
        try:
270
            assets = tarfile.open(fileobj=form.cleaned_data['assets_file'])
271
            assets_file = tarfile.open(fileobj=form.cleaned_data['assets_file'])
271 272
        except tarfile.TarError:
272 273
            messages.error(self.request, _('The assets file is not valid.'))
273 274
            return super(AssetsImport, self).form_valid(form)
274
        media_prefix = default_storage.path('')
275
        for tarinfo in assets.getmembers():
276
            filepath = default_storage.path(tarinfo.name)
277
            if not overwrite and os.path.exists(filepath):
278
                continue
279
            assets.extract(tarinfo, path=media_prefix)
275
        import_assets(assets_file, overwrite)
280 276
        messages.success(self.request, _('The assets file has been imported.'))
281 277
        return super(AssetsImport, self).form_valid(form)
282 278

  
......
286 282
def assets_export(request, *args, **kwargs):
287 283
    fd = BytesIO()
288 284
    assets_file = tarfile.open('assets.tar', 'w', fileobj=fd)
289
    media_prefix = default_storage.path('')
290
    for basedir, dirnames, filenames in os.walk(media_prefix):
291
        for filename in filenames:
292
            assets_file.add(
293
                    os.path.join(basedir, filename),
294
                    os.path.join(basedir, filename)[len(media_prefix):])
295
    assets_file.close()
285
    export_assets(assets_file)
296 286
    return HttpResponse(fd.getvalue(), content_type='application/x-tar')
297 287

  
298 288

  
tests/test_assets.py
1 1
# -*- coding: utf-8 -*-
2 2

  
3 3
import base64
4
import io
5
import os
6
import pytest
7
import sys
8
import tarfile
4 9

  
5
from django.core.urlresolvers import reverse
10
from combo.apps.assets.utils import export_assets, import_assets
11
from django.core.files.storage import default_storage
6 12

  
7
import pytest
13
from django.core.files import File
14
from django.core.management import call_command, CommandError
15
from django.core.urlresolvers import reverse
16
from django.utils.six import BytesIO
8 17

  
9 18
from combo.apps.assets.models import Asset
10 19

  
11 20
pytestmark = pytest.mark.django_db
12 21

  
22

  
23
@pytest.fixture
24
def some_assets():
25
    Asset(key='banner', asset=File(BytesIO(b'test'), 'test.png')).save()
26
    Asset(key='favicon', asset=File(BytesIO(b'test2'), 'test2.png')).save()
27

  
28
def count_asset_files():
29
    nb_assets = 0
30
    media_prefix = default_storage.path('')
31
    for basedir, dirnames, filenames in os.walk(media_prefix):
32
        nb_assets += len(filenames)
33
    return nb_assets
34

  
35
def remove_asset_files():
36
    media_prefix = default_storage.path('')
37
    for basedir, dirnames, filenames in os.walk(media_prefix):
38
        for filename in filenames:
39
            os.remove('%s/%s' % (basedir, filename))
40

  
13 41
def test_asset_set_api(app, john_doe):
14 42
    app.authorization = ('Basic', (john_doe.username, john_doe.username))
15 43
    resp = app.post_json(reverse('api-assets-set', kwargs={'key': 'plop'}), params={
......
42 70
            }
43 71
            }, status=400)
44 72
        assert resp.json.get('err') == 1
73

  
74
def test_import_export_assets(app, some_assets):
75
    assert Asset.objects.count() == 2
76
    assert count_asset_files() == 2
77
    fd = BytesIO()
78
    assets_file = tarfile.open(mode='w', fileobj=fd)
79
    export_assets(assets_file)
80
    tar_bytes = fd.getvalue()
81

  
82
    path = default_storage.path('')
83
    os.remove('%s/assets/test.png' % path)
84
    open('%s/assets/test2.png' % path, 'w').write('foo')
85
    fd = BytesIO(tar_bytes)
86
    assert count_asset_files() == 1
87
    assets_file = tarfile.open(fileobj=fd)
88
    import_assets(assets_file)
89
    assert count_asset_files() == 2
90
    assert open('%s/assets/test.png' % path, 'r').read() == 'test'
91
    assert open('%s/assets/test2.png' % path, 'r').read() == 'foo'
92

  
93
    fd = BytesIO(tar_bytes)
94
    assets_file = tarfile.open(fileobj=fd)
95
    import_assets(assets_file, True)
96
    assert open('%s/assets/test2.png' % path, 'r').read() == 'test2'
97
    remove_asset_files()
98

  
99
def test_import_export_assets_cmd(some_assets, tmpdir):
100
    assert Asset.objects.count() == 2
101
    assert count_asset_files() == 2
102
    call_command('export_assets')
103
    path = default_storage.path('')
104
    os.remove('%s/assets/test.png' % path)
105
    open('%s/assets/test2.png' % path, 'w').write('foo')
106
    assert count_asset_files() == 1
107
    call_command('import_assets', 'assets.tar')
108
    assert count_asset_files() == 2
109
    assert open('%s/assets/test.png' % path, 'r').read() == 'test'
110
    assert open('%s/assets/test2.png' % path, 'r').read() == 'foo'
111
    remove_asset_files()
112

  
113
    with pytest.raises(CommandError, match=r'No such file or directory'):
114
        call_command('export_assets', '--output', '%s/noway/foo.tar' % tmpdir)
115
    with pytest.raises(CommandError, match=r'No such file or directory'):
116
        call_command('import_assets', '%s/foo.tar' % tmpdir)
117
    open('%s/foo.tar' % tmpdir, 'w').write('foo')
118
    with pytest.raises(CommandError, match=r'could not be opened successfully'):
119
        call_command('import_assets', '%s/foo.tar' % tmpdir)
120

  
121
def test_import_export_assets_cmd_flow(some_assets, capsysbinary, monkeypatch):
122
    assert Asset.objects.count() == 2
123
    assert count_asset_files() == 2
124
    call_command('export_assets', output='-')
125
    captured = capsysbinary.readouterr()
126
    path = default_storage.path('')
127
    os.remove('%s/assets/test.png' % path)
128
    open('%s/assets/test2.png' % path, 'w').write('foo')
129
    assert count_asset_files() == 1
130
    with capsysbinary.disabled():
131
        monkeypatch.setattr('sys.stdin', io.TextIOWrapper(BytesIO(captured.out)))
132
        call_command('import_assets', '-', '--overwrite')
133
    assert count_asset_files() == 2
134
    assert open('%s/assets/test.png' % path, 'r').read() == 'test'
135
    assert open('%s/assets/test2.png' % path, 'r').read() == 'test2'
136
    remove_asset_files()
45
-