import datetime import json import os import re import urllib.parse from io import StringIO from unittest import mock import pytest from django.conf import settings from django.contrib.auth.models import Group from django.core.files import File from django.db import connection from django.test import override_settings from django.test.utils import CaptureQueriesContext from django.urls import reverse from django.utils.http import quote try: import mellon # pylint: disable=unused-import except ImportError: mellon = None import requests from combo.apps.assets.models import Asset from combo.data.models import ( ConfigJsonCell, FeedCell, JsonCell, LinkCell, LinkListCell, Page, ParentContentCell, Redirect, SiteSettings, TextCell, ) from combo.profile.models import Profile from .test_manager import login pytestmark = pytest.mark.django_db def test_missing_index(app): Page.objects.all().delete() resp = app.get('/', status=200) assert 'Welcome' in resp.text def test_index(app): Page.objects.all().delete() page = Page(title='Home', slug='index', template_name='standard') page.save() resp = app.get('/index', status=301) assert urllib.parse.urlparse(resp.location).path == '/' resp = app.get('/', status=200) # check {% now %} inside a skeleton_extra_placeholder is interpreted assert str(datetime.datetime.now().year) in resp.text def test_page_contents(app): Page.objects.all().delete() page = Page(title='Home', slug='index', template_name='standard') page.save() cell = TextCell(page=page, placeholder='content', text='Foobar', order=0) cell.save() resp = app.get('/', status=200) assert 'Foobar' in resp.text def test_page_contents_unlogged_only(app, admin_user): Page.objects.all().delete() page = Page(title='Home', slug='index', template_name='standard') page.save() cell = TextCell(page=page, placeholder='content', text='Foobar', order=0, restricted_to_unlogged=True) cell.save() resp = app.get('/', status=200) assert 'Foobar' in resp.text app = login(app) resp = app.get('/', status=200) assert not 'Foobar' in resp.text @pytest.mark.skipif('mellon is None') def test_mellon_login(app): with mock.patch('combo.public.views.get_idps') as get_idps: get_idps.return_value = ['xxx'] resp = app.get('/login/') assert urllib.parse.urlparse(resp.location).path == '/accounts/mellon/login/' resp = app.get('/login/?next=whatever') assert urllib.parse.urlparse(resp.location).query == 'next=whatever' def test_page_contents_group_presence(app, normal_user): group = Group(name='plop') group.save() Page.objects.all().delete() page = Page(title='Home', slug='index', template_name='standard') page.save() cell = TextCell(page=page, placeholder='content', text='Foobar', order=0, public=False) cell.save() cell.groups.set([group]) resp = app.get('/', status=200) assert 'Foobar' not in resp.text app = login(app, username='normal-user', password='normal-user') resp = app.get('/', status=200) assert 'Foobar' not in resp.text normal_user.groups.set([group]) resp = app.get('/', status=200) assert 'Foobar' in resp.text def test_page_contents_group_absence(app, normal_user): group = Group(name='plop') group.save() Page.objects.all().delete() page = Page(title='Home', slug='index', template_name='standard') page.save() cell = TextCell( page=page, placeholder='content', text='Foobar', order=0, public=False, restricted_to_unlogged=True ) cell.save() cell.groups.set([group]) resp = app.get('/', status=200) assert 'Foobar' not in resp.text app = login(app, username='normal-user', password='normal-user') resp = app.get('/', status=200) assert 'Foobar' in resp.text normal_user.groups.set([group]) resp = app.get('/', status=200) assert 'Foobar' not in resp.text def test_page_contents_condition(app, normal_user): page = Page.objects.create(title='Page', slug='page', template_name='standard') cell1 = TextCell.objects.create( page=page, placeholder='content', text='Foobar', order=0, condition='True' ) cell2 = TextCell.objects.create( page=page, placeholder='content', text='Foobaz', order=1, condition='False' ) cell3 = LinkListCell.objects.create(order=1, page=page, placeholder='content') LinkCell.objects.create( page=page, placeholder=cell3.link_placeholder, title='Example Site', url='http://example.net/', order=0, condition='True', ) LinkCell.objects.create( page=page, placeholder=cell3.link_placeholder, title='Other Site', url='http://other.net/', order=1, condition='False', ) app = login(app, username='normal-user', password='normal-user') resp = app.get(page.get_online_url()) assert 'Foobar' in resp.text assert 'Foobaz' not in resp.text assert 'Example Site' in resp.text assert 'Other Site' not in resp.text page.sub_slug = 'foo_id' page.save() cell1.condition = 'foo_id' cell1.save() cell2.condition = 'not foo_id' cell2.save() resp = app.get(page.get_online_url() + '11/') assert 'Foobar' in resp.text assert 'Foobaz' not in resp.text def test_page_footer_acquisition(app): Page.objects.all().delete() page = Page(title='Home', slug='index', template_name='standard') page.save() page_index = page cell = TextCell(page=page, placeholder='footer', text='BARFOO', order=0) cell.save() resp = app.get('/', status=200) assert resp.text.count('BARFOO') == 1 # make sure a parent content cell in the home page doesn't duplicate # cells ParentContentCell(page=page, placeholder='footer', order=0).save() resp = app.get('/', status=200) assert resp.text.count('BARFOO') == 1 page = Page(title='Second', slug='second', template_name='standard') page.save() ParentContentCell(page=page, placeholder='footer', order=0).save() TextCell(page=page, placeholder='footer', text='BAR2FOO', order=1).save() resp = app.get('/second', status=301) assert urllib.parse.urlparse(resp.location).path == '/second/' with CaptureQueriesContext(connection) as ctx: resp = app.get('/second/', status=200) assert resp.text.count('BARFOO') == 1 assert resp.text.count('BAR2FOO') == 1 queries_count_second = len(ctx.captured_queries) # deeper hierarchy page3 = Page(title='Third', slug='third', template_name='standard', parent=page) page3.save() ParentContentCell(page=page3, placeholder='footer', order=0).save() page4 = Page(title='Fourth', slug='fourth', template_name='standard', parent=page3) page4.save() ParentContentCell(page=page4, placeholder='footer', order=0).save() page5 = Page(title='Fifth', slug='fifth', template_name='standard', parent=page4) page5.save() ParentContentCell(page=page5, placeholder='footer', order=0).save() # check growth in SQL queries is limited with CaptureQueriesContext(connection) as ctx: resp = app.get('/second/third/', status=200) assert resp.text.count('BARFOO') == 1 assert resp.text.count('BAR2FOO') == 1 queries_count_third = len(ctx.captured_queries) # +2 for validity info of parent page assert queries_count_third == queries_count_second + 1 with CaptureQueriesContext(connection) as ctx: resp = app.get('/second/third/fourth/', status=200) assert resp.text.count('BARFOO') == 1 assert resp.text.count('BAR2FOO') == 1 queries_count_fourth = len(ctx.captured_queries) # +1 for get_parents_and_self() assert queries_count_fourth == queries_count_second + 1 + 1 # check footer doesn't get duplicated in real index children page6 = Page(title='Sixth', slug='sixth', template_name='standard', parent=page_index) page6.save() ParentContentCell(page=page6, placeholder='footer', order=0).save() resp = app.get('/sixth/', status=200) assert resp.text.count('BARFOO') == 1 def test_list_of_links_acquisition(app): Page.objects.all().delete() index_page = Page(title='Home', slug='index', template_name='standard') index_page.save() cell = LinkListCell.objects.create(order=0, page=index_page, placeholder='footer') LinkCell.objects.create( page=index_page, placeholder=cell.link_placeholder, title='Example Site', url='http://example.net/', order=0, ) LinkCell.objects.create( page=index_page, placeholder=cell.link_placeholder, title='Example2 Site', url='http://example.org/', order=1, ) resp = app.get('/', status=200) assert resp.text.count('Example Site') == 1 assert resp.text.count('Example2 Site') == 1 page = Page(title='Second', slug='second', template_name='standard') page.save() ParentContentCell(page=page, placeholder='footer', order=0).save() resp = app.get('/second/', status=200) assert resp.text.count('Example Site') == 1 assert resp.text.count('Example2 Site') == 1 def test_page_redirect(app): Page.objects.all().delete() page = Page( title='Elsewhere', slug='elsewhere', template_name='standard', redirect_url='http://example.net' ) page.save() resp = app.get('/elsewhere/', status=302) assert resp.location == 'http://example.net' def test_page_templated_redirect(app): Page.objects.all().delete() with override_settings(TEMPLATE_VARS={'test_url': 'http://example.net'}): page = Page(title='Elsewhere', slug='elsewhere', template_name='standard', redirect_url='[test_url]') page.save() resp = app.get('/elsewhere/', status=302) assert resp.location == 'http://example.net' page.redirect_url = '{{test_url}}/ok' page.save() resp = app.get('/elsewhere/', status=302) assert resp.location == 'http://example.net/ok' page.redirect_url = '{{unknown_variable}}' page.save() resp = app.get('/elsewhere/', status=200) page.redirect_url = '{% if error %}' page.save() resp = app.get('/elsewhere/', status=404) def test_page_private_unlogged(app): Page.objects.all().delete() page = Page(title='Home', slug='index', template_name='standard', public=False) page.save() resp = app.get('/', status=302) assert resp.location.endswith('/login/?next=http%3A//testserver/') def test_page_private_logged_in(app, admin_user): Page.objects.all().delete() page = Page(title='Home', slug='index', template_name='standard', public=False) page.save() app = login(app) app.get('/', status=200) def test_page_skeleton(app): Page.objects.all().delete() page = Page( title='Elsewhere', slug='elsewhere', template_name='standard', redirect_url='http://example.net/foo/' ) page.save() # url prefix match resp = app.get('/__skeleton__/?source=%s' % quote('http://example.net/foo/bar')) assert '{% block placeholder-content %}{% block content %}{% endblock %}{% endblock %}' in resp.text assert '{% block placeholder-footer %}{% block footer %}{% endblock %}{% endblock %}' in resp.text assert resp.text.startswith('{% with page_template_name="standard" %}\n') # url netloc match resp = app.get('/__skeleton__/?source=%s' % quote('http://example.net')) assert '{% block placeholder-content %}{% block content %}{% endblock %}{% endblock %}' in resp.text assert '{% block placeholder-footer %}{% block footer %}{% endblock %}{% endblock %}' in resp.text # settings.KNOWN_SERVICES match resp = app.get('/__skeleton__/?source=%s' % quote('http://127.0.0.1:8999/')) assert '{% block placeholder-content %}{% block content %}{% endblock %}{% endblock %}' in resp.text # no match resp = app.get('/__skeleton__/?source=%s' % quote('http://example.com/foo/bar'), status=403) # check with a footer cell TextCell.objects.create(page=page, placeholder='footer', text='Foobar', order=0) TextCell.objects.create(page=page, placeholder='content', text='Foobar', slug='unique', order=1) TextCell.objects.create(page=page, placeholder='content', text='Foobar', slug='dup', order=2) TextCell.objects.create(page=page, placeholder='content', text='Foobar', slug='dup', order=3) resp = app.get('/__skeleton__/?source=%s' % quote('http://example.net')) assert '{% block placeholder-content %}{% block content %}{% endblock %}{% endblock %}' in resp.text assert '{% block placeholder-footer %}{% block footer %}{% endblock %}{% endblock %}' in resp.text assert 'Foobar' in resp.text # check slugs assert 'id="unique"' in resp.text assert 'id="dup"' not in resp.text # check {% now %} inside a skeleton_extra_placeholder is not interpreted assert '{% now ' in resp.text assert '{# generation time #}' in resp.text # check cells in footer are present even if there's no redirection page page.slug = 'index' page.save() resp = app.get('/__skeleton__/?source=%s' % quote('http://127.0.0.1:8999/')) assert '{% block placeholder-content %}{% block content %}{% endblock %}{% endblock %}' in resp.text assert '{% block placeholder-footer %}{% block footer %}{% endblock %}{% endblock %}' in resp.text assert 'Foobar' in resp.text # check link cells provide a full URL other_page = Page(title='Plop', slug='plop', template_name='standard') other_page.save() cell = LinkCell(page=page, placeholder='footer', link_page=other_page, order=0) cell.save() resp = app.get('/__skeleton__/?source=%s' % quote('http://127.0.0.1:8999/')) assert 'http://testserver/plop' in resp.text # check images in text cell use full URL cell = TextCell(page=page, placeholder='footer', order=0) cell.text = ' vs ' cell.save() resp = app.get('/__skeleton__/?source=%s' % quote('http://127.0.0.1:8999/')) assert 'http://testserver/test/foobar.png' in resp.text # check absolute URIs are not modified assert 'src="http://www.example.com/test.png"' in resp.text # check links in text cell use full URL (unless anchors and verbatim) cell.text = ''' vs vs' ''' cell.save() with override_settings(TEMPLATE_VARS={'test_url': 'http://example.net'}): resp = app.get('/__skeleton__/?source=%s' % quote('http://127.0.0.1:8999/')) assert 'href="http://testserver/test/foobar"' in resp.text assert 'href="http://www.example.com/test"' in resp.text assert 'href="#top"' in resp.text assert 'href="http://example.net"' in resp.text assert 'href="{{ registration_url }}"' in resp.text # add a bad redirection page (don't use it, do not crash) page = Page(title='BadRedirection', slug='badredir', template_name='standard', redirect_url='[foo_bar]') page.save() resp = app.get('/__skeleton__/?source=%s' % quote('http://example.net/foo/bar')) resp = app.get('/__skeleton__/?source=%s' % quote('http://example.net/badredir')) # add a page with restricted visibility page = Page.objects.create( title='RestrictedVisibility', slug='restrictedvisibilit', template_name='standard', public=False, exclude_from_navigation=False, ) resp = app.get('/__skeleton__/?source=%s' % quote('http://127.0.0.1:8999/')) assert 'RestrictedVisibility' in resp.text # check 404 skeleton resp = app.get('/__skeleton__/?source=404') assert "This page doesn't exist" in resp.text assert resp.status_code == 200 assert 'href="http://testserver/test/foobar"' in resp.text assert 'href="http://www.example.com/test"' in resp.text assert 'href="#top"' in resp.text assert 'href="http://testserver/plop/"' in resp.text # do not consider empty redirection Page.objects.all().delete() page = Page( title='EmptyRedirection', slug='emptyredir', template_name='standard', redirect_url='{{ is_empty }}' ) page.save() resp = app.get('/__skeleton__/?source=%s' % quote('http://127.0.0.1:8999/')) assert resp.headers['x-combo-page-id'] == '__root' # prefer last match Page.objects.all().delete() page = Page( title='Elsewhere', slug='elsewhere', template_name='standard', redirect_url='http://example.net/foo/' ) page.save() cell = TextCell(page=page, placeholder='footer', text='Foobar1', order=0) cell.save() page = Page( title='Elsewhere2', slug='elsewhere2', template_name='standard', redirect_url='http://example.net/foo/', order=2, ) page.save() cell = TextCell(page=page, placeholder='footer', text='Foobar2', order=0) cell.save() resp = app.get('/__skeleton__/?source=%s' % quote('http://example.net/foo/bar')) assert "Foobar2" in resp.text def test_subpage_location(app): Page.objects.all().delete() page_index = Page(title='Home Page', slug='index', template_name='standard') page_index.save() page_sibling = Page(title='Second top level page', slug='second', template_name='standard') page_sibling.save() page = Page(title='Child of Home', slug='child-home', template_name='standard', parent=page_index) page.save() page = Page(title='Grand child of home', slug='grand-child-home', template_name='standard', parent=page) page.save() page = Page(title='Child of second', slug='child-second', template_name='standard', parent=page_sibling) page.save() page = Page( title='Grand child of second', slug='grand-child-second', template_name='standard', parent=page ) page.save() resp = app.get('/', status=200) assert 'Home Page' in resp.text resp = app.get('/child-home/', status=200) assert 'Child of Home' in resp.text resp = app.get('/child-home/grand-child-home/', status=200) assert 'Grand child of home' in resp.text assert ( urllib.parse.urlparse(app.get('/child-home/grand-child-home', status=301).location).path == '/child-home/grand-child-home/' ) app.get('/grand-child-home/', status=404) resp = app.get('/second/', status=200) assert 'Second top level page' in resp.text resp = app.get('/second/child-second/', status=200) assert 'Child of second' in resp.text resp = app.get('/second/child-second/grand-child-second/', status=200) assert 'Grand child of second' in resp.text def test_repeated_slug(app): Page.objects.all().delete() page1 = Page(title='Home Page', slug='index', template_name='standard') page1.save() page2 = Page(title='Second top level page', slug='second', template_name='standard') page2.save() page3 = Page(title='Child of second', slug='child-second', template_name='standard', parent=page2) page3.save() page4 = Page( title='Grand child of second (named third)', slug='third', template_name='standard', parent=page3 ) page4.save() page5 = Page(title='Third top level page', slug='third', template_name='standard') page5.save() for i, page in enumerate((page1, page2, page3, page4, page5)): page.order = i page.save() resp = app.get('/third/', status=200) assert 'Third top level page' in resp.text resp = app.get('/second/child-second/third/', status=200) assert 'Grand child of second (named third)' in resp.text # reverse page order, and expect same behaviour for i, page in enumerate(reversed((page1, page2, page3, page4, page5))): page.order = i page.save() resp = app.get('/third/', status=200) assert 'Third top level page' in resp.text resp = app.get('/second/child-second/third/', status=200) assert 'Grand child of second (named third)' in resp.text def test_menu(app): Page.objects.all().delete() page = Page.objects.create( title='Page1', slug='index', template_name='standard', exclude_from_navigation=False ) Page.objects.create(title='Page2', slug='page2', template_name='standard', exclude_from_navigation=False) Page.objects.create( title='Page3', slug='page3', template_name='standard', public=False, exclude_from_navigation=False ) resp = app.get('/', status=200) assert 'menu-index' in resp.text assert 'data-menu-page-id="%s"' % page.id assert 'menu-page2' in resp.text assert 'menu-page3' in resp.text def test_404(app): Page.objects.all().delete() resp = app.get('/foobar/', status=404) assert "This page doesn't exist" in resp.text # check footer cells are taken from homepage index_page = Page(title='Home', slug='index', template_name='standard') index_page.save() cell = TextCell(page=index_page, placeholder='footer', text='FOOBAR', order=0) cell.save() resp = app.get('/foobar/', status=404) assert "This page doesn't exist" in resp.text assert "FOOBAR" in resp.text index_page.delete() # check decidated custom page page = Page(title='Not Found', slug='404', template_name='standard') page.save() TextCell(page=page, placeholder='content', text='Custom 404 Text', order=0).save() resp = app.get('/foobar/', status=404) assert 'Custom 404 Text' in resp.text with override_settings(DEBUG=True): # check error page provides an hint when debugging resp = app.get('/foobar/', status=404) assert "can't find the requested page" in resp.text # check native django handler is used if all pages are private page.public = False page.save() resp = app.get('/foobar/', status=404) assert 'Custom 404 Text' not in resp.text assert "This page doesn't exist" not in resp.text # check access using its /404/ slug resp = app.get('/404', status=404) # check 404 skeleton when all pages are private resp = app.get('/__skeleton__/?source=404') assert 'Custom 404 Text' not in resp.text assert "This page doesn't exist" not in resp.text assert resp.status_code == 200 def test_style_demo(app, admin_user): TextCell.objects.all().delete() Page.objects.all().delete() app = login(app) with override_settings(DEBUG=True): resp = app.get('/__style__/', status=200) assert 'class="success"' not in resp.text assert 'class="info"' not in resp.text assert 'class="warning"' not in resp.text assert 'class="error"' not in resp.text assert 'Lorem ipsum' in resp.text assert TextCell.objects.count() == 0 assert Page.objects.count() == 0 resp = app.get('/__style__/?template=standard-sidebar&with_messages=on', status=200) assert 'class="success"' in resp.text assert 'class="info"' in resp.text assert 'class="warning"' in resp.text assert 'class="error"' in resp.text assert 'Lorem ipsum' in resp.text assert 'links-list' in resp.text assert Page.objects.count() == 0 def test_page_async_cell(app, nocache): Page.objects.all().delete() page = Page(title='Home', slug='index', template_name='standard') page.save() cell = FeedCell(page=page, placeholder='content', url='http://example.net', order=1) cell.save() resp = app.get('/', status=200) assert 'data-ajax-cell-must-load="true"' in resp.text def test_ajax_cell(app): Page.objects.all().delete() page = Page(title='Home', slug='index', template_name='standard') page.save() page2 = Page(title='Other', slug='other', template_name='standard') page2.save() cell = TextCell(page=page, placeholder='content', text='

