general: enhance import/export cmdline commands (#15665)
This unifies behaviour with other publik components, it adds support for stdin/stdout using '-' as filename, and adds if-empty and clean flags to the import command.
This commit is contained in:
parent
76007e9fd5
commit
48065634d2
|
@ -15,18 +15,24 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import json
|
||||
from optparse import make_option
|
||||
import sys
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from combo.data.models import Page
|
||||
from combo.data.utils import export_site
|
||||
|
||||
class Command(BaseCommand):
|
||||
args = ['...']
|
||||
args = ''
|
||||
help = 'Export the site'
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--output', metavar='FILE', default=None,
|
||||
help='name of a file to write output to'),
|
||||
)
|
||||
|
||||
def handle(self, json_file='-', *args, **kwargs):
|
||||
if json_file == '-':
|
||||
output = sys.stdout
|
||||
def handle(self, *args, **options):
|
||||
if options['output'] and options['output'] != '-':
|
||||
output = open(options['output'], 'w')
|
||||
else:
|
||||
output = open(json_file, 'w')
|
||||
json.dump(Page.export_all_for_json(), output, indent=2)
|
||||
output = sys.stdout
|
||||
json.dump(export_site(), output, indent=2)
|
||||
|
|
|
@ -15,13 +15,28 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import json
|
||||
from optparse import make_option
|
||||
import sys
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from combo.data.models import Page
|
||||
from combo.data.utils import import_site
|
||||
|
||||
class Command(BaseCommand):
|
||||
args = ['...']
|
||||
args = '<filename>'
|
||||
help = 'Import an exported site'
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--clean', action='store_true', default=False,
|
||||
help='Clean site before importing'),
|
||||
make_option('--if-empty', action='store_true', default=False,
|
||||
help='Import only if site is empty'),
|
||||
)
|
||||
|
||||
def handle(self, json_file, *args, **kwargs):
|
||||
Page.load_serialized_pages(json.load(open(json_file)))
|
||||
def handle(self, filename, *args, **options):
|
||||
if filename == '-':
|
||||
fd = sys.stdin
|
||||
else:
|
||||
fd = open(filename)
|
||||
import_site(json.load(fd),
|
||||
if_empty=options['if_empty'],
|
||||
clean=options['clean'])
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2017 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
from .models import Page
|
||||
|
||||
def export_site():
|
||||
'''Dump site objects to JSON-dumpable dictionnary'''
|
||||
return Page.export_all_for_json()
|
||||
|
||||
|
||||
def import_site(data, if_empty=False, clean=False):
|
||||
if if_empty and Page.objects.count():
|
||||
return
|
||||
|
||||
if clean:
|
||||
Page.objects.all().delete()
|
||||
|
||||
with transaction.atomic():
|
||||
Page.load_serialized_pages(data)
|
|
@ -33,6 +33,7 @@ from django.views.generic import (TemplateView, RedirectView, DetailView,
|
|||
|
||||
from combo.data.models import Page, CellBase, ParentContentCell
|
||||
from combo.data.library import get_cell_class
|
||||
from combo.data.utils import export_site, import_site
|
||||
from combo import plugins
|
||||
|
||||
from .forms import (PageEditTitleForm, PageVisibilityForm, SiteImportForm,
|
||||
|
@ -58,7 +59,7 @@ class SiteExportView(ListView):
|
|||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
response = HttpResponse(content_type='application/json')
|
||||
json.dump(Page.export_all_for_json(), response, indent=2)
|
||||
json.dump(export_site(), response, indent=2)
|
||||
return response
|
||||
|
||||
site_export = SiteExportView.as_view()
|
||||
|
@ -71,7 +72,7 @@ class SiteImportView(FormView):
|
|||
|
||||
def form_valid(self, form):
|
||||
json_site = json.load(self.request.FILES['site_json'])
|
||||
Page.load_serialized_pages(json_site)
|
||||
import_site(json_site)
|
||||
return super(SiteImportView, self).form_valid(form)
|
||||
|
||||
site_import = SiteImportView.as_view()
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
from cStringIO import StringIO
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
from django.core.management import call_command
|
||||
|
||||
from combo.data.models import Page, TextCell
|
||||
from combo.data.utils import export_site, import_site
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def some_data():
|
||||
page = Page(title='One', slug='one')
|
||||
page.save()
|
||||
page = Page(title='Two', slug='two')
|
||||
page.save()
|
||||
page = Page(title='Three', slug='three')
|
||||
page.save()
|
||||
cell = TextCell(page=page, order=0, text='hello world')
|
||||
cell.save()
|
||||
|
||||
|
||||
def get_output_of_command(command, *args, **kwargs):
|
||||
old_stdout = sys.stdout
|
||||
output = sys.stdout = StringIO()
|
||||
call_command(command, *args, **kwargs)
|
||||
sys.stdout = old_stdout
|
||||
return output.getvalue()
|
||||
|
||||
def test_import_export(app, some_data):
|
||||
output = get_output_of_command('export_site')
|
||||
assert len(json.loads(output)) == 3
|
||||
import_site(data=[], clean=True)
|
||||
assert Page.objects.all().count() == 0
|
||||
assert TextCell.objects.all().count() == 0
|
||||
empty_output = get_output_of_command('export_site')
|
||||
assert len(json.loads(empty_output)) == 0
|
||||
|
||||
Page(title='test', slug='test').save()
|
||||
old_stdin = sys.stdin
|
||||
sys.stdin = StringIO(json.dumps([]))
|
||||
assert Page.objects.count() == 1
|
||||
try:
|
||||
call_command('import_site', '-', clean=True)
|
||||
finally:
|
||||
sys.stdin = old_stdin
|
||||
assert Page.objects.count() == 0
|
||||
|
||||
with tempfile.NamedTemporaryFile() as f:
|
||||
f.write(output)
|
||||
f.flush()
|
||||
call_command('import_site', f.name)
|
||||
|
||||
assert Page.objects.count() == 3
|
||||
assert TextCell.objects.all().count() == 1
|
||||
|
||||
import_site(data=[], if_empty=True)
|
||||
assert Page.objects.count() == 3
|
||||
assert TextCell.objects.all().count() == 1
|
||||
|
||||
import_site(data=[], clean=True)
|
||||
tempdir = tempfile.mkdtemp('chrono-test')
|
||||
empty_output = get_output_of_command('export_site', output=os.path.join(tempdir, 't.json'))
|
||||
assert os.path.exists(os.path.join(tempdir, 't.json'))
|
||||
shutil.rmtree(tempdir)
|
|
@ -224,13 +224,13 @@ def test_import_export_management_commands():
|
|||
os.unlink(export_filename)
|
||||
|
||||
cmd = ExportSiteCommand()
|
||||
cmd.handle(export_filename)
|
||||
cmd.handle(output=export_filename)
|
||||
assert os.path.exists(export_filename)
|
||||
|
||||
stdout = sys.stdout
|
||||
try:
|
||||
sys.stdout = StringIO()
|
||||
cmd.handle('-')
|
||||
cmd.handle(output='-')
|
||||
assert sys.stdout.getvalue() == open(export_filename).read()
|
||||
finally:
|
||||
sys.stdout = stdout
|
||||
|
@ -238,7 +238,7 @@ def test_import_export_management_commands():
|
|||
Page.objects.all().delete()
|
||||
|
||||
cmd = ImportSiteCommand()
|
||||
cmd.handle(export_filename)
|
||||
cmd.handle(filename=export_filename, if_empty=False, clean=False)
|
||||
|
||||
new_page_1 = Page.objects.all().order_by('order')[0]
|
||||
new_page_2 = Page.objects.all().order_by('order')[1]
|
||||
|
|
Loading…
Reference in New Issue