combo/tests/test_manager.py

3076 lines
119 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import base64
import datetime
import json
import os
import re
import shutil
import urllib.parse
from io import BytesIO
from unittest import mock
import pytest
from django.conf import settings
from django.contrib.auth.models import Group, Permission
from django.core.files import File
from django.core.files.storage import default_storage
from django.core.serializers.base import DeserializationError
from django.db import connection
from django.template import TemplateSyntaxError
from django.test import override_settings
from django.test.client import RequestFactory
from django.test.utils import CaptureQueriesContext
from django.urls import reverse
from django.utils.encoding import force_bytes, force_str
from django.utils.http import urlencode
from django.utils.timezone import now
from pyquery import PyQuery
from webtest import Upload
from combo.apps.assets.models import Asset
from combo.apps.maps.models import MapLayer
from combo.apps.search.models import SearchCell
from combo.data.forms import LinkCellForm
from combo.data.models import (
CellBase,
ConfigJsonCell,
FeedCell,
JsonCell,
LinkCell,
LinkListCell,
MenuCell,
Page,
PageSnapshot,
ParentContentCell,
SiteSettings,
TextCell,
ValidityInfo,
)
from combo.manager.forms import PageAddForm, PageSelectTemplateForm
from .utils import manager_submit_cell
pytestmark = pytest.mark.django_db
TESTS_DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
def login(app, username='admin', password='admin'):
login_page = app.get('/login/')
login_form = login_page.forms[0]
login_form['username'] = username
login_form['password'] = password
resp = login_form.submit()
assert resp.status_int == 302
return app
def test_unlogged_access(app):
# connect while not being logged in
assert app.get('/manage/', status=302).location.endswith('/login/?next=/manage/')
def test_access(app, admin_user):
app = login(app)
resp = app.get('/manage/', status=200)
assert 'Pages' in resp.text
assert "This site doesn't have any page yet." in resp.text
@pytest.mark.parametrize('collapse', [True, False])
def test_pages_collapse(settings, app, admin_user, collapse):
Page.objects.create(title='One', slug='one')
settings.COMBO_MANAGE_HOME_COLLAPSE_PAGES = collapse
app = login(app)
resp = app.get('/manage/', status=200)
assert resp.context['collapse_pages'] is collapse
assert ('class="page level-0 untoggled"' in resp.text) is collapse
assert ('class="page level-0"' in resp.text) is not collapse
def test_pages_redirection(app, admin_user):
page = Page.objects.create(title='One', slug='one')
app = login(app)
resp = app.get('/manage/')
assert '(redirection)' not in resp
page.redirect_url = 'http://www.example.net'
page.save()
resp = app.get('/manage/')
assert '(redirection)' in resp
def test_pages_in_menu(app, admin_user):
page = Page.objects.create(title='One', slug='one')
app = login(app)
resp = app.get('/manage/')
assert '(navigation)' not in resp
page.exclude_from_navigation = False
page.save()
resp = app.get('/manage/')
assert '(navigation)' in resp
# mix
page.redirect_url = 'http://www.example.net'
page.save()
resp = app.get('/manage/')
assert '(navigation, redirection)' in resp
def test_add_page(app, admin_user):
app = login(app)
resp = app.get('/manage/', status=200)
resp = resp.click('New')
assert resp.forms[0]['title'].value == 'Home' # default title for first page
resp = resp.forms[0].submit()
page = Page.objects.latest('pk')
assert resp.location.endswith('/manage/pages/%s/' % page.pk)
assert page.slug == 'index'
assert page.title == 'Home'
assert page.template_name == 'standard' # default template
assert page.exclude_from_navigation is True
model_root_page = Page.objects.create(slug='modeles', title='Models')
model_a = Page.objects.create(
slug='model-a', title='Model A', template_name='two-columns', parent=model_root_page
)
TextCell.objects.create(page=model_a, placeholder='content', text='Foobar', order=1)
resp = app.get('/manage/pages/add/')
resp.forms[0]['title'].value = 'Page A'
resp.forms[0]['template_name'].value = 'model_model-a'
resp = resp.forms[0].submit()
new_page = Page.objects.latest('pk')
assert resp.location.endswith('/manage/pages/%s/' % new_page.pk)
assert new_page.slug == 'page-a'
assert new_page.title == 'Page A'
assert new_page.template_name == 'two-columns'
assert new_page.parent is None
assert TextCell.objects.filter(page=new_page).count() == 1
assert ParentContentCell.objects.filter(page=new_page).count() == 0
def test_add_second_page(app, admin_user):
Page.objects.all().delete()
page = Page(title='One', slug='one')
page.save()
app = login(app)
resp = app.get('/manage/', status=200)
resp = resp.click('New')
# assert there's no defaul title for the second page
assert resp.forms[0]['title'].value == ''
resp.forms[0]['title'].value = 'Foobar'
resp = resp.forms[0].submit()
assert Page.objects.get(slug='foobar').title == 'Foobar'
def test_add_child_page(app, admin_user):
page = Page(title='One', slug='one')
page.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.pk, status=200)
resp = resp.click(href='.*/add/')
resp.forms[0]['title'].value = 'Two'
resp = resp.forms[0].submit()
child = Page.objects.latest('pk')
assert resp.location.endswith('/manage/pages/%s/' % child.pk)
assert child.title == 'Two'
assert child.parent == page
app.get('/manage/pages/%s/add/' % child.pk, status=200)
app.get('/manage/pages/10000/add/', status=404)
model_root_page = Page.objects.create(slug='modeles', title='Models')
model_a = Page.objects.create(
slug='model-a', title='Model A', template_name='two-columns', parent=model_root_page
)
TextCell.objects.create(page=model_a, placeholder='content', text='Foobar', order=1)
resp = app.get('/manage/pages/%s/add/' % page.pk)
resp.forms[0]['title'].value = 'Page A'
resp.forms[0]['template_name'].value = 'model_model-a'
resp = resp.forms[0].submit()
new_page = Page.objects.latest('pk')
assert resp.location.endswith('/manage/pages/%s/' % new_page.pk)
assert new_page.slug == 'page-a'
assert new_page.title == 'Page A'
assert new_page.template_name == 'two-columns'
assert new_page.parent == page
assert TextCell.objects.filter(page=new_page).count() == 1
assert ParentContentCell.objects.filter(page=new_page).count() == 0
def test_page_add_forms(settings):
settings.COMBO_PUBLIC_TEMPLATES = {
'one': {
'name': 'BBB',
'template': 'combo/page_template.html',
},
'two': {
'name': 'AAA',
'template': 'combo/page_template_sidebar.html',
},
'unknown': {
'name': 'Unknown',
'template': 'combo/unknown.html',
},
}
settings.COMBO_DEFAULT_PUBLIC_TEMPLATE = 'one'
request = RequestFactory().get('/')
form = PageAddForm(request=request)
assert form.fields['template_name'].widget.choices == [('two', 'AAA'), ('one', 'BBB')]
assert form['template_name'].initial == 'one'
model_root_page = Page.objects.create(slug='modeles', title='Models')
form = PageAddForm(request=request)
assert form.fields['template_name'].widget.choices == [('two', 'AAA'), ('one', 'BBB')]
model_a = Page.objects.create(slug='model-a', title='Model A', parent=model_root_page)
Page.objects.create(slug='model-aa', title='Model AA', parent=model_a)
Page.objects.create(slug='model-b', title='Model B', parent=model_root_page)
normal_page = Page.objects.create(slug='models', title='Wrong Models')
Page.objects.create(slug='model-c', title='Not a model (wrong parent)')
Page.objects.create(slug='model-d', title='Again not a model (wrong parent)', parent=normal_page)
form = PageAddForm(request=request)
assert form.fields['template_name'].widget.choices == [
('two', 'AAA'),
('one', 'BBB'),
('model_model-a', 'Model A'),
('model_model-aa', 'Model AA'),
('model_model-b', 'Model B'),
]
settings.COMBO_PUBLIC_TEMPLATES_ROOT_PAGE = None
form = PageAddForm(request=request)
assert form.fields['template_name'].widget.choices == [('two', 'AAA'), ('one', 'BBB')]
def test_edit_page(app, admin_user):
Page.objects.all().delete()
app = login(app)
resp = app.get('/manage/', status=200)
resp = resp.click('New')
resp = resp.forms[0].submit()
resp = resp.follow()
# title
resp = resp.click(href='.*/title')
resp.form['title'].value = 'Home 2'
resp = resp.form.submit()
resp = resp.follow()
assert Page.objects.all()[0].title == 'Home 2'
# description
resp = resp.click(href='.*/description')
resp.form['description'].value = 'second home page'
resp = resp.form.submit()
resp = resp.follow()
assert Page.objects.all()[0].description == 'second home page'
# template name
resp = resp.click(href='.*/template')
assert resp.form['template_name'].value == 'standard'
assert len(resp.form['template_name'].options) == 2 # by default there are 2 templates
resp.form['template_name'].value = 'standard-sidebar'
resp = resp.form.submit()
resp = resp.follow()
assert Page.objects.all()[0].template_name == 'standard-sidebar'
assert resp.click(href='.*/template').form['template_name'].value == 'standard-sidebar'
# redirection
resp = resp.click(href='.*/redirection')
resp.form['redirect_url'].value = 'http://www.example.net'
resp = resp.form.submit()
resp = resp.follow()
assert 'http://www.example.net' in resp.text
assert 'It will also be used as a template for the pages behind this URL.' in resp.text
assert Page.objects.all()[0].redirect_url == 'http://www.example.net'
# redirection (relative url)
resp = resp.click(href='.*/redirection')
resp.form['redirect_url'].value = '/some-page/'
resp = resp.form.submit()
resp = resp.follow()
assert '/some-page/' in resp.text
assert 'It will also be used as a template for the pages behind this URL.' not in resp.text
assert Page.objects.all()[0].redirect_url == '/some-page/'
# redirection (templated url)
resp = resp.click(href='.*/redirection')
resp.form['redirect_url'].value = '{{idp_url}}/whatever'
resp = resp.form.submit()
resp = resp.follow()
assert '{{idp_url}}/whatever' in resp.text
assert 'It will also be used as a template for the pages behind this URL.' in resp.text
assert Page.objects.all()[0].redirect_url == '{{idp_url}}/whatever'
# redirection (error handling)
resp = resp.click(href='.*/redirection')
resp.form['redirect_url'].value = '{{ foo bar }}'
resp = resp.form.submit()
assert 'syntax error:' in resp.text
resp = resp.click('Cancel')
# include from nav, no apply_to_subpages field if page doesn't have children
resp = resp.click(href='.*/include-in-navigation')
assert 'apply_to_subpages' not in resp.form.fields
resp = resp.click('Cancel')
# include from nav not including subpages
page2 = Page.objects.create(title='Two', parent=Page.objects.get(), exclude_from_navigation=True)
resp = resp.click(href='.*/include-in-navigation')
resp.form['include_in_navigation'].checked = True
assert 'apply_to_subpages' in resp.form.fields
resp = resp.form.submit()
resp = resp.follow()
assert Page.objects.all()[0].exclude_from_navigation is False
assert Page.objects.get(pk=page2.pk).exclude_from_navigation is True
# exclude from nav
resp = resp.click(href='.*/include-in-navigation')
resp.form['include_in_navigation'].checked = False
assert 'apply_to_subpages' not in resp.form.fields
resp = resp.form.submit()
resp = resp.follow()
assert Page.objects.all()[0].exclude_from_navigation is True
assert Page.objects.get(pk=page2.pk).exclude_from_navigation is True
# include from nav including subpages
resp = resp.click(href='.*/include-in-navigation')
resp.form['include_in_navigation'].checked = True
resp.form['apply_to_subpages'].checked = True
resp = resp.form.submit()
resp = resp.follow()
assert Page.objects.all()[0].exclude_from_navigation is False
assert Page.objects.get(pk=page2.pk).exclude_from_navigation is False
def test_page_edit_template_form(settings):
page = Page.objects.create(title='One', slug='one', template_name='two')
settings.COMBO_PUBLIC_TEMPLATES = {
'one': {
'name': 'BBB',
'template': 'combo/page_template.html',
},
'two': {
'name': 'AAA',
'template': 'combo/page_template_sidebar.html',
},
'unknown': {
'name': 'Unknown',
'template': 'combo/unknown.html',
},
}
settings.COMBO_DEFAULT_PUBLIC_TEMPLATE = 'one'
# model pages ignored
model_root_page = Page.objects.create(slug='modeles')
Page.objects.create(slug='model-a', title='Model A', parent=model_root_page)
form = PageSelectTemplateForm(instance=page)
assert form.fields['template_name'].widget.choices == [('two', 'AAA'), ('one', 'BBB')]
assert form['template_name'].initial == 'two'
def test_edit_page_cell_invalid_placeholder(freezer, app, admin_user):
freezer.move_to('2020-06-05 12:00:01')
page = Page.objects.create(title='One', slug='one', template_name='standard')
cell = TextCell.objects.create(page=page, placeholder='content', text='Foobar', order=1)
cell.mark_as_invalid('foo_bar_reason')
app = login(app)
ValidityInfo.objects.update(invalid_since=now() - datetime.timedelta(minutes=1))
resp = app.get('/manage/pages/%s/' % page.pk)
assert '<span class="invalid">foo_bar_reason' in resp.text
assert 'This cell will no longer be displayed in 1 day, 23 hours' in resp.text
ValidityInfo.objects.update(invalid_since=now() - datetime.timedelta(minutes=47 * 60 - 1))
resp = app.get('/manage/pages/%s/' % page.pk)
assert '<span class="invalid">foo_bar_reason' in resp.text
assert 'This cell will no longer be displayed in 1 hour, 1 minute.' in resp.text
ValidityInfo.objects.update(invalid_since=now() - datetime.timedelta(minutes=47 * 60 + 29))
resp = app.get('/manage/pages/%s/' % page.pk)
assert '<span class="invalid">foo_bar_reason' in resp.text
assert 'This cell will no longer be displayed in 31 minutes.' in resp.text
ValidityInfo.objects.update(invalid_since=now() - datetime.timedelta(days=2))
resp = app.get('/manage/pages/%s/' % page.pk)
assert '<span class="invalid">foo_bar_reason' in resp.text
assert 'This cell is no longer displayed since June 5, 2020, noon.' in resp.text
ValidityInfo.objects.update(invalid_since=now() - datetime.timedelta(days=2, minutes=10 * 60 + 2))
resp = app.get('/manage/pages/%s/' % page.pk)
assert '<span class="invalid">foo_bar_reason' in resp.text
assert 'This cell is no longer displayed since June 5, 2020, 1:58 a.m.' in resp.text
cell.mark_as_valid()
assert ValidityInfo.objects.exists() is False
resp = app.get('/manage/pages/%s/' % page.pk)
assert '<span class="invalid">foo_bar_reason' not in resp.text
cell2 = LinkListCell.objects.create(order=0, placeholder='content', page=page)
item = LinkCell.objects.create(page=page, placeholder=cell2.link_placeholder, order=0)
item.mark_as_invalid('foo_bar_reason')
cell2.check_validity()
resp = app.get('/manage/pages/%s/' % page.pk)
assert '<span class="invalid">Invalid link</span>' in resp.text
assert '<span class="invalid">foo_bar_reason' in resp.text
assert 'This link will no longer be displayed in 2 days.' in resp.text
ValidityInfo.objects.update(invalid_since=now() - datetime.timedelta(days=2))
resp = app.get('/manage/pages/%s/' % page.pk)
assert '<span class="invalid">Invalid link</span>' in resp.text
assert '<span class="invalid">foo_bar_reason' in resp.text
assert 'This link is no longer displayed since June 5, 2020, noon.' in resp.text
def test_edit_page_optional_placeholder(app, admin_user):
Page.objects.all().delete()
page = Page.objects.create(title='One', slug='one', template_name='standard')
app = login(app)
# empty placeholder footer
resp = app.get('/manage/pages/%s/' % page.pk)
assert re.findall('data-placeholder-key="(.*)">', resp.text) == ['content']
assert len(resp.context['optional_placeholders']) == 1
assert resp.context['optional_placeholders'][0]['key'] == 'footer'
assert resp.context['optional_placeholders'][0]['name'] == 'Footer'
assert resp.context['optional_placeholders'][0]['is_empty'] is True
assert '/manage/pages/%s/?include-section=footer' % page.pk in resp.text
assert 'Footer (empty)' in resp.text
assert '<div class="placeholder" data-placeholder-key="footer">' not in resp.text
# add a ParentContentCell
like_parent_cell = ParentContentCell.objects.create(page=page, placeholder='footer', order=0)
resp = app.get('/manage/pages/%s/' % page.pk)
assert re.findall('data-placeholder-key="(.*)">', resp.text) == ['content']
assert len(resp.context['optional_placeholders']) == 1
assert resp.context['optional_placeholders'][0]['key'] == 'footer'
assert resp.context['optional_placeholders'][0]['name'] == 'Footer'
assert resp.context['optional_placeholders'][0]['is_empty'] is False
assert '/manage/pages/%s/?include-section=footer' % page.pk in resp.text
assert 'Footer (like parent)' in resp.text
assert '<div class="placeholder" data-placeholder-key="footer">' not in resp.text
# click on the edit link
resp = resp.click(href='.*?include-section=footer')
assert re.findall('data-placeholder-key="(.*)">', resp.text) == ['content', 'footer']
assert len(resp.context['optional_placeholders']) == 0
assert '<div class="placeholder" data-placeholder-key="footer">' in resp.text
assert (
'<option data-add-url="/manage/pages/%s/add-cell-to-footer/data_textcell/default/"' % page.pk
in resp.text
)
# add a cell (so there is 2 cells)
resp = app.get('/manage/pages/%s/add-cell-to-footer/data_textcell/default/' % page.pk)
resp = app.get('/manage/pages/%s/' % page.pk)
assert re.findall('data-placeholder-key="(.*)">', resp.text) == ['content', 'footer']
assert len(resp.context['optional_placeholders']) == 0
assert '<div class="placeholder" data-placeholder-key="footer">' in resp.text
# remove parent content cell
like_parent_cell.delete()
resp = app.get('/manage/pages/%s/' % page.pk)
assert re.findall('data-placeholder-key="(.*)">', resp.text) == ['content', 'footer']
assert len(resp.context['optional_placeholders']) == 0
assert '<div class="placeholder" data-placeholder-key="footer">' in resp.text
def test_edit_page_unknown_template(app, admin_user):
Page.objects.all().delete()
page = Page(title='One', slug='one', template_name='broken')
page.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
assert 'Unknown (broken)' in resp.text
def test_page_edit_slug(app, admin_user):
Page.objects.all().delete()
page = Page(title='One', slug='one', template_name='standard')
page.save()
page = Page(title='Two', slug='two', template_name='standard')
page.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
resp = resp.click(href='.*/slug')
resp.form['slug'].value = 'two' # no change
resp = resp.form.submit()
resp = resp.follow()
assert Page.objects.get(id=page.id).slug == 'two'
resp = resp.click(href='.*/slug')
resp.form['slug'].value = 'two2' # valid change
resp = resp.form.submit()
resp = resp.follow()
assert Page.objects.get(id=page.id).slug == 'two2'
resp = resp.click(href='.*/slug')
resp.form['slug'].value = 'one' # duplicate
resp = resp.form.submit()
assert 'Slug must be unique' in resp.text
assert Page.objects.get(id=page.id).slug == 'two2'
def test_page_edit_sub_slug(app, admin_user):
page = Page.objects.create(title='One', slug='one', template_name='two')
app = login(app)
resp = app.get('/manage/pages/%s/' % page.pk)
resp = resp.click(href='.*/slug')
resp.form['slug'].value = 'foobar'
resp.form['sub_slug'].value = '(?P<card-foo_id>[0-9]+)'
resp = resp.form.submit().follow()
page.refresh_from_db()
assert page.slug == 'foobar'
assert page.sub_slug == '(?P<card-foo_id>[0-9]+)'
resp = app.get('/manage/pages/%s/slug' % page.pk)
resp.form['sub_slug'].value = 'card-foo_id'
resp = resp.form.submit().follow()
page.refresh_from_db()
assert page.slug == 'foobar'
assert page.sub_slug == 'card-foo_id'
# bad regexp
resp = app.get('/manage/pages/%s/slug' % page.pk)
resp.form['sub_slug'].value = '(?P< bad group name with spaces >[0-9]+)'
resp = resp.form.submit()
assert resp.context['form'].errors['sub_slug'] == ['Bad Regular expression.']
resp = app.get('/manage/pages/%s/slug' % page.pk)
resp.form['sub_slug'].value = ' bad group name with spaces '
resp = resp.form.submit()
assert resp.context['form'].errors['sub_slug'] == ['Bad Regular expression.']
# clear sub_slug
resp.form['sub_slug'].value = ''
resp = resp.form.submit().follow()
page.refresh_from_db()
assert page.slug == 'foobar'
assert page.sub_slug == ''
def test_page_edit_picture(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)
resp = resp.click(href='.*/picture/')
with open(os.path.join(TESTS_DATA_DIR, 'black.jpeg'), mode='rb') as fd:
resp.form['picture'] = Upload('black.jpeg', fd.read(), 'image/jpeg')
resp = resp.form.submit()
assert resp.location.endswith('/manage/pages/%s/' % page.id)
resp = resp.follow()
assert '<h2>Page - One</h2>' in resp.text
assert '<img' in resp.text
assert 'src="/media/cache/' in resp.text
resp = resp.click(href='.*/remove-picture/')
resp = resp.follow()
assert '<h2>Page - One</h2>' in resp.text
assert '<img' not in resp.text
resp = resp.click(href='.*/picture/')
resp.form['picture'] = Upload('black.svg', b'<svg xmlns="http://www.w3.org/2000/svg"/>', 'image/svg+xml')
resp = resp.form.submit()
assert resp.location.endswith('/manage/pages/%s/' % page.id)
resp = resp.follow()
assert '<h2>Page - One</h2>' in resp.text
assert '<img' in resp.text
assert Page.objects.get(id=page.id).picture.url in resp.text
def test_page_edit_extra_variables(app, admin_user):
app = login(app)
page = Page.objects.create(title='One', slug='one', template_name='standard')
assert page.extra_variables == {}
resp = app.get('/manage/pages/%s/' % page.id)
assert '<label>Extra variables:</label>' not in resp.text
resp = resp.click(href='.*/extra-variables/')
resp.form['form-0-key'] = 'foo'
resp.form['form-0-value'] = 'bar'
resp = resp.form.submit().follow()
page.refresh_from_db()
assert page.extra_variables == {'foo': 'bar'}
assert '<label>Extra variables:</label>' in resp.text
assert '<i>foo</i>' in resp
resp = resp.click(href='.*/extra-variables/', index=0)
assert resp.form['form-TOTAL_FORMS'].value == '2'
assert resp.form['form-0-key'].value == 'foo'
assert resp.form['form-0-value'].value == 'bar'
assert resp.form['form-1-key'].value == ''
assert resp.form['form-1-value'].value == ''
resp.form['form-0-value'] = 'bar-bis'
resp.form['form-1-key'] = 'blah'
resp.form['form-1-value'] = 'baz'
resp = resp.form.submit().follow()
page.refresh_from_db()
assert page.extra_variables == {
'foo': 'bar-bis',
'blah': 'baz',
}
assert '<i>blah</i>, <i>foo</i>' in resp
resp = resp.click(href='.*/extra-variables/', index=0)
assert resp.form['form-TOTAL_FORMS'].value == '3'
assert resp.form['form-0-key'].value == 'blah'
assert resp.form['form-0-value'].value == 'baz'
assert resp.form['form-1-key'].value == 'foo'
assert resp.form['form-1-value'].value == 'bar-bis'
assert resp.form['form-2-key'].value == ''
assert resp.form['form-2-value'].value == ''
resp.form['form-1-key'] = 'foo'
resp.form['form-1-value'] = 'bar'
resp.form['form-0-key'] = ''
resp = resp.form.submit().follow()
page.refresh_from_db()
assert page.extra_variables == {
'foo': 'bar',
}
assert '<i>foo</i>' in resp
def test_page_placeholder_restricted_visibility(app, admin_user):
app = login(app)
page = Page.objects.create()
group = Group.objects.create(name='foobar')
resp = app.get(reverse('combo-manager-homepage'))
assert '<span class="visibility-summary"' not in resp.text
page.public = False
page.save()
resp = app.get(reverse('combo-manager-homepage'))
assert '<span class="visibility-summary" title="Restricted visibility">' in resp.text
assert 'foobar' not in resp.text
page.groups.set([group])
resp = app.get(reverse('combo-manager-homepage'))
assert '<span class="visibility-summary" title="Restricted visibility">' in resp.text
assert 'foobar' in resp.text
def test_edit_page_navigation(app, admin_user):
page1 = Page.objects.create(title='One', slug='one', parent=None, template_name='standard')
page2 = Page.objects.create(title='Two', slug='two', parent=page1, template_name='standard')
page3 = Page.objects.create(title='Three', slug='three', parent=page1, template_name='standard')
page4 = Page.objects.create(title='Four', slug='four', parent=None, template_name='standard')
app = login(app)
resp = app.get('/manage/pages/%s/' % page1.pk)
assert '<li class="nav-up"' not in resp.text
assert '<li class="nav-left"' not in resp.text
assert (
'<li class="nav-right"><a href="/manage/pages/%s/">%s</a></li>' % (page2.pk, page2.title) in resp.text
)
resp = app.get('/manage/pages/%s/' % page2.pk)
assert '<li class="nav-up"><a href="/manage/pages/%s/">%s</a></li>' % (page1.pk, page1.title) in resp.text
assert (
'<li class="nav-left"><a href="/manage/pages/%s/">%s</a></li>' % (page1.pk, page1.title) in resp.text
)
assert (
'<li class="nav-right"><a href="/manage/pages/%s/">%s</a></li>' % (page3.pk, page3.title) in resp.text
)
resp = app.get('/manage/pages/%s/' % page4.pk)
assert '<li class="nav-up"' not in resp.text
assert (
'<li class="nav-left"><a href="/manage/pages/%s/">%s</a></li>' % (page3.pk, page3.title) in resp.text
)
assert '<li class="nav-right"' not in resp.text
def test_edit_page_num_queries(settings, app, admin_user):
settings.COMBO_CELL_ASSET_SLOTS = {}
page = Page.objects.create(title='One', slug='one', parent=None, template_name='standard')
page2 = Page.objects.create(title='Two', slug='two', parent=page, template_name='standard')
MenuCell.objects.create(page=page, order=0)
TextCell.objects.create(page=page, placeholder='content', text='Foobar', order=1)
LinkCell.objects.create(page=page, placeholder='content', link_page=page2, order=2)
JsonCell.objects.create(page=page, placeholder='content', order=3, url='http://example.com')
SearchCell.objects.create(page=page, _search_services={'data': ['_text']}, order=4)
cell = LinkListCell.objects.create(order=5, placeholder='content', page=page)
for i in range(5):
LinkCell.objects.create(
page=page,
placeholder=cell.link_placeholder,
title='Foo %s' % i,
url='http://example.net/',
link_page=page,
order=i + 1,
)
app = login(app)
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) == 40
def test_delete_page(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)
resp = resp.click('Delete')
assert '<button class="delete-button">Delete</button>' in resp.text
resp = resp.forms[0].submit()
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')
page.save()
page2 = Page(title='Two', slug='two', parent=page, template_name='standard')
page2.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
resp = resp.click('Delete')
assert '<button class="delete-button">Delete</button>' in resp.text
assert 'Delete only this page' in resp.text
assert 'Delete this page and all subpages' in resp.text
resp.form['choice'].value = 'delete-one'
resp = resp.forms[0].submit()
assert resp.location.endswith('/manage/')
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')
page.save()
page2 = Page(title='Two', slug='two', parent=page, template_name='standard')
page2.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
resp = resp.click('Delete')
assert '<button class="delete-button">Delete</button>' in resp.text
assert 'Delete only this page' in resp.text
assert 'Delete this page and all subpages' in resp.text
resp.form['choice'].value = 'delete-all'
resp = resp.forms[0].submit()
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')
page1.save()
page2 = Page(title='Two', slug='two', parent=None, order=1, template_name='standard')
page2.save()
page3 = Page(title='Three', slug='three', parent=page2, order=2, template_name='standard')
page3.save()
page4 = Page(title='Four', slug='four', parent=page2, order=3, template_name='standard')
page4.save()
app = login(app)
ordered_ids = [x.id for x in Page.get_as_reordered_flat_hierarchy(Page.objects.all())]
assert ordered_ids == [page1.id, page2.id, page3.id, page4.id]
# invalid method
app.get('/manage/pages/order', status=405)
# missing params
app.post(
'/manage/pages/order',
params={
'moved-page-new-parent': 42,
'new-order': '42',
},
status=400,
)
app.post(
'/manage/pages/order',
params={
'moved-page-id': 42,
'new-order': '42',
},
status=400,
)
app.post(
'/manage/pages/order',
params={
'moved-page-id': 42,
'moved-page-new-parent': 42,
},
status=400,
)
# missing page3 in order
app.post(
'/manage/pages/order',
params={
'moved-page-id': page4.id,
'moved-page-new-parent': page2.id,
'new-order': ','.join([str(x) for x in [page2.id, page1.id, page4.id]]),
},
)
# not changed
ordered_ids = [x.id for x in Page.get_as_reordered_flat_hierarchy(Page.objects.all())]
assert ordered_ids == [page1.id, page2.id, page3.id, page4.id]
# move page4 before page3
app.post(
'/manage/pages/order',
params={
'moved-page-id': page4.id,
'moved-page-new-parent': page2.id,
'new-order': ','.join([str(x) for x in [page1.id, page2.id, page4.id, page3.id]]),
},
)
ordered_ids = [x.id for x in Page.get_as_reordered_flat_hierarchy(Page.objects.all())]
assert ordered_ids == [page1.id, page2.id, page4.id, page3.id]
# move page4 to level0
app.post(
'/manage/pages/order',
params={
'moved-page-id': page4.id,
'moved-page-new-parent': '',
'new-order': ','.join([str(x) for x in [page1.id, page4.id, page2.id, page3.id]]),
},
)
ordered_ids = [x.id for x in Page.get_as_reordered_flat_hierarchy(Page.objects.all())]
assert ordered_ids == [page1.id, page4.id, page2.id, page3.id]
# change slug to check for autochange on duplicate
page4.slug = 'three'
page4.save()
# move it as a sibling of page3
app.post(
'/manage/pages/order',
params={
'moved-page-id': page4.id,
'moved-page-new-parent': page2.id,
'new-order': ','.join([str(x) for x in [page1.id, page2.id, page4.id, page3.id]]),
},
)
assert Page.objects.get(id=page4.id).slug.startswith('three-')
# move a parent page as a child of its own child
for page in (page1, page2, page3, page4):
page.parent = None
page.save()
page2.parent = page1
page2.save()
page3.parent = page2
page3.save()
app.post(
'/manage/pages/order',
params={
'moved-page-id': page1.id,
'moved-page-new-parent': page3.id,
'new-order': ','.join([str(x) for x in [page2.id, page3.id, page1.id, page4.id]]),
},
)
assert Page.objects.get(id=page2.id).parent_id is None
for page in (page1, page2, page3, page4):
page.parent = None
page.save()
page2.parent = page1
page2.save()
page3.parent = page2
page3.save()
page4.parent = page3
page4.save()
app.post(
'/manage/pages/order',
params={
'moved-page-id': page2.id,
'moved-page-new-parent': page4.id,
'new-order': ','.join([str(x) for x in [page1.id, page3.id, page4.id, page2.id]]),
},
)
assert Page.objects.get(id=page1.id).parent_id is None
assert Page.objects.get(id=page3.id).parent_id == page1.id
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_snapshot(freezer, app, admin_user):
Page.objects.all().delete()
page = Page(title='One', slug='one', template_name='standard')
page.save()
app = login(app)
freezer.move_to('2020-07-16')
resp = app.get('/manage/pages/%s/' % page.id, status=200)
resp = resp.click(href='.*/title')
resp.form['title'].value = 'Initial Title'
resp = resp.form.submit()
resp = resp.follow()
freezer.move_to('2020-07-17')
resp = resp.click(href='.*/title')
resp.form['title'].value = 'Updated Title'
resp = resp.form.submit()
history = app.get('/manage/pages/%s/history' % page.id, status=200)
resp = history.click('export', index=0, verbose=True)
snapshot_page = Page.snapshots.latest('pk')
assert snapshot_page.uuid != page.uuid
assert resp.headers['content-type'] == 'application/json'
assert resp.headers['content-disposition'] == 'attachment; filename="export_page_one_20200717.json"'
assert resp.json['fields']['title'] == 'Updated Title'
resp = history.click('export', index=1, verbose=True)
snapshot_page = Page.snapshots.latest('pk')
assert snapshot_page.uuid != page.uuid
assert resp.headers['content-type'] == 'application/json'
assert resp.headers['content-disposition'] == 'attachment; filename="export_page_one_20200716.json"'
assert resp.json['fields']['title'] == 'Initial Title'
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_with_redirection(app, admin_user):
page1 = Page.objects.create(title='One', slug='one', template_name='standard')
page2 = Page.objects.create(
title='Two', slug='two', template_name='standard', redirect_url='http://www.example.net'
)
LinkCell.objects.create(page=page1, placeholder='content', link_page=page2, order=0)
app = login(app)
resp = app.get('/manage/pages/%s/' % page1.pk)
resp = resp.click('Export')
assert resp.json['pages'][0]['cells'][0]['fields']['link_page'] == [
'two'
] # and not http://www.example.net
def test_export_page_order():
page1 = Page.objects.create(title='One', slug='one', template_name='standard')
page2 = Page.objects.create(title='Two', slug='two', parent=page1, template_name='standard')
page3 = Page.objects.create(title='Three', slug='three', parent=page2, template_name='standard')
page4 = Page.objects.create(title='Four', slug='four', parent=page1, template_name='standard')
random_list = [page3, page4, page1, page2]
ordered_list = Page.get_as_reordered_flat_hierarchy(random_list)
assert ordered_list in ([page1, page4, page2, page3], [page1, page2, page3, page4])
def test_site_export_import_json(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')
page3 = Page.objects.create(title='Three', slug='three', parent=page2, template_name='standard')
page4 = Page.objects.create(title='Four', slug='four', parent=page1, template_name='standard')
TextCell.objects.create(page=page1, placeholder='content', text='Foobar', order=0)
LinkCell.objects.create(page=page1, placeholder='content', link_page=page2, order=0)
LinkCell.objects.create(page=page2, placeholder='content', link_page=page1, order=0)
TextCell.objects.create(page=page3, placeholder='content', text='Foobar', order=0)
TextCell.objects.create(page=page3, placeholder='content', text='Foobar', order=0)
TextCell.objects.create(page=page4, placeholder='content', text='Foobar', order=0)
TextCell.objects.create(page=page4, placeholder='content', text='Foobar', order=0)
app = login(app)
resp = app.get('/manage/')
resp = resp.click('Export Site')
resp = resp.form.submit()
assert resp.headers['content-type'] == 'application/json'
site_export = resp.body
resp = app.get('/manage/')
resp = resp.click('Import Site')
resp.form['site_file'] = Upload('site-export.json', site_export, 'application/json')
with CaptureQueriesContext(connection) as ctx:
resp = resp.form.submit()
assert len(ctx.captured_queries) in [304, 305]
assert Page.objects.count() == 4
assert PageSnapshot.objects.all().count() == 4
Page.objects.all().delete()
assert LinkCell.objects.count() == 0
resp = app.get('/manage/')
resp = resp.click('Import Site')
resp.form['site_file'] = Upload('site-export.json', site_export, 'application/json')
with CaptureQueriesContext(connection) as ctx:
resp = resp.form.submit()
assert len(ctx.captured_queries) == 274
assert set(Page.objects.get(slug='one').related_cells['cell_types']) == {'data_textcell', 'data_linkcell'}
assert Page.objects.count() == 4
assert LinkCell.objects.count() == 2
assert LinkCell.objects.get(page__slug='one').link_page.slug == 'two'
assert LinkCell.objects.get(page__slug='two').link_page.slug == 'one'
assert PageSnapshot.objects.all().count() == 8
resp = app.get('/manage/')
resp = resp.click('Export Site')
resp.form['pages'] = False
resp = resp.form.submit()
site_export = resp.body
site_json = json.loads(site_export.decode())
assert 'pages' not in site_json
assert 'pwa' in site_json
assert 'site_settings' in site_json
Page.objects.all().delete()
resp = app.get('/manage/')
resp = resp.click('Import Site')
resp.form['site_file'] = Upload('site-export.json', site_export, 'application/json')
assert Page.objects.count() == 0
# check with invalid file
resp = app.get('/manage/')
resp = resp.click('Import Site')
resp.form['site_file'] = Upload('site-export.json', b'invalid content', 'application/json')
resp = resp.form.submit()
assert 'File is not in the expected TAR or JSON format.' in resp.text
def test_site_export_import_tar(app, admin_user):
Page.objects.all().delete()
page1 = Page(title='One', slug='one', template_name='standard')
page1.save()
cell = TextCell(page=page1, placeholder='content', text='Foobar', order=0)
cell.save()
Asset(key='collectivity:banner', asset=File(BytesIO(b'test'), 'test.png')).save()
path = default_storage.path('')
with open('%s/assets/test.png' % path) as fd:
assert fd.read() == 'test'
app = login(app)
resp = app.get('/manage/')
resp = resp.click('Export Site')
resp.form['asset_files'] = True
resp = resp.form.submit()
assert resp.headers['content-type'] == 'application/x-tar'
site_export = resp.body
Page.objects.all().delete()
Asset.objects.filter(key='collectivity:banner').delete()
assert Page.objects.count() == 0
assert TextCell.objects.count() == 0
assert Asset.objects.filter(key='collectivity:banner').count() == 0
with open('%s/assets/test.png' % path, 'w') as fd:
fd.write('foo')
resp = app.get('/manage/')
resp = resp.click('Import Site')
resp.form['site_file'] = Upload('site-export.json', site_export, 'application/json')
resp = resp.form.submit()
assert Page.objects.count() == 1
assert PageSnapshot.objects.all().count() == 1
assert TextCell.objects.count() == 1
assert Asset.objects.filter(key='collectivity:banner').count() == 1
with open('%s/assets/test.png' % path) as fd:
assert fd.read() == 'foo'
os.remove('%s/assets/test.png' % path)
resp = app.get('/manage/')
resp = resp.click('Import Site')
resp.form['site_file'] = Upload('site-export.json', site_export, 'application/json')
resp = resp.form.submit()
with open('%s/assets/test.png' % path) as fd:
assert fd.read() == 'test'
resp = app.get('/manage/')
resp = resp.click('Export Site')
resp.form['assets'] = False
resp.form['asset_files'] = True
resp = resp.form.submit() # no error
def test_site_export_import_missing_group(app, admin_user):
Page.objects.all().delete()
group = Group.objects.create(name='foobar')
page1 = Page(title='One', slug='one', template_name='standard')
page1.save()
page1.groups.set([group])
app = login(app)
resp = app.get('/manage/')
resp = resp.click('Export Site')
resp = resp.form.submit()
assert resp.headers['content-type'] == 'application/json'
site_export = resp.body
Page.objects.all().delete()
group.delete()
app = login(app)
resp = app.get('/manage/')
resp = resp.click('Import Site')
resp.form['site_file'] = Upload('site-export.json', site_export, 'application/json')
resp = resp.form.submit()
assert 'Missing groups: foobar' in resp.text
def test_site_export_import_unknown_parent(app, admin_user):
Page.objects.create(title='One', slug='one', template_name='standard')
Page.objects.create(title='Two', slug='two', template_name='standard')
app = login(app)
resp = app.get('/manage/')
resp = resp.click('Export Site')
resp = resp.form.submit()
payload = json.loads(force_str(resp.body))
payload['pages'][0]['fields']['exclude_from_navigation'] = False
payload['pages'][0]['fields']['parent'] = ['unknown-parent']
resp = app.get('/manage/')
resp = resp.click('Import Site')
resp.form['site_file'] = Upload('site-export.json', force_bytes(json.dumps(payload)), 'application/json')
resp = resp.form.submit().follow()
assert (
'Unknown parent for page &quot;One&quot;; parent has been reset and page was excluded from navigation.'
in resp.text
)
def test_site_export_import_unknown_page(app, admin_user):
page = Page.objects.create(title='One', slug='one')
page2 = Page.objects.create(title='Two', slug='two')
MenuCell.objects.create(root_page=page2, page=page, order=0, slug='carrousel', placeholder='content')
app = login(app)
resp = app.get('/manage/site-export')
resp = resp.form.submit()
payload = json.loads(force_str(resp.body))
payload['pages'][0]['cells'][0]['fields']['root_page'] = ['unknown-parent']
Page.objects.all().delete()
resp = app.get('/manage/site-import')
resp.form['site_file'] = Upload('site-export.json', force_bytes(json.dumps(payload)), 'application/json')
resp = resp.form.submit()
assert resp.context['form'].errors['site_file'] == ['Unknown page "unknown-parent".']
assert Page.objects.count() == 0
resp = app.get('/manage/site-import')
resp.form['site_file'] = Upload('site-export.json', force_bytes(json.dumps(payload)), 'application/json')
with mock.patch('combo.data.models.Page.load_serialized_pages') as mock_load:
mock_load.side_effect = DeserializationError('foo bar')
resp = resp.form.submit()
assert resp.context['form'].errors['site_file'] == ['foo bar']
resp = app.get('/manage/site-import')
resp.form['site_file'] = Upload('site-export.json', force_bytes(json.dumps(payload)), 'application/json')
with mock.patch('combo.data.models.Page.load_serialized_pages') as mock_load:
mock_load.side_effect = DeserializationError("Page matching query does not exist.")
resp = resp.form.submit()
assert resp.context['form'].errors['site_file'] == ['Page matching query does not exist.']
resp = app.get('/manage/site-import')
resp.form['site_file'] = Upload('site-export.json', force_bytes(json.dumps(payload)), 'application/json')
with mock.patch('combo.data.models.Page.load_serialized_pages') as mock_load:
mock_load.side_effect = DeserializationError("Page matching query does not exist. '['blah")
resp = resp.form.submit()
assert resp.context['form'].errors['site_file'] == ['Unknown page "blah".']
def test_invalid_cell_report(app, admin_user):
app = login(app)
resp = app.get('/manage/cells/invalid-report/')
assert resp.context['object_list'] == []
page = Page.objects.create(title='One', slug='one', template_name='standard')
cell = TextCell.objects.create(page=page, placeholder='content', text='Foobar', order=0)
resp = app.get('/manage/cells/invalid-report/')
assert resp.context['object_list'] == []
cell.mark_as_invalid('foo_bar_reason')
resp = app.get('/manage/cells/invalid-report/')
assert resp.context['object_list'] == [cell]
assert f'<a href="/manage/pages/{page.pk}/">{page.title}</a>' in resp.text
assert (
f'<a href="/manage/pages/{page.pk}/#cell-{cell.get_reference()}">{cell.get_label()}</a>' in resp.text
)
# cells from snapshot are not reported
snapshot = PageSnapshot.objects.create(page=page)
page.snapshot = snapshot
page.save()
resp = app.get('/manage/cells/invalid-report/')
assert resp.context['object_list'] == []
# cells used in LinkListCell are not reported
page.snapshot = None
page.save()
cell2 = LinkListCell.objects.create(order=0, placeholder='content', page=page)
item = LinkCell.objects.create(page=page, placeholder=cell2.link_placeholder, order=0)
item.mark_as_invalid('foo_bar_reason')
resp = app.get('/manage/cells/invalid-report/')
assert resp.context['object_list'] == [cell]
# cells without placeholder are not reported
cell.placeholder = ''
cell.save()
resp = app.get('/manage/cells/invalid-report/')
assert resp.context['object_list'] == []
# cells with unknown placeholder are not reported
request = RequestFactory().get('/')
assert [p.key for p in page.get_placeholders(request, traverse_cells=True)] == ['content', 'footer']
cell.placeholder = 'foobar'
cell.save()
resp = app.get('/manage/cells/invalid-report/')
assert resp.context['object_list'] == []
cell.placeholder = 'footer'
cell.save()
resp = app.get('/manage/cells/invalid-report/')
assert resp.context['object_list'] == [cell]
# unknown config cell
cell2 = ConfigJsonCell.objects.create(
page=page,
placeholder='content',
order=1,
key='test-config-json-cell',
)
cell2.mark_as_invalid('settings_not_found')
resp = app.get('/manage/cells/invalid-report/')
assert resp.context['object_list'] == [cell, cell2]
def test_duplicate_page(app, admin_user):
page = Page.objects.create(
title='One', slug='one', template_name='standard', exclude_from_navigation=False
)
TextCell.objects.create(page=page, placeholder='content', text='Foobar', order=0)
app = login(app)
resp = app.get('/manage/pages/%s/' % page.pk)
resp = resp.click(href='/%s/duplicate' % page.pk)
resp = resp.form.submit()
new_page = Page.objects.latest('pk')
assert resp.status_int == 302
assert resp.location.endswith('/manage/pages/%s/' % new_page.pk)
resp = resp.follow()
assert (
'Page %s has been duplicated, it has been marked as excluded from navigation.' % page.title
in resp.text
)
assert new_page.exclude_from_navigation is True
assert new_page.uuid != page.uuid
page.exclude_from_navigation = True
page.save()
resp = app.get('/manage/pages/%s/' % page.pk)
resp = resp.click(href='/%s/duplicate' % page.pk)
resp = resp.form.submit()
new_page = Page.objects.latest('pk')
assert resp.status_int == 302
assert resp.location.endswith('/manage/pages/%s/' % new_page.pk)
resp = resp.follow()
assert 'Page %s has been duplicated.' % page.title in resp.text
assert new_page.exclude_from_navigation is True
resp = app.get('/manage/pages/%s/' % page.pk)
resp = resp.click(href='/%s/duplicate' % page.pk)
resp.form['title'] = 'Two'
resp = resp.form.submit()
new_page = Page.objects.latest('pk')
assert resp.status_int == 302
assert resp.location.endswith('/manage/pages/%s/' % new_page.pk)
assert new_page.title == 'Two'
assert new_page.slug == 'two'
def test_add_edit_cell(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)
# click on first option link, this should add a text cell
resp = app.get(resp.html.find('option').get('data-add-url'))
cells = CellBase.get_cells(page_id=page.id)
assert len(cells) == 1
assert isinstance(cells[0], TextCell)
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.id, cells[0].get_reference()))
resp = app.get('/manage/pages/%s/' % page.id)
assert ('data-cell-reference="%s"' % cells[0].get_reference()) in resp.text
resp.forms[0]['c%s-text' % cells[0].get_reference()].value = 'Hello world'
manager_submit_cell(resp.forms[0])
resp = app.get('/manage/pages/%s/' % page.id)
assert resp.forms[0]['c%s-text' % cells[0].get_reference()].value == 'Hello world'
resp = app.get('/manage/pages/%s/' % page.id)
assert ('data-cell-reference="%s"' % cells[0].get_reference()) in resp.text
resp.forms[0]['c%s-text' % cells[0].get_reference()].value = 'World Hello'
manager_submit_cell(resp.forms[0])
assert resp.forms[0]['c%s-text' % cells[0].get_reference()].value == 'World Hello'
resp = app.get('/manage/pages/%s/' % page.id)
assert resp.forms[0]['c%s-text' % cells[0].get_reference()].value == 'World Hello'
def test_add_all_basic_cell_types(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)
for option in resp.html.find_all('option'):
if '/data_' not in option.get('data-add-url'):
continue
resp = app.get(option.get('data-add-url'))
resp = resp.follow()
@mock.patch('combo.data.models.TextCell.max_one_by_page', mock.PropertyMock(return_value=True))
def test_add_cell_max_one_by_page(app, admin_user):
page = Page.objects.create(title='One', slug='one', template_name='standard')
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
add_text_cell_url = resp.html.find('option').get('data-add-url')
resp = app.get(add_text_cell_url)
cells = CellBase.get_cells(page_id=page.id)
assert len(cells) == 1
assert isinstance(cells[0], TextCell)
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.id, cells[0].get_reference()))
resp = app.get('/manage/pages/%s/' % page.id)
assert add_text_cell_url not in resp.text
# try to add cell anyway
resp = app.get(add_text_cell_url, status=403)
SEARCH_SERVICES = {
'search1': {
'label': 'Search 1',
'url': 'http://www.example.net/search/?q=%(q)s',
}
}
class SearchServices:
def __init__(self, search_services):
self.search_services = search_services
def __enter__(self):
settings.COMBO_SEARCH_SERVICES = self.search_services
def __exit__(self, *args, **kwargs):
settings.COMBO_SEARCH_SERVICES = {}
def test_search_cell(app, admin_user):
Page.objects.all().delete()
page = Page(title='One', slug='one', template_name='standard')
page.save()
# add
app = login(app)
assert SearchCell.objects.count() == 0
with SearchServices(SEARCH_SERVICES):
resp = app.get('/manage/pages/%s/' % page.id)
for option in resp.html.find_all('option'):
if 'content/search_' in option.get('data-add-url'):
resp = app.get(option.get('data-add-url'))
resp = resp.follow()
assert SearchCell.objects.count() == 1
cell = SearchCell.objects.first()
# delete
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
resp = resp.click(href='/search_searchcell-%s/delete' % cell.id)
resp = resp.form.submit()
assert SearchCell.objects.count() == 0
def test_delete_cell(app, admin_user):
Page.objects.all().delete()
page = Page(title='One', slug='one', template_name='standard')
page.save()
cell = TextCell(page=page, placeholder='content', text='Foobar', order=0)
cell.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
resp = resp.click(href='/data_textcell-%s/delete' % cell.id)
resp = resp.form.submit()
assert TextCell.objects.count() == 0
def test_duplicate_cell(app, admin_user):
page = Page.objects.create(title='One', slug='one', template_name='standard')
cell = TextCell.objects.create(page=page, placeholder='content', text='Foobar', order=1, slug='foobar')
app = login(app)
resp = app.get('/manage/pages/%s/' % page.pk)
resp = resp.click(href='/data_textcell-%s/duplicate' % cell.pk)
assert resp.form['target_page'].value == str(page.pk)
resp = resp.form.submit()
assert TextCell.objects.count() == 2
new_cell = TextCell.objects.latest('pk')
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, new_cell.get_reference()))
resp = resp.follow()
assert 'Cell %s has been duplicated.' % cell in resp.text
assert new_cell.slug == ''
assert new_cell.order == 2
assert new_cell.page == page
assert new_cell.placeholder == 'content'
assert PageSnapshot.objects.filter(page=page).count() == 1
assert PageSnapshot.objects.get(page=page).comment == 'duplicated cell "Text (Foobar)"'
other_page = Page.objects.create(title='Two', slug='two', template_name='standard')
resp = app.get('/manage/pages/%s/' % page.pk)
resp = resp.click(href='/data_textcell-%s/duplicate' % cell.pk)
resp.form['target_page'] = other_page.pk
resp = resp.form.submit()
assert TextCell.objects.count() == 3
new_cell = TextCell.objects.latest('pk')
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (other_page.pk, new_cell.get_reference()))
resp = resp.follow()
assert 'Cell %s has been duplicated.' % cell in resp.text
assert new_cell.slug == 'foobar'
assert new_cell.order == 1
assert new_cell.page == other_page
assert new_cell.placeholder == 'content'
assert PageSnapshot.objects.filter(page=page).count() == 1
assert PageSnapshot.objects.filter(page=other_page).count() == 1
assert (
PageSnapshot.objects.get(page=other_page).comment
== 'added cell "Text (Foobar)", duplicated from "One" page'
)
resp = app.get('/manage/pages/%s/' % page.pk)
resp = resp.click(href='/data_textcell-%s/duplicate' % cell.pk)
resp.form['target_page'] = other_page.pk
resp = resp.form.submit()
assert TextCell.objects.count() == 4
new_cell = TextCell.objects.latest('pk')
assert new_cell.order == 2
assert PageSnapshot.objects.filter(page=page).count() == 1
assert PageSnapshot.objects.filter(page=other_page).count() == 2
app.get('/manage/pages/%s/cell/data_textcell-%s/duplicate' % (0, cell.pk), status=404)
app.get('/manage/pages/%s/cell/data_textcell-%s/duplicate' % (page.pk, 0), status=404)
def test_edit_cell_visibility(app, admin_user):
Page.objects.all().delete()
page = Page(title='One', slug='one', template_name='standard')
page.save()
cell = TextCell(page=page, placeholder='content', text='Foobar', order=0)
cell.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
assert resp.form['cdata_textcell-%s-visibility' % cell.id].value == 'all'
assert 'cdata_textcell-%s-condition' % cell.id in resp
resp.form['cdata_textcell-%s-visibility' % cell.id] = 'logged'
resp = resp.form.submit('submit')
assert TextCell.objects.get(id=cell.id).public is False
assert TextCell.objects.get(id=cell.id).restricted_to_unlogged is False
assert TextCell.objects.get(id=cell.id).groups.count() == 0
resp = app.get('/manage/pages/%s/' % page.id)
assert resp.form['cdata_textcell-%s-visibility' % cell.id].value == 'logged'
resp.form['cdata_textcell-%s-visibility' % cell.id] = 'unlogged'
resp = resp.form.submit('submit')
assert TextCell.objects.get(id=cell.id).restricted_to_unlogged is True
assert TextCell.objects.get(id=cell.id).public is True
assert TextCell.objects.get(id=cell.id).groups.count() == 0
resp = app.get('/manage/pages/%s/' % page.id)
assert resp.form['cdata_textcell-%s-visibility' % cell.id].value == 'unlogged'
resp.form['cdata_textcell-%s-visibility' % cell.id] = 'all'
resp = resp.form.submit('submit')
assert TextCell.objects.get(id=cell.id).restricted_to_unlogged is False
assert TextCell.objects.get(id=cell.id).public is True
assert TextCell.objects.get(id=cell.id).groups.count() == 0
group1 = Group(name='A group')
group1.save()
group2 = Group(name='Another group')
group2.save()
resp = app.get('/manage/pages/%s/' % page.id)
resp.form['cdata_textcell-%s-visibility' % cell.id] = 'groups-any'
resp.form['cdata_textcell-%s-groups' % cell.id] = [group1.id]
resp = resp.form.submit('submit')
assert TextCell.objects.get(id=cell.id).restricted_to_unlogged is False
assert TextCell.objects.get(id=cell.id).public is False
assert TextCell.objects.get(id=cell.id).groups.count() == 1
assert TextCell.objects.get(id=cell.id).groups.all()[0].name == 'A group'
resp = app.get('/manage/pages/%s/' % page.id)
resp.form['cdata_textcell-%s-visibility' % cell.id] = 'groups-none'
resp.form['cdata_textcell-%s-groups' % cell.id] = [group2.id]
resp = resp.form.submit('submit')
assert TextCell.objects.get(id=cell.id).restricted_to_unlogged is True
assert TextCell.objects.get(id=cell.id).public is False
assert TextCell.objects.get(id=cell.id).groups.count() == 1
assert TextCell.objects.get(id=cell.id).groups.all()[0].name == 'Another group'
resp = app.get('/manage/pages/%s/' % page.id)
resp.form['cdata_textcell-%s-condition' % cell.pk] = 'False #'
resp = resp.form.submit('submit')
assert resp.json['errorlist']['visibility']['condition'] == ['Invalid syntax.']
resp = app.get('/manage/pages/%s/' % page.id)
resp.form['cdata_textcell-%s-condition' % cell.pk] = 'False'
resp = resp.form.submit('submit')
assert TextCell.objects.get(id=cell.id).condition == 'False'
def test_edit_cell_options(app, admin_user):
Page.objects.all().delete()
page = Page(title='One', slug='one', template_name='standard')
page.save()
cell = TextCell(page=page, placeholder='content', text='Foobar', order=0)
cell.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
assert resp.form['cdata_textcell-%s-slug' % cell.id].value == ''
assert resp.form['cdata_textcell-%s-extra_css_class' % cell.id].value == ''
resp.form['cdata_textcell-%s-slug' % cell.id] = 'SLUG'
resp = resp.form.submit('submit')
assert TextCell.objects.get(id=cell.id).slug == 'SLUG'
assert 'SLUG' in app.get('/manage/pages/%s/' % page.id)
resp = app.get('/manage/pages/%s/' % page.id)
resp.form['cdata_textcell-%s-slug' % cell.id] = ''
resp = resp.form.submit('submit')
assert TextCell.objects.get(id=cell.id).slug == ''
assert 'SLUG' not in app.get('/manage/pages/%s/' % page.id)
resp = app.get('/manage/pages/%s/' % page.id)
resp.form['cdata_textcell-%s-extra_css_class' % cell.id] = 'CSS'
resp = resp.form.submit('submit')
assert TextCell.objects.get(id=cell.id).extra_css_class == 'CSS'
assert '[CSS]' in app.get('/manage/pages/%s/' % page.id)
def test_edit_cell_options_template(app, admin_user):
Page.objects.all().delete()
page = Page(title='One', slug='one', template_name='standard')
page.save()
cell = TextCell(page=page, placeholder='content', text='Foobar', order=0)
cell.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
assert 'cdata_textcell-%s-template_name' % cell.id not in resp.form.fields
assert resp.form['cdata_textcell-%s-slug' % cell.id].value == ''
assert resp.form['cdata_textcell-%s-extra_css_class' % cell.id].value == ''
with override_settings(COMBO_CELL_TEMPLATES={'data_textcell': {'extra': {'label': 'Extra'}}}):
resp = app.get('/manage/pages/%s/' % page.id)
resp.form['cdata_textcell-%s-template_name' % cell.id].value = 'extra'
resp = resp.form.submit('submit')
assert TextCell.objects.get(id=cell.id).template_name == 'extra'
assert '(Extra)' in app.get('/manage/pages/%s/' % page.id)
def test_edit_cell_order(app, admin_user):
Page.objects.all().delete()
page = Page(title='One', slug='one', template_name='standard')
page.save()
cells = []
for i in range(5):
cells.append(TextCell(page=page, placeholder='content', text='Foobar%s' % i, order=i))
cells[-1].save()
params = []
new_order = [2, 3, 1, 4, 5]
for i, (cell, dummy) in enumerate(zip(cells, new_order)):
params.append(('ph_data_textcell-%s' % cell.id, 'content')) # no placeholder change
params.append(('pos_data_textcell-%s' % cell.id, str(new_order[i])))
app = login(app)
resp = app.get('/manage/pages/%s/order?%s' % (page.id, urlencode(params)))
assert resp.status_code == 204
for i, cell in enumerate(cells):
assert TextCell.objects.get(id=cell.id).order == new_order[i]
def test_edit_cell_attribute_ajax(settings, app, admin_user):
Page.objects.all().delete()
page = Page(title='One', slug='one', template_name='standard')
page.save()
cell = TextCell(page=page, placeholder='content', text='Foobar', order=0)
cell.save()
app = login(app)
params = {
'text': 'new text',
}
app.post(
f'/manage/pages/{page.id}/cell/data_textcell-{cell.id}/attribute/text/', params=params, status=403
)
resp = app.get(f'/manage/pages/{page.id}/')
params = {
'text': 'new text',
'csrfmiddlewaretoken': PyQuery(resp.text).find('[name=csrfmiddlewaretoken]').val(),
}
resp = app.post(f'/manage/pages/{page.id}/cell/data_textcell-{cell.id}/attribute/text/', params=params)
assert resp.json == {'err': 0}
assert TextCell.objects.get(id=cell.id).text == 'new text'
params = {
'placeholder': '',
'csrfmiddlewaretoken': params['csrfmiddlewaretoken'],
}
resp = app.post(
f'/manage/pages/{page.id}/cell/data_textcell-{cell.id}/attribute/placeholder/', params=params
)
assert resp.json == {'err': 1, 'err_desc': 'not-attribute-from-default-form'}
feedcell = FeedCell.objects.create(page=page, placeholder='content', order=1)
params = {
'limit': 'foo',
'csrfmiddlewaretoken': params['csrfmiddlewaretoken'],
}
resp = app.post(
f'/manage/pages/{page.id}/cell/data_feedcell-{feedcell.id}/attribute/limit/', params=params
)
assert resp.json == {
'err': 1,
'err_desc': 'form-error-see-errorlist',
'errorlist': {'limit': ['Enter a whole number.']},
}
def test_edit_text_cell(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)
data_add_url = [x for x in resp.html.find_all('option') if x.text == 'Text'][0].get('data-add-url')
resp = app.get(data_add_url)
cells = CellBase.get_cells(page_id=page.id)
assert len(cells) == 1
assert isinstance(cells[0], TextCell)
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.id, cells[0].get_reference()))
resp = app.get('/manage/pages/%s/' % page.id)
resp.form['cdata_textcell-%s-text' % cells[0].id].value = 'Hello : World'
resp = resp.form.submit()
assert TextCell.objects.get(id=cells[0].id).text == 'Hello : World'
with override_settings(LANGUAGE_CODE='fr-fr'):
resp = app.get('/manage/pages/%s/' % page.id)
resp.form['cdata_textcell-%s-text' % cells[0].id].value = 'Hello : World'
resp = resp.form.submit()
assert TextCell.objects.get(id=cells[0].id).text == 'Hello\u00a0: World'
def test_edit_json_cell(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)
data_add_url = [x for x in resp.html.find_all('option') if x.text == 'JSON Prototype'][0].get(
'data-add-url'
)
resp = app.get(data_add_url)
cells = CellBase.get_cells(page_id=page.id)
assert len(cells) == 1
assert isinstance(cells[0], JsonCell)
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.id, cells[0].get_reference()))
resp = app.get('/manage/pages/%s/' % page.id)
resp.form['cdata_jsoncell-%s-url' % cells[0].id].value = 'xxx'
resp = resp.form.submit()
assert 'Enter a valid URL.' in resp.text
assert JsonCell.objects.get(id=cells[0].id).url == ''
resp = app.get('/manage/pages/%s/' % page.id)
resp.form['cdata_jsoncell-%s-url' % cells[0].id].value = 'https://www.example.net/'
resp = resp.form.submit()
assert JsonCell.objects.get(id=cells[0].id).url == 'https://www.example.net/'
resp = app.get('/manage/pages/%s/' % page.id)
resp.form['cdata_jsoncell-%s-url' % cells[0].id].value = '{{url}}'
resp = resp.form.submit()
assert JsonCell.objects.get(id=cells[0].id).url == '{{url}}'
def test_edit_config_json_cell(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)
options = [x.text for x in resp.html.find_all('option')]
assert 'Foobar' not in options
templates_settings = [settings.TEMPLATES[0].copy()]
templates_settings[0]['DIRS'] = ['%s/templates-1' % os.path.abspath(os.path.dirname(__file__))]
with override_settings(
JSON_CELL_TYPES={'test-config-json-cell': {'name': 'Foobar', 'url': 'http://test/'}},
TEMPLATES=templates_settings,
):
resp = app.get('/manage/pages/%s/' % page.id)
options = [x.text for x in resp.html.find_all('option')]
assert 'Foobar' in options
data_add_url = [x for x in resp.html.find_all('option') if x.text == 'Foobar'][0].get('data-add-url')
resp = app.get(data_add_url)
cells = CellBase.get_cells(page_id=page.id)
assert len(cells) == 1
assert isinstance(cells[0], ConfigJsonCell)
assert cells[0].key == 'test-config-json-cell'
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.id, cells[0].get_reference()))
resp = app.get('/manage/pages/%s/' % page.id)
assert ('data-cell-reference="%s"' % cells[0].get_reference()) in resp.text
assert [x.attrib['data-tab-slug'] for x in resp.pyquery('[data-tab-slug]')] == [
'visibility',
'appearance',
]
# make it configurable
with override_settings(
JSON_CELL_TYPES={
'test-config-json-cell': {
'name': 'Foobar',
'url': 'http://test/',
'form': [
{
'label': 'Test',
'type': 'string',
'varname': 'test',
},
{
'label': 'Test bool',
'type': 'bool',
'varname': 'test2',
'required': False,
},
{
'label': 'Test no type is string',
'varname': 'test3',
},
{
'label': 'Test text',
'type': 'text',
'varname': 'test4',
'required': False,
},
],
}
},
TEMPLATES=templates_settings,
):
resp = app.get('/manage/pages/%s/' % page.id)
assert [x.attrib['data-tab-slug'] for x in resp.pyquery('[data-tab-slug]')] == [
'general',
'visibility',
'appearance',
]
resp.form['c%s-parameter_test' % cells[0].get_reference()].value = 'Hello world'
resp.form['c%s-parameter_test3' % cells[0].get_reference()].value = 'Hello again'
manager_submit_cell(resp.form)
# test form error
resp = app.get('/manage/pages/%s/' % page.id)
resp.form['c%s-parameter_test' % cells[0].get_reference()].value = ''
resp.form['c%s-parameter_test3' % cells[0].get_reference()].value = 'Hello'
resp = manager_submit_cell(resp.form, expect_errors=True)
assert resp.context['form'].errors['parameter_test'] == ['This field is required.']
resp = app.get('/manage/pages/%s/' % page.id)
assert resp.form['c%s-parameter_test' % cells[0].get_reference()].value == 'Hello world'
assert resp.form['c%s-parameter_test2' % cells[0].get_reference()].checked is False
assert resp.form['c%s-parameter_test3' % cells[0].get_reference()].value == 'Hello again'
resp = app.get('/manage/pages/%s/' % page.id)
assert ('data-cell-reference="%s"' % cells[0].get_reference()) in resp.text
resp.forms[0]['c%s-parameter_test' % cells[0].get_reference()].value = 'World Hello'
resp.forms[0]['c%s-parameter_test2' % cells[0].get_reference()].checked = True
assert resp.form['c%s-parameter_test4' % cells[0].get_reference()].tag == 'textarea'
resp.forms[0]['c%s-parameter_test4' % cells[0].get_reference()].value = 'Text Area'
resp = manager_submit_cell(resp.form)
assert resp.json['tabs']['general']['form'].strip().startswith('<p><label')
resp = app.get('/manage/pages/%s/' % page.id)
assert resp.form['c%s-parameter_test' % cells[0].get_reference()].value == 'World Hello'
assert resp.form['c%s-parameter_test2' % cells[0].get_reference()].checked is True
assert resp.form['c%s-parameter_test3' % cells[0].get_reference()].value == 'Hello again'
assert resp.form['c%s-parameter_test4' % cells[0].get_reference()].value == 'Text Area'
def test_logout(app, admin_user):
app = login(app)
app.get('/logout/')
assert app.get('/manage/', status=302).location.endswith('/login/?next=/manage/')
def test_asset_management(app, admin_user):
app = login(app)
resp = app.get('/manage/assets/')
assert 'have any asset yet.' in resp.text
# test upload
resp = app.get('/manage/assets/')
resp = resp.click('Upload')
resp.form['upload'] = Upload(
'test.png',
base64.decodebytes(
b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='
),
'image/png',
)
resp = resp.form.submit().follow()
# check file is now there
resp = app.get('/manage/assets/')
assert 'have any asset yet.' not in resp.text
# check there is also a thumbnail
thumbnail_filename = re.findall('src="/media/(.*thumb.*)"', resp.text)[0]
thumbnail_path = default_storage.path(thumbnail_filename)
assert os.path.exists(thumbnail_path)
with open(thumbnail_path, mode='rb') as fd:
thumbnail_contents = fd.read()
# check overwriting
resp = resp.click('Overwrite')
# test with the same extension but uppercased
resp.form['upload'] = Upload(
'test.PNG',
base64.decodebytes(
b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAgAABAADRWoApgAA\nAABJRU5ErkJggg=='
),
'image/png',
)
resp = resp.form.submit().follow()
resp.click('test.png')
assert re.findall('src="/media/(.*thumb.*)"', resp.text)[0] == thumbnail_filename
assert os.path.exists(thumbnail_path)
with open(thumbnail_path, mode='rb') as fd:
thumbnail_contents_new = fd.read()
assert thumbnail_contents_new != thumbnail_contents
# try to overwrite with a different mimetype
resp = resp.click('Overwrite')
resp.form['upload'] = Upload(
'test.pdf',
base64.decodebytes(
b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAgAABAADRWoApgAA\nAABJRU5ErkJggg=='
),
'application/pdf',
)
with mock.patch('combo.apps.assets.views.default_storage.delete') as mock_delete:
resp = resp.form.submit().follow()
# original file was not deleted
assert mock_delete.call_args_list == []
messages = resp.context['messages']
assert len(messages._loaded_messages) == 1
assert messages._loaded_messages[0].message == 'You have to upload a file with the same extension (.png).'
# test deletion
resp = resp.click('Delete')
assert 'Are you sure you want to delete' in resp.text
resp = resp.form.submit().follow()
assert 'have any asset yet.' in resp.text
# bad filename
filenames = [
'test.png',
'uploads/..test.png',
'uploads/.%00.test.png',
]
for filename in filenames:
resp = app.get('/manage/assets/overwrite/?img=%s' % filename)
resp.form['upload'] = Upload(
'test.png',
base64.decodebytes(
b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAgAABAADRWoApgAA\nAABJRU5ErkJggg=='
),
'image/png',
)
resp = resp.form.submit(status=403)
resp = app.get('/manage/assets/delete?img=%s' % filename)
resp = resp.form.submit(status=403)
def test_asset_management_anchor(app, admin_user):
app = login(app)
resp = app.get('/manage/assets/')
assert 'have any asset yet.' in resp.text
for dummy in range(25):
filepath = os.path.join(settings.CKEDITOR_UPLOAD_PATH, 'cc.png')
pix = b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='
default_storage.save(filepath, BytesIO(base64.decodebytes(pix)))
# test upload
resp = app.get('/manage/assets/')
resp = resp.click('Upload')
resp.form['upload'] = Upload(
'test.png',
base64.decodebytes(
b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='
),
'image/png',
)
resp = resp.form.submit()
assert resp.location.endswith('?page=3')
resp = resp.follow()
# overwrite
resp = resp.click(href=re.compile('overwrite.*test.png'))
resp.form['upload'] = Upload(
'aa.png',
base64.decodebytes(
b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAgAABAADRWoApgAA\nAABJRU5ErkJggg=='
),
'image/png',
)
resp = resp.form.submit()
assert resp.location.endswith('?page=3')
resp = resp.follow()
# delete
resp = resp.click(href=re.compile('delete.*test.png'))
resp = resp.form.submit()
resp = resp.follow()
assert 'test.png' not in resp.text
def test_asset_management_search(app, admin_user):
app = login(app)
filepath = os.path.join(settings.CKEDITOR_UPLOAD_PATH, 'test.png')
pix = b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='
default_storage.save(filepath, BytesIO(base64.decodebytes(pix)))
assert os.path.exists(default_storage.path(filepath))
filepath = os.path.join(settings.CKEDITOR_UPLOAD_PATH, 'test.txt')
default_storage.save(filepath, BytesIO(b'hello world'))
filepath = os.path.join(settings.CKEDITOR_UPLOAD_PATH, 'foo.txt')
default_storage.save(filepath, BytesIO(b'foo bar baz'))
resp = app.get('/manage/assets/')
assert resp.text.count('<tr class="asset') == 3
resp.form['q'] = 'test'
resp = resp.form.submit()
assert resp.text.count('<tr class="asset') == 2
def test_assets_browse(app, admin_user):
app = login(app)
resp = app.get('/manage/ckeditor/browse/?CKEditor=foo&CKEditorFuncNum=0')
assert 'No files found' in resp.text
filepath = os.path.join(settings.CKEDITOR_UPLOAD_PATH, 'aa.png')
pix = b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='
default_storage.save(filepath, BytesIO(base64.decodebytes(pix)))
filepath = os.path.join(settings.CKEDITOR_UPLOAD_PATH, 'bb.pdf')
pix = b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='
default_storage.save(filepath, BytesIO(base64.decodebytes(pix)))
filepath = os.path.join(settings.CKEDITOR_UPLOAD_PATH, 'cc.png')
pix = b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='
default_storage.save(filepath, BytesIO(base64.decodebytes(pix)))
resp = app.get('/manage/ckeditor/browse/?CKEditor=foo&CKEditorFuncNum=0')
assert 'No files found' not in resp.text
resp.form['q'] = 'test'
resp = resp.form.submit()
assert 'CKEditor=foo' in resp.request.url
assert 'CKEditorFuncNum=0' in resp.request.url
assert resp.text.count('<tr class="asset') == 0
resp.form['q'] = 'png'
resp = resp.form.submit()
assert 'CKEditor=foo' in resp.request.url
assert 'CKEditorFuncNum=0' in resp.request.url
assert resp.text.count('<tr class="asset') == 2
for dummy in range(25):
filepath = os.path.join(settings.CKEDITOR_UPLOAD_PATH, 'cc.png')
pix = b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='
default_storage.save(filepath, BytesIO(base64.decodebytes(pix)))
resp = app.get('/manage/ckeditor/browse/?CKEditor=foo&CKEditorFuncNum=0')
resp = resp.click(href='.*page=2.*')
assert 'CKEditor=foo' in resp.request.url
assert 'CKEditorFuncNum=0' in resp.request.url
def test_asset_slots_management(app, admin_user):
app = login(app)
assert Asset.objects.count() == 0
with override_settings(COMBO_ASSET_SLOTS={'collectivity:banner': {'label': 'Banner'}}):
resp = app.get('/manage/assets/')
assert 'Banner <span class="not-defined">' in resp.text
assert '>Delete<' not in resp.text
resp = resp.click('Overwrite')
resp.form['upload'] = Upload(
'test.png',
base64.decodebytes(
b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAgAABAADRWoApgAA\nAABJRU5ErkJggg=='
),
'image/png',
)
resp = resp.form.submit()
assert resp.location.endswith('?page=1')
resp = resp.follow()
assert 'test.png' in resp.text
assert '>Delete<' in resp.text
assert Asset.objects.filter(key='collectivity:banner').count() == 1
assert app.get('/assets/collectivity:banner', status=302)['location'].endswith('test.png')
# upload a new version of image
resp = resp.click('Overwrite')
resp.form['upload'] = Upload(
'test2.png',
base64.decodebytes(
b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAgAABAADRWoApgAA\nAABJRU5ErkJggg=='
),
'image/png',
)
resp = resp.form.submit().follow()
assert 'test2.png' in resp.text
assert '<img data-href' in resp.text # thumbnail
assert '>Delete<' in resp.text
assert Asset.objects.filter(key='collectivity:banner').count() == 1
resp = resp.click('Delete')
resp = resp.form.submit()
assert resp.location.endswith('?page=1')
resp = resp.follow()
assert 'Banner <span class="not-defined">' in resp.text
assert '>Delete<' not in resp.text
assert Asset.objects.filter(key='collectivity:banner').count() == 0
resp = app.get('/assets/collectivity:banner', status=404)
with override_settings(
COMBO_ASSET_SLOTS={'collectivity:cgu': {'label': 'CGU', 'asset-type': 'document'}}
):
resp = app.get('/manage/assets/')
assert 'CGU <span class="not-defined">' in resp.text
assert '>Delete<' not in resp.text
resp = resp.click('Overwrite')
resp.form['upload'] = Upload('test.pdf', b'PDF-1.4 ...', 'application.pdf')
resp = resp.form.submit().follow()
assert 'test.pdf' in resp.text
assert '<img data-href' not in resp.text # no thumbnail
assert '>Delete<' in resp.text
assert Asset.objects.filter(key='collectivity:cgu').count() == 1
# badly remove the file, manager page is still visible
os.unlink(Asset.objects.filter(key='collectivity:cgu')[0].asset.path)
resp = app.get('/manage/assets/')
assert '>CGU<' in resp.text
# with bad settings
with override_settings(
COMBO_ASSET_SLOTS={
'collectivity:cgu': {}, # missing label
'foo': {'label': 'bar'},
}
):
app.get('/manage/assets/') # no error
def test_asset_slots_for_cells_management(settings, app, admin_user):
app = login(app)
assert Asset.objects.count() == 0
settings.COMBO_CELL_ASSET_SLOTS = {'data_textcell': {'picture': {'prefix': 'Picture'}}}
page = Page.objects.create(title='xxx', slug='test_cell_assets', template_name='standard')
cell = TextCell.objects.create(page=page, order=0, slug='foo')
resp = app.get('/manage/assets/slots/%s/' % cell.get_reference())
assert '>Overwrite<' in resp.text
assert '>Delete<' not in resp.text
resp = resp.click('Overwrite')
resp.form['upload'] = Upload(
'test.png',
base64.decodebytes(
b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAgAABAADRWoApgAA\nAABJRU5ErkJggg=='
),
'image/png',
)
resp = resp.form.submit()
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
assert Asset.objects.filter(key=cell.get_asset_slot_key('picture')).count() == 1
assert app.get('/assets/%s' % cell.get_asset_slot_key('picture'), status=302)['location'].endswith(
'test.png'
)
# upload a new version of image
resp = app.get('/manage/assets/slots/%s/' % cell.get_reference())
resp = resp.click('Overwrite')
resp.form['upload'] = Upload(
'test2.png',
base64.decodebytes(
b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAgAABAADRWoApgAA\nAABJRU5ErkJggg=='
),
'image/png',
)
resp = resp.form.submit()
resp = app.get('/manage/assets/slots/%s/' % cell.get_reference())
assert 'test2.png' in resp.text
assert '<img data-href' in resp.text # thumbnail
assert '>Delete<' in resp.text
assert Asset.objects.filter(key=cell.get_asset_slot_key('picture')).count() == 1
# delete the image
resp = app.get('/manage/assets/slots/%s/' % cell.get_reference())
resp = resp.click('Delete')
resp = resp.form.submit()
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
resp = app.get('/manage/assets/slots/%s/' % cell.get_reference())
assert '>Overwrite<' in resp.text
assert '>Delete<' not in resp.text
assert Asset.objects.filter(key=cell.get_asset_slot_key('picture')).count() == 0
assert app.get('/assets/%s' % cell.get_asset_slot_key('picture'), status=404)
def test_asset_slots_for_map_layers_management(app, admin_user):
app = login(app)
assert Asset.objects.count() == 0
with override_settings(COMBO_MAP_LAYER_ASSET_SLOTS={'marker': {'prefix': 'Marker'}}):
resp = app.get('/manage/assets/')
assert 'Marker — &quot;bicycles&quot; map layer <span class="not-defined">' not in resp
layer = MapLayer()
layer.label = 'bicycles'
layer.geojson_url = 'http://example.org/geojson'
layer.marker_colour = 'FF0000'
layer.icon = 'fa-bicycle'
layer.icon_colour = '0000FF'
layer.save()
resp = app.get('/manage/assets/')
assert 'Marker — &quot;bicycles&quot; map layer <span class="not-defined">' in resp
resp = resp.click('Overwrite')
assert resp.request.url.endswith('/maplayer:marker:bicycles/')
def test_asset_slots_export(app, admin_user):
app = login(app)
with override_settings(COMBO_ASSET_SLOTS={'collectivity:banner': {'label': 'Banner'}}):
resp = app.get('/manage/assets/')
resp = resp.click('Overwrite')
resp.form['upload'] = Upload(
'test.png',
base64.decodebytes(
b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAgAABAADRWoApgAA\nAABJRU5ErkJggg=='
),
'image/png',
)
resp = resp.form.submit().follow()
assert Asset.objects.filter(key='collectivity:banner').count() == 1
resp = resp.click('Export')
assert resp.content_type == 'application/x-tar'
content = resp.content
Asset.objects.filter(key='collectivity:banner').delete()
resp = app.get('/manage/assets/')
resp = resp.click('Import')
resp.form['assets_file'] = Upload('test.tar', content)
resp = resp.form.submit()
assert Asset.objects.filter(key='collectivity:banner').count() == 1
def test_serve_asset(settings, app, admin_user):
settings.COMBO_ASSET_SLOTS = {'collectivity:banner': {'label': 'Banner'}}
app = login(app)
resp = app.get('/manage/assets/')
assert 'Banner <span class="not-defined">' in resp.text
assert '>Delete<' not in resp.text
resp = resp.click('Overwrite')
resp.form['upload'] = Upload(
'test.png',
base64.decodebytes(
b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAgAABAADRWoApgAA\nAABJRU5ErkJggg=='
),
'image/png',
)
resp = resp.form.submit().follow()
assert Asset.objects.filter(key='collectivity:banner').count() == 1
asset = Asset.objects.latest('pk')
assert app.get('/assets/collectivity:banner', status=302)['location'].endswith('test.png')
assert app.get('/assets/collectivity:banner?width=200', status=302)['location'].startswith(
'/media/cache/'
)
assert app.get('/assets/collectivity:banner?height=200', status=302)['location'].startswith(
'/media/cache/'
)
assert app.get('/assets/collectivity:banner?crop=center', status=302)['location'].endswith('test.png')
assert app.get('/assets/collectivity:banner?width=200&crop=center', status=302)['location'].startswith(
'/media/cache/'
)
# file is missing
os.remove(asset.asset.path)
app.get('/assets/collectivity:banner', status=404)
# upload a SVG
resp = resp.click('Delete')
resp = resp.form.submit()
assert Asset.objects.filter(key='collectivity:banner').count() == 0
resp = app.get('/manage/assets/')
assert 'Banner <span class="not-defined">' in resp.text
assert '>Delete<' not in resp.text
resp = resp.click('Overwrite')
resp.form['upload'] = Upload(
'test.svg',
base64.decodebytes(
b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAgAABAADRWoApgAA\nAABJRU5ErkJggg=='
),
'image/svg+xml',
)
resp = resp.form.submit()
assert Asset.objects.filter(key='collectivity:banner').count() == 1
# SVG: no thumbnail !
assert app.get('/assets/collectivity:banner', status=302)['location'].endswith('test.svg')
assert app.get('/assets/collectivity:banner?width=200', status=302)['location'].endswith('test.svg')
assert app.get('/assets/collectivity:banner?height=200', status=302)['location'].endswith('test.svg')
assert app.get('/assets/collectivity:banner?crop=center', status=302)['location'].endswith('test.svg')
assert app.get('/assets/collectivity:banner?width=200&crop=center', status=302)['location'].endswith(
'test.svg'
)
# unknown Asset key
app.get('/assets/foo:bar', status=404)
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.decodebytes(
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)
app = login(app)
resp = app.get('/manage/menu.json')
assert resp.headers['content-type'] == 'application/json'
assert resp.json[0]['label'] == 'Content Management'
resp = app.get('/manage/menu.json?callback=fooBar')
assert resp.headers['content-type'] == 'application/javascript'
assert resp.text.startswith('fooBar([{"')
def test_page_multiple_link_cells(app, admin_user):
Page.objects.all().delete()
page = Page(title='One', slug='one', template_name='standard')
page.save()
for i in range(10):
Page(title='page %d' % i, slug='page%d' % i, template_name='standard').save()
cell = LinkCell(page=page, placeholder='content', order=0)
cell.save()
patched_orig = Page.get_as_reordered_flat_hierarchy
app = login(app)
with mock.patch('combo.data.models.Page.get_as_reordered_flat_hierarchy') as func:
func.side_effect = patched_orig
app.get('/manage/pages/%s/' % page.id)
# only 1 call for combo.data.forms.get_page_choices
# 1 call for get_previous_page and 1 call for get_next_page
assert func.call_count == 3
def test_page_cell_placeholder(app, admin_user):
page = Page(title='One', slug='one', template_name='standard')
page.save()
cell = JsonCell(page=page, placeholder='content', order=0, url='http://example.com')
cell.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
assert re.findall('data-placeholder-key="(.*)">', resp.text) == ['content']
# check a placeholder within a cell is included
cell.template_string = '{% placeholder "foobar" name="Foobar" %}'
cell.save()
resp = app.get('/manage/pages/%s/' % page.id)
assert re.findall('data-placeholder-key="(.*)">', resp.text) == ['content', 'foobar']
assert re.findall('<h2>(.*)</h2>', resp.text) == ['Page - One', 'Content', 'JSON Prototype / Foobar']
def test_page_discover_placeholder_with_error_cells(app, admin_user):
page = Page(title='One', slug='one', template_name='standard')
page.save()
cell = JsonCell(page=page, placeholder='content', order=0, url='xx')
cell.template_string = '{% xxx %}'
cell.save()
app = login(app)
with mock.patch('combo.utils.requests.get'):
with pytest.raises(TemplateSyntaxError):
cell.render({})
resp = app.get('/manage/pages/%s/' % page.id)
assert re.findall('data-placeholder-key="(.*)">', resp.text) == ['content']
def test_page_versionning(app, admin_user):
page = Page(title='One', slug='one')
page.save()
cell1 = TextCell(page=page, placeholder='content', text='Foobar1', order=0)
cell1.save()
cell2 = TextCell(page=page, placeholder='content', text='Foobar2', order=1)
cell2.save()
cell3 = TextCell(page=page, placeholder='content', text='Foobar3', order=2)
cell3.save()
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id, status=200)
# update title
resp = resp.click(href='.*/title')
resp.form['title'].value = 'One Two'
resp = resp.form.submit()
resp = resp.follow()
assert Page.objects.all()[0].title == 'One Two'
assert PageSnapshot.objects.all().count() == 1
# change cell text
resp.forms[0]['c%s-text' % cell1.get_reference()].value = 'Hello world'
manager_submit_cell(resp.forms[0])
assert PageSnapshot.objects.all().count() == 2
# reorder cells
params = []
for i, cell in enumerate([cell3, cell1, cell2]):
params.append(('ph_data_textcell-%s' % cell.id, 'content')) # no placeholder change
params.append(('pos_data_textcell-%s' % cell.id, str(i)))
app.get('/manage/pages/%s/order?%s' % (page.id, urlencode(params)))
assert PageSnapshot.objects.all().count() == 3
resp = resp.click('History')
assert (
resp.text.index('reordered cells')
< resp.text.index('changed cell')
< resp.text.index('changed title')
)
with CaptureQueriesContext(connection) as ctx:
resp2 = resp.click('view', index=1)
assert len(ctx.captured_queries) == 70
assert Page.snapshots.latest('pk').related_cells == {'cell_types': ['data_textcell']}
snapshot_page = Page.snapshots.latest('pk')
assert snapshot_page.uuid != page.uuid
assert resp2.text.index('Hello world') < resp2.text.index('Foobar3')
resp2 = resp.click('view', index=0)
assert resp2.text.index('Hello world') > resp2.text.index('Foobar3')
resp2 = resp.click('view', index=2)
assert 'Foobar1' in resp2.text
assert 'Hello world' not in resp2.text
assert Page.objects.all().count() == 1
assert PageSnapshot.objects.filter(page=page)[0].user.username == 'admin'
# check with asynchronous cells
resp = app.get('/manage/pages/%s/add-cell-to-content/data_jsoncell/default/' % page.id)
resp = resp.follow()
cell_id = JsonCell.objects.last().id
resp.forms[3]['cdata_jsoncell-%s-template_string' % cell_id].value = 'A{{json.data.0.text}}B'
resp.forms[3]['cdata_jsoncell-%s-url' % cell_id].value = 'http://example.com'
manager_submit_cell(resp.forms[3])
assert PageSnapshot.objects.all().count() == 5 # add + change
resp.forms[3]['cdata_jsoncell-%s-template_string' % cell_id].value = 'C{{json.data.0.text}}D'
manager_submit_cell(resp.forms[3])
assert PageSnapshot.objects.all().count() == 6
resp.forms[1]['c%s-text' % cell1.get_reference()].value = 'Foo back to 1'
manager_submit_cell(resp.forms[1])
resp = resp.click('History')
assert 'added cell' in resp.text
resp2 = resp.click('view', index=1)
json_cell_url = re.findall(r'/ajax/cell/.*/data_jsoncell-.*/', resp2.text)[0]
with mock.patch('combo.utils.requests.get') as requests_get:
data = {'data': [{'url': 'xxx', 'text': 'xxx'}]}
requests_get.return_value = mock.Mock(json=lambda: data, status_code=200)
resp3 = app.get(json_cell_url)
assert resp3.text.strip() == 'CxxxD'
# previous version should return AxxxB
resp2 = resp.click('view', index=2)
json_cell_url = re.findall(r'/ajax/cell/.*/data_jsoncell-.*/', resp2.text)[0]
with mock.patch('combo.utils.requests.get') as requests_get:
data = {'data': [{'url': 'xxx', 'text': 'xxx'}]}
requests_get.return_value = mock.Mock(json=lambda: data, status_code=200)
resp3 = app.get(json_cell_url)
assert resp3.text.strip() == 'AxxxB'
# check anonymous users can't get to cells from snapshots
app.get('/logout/')
resp3 = app.get(json_cell_url, status=403)
# restore a snapshot
app = login(app)
resp = resp.click('restore', index=6)
with CaptureQueriesContext(connection) as ctx:
resp = resp.form.submit().follow()
assert len(ctx.captured_queries) == 147
resp2 = resp.click('See online')
assert resp2.text.index('Foobar1') < resp2.text.index('Foobar2') < resp2.text.index('Foobar3')
# save snapshot with a label
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id, status=200)
resp = resp.click('Save snapshot')
resp.form['label'] = 'CGUv1'
resp = resp.form.submit().follow()
assert page.pagesnapshot_set.latest('pk').label == 'CGUv1'
# clean it up
Page.snapshots.all().delete()
assert Page.objects.count() == 1
assert TextCell.objects.count() == 3
assert JsonCell.objects.count() == 0
# page does not exists
app.get('/manage/pages/0/history', status=404)
def test_restore_page_attributes(app):
page1 = Page.objects.create(title='One', slug='one')
page2 = Page.objects.create(title='Two', slug='two', parent=page1, exclude_from_navigation=False)
page3 = Page.objects.create(title='Three', slug='three')
page4 = Page.objects.create(title='Four', slug='four')
assert page1.order == 1
assert page2.order == 2
assert page3.order == 3
assert page4.order == 4
PageSnapshot.take(page2)
snapshot = PageSnapshot.objects.latest('pk')
# move page2, remove page 1
page2.parent = page4
page2.order = 3
page2.save()
page1.delete()
page3.order = 1
page3.save()
page4.order = 2
page4.save()
assert page3.order == 1
assert page4.order == 2
assert page2.order == 3
# restore snapshot
page2 = snapshot.restore()
# check some attributes
assert page2.order == 3
assert page2.parent == page4
assert page2.exclude_from_navigation is False
PageSnapshot.take(page2)
snapshot = PageSnapshot.objects.latest('pk')
# exclude page2 from navigation
page2.exclude_from_navigation = True
page2.save()
# restore snapshot
page2 = snapshot.restore()
# check exclude_from_navigation param
assert page2.exclude_from_navigation is True
def test_restore_snapshot_duplicated_slug(app, admin_user):
root_page = Page.objects.create(title='root', slug='root')
page = Page.objects.create(title='One', slug='one', parent=root_page)
PageSnapshot.take(page)
other_root_page = Page.objects.create(title='other_root', slug='other_root')
# same slug, in another location
Page.objects.create(title='One', slug='one', parent=other_root_page)
# and try to restore the snapshot
app = login(app)
resp = app.get('/manage/pages/%s/' % page.pk)
resp = resp.click('History')
resp = resp.click('restore')
# should succeed
resp.form.submit()
def test_view_old_snapshot(app, admin_user):
page = Page.objects.create(title='One', slug='one')
PageSnapshot.take(page)
snapshot = PageSnapshot.objects.get()
# delete creation_timestamp field
del snapshot.serialization['fields']['creation_timestamp']
snapshot.save()
# and try to visit the snapshot
app = login(app)
resp = app.get('/manage/pages/%s/' % page.pk)
resp = resp.click('History')
# should succeed
resp = resp.click('view')
def test_view_snapshot_redirect_url(app, admin_user):
page = Page.objects.create(title='One', slug='one', redirect_url='http://example.com')
PageSnapshot.take(page)
app = login(app)
resp = app.get('/manage/pages/%s/' % page.pk)
resp = resp.click('History')
resp = resp.click('view')
assert resp.location == 'http://example.com'
def test_django_admin(app, admin_user):
resp = app.get('/admin/')
resp = resp.follow() # -> /admin/login/
resp = resp.follow() # -> /login/
resp.form['username'] = 'admin'
resp.form['password'] = 'admin'
resp = resp.form.submit()
resp = resp.follow() # -> /admin/
assert '/admin/logout/' in resp.text
resp = resp.click(href='/admin/logout/')
resp = resp.follow() # -> /logout/
assert urllib.parse.urlparse(resp.location).path == '/'
def test_json_cell_syntax_validation(app, admin_user):
Page.objects.all().delete()
page = Page(title='One', slug='one')
page.save()
app = login(app)
# syntax error
resp = app.get('/manage/pages/%s/add-cell-to-content/data_jsoncell/default/' % page.id)
resp = resp.follow()
cell_id = JsonCell.objects.last().id
resp.forms[0]['cdata_jsoncell-%s-template_string' % cell_id].value = '{% syntax|error %}'
resp.forms[0]['cdata_jsoncell-%s-url' % cell_id].value = 'http://example.com'
resp = resp.forms[0].submit()
assert resp.json['errorlist']['general']['template_string']
assert JsonCell.objects.count() == 1
assert JsonCell.objects.first().template_string is None
# valid syntax
resp = app.get('/manage/pages/%s/' % page.id)
resp.forms[0]['cdata_jsoncell-%s-template_string' % cell_id].value = '{{ ok }}'
resp.forms[0]['cdata_jsoncell-%s-url' % cell_id].value = 'http://example.com'
manager_submit_cell(resp.forms[0])
assert 'syntax error' not in resp.text
assert JsonCell.objects.count() == 1
assert JsonCell.objects.first().template_string == '{{ ok }}'
def test_link_cell_setup():
form = LinkCellForm()
assert 'extra_css_class' not in form.fields
def test_manager_link_cell_tabs(app, admin_user):
page = Page.objects.create(title='One', slug='one', template_name='standard')
cell = LinkCell.objects.create(order=0, placeholder='content', page=page)
app = login(app)
resp = app.get('/manage/pages/%s/' % page.pk)
assert not resp.pyquery('#tab-%s-general.pk-tabs--button-marker' % cell.get_reference())
assert resp.pyquery('[data-tab-slug="general"] input[name$="title"]')
assert not resp.pyquery('#tab-%s-visibility.pk-tabs--button-marker' % cell.get_reference())
assert not resp.pyquery('[data-tab-slug="appearance"] input[name$="title"]')
cell.title = "Custom"
cell.public = False
cell.save()
resp = app.get('/manage/pages/%s/' % page.pk)
assert resp.pyquery('#tab-%s-general.pk-tabs--button-marker' % cell.get_reference())
assert resp.pyquery('#tab-%s-visibility.pk-tabs--button-marker' % cell.get_reference())
def test_link_cell_validation():
form = LinkCellForm(data={'url': 'http://example.com'})
assert form.is_valid() is True
form = LinkCellForm(data={'url': '{{test_url}}'})
assert form.is_valid() is True
form = LinkCellForm(data={'url': '{{{test_url}}'})
assert form.is_valid() is False
assert form.errors['url'] == ["syntax error: Could not parse the remainder: '{test_url' from '{test_url'"]
def test_add_edit_delete_list_link_item(app, admin_user):
Page.objects.all().delete()
page = Page.objects.create(title='One', slug='one', template_name='standard')
cell = LinkListCell.objects.create(order=0, placeholder='content', page=page)
app = login(app)
resp = app.get('/manage/pages/%s/' % page.pk)
assert PageSnapshot.objects.count() == 0
resp = resp.click(href='.*/add-link/link$')
assert 'condition' in resp.context['form'].fields
resp.forms[0]['title'] = 'Hello world'
resp.forms[0]['url'] = 'http://example.com'
resp.forms[0]['extra_css_class'] = 'foobar'
resp = resp.forms[0].submit()
assert resp.status_int == 302
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
assert LinkCell.objects.count() == 1
item = LinkCell.objects.get()
assert item.title == 'Hello world'
assert item.url == 'http://example.com'
assert item.page == page
assert item.placeholder == cell.link_placeholder
assert item.extra_css_class == 'foobar'
assert PageSnapshot.objects.count() == 1
resp = resp.follow()
resp = resp.click(href='.*/link/%s/$' % item.get_reference())
resp.forms[0]['title'] = 'Hello world 2'
resp.forms[0]['url'] = 'http://example2.com'
resp = resp.forms[0].submit()
assert resp.status_int == 302
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
assert LinkCell.objects.count() == 1
item.refresh_from_db()
assert item.title == 'Hello world 2'
assert item.url == 'http://example2.com'
assert PageSnapshot.objects.count() == 2
resp = resp.follow()
resp = resp.click(href='.*/link/%s/delete' % item.get_reference())
resp = resp.forms[0].submit()
assert resp.status_int == 302
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
assert LinkCell.objects.count() == 0
assert PageSnapshot.objects.count() == 3
resp = resp.follow()
resp = resp.click(href='.*/add-link/link$')
resp.forms[0]['condition'] = 'False #'
resp = resp.forms[0].submit()
assert resp.context['form'].errors['condition'] == ['Invalid syntax.']
resp.forms[0]['condition'] = 'True'
resp = resp.forms[0].submit()
item = LinkCell.objects.get()
assert item.condition == 'True'
def test_edit_link_list_order(app, admin_user):
Page.objects.all().delete()
page = Page.objects.create(title='One', slug='one', template_name='standard')
cell = LinkListCell.objects.create(order=0, page=page)
items = []
for i in range(5):
items.append(
LinkCell.objects.create(
page=page,
placeholder=cell.link_placeholder,
title='Foo %s' % i,
url='http://example.net/',
link_page=page,
order=i + 1,
)
)
params = []
new_order = [2, 3, 1, 4, 5]
for item, new_pos in zip(items, new_order):
params.append(('pos_%s' % item.pk, str(new_pos)))
app = login(app)
resp = app.get('/manage/pages/%s/cell/%s/order?%s' % (page.pk, cell.get_reference(), urlencode(params)))
assert resp.status_code == 204
for i, item in enumerate(items):
item.refresh_from_db()
assert item.order == new_order[i]
def test_restricted_page_edit(app, admin_user, john_doe, jane_doe):
group = Group.objects.create(name='page manager')
group.permissions.add(Permission.objects.get(codename='add_page'))
group.permissions.add(Permission.objects.get(codename='change_page'))
jane_doe.groups.set([group])
group = Group.objects.create(name='foobar')
john_doe.groups.set([group])
Page.objects.all().delete()
page1 = Page.objects.create(title='One', slug='one', parent=None, template_name='standard')
page2 = Page.objects.create(title='Two', slug='two', parent=page1, template_name='standard')
page3 = Page.objects.create(title='Three', slug='three', parent=page1, template_name='standard')
page4 = Page.objects.create(title='Four', slug='four', parent=None, template_name='standard')
app = login(app, username='john.doe', password='john.doe')
resp = app.get('/manage/', status=403)
page4.edit_role = group
page4.save()
resp = app.get('/manage/', status=200)
assert [x.attrib['href'] for x in PyQuery(resp.text).find('a[href^="/manage/pages/"]')] == [
'/manage/pages/%s/' % page4.id,
]
app.get('/manage/pages/%s/' % page1.id, status=403)
resp = app.get('/manage/pages/%s/' % page4.id, status=200)
# check all page links are ok
page_links = [x.attrib['href'] for x in PyQuery(resp.text).find('a[href^="/manage/"]')]
for target in page_links:
app.get(target, status=200)
# relogin as admin to get all links
app = login(app)
resp = app.get('/manage/pages/%s/' % page4.id, status=200)
admin_links = [x.attrib['href'] for x in PyQuery(resp.text).find('a[href^="/manage/"]')]
# login as page manager, check it has the same permissions as the admin
app = login(app, username='jane.doe', password='jane.doe')
resp = app.get('/manage/pages/%s/' % page4.id, status=200)
manager_links = [x.attrib['href'] for x in PyQuery(resp.text).find('a[href^="/manage/"]')]
assert manager_links == admin_links
# back to normal user
app = login(app, username='john.doe', password='john.doe')
only_admin_links = [x for x in admin_links if x not in page_links]
for target in only_admin_links:
app.get(target, status=403)
# check some important pages are correctly limited to admins
assert '/manage/pages/%s/delete' % page4.id in only_admin_links
assert '/manage/pages/%s/edit-roles/' % page4.id in only_admin_links
# check combo.apps pages are forbidden
app.get('/manage/pwa/', status=403)
app.get('/manage/maps/', status=403)
app.get('/manage/lingo/', status=403)
# check page can be modified for real, add a cell
resp = app.get('/manage/pages/%s/' % page4.id)
resp = app.get(resp.html.find('option').get('data-add-url'))
cells = CellBase.get_cells(page_id=page4.id)
assert len(cells) == 1
# check it's not possible to add a page
app.get('/manage/pages/add/', status=403)
# give access to children of page1
page1.subpages_edit_role = group
page1.save()
resp = app.get('/manage/', status=200)
assert [x.attrib['href'] for x in PyQuery(resp.text).find('a[href^="/manage/pages/"]')] == [
'/manage/pages/add/',
'/manage/pages/%s/' % page2.id,
'/manage/pages/%s/' % page3.id,
'/manage/pages/%s/' % page4.id,
]
resp = app.get('/manage/pages/%s/' % page2.id)
resp = app.get(resp.html.find('option').get('data-add-url'))
cells = CellBase.get_cells(page_id=page2.id)
assert len(cells) == 1
# add a subpage, the "new page" dialog will have an extra "parent" field.
resp = app.get('/manage/', status=200)
resp = app.get('/manage/pages/add/')
resp.forms[0]['title'].value = 'Foobar'
assert resp.forms[0]['parent'].options == [(str(page1.id), False, 'One')]
resp = resp.forms[0].submit()
page5 = Page.objects.get(slug='foobar')
assert page5.parent_id == page1.id
# check levels are adjusted on index page
resp = app.get('/manage/', status=200)
assert [
(int(x.attrib['data-page-id']), int(x.attrib['data-level']))
for x in PyQuery(resp.text).find('div.page')
] == [
(page2.id, 0),
(page3.id, 0),
(page5.id, 0),
(page4.id, 0),
]
# make page1 editable (-> visible), this will push subpages a level down, but
# the independant page4 will stay at level 0.
page1.edit_role = group
page1.save()
resp = app.get('/manage/', status=200)
assert [
(int(x.attrib['data-page-id']), int(x.attrib['data-level']))
for x in PyQuery(resp.text).find('div.page')
] == [
(page1.id, 0),
(page2.id, 1),
(page3.id, 1),
(page5.id, 1),
(page4.id, 0),
]
def test_redirect_to_page_edit(app, admin_user):
page = Page.objects.create(title='One', slug='one')
page2 = Page.objects.create(title='Two', slug='two', parent=page)
page3 = Page.objects.create(title='User', slug='users', sub_slug='(?P<blah>[a-z]+)')
app = login(app)
resp = app.get('/manage/pages/one/', status=302)
assert resp.location == '/manage/pages/%s/' % page.pk
resp = app.get('/manage/pages/one/two/', status=302)
assert resp.location == '/manage/pages/%s/' % page2.pk
resp = app.get('/manage/pages/users/', status=302)
assert resp.location == '/manage/pages/%s/' % page3.pk
resp = app.get('/manage/pages/users/xxx/', status=302)
assert resp.location == '/manage/pages/%s/' % page3.pk
resp = app.get('/manage/pages/missing/', status=404)
def test_site_settings(app, admin_user):
public_page = Page.objects.create(title='Public', slug='public')
private_page = Page.objects.create(title='Private', slug='private', public=False)
app = login(app)
resp = app.get('/manage/')
resp = resp.click('Site Settings')
resp.form['welcome_page'].select(text='Other:')
resp.form['welcome_page_path'] = '/welcome/'
resp.form['initial_login_page'].select(text='Other:')
resp.form['initial_login_page_path'] = '/initial-login/'
resp.form.submit()
site_settings = SiteSettings.get_singleton()
assert site_settings.welcome_page_path == '/welcome/'
assert site_settings.initial_login_page_path == '/initial-login/'
resp.form['welcome_page_path'] = ''
resp.form['initial_login_page_path'] = ''
resp.form.submit()
site_settings.refresh_from_db()
assert site_settings.welcome_page_path == ''
assert site_settings.initial_login_page_path == ''
assert list(resp.context['form'].fields['welcome_page'].queryset) == [public_page]
assert list(resp.context['form'].fields['initial_login_page'].queryset) == [public_page, private_page]
resp.form['welcome_page'].select(text='Public')
resp.form['initial_login_page'].select(text='Private')
resp.form.submit()
site_settings.refresh_from_db()
assert site_settings.welcome_page == public_page
assert site_settings.initial_login_page == private_page
def test_manager_placeholder_grid(app, admin_user):
page = Page.objects.create(title='Page', slug='page')
cell = TextCell.objects.create(page=page, placeholder='content', text='Foobar', order=1)
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
assert 'fx_size' not in resp.forms[0].fields
resp = app.get('/manage/pages/%s/' % page.id)
resp = resp.click('Options', href=r'placeholder.*options')
resp.form['fx_grid_layout'].select(text='2 columns')
resp = resp.form.submit().follow()
assert page.pagesnapshot_set.latest('pk').comment == 'modified options for "Content" placeholder'
page.refresh_from_db()
assert page.placeholder_options['content']['fx_grid_layout'] == 'fx-grid--t2'
resp = resp.click('Options', href=r'placeholder.*options')
assert resp.form['fx_grid_layout'].value == 'fx-grid--t2'
resp = app.get('/manage/pages/%s/' % page.id)
assert 'fx_size' not in resp.forms[0].fields
resp.forms[0]['cdata_textcell-%s-fx_size' % cell.id].select('size--t1-2')
manager_submit_cell(resp.forms[0])
cell.refresh_from_db()
assert cell.extra_css_class == 'size--t1-2'
assert resp.forms[0]['cdata_textcell-%s-fx_size' % cell.id].value == 'size--t1-2'
assert not resp.forms[0]['cdata_textcell-%s-extra_css_class' % cell.id].value
resp.forms[0]['cdata_textcell-%s-extra_css_class' % cell.id] = 'plop'
resp_sub = manager_submit_cell(resp.forms[0])
assert resp_sub.json['extra_css_class'] == 'plop'
cell.refresh_from_db()
assert set(cell.extra_css_class.split()) == {'plop', 'size--t1-2'}