Foobar

', order=0) cell.save() resp = app.get( reverse( 'combo-public-ajax-page-cell', kwargs={'page_pk': page.id, 'cell_reference': cell.get_reference()} ) ) assert resp.text.strip() == '

Foobar

' resp = app.get( reverse( 'combo-public-ajax-page-cell', kwargs={'page_pk': page2.id, 'cell_reference': cell.get_reference()}, ), status=404, ) resp = app.get( reverse( 'combo-public-ajax-page-cell', kwargs={'page_pk': '100', 'cell_reference': cell.get_reference()} ), status=404, ) page.public = False page.save() resp = app.get( reverse( 'combo-public-ajax-page-cell', kwargs={'page_pk': page.id, 'cell_reference': cell.get_reference()} ), status=403, ) page.public = True page.save() cell.public = False cell.save() resp = app.get('/', status=200) assert not 'FOOBAR' in resp.text resp = app.get( reverse( 'combo-public-ajax-page-cell', kwargs={'page_pk': page.id, 'cell_reference': cell.get_reference()} ), status=403, ) def test_initial_login_page(app, admin_user): Page.objects.all().delete() page = Page(title='Home', slug='index', template_name='standard') page.save() page = Page(title='Initial Login', slug='initial-login', template_name='standard', public=False) page.save() site_settings = SiteSettings.get_singleton() site_settings.initial_login_page_path = '/initial-login/' site_settings.save() resp = app.get('/', status=200) # first visit app = login(app) resp = app.get('/', status=302) assert urllib.parse.urlparse(resp.location).path == '/initial-login/' # visit again resp = app.get('/', status=200) # delete initial login info profile = Profile.objects.get(user=admin_user) profile.initial_login_view_timestamp = None profile.save(update_fields=['initial_login_view_timestamp']) with override_settings( TEMPLATE_VARS={ 'portal_url': settings.SITE_BASE_URL, 'idp_url': settings.KNOWN_SERVICES['authentic']['idp']['url'], } ): # add templated login page path site_settings.initial_login_page_path = '{{portal_url}}/foo/' site_settings.save(update_fields=['initial_login_page_path']) # "first" visit app = login(app) resp = app.get('/', status=302) assert resp.location == 'http://localhost/foo/' # delete initial login info, again profile = Profile.objects.get(user=admin_user) profile.initial_login_view_timestamp = None profile.save(update_fields=['initial_login_view_timestamp']) # add templated login page path to idp url site_settings.initial_login_page_path = '{{idp_url}}/bar/' site_settings.save(update_fields=['initial_login_page_path']) # "first" visit app = login(app) resp = app.get('/', status=302) assert resp.location == 'https://authentic.example.org/bar/' profile.initial_login_view_timestamp = None profile.save(update_fields=['initial_login_view_timestamp']) site_settings.initial_login_page = page site_settings.save() # first visit app = login(app) resp = app.get('/', status=302) assert urllib.parse.urlparse(resp.location).path == '/initial-login/' # visit again resp = app.get('/', status=200) def test_welcome_page(app, admin_user): Page.objects.all().delete() page = Page(title='Home', slug='index', template_name='standard') page.save() page = Page(title='Welcome', slug='welcome', template_name='standard') page.save() site_settings = SiteSettings.get_singleton() site_settings.welcome_page_path = '/welcome/' site_settings.save() app.cookiejar.clear() resp = app.get('/', status=302) assert urllib.parse.urlparse(resp.location).path == '/welcome/' resp = app.get('/', status=200) app.cookiejar.clear() resp = app.get('/', status=302) assert urllib.parse.urlparse(resp.location).path == '/welcome/' app.cookiejar.clear() app = login(app) resp = app.get('/', status=200) with override_settings( TEMPLATE_VARS={ 'portal_url': settings.SITE_BASE_URL, 'idp_url': settings.KNOWN_SERVICES['authentic']['idp']['url'], } ): # add templated welcome page path site_settings.welcome_page_path = '{{portal_url}}/foo/' site_settings.save(update_fields=['welcome_page_path']) app.cookiejar.clear() resp = app.get('/', status=302) assert resp.location == 'http://localhost/foo/' # add templated login page path to idp url site_settings.welcome_page_path = '{{idp_url}}/bar/' site_settings.save(update_fields=['welcome_page_path']) app.cookiejar.clear() resp = app.get('/', status=302) assert resp.location == 'https://authentic.example.org/bar/' site_settings.welcome_page = page site_settings.save() app.cookiejar.clear() resp = app.get('/', status=302) assert urllib.parse.urlparse(resp.location).path == '/welcome/' resp = app.get('/', status=200) def test_post_cell(app): Page.objects.all().delete() page = Page(title='Home', slug='index', template_name='standard') page.save() cell = TextCell(page=page, placeholder='content', text='

