manager: export page and subpages (#44667)

This commit is contained in:
Lauréline Guérin 2020-07-16 14:41:53 +02:00 committed by Frédéric Péters
parent 685ad0376c
commit 95943cf340
6 changed files with 118 additions and 11 deletions

View File

@ -321,7 +321,7 @@ class Page(models.Model):
return None
@classmethod
def get_as_reordered_flat_hierarchy(cls, object_list):
def get_as_reordered_flat_hierarchy(cls, object_list, root_page=None):
reordered = []
parenting = collections.defaultdict(list)
for page in object_list:
@ -329,7 +329,7 @@ class Page(models.Model):
def fill_list(object_sublist, level=0, parent=None):
for page in object_sublist:
parent_id = parent.pk if parent else None
if page.parent_id == parent_id:
if page.parent_id == parent_id or page == root_page and parent is None:
page.level = level
reordered.append(page)
if page.id in parenting:

View File

@ -34,6 +34,10 @@ class PageDuplicateForm(forms.Form):
title = forms.CharField(label=_('New title'), max_length=150, required=False)
class PageExportForm(forms.Form):
with_subpages = forms.BooleanField(label=_('Also export subpages'), required=False)
class PageEditTitleForm(forms.ModelForm):
class Meta:
model = Page

View File

@ -0,0 +1,18 @@
{% extends "combo/manager_base.html" %}
{% load i18n %}
{% block appbar %}
<h2>{% trans "Export Page" %}</h2>
{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<div class="buttons">
<button class="submit-button">{% trans "Export" %}</button>
<a class="cancel" href="{% url 'combo-manager-page-view' pk=page.pk %}">{% trans 'Cancel' %}</a>
</div>
</form>
{% endblock %}

View File

@ -10,7 +10,7 @@
<a class="action-see-online" href="{{ object.get_online_url }}">{% trans 'See online' %}</a>
<ul class="extra-actions-menu">
<li><a class="action-history" href="{% url 'combo-manager-page-history' pk=object.id %}">{% trans 'History' %}</a></li>
<li><a class="action-export" download href="{% url 'combo-manager-page-export' pk=object.id %}">{% trans 'Export' %}</a></li>
<li><a {% if page_has_subpages %}rel="popup" data-autoclose-dialog="true" {% endif %}class="action-export" href="{% url 'combo-manager-page-export' pk=object.id %}">{% trans 'Export' %}</a></li>
<li><a class="action-add-child" rel="popup" href="{% url 'combo-manager-page-add-child' pk=object.id %}">{% trans 'Add a child page' %}</a></li>
<li><a rel="popup" class="action-duplicate" href="{% url 'combo-manager-page-duplicate' pk=object.id %}">{% trans 'Duplicate' %}</a></li>
<li><a class="action-delete" rel="popup" href="{% url 'combo-manager-page-delete' pk=object.id %}">{% trans 'Delete' %}</a></li>

View File

@ -15,8 +15,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import hashlib
import datetime
import json
import os
from operator import attrgetter
from django.conf import settings
@ -43,7 +43,7 @@ from combo import plugins
from .forms import (PageEditTitleForm, PageVisibilityForm, SiteImportForm,
PageEditRedirectionForm, PageSelectTemplateForm, PageEditSlugForm,
PageEditPictureForm, PageEditIncludeInNavigationForm,
PageEditDescriptionForm, CellVisibilityForm, PageDuplicateForm)
PageEditDescriptionForm, CellVisibilityForm, PageDuplicateForm, PageExportForm)
class HomepageView(ListView):
@ -287,6 +287,7 @@ class PageView(DetailView):
del cell_type_groups['data']
context['cell_type_groups'] = list(cell_type_groups.items())
context['cell_type_groups'].sort(key=lambda x: x[0])
context['page_has_subpages'] = self.object.get_children().exists()
cells = CellBase.get_cells(page=self.object, prefetch_validity_info=True)
self.object.prefetched_cells = cells
@ -369,17 +370,49 @@ class PageDeleteView(DeleteView):
def get_success_url(self):
return reverse('combo-manager-homepage')
page_delete = PageDeleteView.as_view()
class PageExportView(DetailView):
model = Page
class PageExportView(FormView):
form_class = PageExportForm
template_name = 'combo/page_export.html'
def render_to_response(self, context, **response_kwargs):
def get_object(self):
return get_object_or_404(Page, pk=self.kwargs['pk'])
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['page'] = self.get_object()
return context
def export(self, with_children=False):
instance = self.get_object()
response = HttpResponse(content_type='application/json')
json.dump({'pages': [self.object.get_serialized_page()]}, response, indent=2)
today = datetime.date.today()
if with_children:
pages = instance.get_descendants_and_me()
ordered_pages = Page.get_as_reordered_flat_hierarchy(pages, root_page=instance)
filename_part = '{}_and_subpages'.format(instance.slug)
else:
ordered_pages = [instance]
filename_part = instance.slug
response['Content-Disposition'] = 'attachment; filename="export_page_{}_{}.json"'.format(
filename_part, today.strftime('%Y%m%d')
)
json.dump({'pages': [p.get_serialized_page() for p in ordered_pages]}, response, indent=2)
return response
def get(self, request, *args, **kwargs):
subpages = self.get_object().get_children()
if not subpages.exists():
return self.export()
return super().get(request, *args, **kwargs)
def form_valid(self, form):
return self.export(with_children=form.cleaned_data['with_subpages'])
page_export = PageExportView.as_view()

View File

@ -406,7 +406,7 @@ def test_edit_page_num_queries(settings, app, admin_user):
app.get('/manage/pages/%s/' % page.pk) # load once to populate caches
with CaptureQueriesContext(connection) as ctx:
app.get('/manage/pages/%s/' % page.pk)
assert len(ctx.captured_queries) == 31
assert len(ctx.captured_queries) == 32
def test_delete_page(app, admin_user):
@ -421,6 +421,7 @@ def test_delete_page(app, admin_user):
assert resp.location.endswith('/manage/')
assert Page.objects.count() == 0
def test_delete_page_keep_child(app, admin_user):
Page.objects.all().delete()
page = Page(title='One', slug='one', template_name='standard')
@ -440,6 +441,7 @@ def test_delete_page_keep_child(app, admin_user):
assert Page.objects.count() == 1
assert Page.objects.get(id=page2.id) == page2
def test_delete_page_and_subpage(app, admin_user):
Page.objects.all().delete()
page = Page(title='One', slug='one', template_name='standard')
@ -458,6 +460,7 @@ def test_delete_page_and_subpage(app, admin_user):
assert resp.location.endswith('/manage/')
assert Page.objects.count() == 0
def test_page_reorder(app, admin_user):
Page.objects.all().delete()
page1 = Page(title='One', slug='one', parent=None, order=0, template_name='standard')
@ -531,17 +534,66 @@ def test_page_reorder(app, admin_user):
assert Page.objects.get(id=page3.id).parent_id == page1.id
def test_export_page(app, admin_user):
def test_export_page(freezer, app, admin_user):
Page.objects.all().delete()
page = Page(title='One', slug='one', template_name='standard')
page.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
freezer.move_to('2020-07-16')
resp = resp.click('Export')
assert resp.headers['content-type'] == 'application/json'
assert resp.headers['content-disposition'] == 'attachment; filename="export_page_one_20200716.json"'
assert resp.json['pages'][0].get('fields').get('slug') == 'one'
def test_export_page_without_child(freezer, app, admin_user):
page = Page.objects.create(title='One', slug='one', template_name='standard')
Page.objects.create(title='Two', slug='two', parent=page, template_name='standard')
app = login(app)
resp = app.get('/manage/pages/%s/' % page.pk)
freezer.move_to('2020-07-16')
resp = resp.click('Export')
resp.form['with_subpages'].value = False
resp = resp.forms[0].submit()
assert resp.headers['content-type'] == 'application/json'
assert resp.headers['content-disposition'] == 'attachment; filename="export_page_one_20200716.json"'
assert len(resp.json['pages']) == 1
assert resp.json['pages'][0].get('fields').get('slug') == 'one'
def test_export_page_and_child(freezer, app, admin_user):
page1 = Page.objects.create(title='One', slug='one', template_name='standard')
page2 = Page.objects.create(title='Two', slug='two', parent=page1, template_name='standard')
Page.objects.create(title='Three', slug='three', parent=page2, template_name='standard')
Page.objects.create(title='Four', slug='four', parent=page1, template_name='standard')
freezer.move_to('2020-07-16')
app = login(app)
resp = app.get('/manage/pages/%s/' % page1.pk)
resp = resp.click('Export')
resp.form['with_subpages'].value = True
resp = resp.forms[0].submit()
assert resp.headers['content-type'] == 'application/json'
assert resp.headers['content-disposition'] == 'attachment; filename="export_page_one_and_subpages_20200716.json"'
assert len(resp.json['pages']) == 4
assert resp.json['pages'][0].get('fields').get('slug') == 'one'
assert resp.json['pages'][1].get('fields').get('slug') == 'two'
assert resp.json['pages'][2].get('fields').get('slug') == 'three'
assert resp.json['pages'][3].get('fields').get('slug') == 'four'
resp = app.get('/manage/pages/%s/' % page2.pk)
resp = resp.click('Export')
resp.form['with_subpages'].value = True
resp = resp.forms[0].submit()
assert resp.headers['content-type'] == 'application/json'
assert resp.headers['content-disposition'] == 'attachment; filename="export_page_two_and_subpages_20200716.json"'
assert len(resp.json['pages']) == 2
assert resp.json['pages'][0].get('fields').get('slug') == 'two'
assert resp.json['pages'][1].get('fields').get('slug') == 'three'
def test_export_page_order():
Page.objects.all().delete()
page1 = Page.objects.create(title='One', slug='one', template_name='standard')