0001-import-do-not-fail-if-page.parent-is-not-found-22889.patch
combo/data/models.py | ||
---|---|---|
29 | 29 | |
30 | 30 |
from django.apps import apps |
31 | 31 |
from django.conf import settings |
32 |
from django.contrib import messages |
|
32 | 33 |
from django.contrib.auth.models import Group |
33 | 34 |
from django.contrib.contenttypes.fields import GenericForeignKey |
34 | 35 |
from django.contrib.contenttypes.fields import GenericRelation |
... | ... | |
399 | 400 |
return serialized_page |
400 | 401 | |
401 | 402 |
@classmethod |
402 |
def load_serialized_page(cls, json_page, snapshot=None): |
|
403 |
def load_serialized_page(cls, json_page, snapshot=None, request=None):
|
|
403 | 404 |
json_page['model'] = 'data.page' |
404 | 405 |
json_page['fields']['groups'] = [[x] for x in json_page['fields']['groups'] if isinstance(x, six.string_types)] |
405 | 406 |
page, created = Page.objects.get_or_create(slug=json_page['fields']['slug'], snapshot=snapshot) |
406 | 407 |
json_page['pk'] = page.id |
408 |
parent_slug = json_page['fields'].get('parent') or [] |
|
409 |
if parent_slug and not Page.objects.filter(slug=parent_slug[0]).exists(): |
|
410 |
# parent not found, remove it and exclude page from navigation |
|
411 |
json_page['fields'].pop('parent') |
|
412 |
json_page['fields']['exclude_from_navigation'] = True |
|
413 |
if request: |
|
414 |
messages.warning( |
|
415 |
request, |
|
416 |
_('Unknown parent for page "%s"; parent has been reset and page was excluded from navigation.') |
|
417 |
% json_page['fields']['title']) |
|
407 | 418 |
page = next(serializers.deserialize('json', json.dumps([json_page]), ignorenonexistent=True)) |
408 | 419 |
page.object.snapshot = snapshot |
409 | 420 |
page.save() |
... | ... | |
414 | 425 |
else: |
415 | 426 |
cell['fields']['page'] = page.object.natural_key() |
416 | 427 | |
417 | ||
418 | 428 |
# if there were cells, remove them |
419 | 429 |
for cell in CellBase.get_cells(page_id=page.object.id): |
420 | 430 |
cell.delete() |
... | ... | |
433 | 443 |
cell.object.import_subobjects(cell_data) |
434 | 444 | |
435 | 445 |
@classmethod |
436 |
def load_serialized_pages(cls, json_site): |
|
446 |
def load_serialized_pages(cls, json_site, request=None):
|
|
437 | 447 |
cells = [] |
438 | 448 |
for json_page in json_site: |
439 |
cls.load_serialized_page(json_page) |
|
449 |
cls.load_serialized_page(json_page, request=request)
|
|
440 | 450 |
cells.extend(json_page.get('cells')) |
441 | 451 |
cls.load_serialized_cells(cells) |
442 | 452 | |
443 |
# 2nd pass to set parents |
|
444 |
for json_page in json_site: |
|
445 |
if json_page.get('parent_slug'): |
|
446 |
page = Page.objects.get(slug=json_page['fields']['slug']) |
|
447 |
page.parent = Page.objects.get(slug=json_page.get('parent_slug')) |
|
448 |
page.save() |
|
449 | ||
450 | 453 |
@classmethod |
451 | 454 |
def export_all_for_json(cls): |
452 | 455 |
ordered_pages = Page.get_as_reordered_flat_hierarchy(cls.objects.all()) |
combo/data/utils.py | ||
---|---|---|
47 | 47 |
} |
48 | 48 | |
49 | 49 | |
50 |
def import_site(data, if_empty=False, clean=False): |
|
50 |
def import_site(data, if_empty=False, clean=False, request=None):
|
|
51 | 51 |
if isinstance(data, list): |
52 | 52 |
# old export form with a list of pages, convert it to new dictionary |
53 | 53 |
# format. |
... | ... | |
80 | 80 | |
81 | 81 |
MapLayer.load_serialized_objects(data.get('map-layers') or []) |
82 | 82 |
Asset.load_serialized_objects(data.get('assets') or []) |
83 |
Page.load_serialized_pages(data.get('pages') or []) |
|
83 |
Page.load_serialized_pages(data.get('pages') or [], request=request)
|
|
84 | 84 | |
85 | 85 |
if data.get('pwa'): |
86 | 86 |
PwaSettings.load_serialized_settings(data['pwa'].get('settings')) |
combo/manager/views.py | ||
---|---|---|
85 | 85 |
return self.form_invalid(form) |
86 | 86 | |
87 | 87 |
try: |
88 |
import_site(json_site) |
|
88 |
import_site(json_site, request=self.request)
|
|
89 | 89 |
except MissingGroups as e: |
90 | 90 |
form.add_error('site_json', force_text(e)) |
91 | 91 |
return self.form_invalid(form) |
92 | 92 | |
93 | 93 |
return super(SiteImportView, self).form_valid(form) |
94 | 94 | |
95 | ||
95 | 96 |
site_import = SiteImportView.as_view() |
96 | 97 | |
97 | 98 |
tests/test_import_export.py | ||
---|---|---|
26 | 26 | |
27 | 27 |
@pytest.fixture |
28 | 28 |
def some_data(): |
29 |
page = Page(title='One', slug='one') |
|
30 |
page.save() |
|
31 |
page = Page(title='Two', slug='two') |
|
32 |
page.save() |
|
33 |
page = Page(title='Three', slug='three') |
|
34 |
page.save() |
|
29 |
Page.objects.create(title='One', slug='one') |
|
30 |
Page.objects.create(title='Two', slug='two') |
|
31 |
page = Page.objects.create(title='Three', slug='three') |
|
35 | 32 |
cell = TextCell(page=page, order=0, text='hello world', placeholder='content') |
36 | 33 |
cell.save() |
37 | 34 | |
35 | ||
38 | 36 |
@pytest.fixture |
39 | 37 |
def some_map_layers(): |
40 | 38 |
MapLayer(label='Foo', slug='foo', geojson_url='http://example.net/foo/').save() |
... | ... | |
52 | 50 |
sys.stdout = old_stdout |
53 | 51 |
return output.getvalue() |
54 | 52 | |
53 | ||
55 | 54 |
def test_import_export(app, some_data): |
56 | 55 |
output = get_output_of_command('export_site') |
57 | 56 |
assert len(json.loads(output)['pages']) == 3 |
... | ... | |
89 | 88 |
assert os.path.exists(os.path.join(tempdir, 't.json')) |
90 | 89 |
shutil.rmtree(tempdir) |
91 | 90 | |
91 | ||
92 |
def test_import_export_with_parent(app, some_data): |
|
93 |
output = get_output_of_command('export_site') |
|
94 |
payload = json.loads(output) |
|
95 |
payload['pages'][1]['fields']['parent'] = ['one'] |
|
96 | ||
97 |
Page.objects.all().delete() |
|
98 |
import_site(data=payload) |
|
99 | ||
100 |
assert Page.objects.count() == 3 |
|
101 |
two = Page.objects.get(slug='two') |
|
102 |
assert two.parent.slug == 'one' |
|
103 | ||
104 | ||
105 |
def test_import_export_with_unknown_parent(app, some_data): |
|
106 |
output = get_output_of_command('export_site') |
|
107 |
payload = json.loads(output) |
|
108 |
payload['pages'][0]['fields']['exclude_from_navigation'] = False |
|
109 |
payload['pages'][0]['fields']['parent'] = ['unknown-parent'] |
|
110 | ||
111 |
Page.objects.all().delete() |
|
112 |
import_site(data=payload) |
|
113 | ||
114 |
assert Page.objects.count() == 3 |
|
115 |
for page in Page.objects.all(): |
|
116 |
assert page.parent is None |
|
117 |
one = Page.objects.get(slug='one') |
|
118 |
assert one.exclude_from_navigation is True |
|
119 | ||
120 | ||
92 | 121 |
def test_backward_compatibility_import(app, some_data): |
93 | 122 |
old_export = Page.export_all_for_json() |
94 | 123 |
Page.objects.all().delete() |
tests/test_manager.py | ||
---|---|---|
15 | 15 |
from django.test import override_settings |
16 | 16 |
from django.test.client import RequestFactory |
17 | 17 |
from django.test.utils import CaptureQueriesContext |
18 |
from django.utils.encoding import force_bytes |
|
18 | 19 |
from django.utils.http import urlencode |
19 | 20 |
from django.utils.six import BytesIO |
20 | 21 |
from django.utils.six.moves.urllib import parse as urlparse |
... | ... | |
585 | 586 |
resp = resp.form.submit() |
586 | 587 |
assert 'File is not in the expected JSON format.' in resp.text |
587 | 588 | |
589 | ||
588 | 590 |
def test_site_export_import_missing_group(app, admin_user): |
589 | 591 |
Page.objects.all().delete() |
590 | 592 |
group = Group.objects.create(name='foobar') |
... | ... | |
609 | 611 |
assert 'Missing groups: foobar' in resp.text |
610 | 612 | |
611 | 613 | |
614 |
def test_site_export_import_unknown_parent(app, admin_user): |
|
615 |
Page.objects.create(title='One', slug='one', template_name='standard') |
|
616 |
Page.objects.create(title='Two', slug='two', template_name='standard') |
|
617 | ||
618 |
app = login(app) |
|
619 |
resp = app.get('/manage/') |
|
620 |
resp = resp.click('Export Site') |
|
621 |
payload = json.loads(resp.body) |
|
622 |
payload['pages'][0]['fields']['exclude_from_navigation'] = False |
|
623 |
payload['pages'][0]['fields']['parent'] = ['unknown-parent'] |
|
624 | ||
625 |
resp = app.get('/manage/') |
|
626 |
resp = resp.click('Import Site') |
|
627 |
resp.form['site_json'] = Upload('site-export.json', force_bytes(json.dumps(payload)), 'application/json') |
|
628 |
resp = resp.form.submit().follow() |
|
629 |
assert 'Unknown parent for page "One"; parent has been reset and page was excluded from navigation.' in resp.text |
|
630 | ||
631 | ||
612 | 632 |
def test_invalid_cell_report(app, admin_user): |
613 | 633 |
app = login(app) |
614 | 634 |
resp = app.get('/manage/cells/invalid-report/') |
615 |
- |