Foobar

', order=0) cell.save() # check it's not possible to post to cell that doesn't have POST support. resp = app.post( reverse( 'combo-public-ajax-page-cell', kwargs={'page_pk': page.id, 'cell_reference': cell.get_reference()} ), params={'hello': 'world'}, status=403, ) 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-post-cell': { 'name': 'Foobar', 'url': 'http://test-post-cell/{{slug}}/', 'form': [ {'label': 'slug', 'varname': 'slug', 'required': True}, ], 'actions': { 'create': { 'url': 'http://test-post-cell/{{slug}}/create/', }, 'update': { 'url': 'http://test-post-cell/{{slug}}/update/', 'method': 'PATCH', }, 'search': { 'url': 'http://test-post-cell/{{slug}}/search/', 'method': 'GET', 'response': 'raw', }, 'download': { 'url': 'http://test-post-cell/{{slug}}/download/', 'method': 'GET', 'response': 'raw', }, }, } }, TEMPLATES=templates_settings, ): cell = ConfigJsonCell(page=page, placeholder='content', order=0) cell.key = 'test-post-cell' cell.parameters = {'slug': 'slug'} cell.save() with mock.patch('combo.utils.requests.get') as requests_get: requests_get.return_value = mock.Mock(content=json.dumps({'hello': 'world'}), status_code=200) resp = app.get('/', status=200) resp.form['value'] = 'plop' resp.form.fields['items[]'][0].checked = True with mock.patch('combo.utils.requests.request') as requests_post: requests_post.return_value = mock.Mock(content=json.dumps({'err': 0}), status_code=200) resp2 = resp.form.submit() assert requests_post.call_args[0][0] == 'POST' assert requests_post.call_args[0][1] == 'http://test-post-cell/slug/create/' assert requests_post.call_args[1]['json'] == {'value': 'plop', 'items': ['1']} assert urllib.parse.urlparse(resp2.location).path == '/' # check ajax call with mock.patch('combo.utils.requests.request') as requests_post: requests_post.return_value = mock.Mock(content=json.dumps({'err': 0}), status_code=200) resp2 = resp.form.submit(headers={'x-requested-with': 'XMLHttpRequest'}) assert requests_post.call_args[0][0] == 'POST' assert requests_post.call_args[0][1] == 'http://test-post-cell/slug/create/' assert requests_post.call_args[1]['json'] == {'value': 'plop', 'items': ['1']} assert resp2.text.startswith(', it will add the user to context for sub_slug in ['(?P[0-9a-z]+)', 'user_id']: page2.sub_slug = sub_slug page2.save() cell.template_string = 'XX{{ selected_user.username }}YY' cell.save() resp = app.get('/users/%s/' % john_doe.id, status=200) assert 'XXjohn.doeYY' in resp.text # bad user id => no selected_user resp = app.get('/users/9999999/', status=200) assert 'XXYY' in resp.text resp = app.get('/users/abc/', status=200) assert 'XXYY' in resp.text # custom behaviour for , it will add the SAML user to context with mock.patch('combo.profile.utils.UserSAMLIdentifier') as user_saml: class DoesNotExist(Exception): pass user_saml.DoesNotExist = DoesNotExist def side_effect(*args, **kwargs): name_id = kwargs['name_id'] if name_id == 'foo': raise user_saml.DoesNotExist return mock.Mock(user=john_doe) mocked_objects = mock.Mock() mocked_objects.get = mock.Mock(side_effect=side_effect) user_saml.objects = mocked_objects page2.sub_slug = '(?P[0-9a-z.]+)' page2.save() resp = app.get('/users/john.doe/', status=200) assert 'XXjohn.doeYY' in resp.text # unknown name id => no selected_user resp = app.get('/users/foo/', status=200) assert 'XXYY' in resp.text # sub_slug can contain '-' (if card slug for example) page3 = Page.objects.create(title='Card Foo', slug='foo', template_name='standard') for sub_slug in ['(?P[0-9]+)', 'card-foo-bar_id']: page3.sub_slug = sub_slug page3.save() resp = app.get('/foo/42/', status=200) assert resp.context['card-foo-bar_id'] == '42' assert resp.context['card_foo_bar_id'] == '42' def test_page_extra_variables(app): page = Page.objects.create( title='Home', slug='page', template_name='standard', extra_variables={ 'foo': 'bar', 'bar_id': '{{ 40|add:2 }}', 'syntax_error': '{% for %}', 'variable_error': '{{ "foo"|add:user.email }}', }, ) cell = JsonCell.objects.create( page=page, url='http://example.net', order=0, placeholder='content', template_string='XX{{ foo }}YY{{ bar_id }}ZZ{{ baz_user }}AA{{ syntax_error }}BB{{ variable_error }}CC', ) with mock.patch('combo.utils.requests.get') as requests_get: requests_get.return_value = mock.Mock(content='{}', status_code=200) resp = app.get('/page/') assert '
XXbarYY42ZZAABBCC
' in resp with mock.patch('combo.utils.requests.get') as requests_get: requests_get.return_value = mock.Mock(content='{}', status_code=200) resp = app.get( reverse( 'combo-public-ajax-page-cell', kwargs={'page_pk': page.pk, 'cell_reference': cell.get_reference()}, ) ) assert resp.text.strip() == 'XXbarYY42ZZAABBCC' # check sub_slug/extra_variables override page.sub_slug = '(?P[a-z]+)' page.save() cell.template_string = 'XX{{ foo }}YY{{ bar_id }}ZZ{{ fooo }}AA' cell.save() with mock.patch('combo.utils.requests.get') as requests_get: requests_get.return_value = mock.Mock(content='{}', status_code=200) resp = app.get('/page/baz/') assert '
XXbarYY42ZZbazAA
' in resp page.sub_slug = '(?P[a-z]+)' page.save() with mock.patch('combo.utils.requests.get') as requests_get: requests_get.return_value = mock.Mock(content='{}', status_code=200) resp = app.get('/page/baz/') assert '
XXbarYY42ZZAA
' in resp def test_cell_slugs(app): Page.objects.all().delete() page = Page(title='Home', slug='index', template_name='standard') page.save() TextCell(page=page, placeholder='content', text='Foobar', order=0).save() TextCell(page=page, placeholder='content', text='Foobar', slug='unique', order=1).save() TextCell(page=page, placeholder='content', text='Foobar', slug='dup', order=2).save() TextCell(page=page, placeholder='content', text='Foobar', slug='dup', order=3).save() resp = app.get('/', status=200) assert 'id="unique"' in resp.text assert 'id="dup"' not in resp.text def test_missing_trailing_slashes(app): # redirect to path with slash assert urllib.parse.urlparse(app.get('/login', status=301).location).path == '/login/' assert urllib.parse.urlparse(app.get('/foo', status=301).location).path == '/foo/' # don't be tricked by double slashes assert urllib.parse.urlparse(app.get('//foo', status=301).location).path == '/foo/' def test_cell_asset_css_classes(settings, app, admin_user): settings.COMBO_CELL_ASSET_SLOTS = {} page = Page.objects.create(title='Home', slug='index', template_name='standard') cell = TextCell.objects.create(page=page, placeholder='content', order=0, slug='foo', text='foo') # test direct access assert cell.asset_css_classes == '' # and test asset preload resp = app.get('/', status=200) assert 'class="cell text-cell textcell foo"' in re.sub(r' +', ' ', resp.text) settings.COMBO_CELL_ASSET_SLOTS = {'data_textcell': {'picture': {'prefix': 'Picture'}}} cell = TextCell.objects.get(pk=cell.pk) assert cell.asset_css_classes == '' resp = app.get('/', status=200) assert 'class="cell text-cell textcell foo"' in re.sub(r' +', ' ', resp.text) Asset.objects.create(key=cell.get_asset_slot_key('picture'), asset=File(StringIO('test'), 'test.png')) cell = TextCell.objects.get(pk=cell.pk) assert cell.asset_css_classes == 'has-asset-picture has-any-asset has-all-assets' resp = app.get('/', status=200) assert 'class="cell text-cell textcell has-asset-picture has-any-asset has-all-assets foo"' in re.sub( r' +', ' ', resp.text ) settings.COMBO_CELL_ASSET_SLOTS = { 'data_textcell': {'picture': {'prefix': 'Picture'}, 'foo': {'prefix': 'Foo'}} } cell = TextCell.objects.get(pk=cell.pk) assert cell.asset_css_classes == 'has-asset-picture has-any-asset' resp = app.get('/', status=200) assert 'class="cell text-cell textcell has-asset-picture has-any-asset foo"' in re.sub( r' +', ' ', resp.text ) Asset.objects.create(key=cell.get_asset_slot_key('foo'), asset=File(StringIO('test'), 'test.png')) cell = TextCell.objects.get(pk=cell.pk) assert cell.asset_css_classes == 'has-asset-foo has-asset-picture has-any-asset has-all-assets' resp = app.get('/', status=200) assert ( 'class="cell text-cell textcell has-asset-foo has-asset-picture has-any-asset has-all-assets foo"' in re.sub(r' +', ' ', resp.text) ) Asset.objects.all().delete() cell = TextCell.objects.get(pk=cell.pk) assert cell.asset_css_classes == '' resp = app.get('/', status=200) assert 'class="cell text-cell textcell foo"' in re.sub(r' +', ' ', resp.text) def test_placeholder_grid(app): Page.objects.all().delete() page = Page(title='Home', slug='index', template_name='standard') page.save() TextCell.objects.create(page=page, placeholder='content', text='Foobar', order=1) resp = app.get('/', status=200) # no placeholder div if there's no grid layout assert resp.pyquery('#content > .text-cell') page.placeholder_options = {'content': {'fx_grid_layout': 'fx-grid'}} page.save() resp = app.get('/', status=200) # placeholder div in between assert resp.pyquery('#content > .fx-grid > .text-cell')