diff --git a/wagtail/wagtailadmin/tests/test_account_management.py b/wagtail/wagtailadmin/tests/test_account_management.py
index 95d54d7d9..3553b8fba 100644
--- a/wagtail/wagtailadmin/tests/test_account_management.py
+++ b/wagtail/wagtailadmin/tests/test_account_management.py
@@ -1,17 +1,17 @@
from django.test import TestCase
-from wagtail.tests.utils import login, unittest
+from wagtail.tests.utils import unittest, WagtailTestUtils
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.core import mail
-class TestAuthentication(TestCase):
+class TestAuthentication(TestCase, WagtailTestUtils):
"""
This tests that users can login and logout of the admin interface
"""
def setUp(self):
- login(self.client)
+ self.login()
def test_login_view(self):
"""
@@ -44,12 +44,12 @@ class TestAuthentication(TestCase):
# Check that the user was redirected to the dashboard
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailadmin_home'))
# Check that the user was logged in
self.assertTrue('_auth_user_id' in self.client.session)
self.assertEqual(self.client.session['_auth_user_id'], User.objects.get(username='test').id)
- @unittest.expectedFailure # See: https://github.com/torchbox/wagtail/issues/25
def test_already_logged_in_redirect(self):
"""
This tests that a user who is already logged in is automatically
@@ -61,27 +61,63 @@ class TestAuthentication(TestCase):
# Check that the user was redirected to the dashboard
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailadmin_home'))
def test_logout(self):
"""
This tests that the user can logout
"""
- # Get logout page page
+ # Get logout page
response = self.client.get(reverse('wagtailadmin_logout'))
# Check that the user was redirected to the login page
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailadmin_login'))
# Check that the user was logged out
self.assertFalse('_auth_user_id' in self.client.session)
+ def test_not_logged_in_redirect(self):
+ """
+ This tests that a not logged in user is redirected to the
+ login page
+ """
+ # Logout
+ self.client.logout()
-class TestAccountSection(TestCase):
+ # Get dashboard
+ response = self.client.get(reverse('wagtailadmin_home'))
+
+ # Check that the user was redirected to the login page and that next was set correctly
+ self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailadmin_login') + '?next=' + reverse('wagtailadmin_home'))
+
+ def test_not_logged_in_redirect_default_settings(self):
+ """
+ This does the same as the above test but checks that it
+ redirects to the correct place when the user has not set
+ the LOGIN_URL setting correctly
+ """
+ # Logout
+ self.client.logout()
+
+ # Get dashboard with default LOGIN_URL setting
+ with self.settings(LOGIN_URL='django.contrib.auth.views.login'):
+ response = self.client.get(reverse('wagtailadmin_home'))
+
+ # Check that the user was redirected to the login page and that next was set correctly
+ # Note: The user will be redirected to 'django.contrib.auth.views.login' but
+ # this must be the same URL as 'wagtailadmin_login'
+ self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailadmin_login') + '?next=' + reverse('wagtailadmin_home'))
+
+
+class TestAccountSection(TestCase, WagtailTestUtils):
"""
This tests that the accounts section is working
"""
def setUp(self):
- login(self.client)
+ self.login()
def test_account_view(self):
"""
@@ -117,8 +153,9 @@ class TestAccountSection(TestCase):
}
response = self.client.post(reverse('wagtailadmin_account_change_password'), post_data)
- # Check that the user was redirected
+ # Check that the user was redirected to the account page
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailadmin_account'))
# Check that the password was changed
self.assertTrue(User.objects.get(username='test').check_password('newpassword'))
@@ -146,7 +183,7 @@ class TestAccountSection(TestCase):
self.assertTrue(User.objects.get(username='test').check_password('password'))
-class TestPasswordReset(TestCase):
+class TestPasswordReset(TestCase, WagtailTestUtils):
"""
This tests that the password reset is working
"""
@@ -176,8 +213,9 @@ class TestPasswordReset(TestCase):
}
response = self.client.post(reverse('password_reset'), post_data)
- # Check that the user was redirected
+ # Check that the user was redirected to the done page
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('password_reset_done'))
# Check that a password reset email was sent to the user
self.assertEqual(len(mail.outbox), 1)
@@ -267,8 +305,9 @@ class TestPasswordReset(TestCase):
}
response = self.client.post(reverse('password_reset_confirm', kwargs=self.url_kwargs), post_data)
- # Check that the user was redirected
+ # Check that the user was redirected to the complete page
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('password_reset_complete'))
# Check that the password was changed
self.assertTrue(User.objects.get(username='test').check_password('newpassword'))
diff --git a/wagtail/wagtailadmin/tests/test_page_chooser.py b/wagtail/wagtailadmin/tests/test_page_chooser.py
new file mode 100644
index 000000000..475e4f7b1
--- /dev/null
+++ b/wagtail/wagtailadmin/tests/test_page_chooser.py
@@ -0,0 +1,130 @@
+from django.test import TestCase
+from django.core.urlresolvers import reverse
+
+from wagtail.wagtailcore.models import Page
+from wagtail.tests.models import SimplePage
+from wagtail.tests.utils import WagtailTestUtils
+
+
+class TestChooserBrowse(TestCase, WagtailTestUtils):
+ def setUp(self):
+ self.root_page = Page.objects.get(id=2)
+
+ # Add child page
+ self.child_page = SimplePage()
+ self.child_page.title = "foobarbaz"
+ self.child_page.slug = "foobarbaz"
+ self.root_page.add_child(instance=self.child_page)
+
+ self.login()
+
+ def get(self, params={}):
+ return self.client.get(reverse('wagtailadmin_choose_page'), params)
+
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailadmin/chooser/browse.html')
+
+ def test_search(self):
+ response = self.get({'q': "foobarbaz"})
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "There is one match")
+ self.assertContains(response, "foobarbaz")
+
+ def test_search_no_results(self):
+ response = self.get({'q': "quux"})
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "There are 0 matches")
+
+ def test_get_invalid(self):
+ response = self.get({'page_type': 'foo.bar'})
+ self.assertEqual(response.status_code, 404)
+
+
+class TestChooserBrowseChild(TestCase, WagtailTestUtils):
+ def setUp(self):
+ self.root_page = Page.objects.get(id=2)
+
+ # Add child page
+ self.child_page = SimplePage()
+ self.child_page.title = "foobarbaz"
+ self.child_page.slug = "foobarbaz"
+ self.root_page.add_child(instance=self.child_page)
+
+ self.login()
+
+ def get(self, params={}):
+ return self.client.get(reverse('wagtailadmin_choose_page_child',
+ args=(self.root_page.id,)), params)
+
+ def get_invalid(self, params={}):
+ return self.client.get(reverse('wagtailadmin_choose_page_child',
+ args=(9999999,)), params)
+
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailadmin/chooser/browse.html')
+
+ def test_search(self):
+ response = self.get({'q': "foobarbaz"})
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "There is one match")
+ self.assertContains(response, "foobarbaz")
+
+ def test_search_no_results(self):
+ response = self.get({'q': "quux"})
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "There are 0 matches")
+
+ def test_get_invalid(self):
+ self.assertEqual(self.get_invalid().status_code, 404)
+
+
+class TestChooserExternalLink(TestCase, WagtailTestUtils):
+ def setUp(self):
+ self.login()
+
+ def get(self, params={}):
+ return self.client.get(reverse('wagtailadmin_choose_page_external_link'), params)
+
+ def post(self, post_data={}):
+ return self.client.post(reverse('wagtailadmin_choose_page_external_link'), post_data)
+
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailadmin/chooser/external_link.html')
+
+ def test_get_with_param(self):
+ self.assertEqual(self.get({'prompt_for_link_text': 'foo'}).status_code, 200)
+
+ def test_create_link(self):
+ request = self.post({'url': 'http://www.example.com'})
+ self.assertContains(request, "'url': 'http://www.example.com/',")
+ self.assertContains(request, "'title': 'http://www.example.com/'")
+
+
+class TestChooserEmailLink(TestCase, WagtailTestUtils):
+ def setUp(self):
+ self.login()
+
+ def get(self, params={}):
+ return self.client.get(reverse('wagtailadmin_choose_page_email_link'), params)
+
+ def post(self, post_data={}):
+ return self.client.post(reverse('wagtailadmin_choose_page_email_link'), post_data)
+
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailadmin/chooser/email_link.html')
+
+ def test_get_with_param(self):
+ self.assertEqual(self.get({'prompt_for_link_text': 'foo'}).status_code, 200)
+
+ def test_create_link(self):
+ request = self.post({'email_address': 'example@example.com'})
+ self.assertContains(request, "'url': 'mailto:example@example.com',")
+ self.assertContains(request, "'title': 'example@example.com'")
diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py
index 14e57a35e..12c42f3e4 100644
--- a/wagtail/wagtailadmin/tests/test_pages_views.py
+++ b/wagtail/wagtailadmin/tests/test_pages_views.py
@@ -1,13 +1,13 @@
from django.test import TestCase
from wagtail.tests.models import SimplePage, EventPage
-from wagtail.tests.utils import login, unittest
+from wagtail.tests.utils import unittest, WagtailTestUtils
from wagtail.wagtailcore.models import Page, PageRevision
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User, Permission
from django.core import mail
-class TestPageExplorer(TestCase):
+class TestPageExplorer(TestCase, WagtailTestUtils):
def setUp(self):
# Find root page
self.root_page = Page.objects.get(id=2)
@@ -19,7 +19,7 @@ class TestPageExplorer(TestCase):
self.root_page.add_child(instance=self.child_page)
# Login
- login(self.client)
+ self.login()
def test_explore(self):
response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, )))
@@ -28,13 +28,13 @@ class TestPageExplorer(TestCase):
self.assertTrue(response.context['pages'].paginator.object_list.filter(id=self.child_page.id).exists())
-class TestPageCreation(TestCase):
+class TestPageCreation(TestCase, WagtailTestUtils):
def setUp(self):
# Find root page
self.root_page = Page.objects.get(id=2)
# Login
- self.user = login(self.client)
+ self.user = self.login()
def test_add_subpage(self):
response = self.client.get(reverse('wagtailadmin_pages_add_subpage', args=(self.root_page.id, )))
@@ -86,6 +86,7 @@ class TestPageCreation(TestCase):
# Should be redirected to explorer page
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailadmin_explore', args=(self.root_page.id, )))
# Find the page and check it
page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific
@@ -104,6 +105,7 @@ class TestPageCreation(TestCase):
# Should be redirected to explorer page
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailadmin_explore', args=(self.root_page.id, )))
# Find the page and check it
page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific
@@ -126,6 +128,7 @@ class TestPageCreation(TestCase):
# Should be redirected to explorer page
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailadmin_explore', args=(self.root_page.id, )))
# Find the page and check it
page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific
@@ -186,7 +189,7 @@ class TestPageCreation(TestCase):
self.assertContains(response, "New page!")
-class TestPageEdit(TestCase):
+class TestPageEdit(TestCase, WagtailTestUtils):
def setUp(self):
# Find root page
self.root_page = Page.objects.get(id=2)
@@ -206,7 +209,7 @@ class TestPageEdit(TestCase):
self.root_page.add_child(instance=self.event_page)
# Login
- self.user = login(self.client)
+ self.user = self.login()
def test_page_edit(self):
# Tests that the edit page loads
@@ -238,6 +241,7 @@ class TestPageEdit(TestCase):
# Should be redirected to explorer page
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailadmin_explore', args=(self.root_page.id, )))
# The page should have "has_unpublished_changes" flag set
child_page_new = SimplePage.objects.get(id=self.child_page.id)
@@ -255,6 +259,7 @@ class TestPageEdit(TestCase):
# Should be redirected to explorer page
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailadmin_explore', args=(self.root_page.id, )))
# Check that the page was edited
child_page_new = SimplePage.objects.get(id=self.child_page.id)
@@ -278,6 +283,7 @@ class TestPageEdit(TestCase):
# Should be redirected to explorer page
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailadmin_explore', args=(self.root_page.id, )))
# The page should have "has_unpublished_changes" flag set
child_page_new = SimplePage.objects.get(id=self.child_page.id)
@@ -306,7 +312,7 @@ class TestPageEdit(TestCase):
self.assertContains(response, "I've been edited!")
-class TestPageDelete(TestCase):
+class TestPageDelete(TestCase, WagtailTestUtils):
def setUp(self):
# Find root page
self.root_page = Page.objects.get(id=2)
@@ -318,7 +324,7 @@ class TestPageDelete(TestCase):
self.root_page.add_child(instance=self.child_page)
# Login
- self.user = login(self.client)
+ self.user = self.login()
def test_page_delete(self):
response = self.client.get(reverse('wagtailadmin_pages_delete', args=(self.child_page.id, )))
@@ -344,15 +350,16 @@ class TestPageDelete(TestCase):
# Should be redirected to explorer page
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailadmin_explore', args=(self.root_page.id, )))
# Check that the page is gone
self.assertEqual(Page.objects.filter(path__startswith=self.root_page.path, slug='hello-world').count(), 0)
-class TestPageSearch(TestCase):
+class TestPageSearch(TestCase, WagtailTestUtils):
def setUp(self):
# Login
- login(self.client)
+ self.login()
def get(self, params=None, **extra):
return self.client.get(reverse('wagtailadmin_pages_search'), params or {}, **extra)
@@ -390,7 +397,7 @@ class TestPageSearch(TestCase):
self.assertTrue(any([r.slug == 'root' for r in results]))
-class TestPageMove(TestCase):
+class TestPageMove(TestCase, WagtailTestUtils):
def setUp(self):
# Find root page
self.root_page = Page.objects.get(id=2)
@@ -413,7 +420,7 @@ class TestPageMove(TestCase):
self.section_a.add_child(instance=self.test_page)
# Login
- self.user = login(self.client)
+ self.user = self.login()
def test_page_move(self):
response = self.client.get(reverse('wagtailadmin_pages_move', args=(self.test_page.id, )))
@@ -442,18 +449,18 @@ class TestPageMove(TestCase):
self.assertEqual(response.status_code, 200)
-class TestPageUnpublish(TestCase):
+class TestPageUnpublish(TestCase, WagtailTestUtils):
def setUp(self):
- self.user = login(self.client)
+ self.user = self.login()
# Create a page to unpublish
- root_page = Page.objects.get(id=2)
+ self.root_page = Page.objects.get(id=2)
self.page = SimplePage(
title="Hello world!",
slug='hello-world',
live=True,
)
- root_page.add_child(instance=self.page)
+ self.root_page.add_child(instance=self.page)
def test_unpublish_view(self):
"""
@@ -502,14 +509,15 @@ class TestPageUnpublish(TestCase):
'foo': "Must post something or the view won't see this as a POST request",
})
- # Check that the user was redirected
+ # Check that the user was redirected to the explore page
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailadmin_explore', args=(self.root_page.id, )))
# Check that the page was unpublished
self.assertFalse(SimplePage.objects.get(id=self.page.id).live)
-class TestApproveRejectModeration(TestCase):
+class TestApproveRejectModeration(TestCase, WagtailTestUtils):
def setUp(self):
self.submitter = User.objects.create_superuser(
username='submitter',
@@ -517,7 +525,7 @@ class TestApproveRejectModeration(TestCase):
password='password',
)
- self.user = login(self.client)
+ self.user = self.login()
# Create a page and submit it for moderation
root_page = Page.objects.get(id=2)
@@ -540,8 +548,9 @@ class TestApproveRejectModeration(TestCase):
'foo': "Must post something or the view won't see this as a POST request",
})
- # Check that the user was redirected
+ # Check that the user was redirected to the dashboard
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailadmin_home'))
# Page must be live
self.assertTrue(Page.objects.get(id=self.page.id).live)
@@ -591,8 +600,9 @@ class TestApproveRejectModeration(TestCase):
'foo': "Must post something or the view won't see this as a POST request",
})
- # Check that the user was redirected
+ # Check that the user was redirected to the dashboard
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailadmin_home'))
# Page must not be live
self.assertFalse(Page.objects.get(id=self.page.id).live)
@@ -645,11 +655,11 @@ class TestApproveRejectModeration(TestCase):
self.assertContains(response, "Hello world!")
-class TestContentTypeUse(TestCase):
+class TestContentTypeUse(TestCase, WagtailTestUtils):
fixtures = ['test.json']
def setUp(self):
- self.user = login(self.client)
+ self.user = self.login()
def test_content_type_use(self):
# Get use of event page
diff --git a/wagtail/wagtailadmin/tests/tests.py b/wagtail/wagtailadmin/tests/tests.py
index 12aa28a0b..f014c026d 100644
--- a/wagtail/wagtailadmin/tests/tests.py
+++ b/wagtail/wagtailadmin/tests/tests.py
@@ -1,26 +1,26 @@
from django.test import TestCase
from wagtail.tests.models import SimplePage, EventPage
-from wagtail.tests.utils import login, unittest
+from wagtail.tests.utils import unittest, WagtailTestUtils
from wagtail.wagtailcore.models import Page
from wagtail.wagtailadmin.tasks import send_email_task
from django.core.urlresolvers import reverse
from django.core import mail
-class TestHome(TestCase):
+class TestHome(TestCase, WagtailTestUtils):
def setUp(self):
# Login
- login(self.client)
+ self.login()
- def test_status_code(self):
+ def test_simple(self):
response = self.client.get(reverse('wagtailadmin_home'))
self.assertEqual(response.status_code, 200)
-class TestEditorHooks(TestCase):
+class TestEditorHooks(TestCase, WagtailTestUtils):
def setUp(self):
self.homepage = Page.objects.get(id=2)
- login(self.client)
+ self.login()
def test_editor_css_and_js_hooks_on_add(self):
response = self.client.get(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.homepage.id)))
diff --git a/wagtail/wagtailadmin/urls.py b/wagtail/wagtailadmin/urls.py
index 8fbf2f6e7..819caa92c 100644
--- a/wagtail/wagtailadmin/urls.py
+++ b/wagtail/wagtailadmin/urls.py
@@ -5,15 +5,8 @@ from wagtail.wagtailadmin.forms import LoginForm, PasswordResetForm
from wagtail.wagtailadmin.views import account, chooser, home, pages, tags, userbar
from wagtail.wagtailadmin import hooks
-urlpatterns = [
- url(
- r'^login/$', 'django.contrib.auth.views.login', {
- 'template_name': 'wagtailadmin/login.html',
- 'authentication_form': LoginForm,
- 'extra_context': {'show_password_reset': getattr(settings, 'WAGTAIL_PASSWORD_MANAGEMENT_ENABLED', True)},
- }, name='wagtailadmin_login'
- ),
+urlpatterns = [
# Password reset
url(
r'^password_reset/$', 'django.contrib.auth.views.password_reset', {
@@ -81,6 +74,7 @@ urlpatterns += [
url(r'^tag-autocomplete/$', tags.autocomplete, name='wagtailadmin_tag_autocomplete'),
+ url(r'^login/$', account.login, name='wagtailadmin_login'),
url(r'^account/$', account.account, name='wagtailadmin_account'),
url(r'^account/change_password/$', account.change_password, name='wagtailadmin_account_change_password'),
url(r'^logout/$', account.logout, name='wagtailadmin_logout'),
@@ -90,6 +84,13 @@ urlpatterns += [
]
+# This is here to make sure that 'django.contrib.auth.views.login' is reversed correctly
+# It must be placed after 'wagtailadmin_login' to prevent this from being used
+urlpatterns += [
+ url(r'^login/$', 'django.contrib.auth.views.login'),
+]
+
+
# Import additional urlpatterns from any apps that define a register_admin_urls hook
for fn in hooks.get_hooks('register_admin_urls'):
urls = fn()
diff --git a/wagtail/wagtailadmin/views/account.py b/wagtail/wagtailadmin/views/account.py
index 8479ea6b0..c5e461f55 100644
--- a/wagtail/wagtailadmin/views/account.py
+++ b/wagtail/wagtailadmin/views/account.py
@@ -3,8 +3,13 @@ from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth.forms import SetPasswordForm
from django.contrib.auth.decorators import permission_required
-from django.contrib.auth.views import logout as auth_logout
+from django.contrib.auth.views import logout as auth_logout, login as auth_login
from django.utils.translation import ugettext as _
+from django.views.decorators.debug import sensitive_post_parameters
+from django.views.decorators.cache import never_cache
+
+from wagtail.wagtailadmin import forms
+
@permission_required('wagtailadmin.access_admin')
def account(request):
@@ -37,6 +42,21 @@ def change_password(request):
})
+@sensitive_post_parameters()
+@never_cache
+def login(request):
+ if request.user.is_authenticated():
+ return redirect('wagtailadmin_home')
+ else:
+ return auth_login(request,
+ template_name='wagtailadmin/login.html',
+ authentication_form=forms.LoginForm,
+ extra_context={
+ 'show_password_reset': getattr(settings, 'WAGTAIL_PASSWORD_MANAGEMENT_ENABLED', True),
+ },
+ )
+
+
def logout(request):
response = auth_logout(request, next_page = 'wagtailadmin_login')
diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py
index 30ffc8a77..b7e3d349e 100644
--- a/wagtail/wagtailcore/models.py
+++ b/wagtail/wagtailcore/models.py
@@ -394,23 +394,23 @@ class Page(MP_Node, ClusterableModel, Indexed):
return revision.as_page_object()
- def get_context(self, request):
+ def get_context(self, request, *args, **kwargs):
return {
'self': self,
'request': request,
}
- def get_template(self, request):
+ def get_template(self, request, *args, **kwargs):
if request.is_ajax():
return self.ajax_template or self.template
else:
return self.template
- def serve(self, request):
+ def serve(self, request, *args, **kwargs):
return TemplateResponse(
request,
- self.get_template(request),
- self.get_context(request)
+ self.get_template(request, *args, **kwargs),
+ self.get_context(request, *args, **kwargs)
)
def is_navigable(self):
diff --git a/wagtail/wagtailcore/tests.py b/wagtail/wagtailcore/tests.py
deleted file mode 100644
index df66c6b93..000000000
--- a/wagtail/wagtailcore/tests.py
+++ /dev/null
@@ -1,860 +0,0 @@
-from django.test import TestCase, Client
-from django.http import HttpRequest, Http404
-from django.core import management
-from StringIO import StringIO
-
-from django.contrib.auth.models import User
-
-from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy
-from wagtail.tests.models import EventPage, EventIndex, SimplePage
-
-
-class TestRouting(TestCase):
- fixtures = ['test.json']
-
- def test_find_site_for_request(self):
- default_site = Site.objects.get(is_default_site=True)
- events_page = Page.objects.get(url_path='/home/events/')
- events_site = Site.objects.create(hostname='events.example.com', root_page=events_page)
-
- # requests without a Host: header should be directed to the default site
- request = HttpRequest()
- request.path = '/'
- self.assertEqual(Site.find_for_request(request), default_site)
-
- # requests with a known Host: header should be directed to the specific site
- request = HttpRequest()
- request.path = '/'
- request.META['HTTP_HOST'] = 'events.example.com'
- self.assertEqual(Site.find_for_request(request), events_site)
-
- # requests with an unrecognised Host: header should be directed to the default site
- request = HttpRequest()
- request.path = '/'
- request.META['HTTP_HOST'] = 'unknown.example.com'
- self.assertEqual(Site.find_for_request(request), default_site)
-
- def test_urls(self):
- default_site = Site.objects.get(is_default_site=True)
- homepage = Page.objects.get(url_path='/home/')
- christmas_page = Page.objects.get(url_path='/home/events/christmas/')
-
- # Basic installation only has one site configured, so page.url will return local URLs
- self.assertEqual(homepage.full_url, 'http://localhost/')
- self.assertEqual(homepage.url, '/')
- self.assertEqual(homepage.relative_url(default_site), '/')
-
- self.assertEqual(christmas_page.full_url, 'http://localhost/events/christmas/')
- self.assertEqual(christmas_page.url, '/events/christmas/')
- self.assertEqual(christmas_page.relative_url(default_site), '/events/christmas/')
-
- def test_urls_with_multiple_sites(self):
- events_page = Page.objects.get(url_path='/home/events/')
- events_site = Site.objects.create(hostname='events.example.com', root_page=events_page)
-
- default_site = Site.objects.get(is_default_site=True)
- homepage = Page.objects.get(url_path='/home/')
- christmas_page = Page.objects.get(url_path='/home/events/christmas/')
-
- # with multiple sites, page.url will return full URLs to ensure that
- # they work across sites
- self.assertEqual(homepage.full_url, 'http://localhost/')
- self.assertEqual(homepage.url, 'http://localhost/')
- self.assertEqual(homepage.relative_url(default_site), '/')
- self.assertEqual(homepage.relative_url(events_site), 'http://localhost/')
-
- self.assertEqual(christmas_page.full_url, 'http://events.example.com/christmas/')
- self.assertEqual(christmas_page.url, 'http://events.example.com/christmas/')
- self.assertEqual(christmas_page.relative_url(default_site), 'http://events.example.com/christmas/')
- self.assertEqual(christmas_page.relative_url(events_site), '/christmas/')
-
- def test_request_routing(self):
- homepage = Page.objects.get(url_path='/home/')
- christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
-
- request = HttpRequest()
- request.path = '/events/christmas/'
- response = homepage.route(request, ['events', 'christmas'])
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.context_data['self'], christmas_page)
- used_template = response.resolve_template(response.template_name)
- self.assertEqual(used_template.name, 'tests/event_page.html')
-
- def test_route_to_unknown_page_returns_404(self):
- homepage = Page.objects.get(url_path='/home/')
-
- request = HttpRequest()
- request.path = '/events/quinquagesima/'
- with self.assertRaises(Http404):
- homepage.route(request, ['events', 'quinquagesima'])
-
- def test_route_to_unpublished_page_returns_404(self):
- homepage = Page.objects.get(url_path='/home/')
-
- request = HttpRequest()
- request.path = '/events/tentative-unpublished-event/'
- with self.assertRaises(Http404):
- homepage.route(request, ['events', 'tentative-unpublished-event'])
-
-
-class TestServeView(TestCase):
- fixtures = ['test.json']
-
- def setUp(self):
- # Explicitly clear the cache of site root paths. Normally this would be kept
- # in sync by the Site.save logic, but this is bypassed when the database is
- # rolled back between tests using transactions.
- from django.core.cache import cache
- cache.delete('wagtail_site_root_paths')
-
- def test_serve(self):
- response = self.client.get('/events/christmas/')
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.templates[0].name, 'tests/event_page.html')
- christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
- self.assertEqual(response.context['self'], christmas_page)
-
- self.assertContains(response, '
Christmas
')
- self.assertContains(response, '
Event
')
-
- def test_serve_unknown_page_returns_404(self):
- response = self.client.get('/events/quinquagesima/')
- self.assertEqual(response.status_code, 404)
-
- def test_serve_unpublished_page_returns_404(self):
- response = self.client.get('/events/tentative-unpublished-event/')
- self.assertEqual(response.status_code, 404)
-
- def test_serve_with_multiple_sites(self):
- events_page = Page.objects.get(url_path='/home/events/')
- Site.objects.create(hostname='events.example.com', root_page=events_page)
-
- response = self.client.get('/christmas/', HTTP_HOST='events.example.com')
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.templates[0].name, 'tests/event_page.html')
- christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
- self.assertEqual(response.context['self'], christmas_page)
-
- self.assertContains(response, '
Christmas
')
- self.assertContains(response, '
Event
')
-
- # same request to the default host should return a 404
- c = Client()
- response = c.get('/christmas/', HTTP_HOST='localhost')
- self.assertEqual(response.status_code, 404)
-
- def test_serve_with_custom_context(self):
- response = self.client.get('/events/')
- self.assertEqual(response.status_code, 200)
-
- # should render the whole page
- self.assertContains(response, '
Events
')
-
- # response should contain data from the custom 'events' context variable
- self.assertContains(response, '
Christmas')
-
- def test_ajax_response(self):
- response = self.client.get('/events/', HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(response.status_code, 200)
-
- # should only render the content of includes/event_listing.html, not the whole page
- self.assertNotContains(response, '
Events
')
- self.assertContains(response, '
Christmas')
-
-
-class TestStaticSitePaths(TestCase):
- def setUp(self):
- self.root_page = Page.objects.get(id=1)
-
- # For simple tests
- self.home_page = self.root_page.add_child(instance=SimplePage(title="Homepage", slug="home"))
- self.about_page = self.home_page.add_child(instance=SimplePage(title="About us", slug="about"))
- self.contact_page = self.home_page.add_child(instance=SimplePage(title="Contact", slug="contact"))
-
- # For custom tests
- self.event_index = self.root_page.add_child(instance=EventIndex(title="Events", slug="events"))
- for i in range(20):
- self.event_index.add_child(instance=EventPage(title="Event " + str(i), slug="event" + str(i)))
-
- def test_local_static_site_paths(self):
- paths = list(self.about_page.get_static_site_paths())
-
- self.assertEqual(paths, ['/'])
-
- def test_child_static_site_paths(self):
- paths = list(self.home_page.get_static_site_paths())
-
- self.assertEqual(paths, ['/', '/about/', '/contact/'])
-
- def test_custom_static_site_paths(self):
- paths = list(self.event_index.get_static_site_paths())
-
- # Event index path
- expected_paths = ['/']
-
- # One path for each page of results
- expected_paths.extend(['/' + str(i + 1) + '/' for i in range(5)])
-
- # One path for each event page
- expected_paths.extend(['/event' + str(i) + '/' for i in range(20)])
-
- paths.sort()
- expected_paths.sort()
- self.assertEqual(paths, expected_paths)
-
-
-class TestPageUrlTags(TestCase):
- fixtures = ['test.json']
-
- def test_pageurl_tag(self):
- response = self.client.get('/events/')
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, '
Christmas')
-
- def test_slugurl_tag(self):
- response = self.client.get('/events/christmas/')
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, '
Back to events index')
-
-
-class TestPagePermission(TestCase):
- fixtures = ['test.json']
-
- def test_nonpublisher_page_permissions(self):
- event_editor = User.objects.get(username='eventeditor')
- homepage = Page.objects.get(url_path='/home/')
- christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
- unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
- someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
-
- homepage_perms = homepage.permissions_for_user(event_editor)
- christmas_page_perms = christmas_page.permissions_for_user(event_editor)
- unpub_perms = unpublished_event_page.permissions_for_user(event_editor)
- someone_elses_event_perms = someone_elses_event_page.permissions_for_user(event_editor)
-
- self.assertFalse(homepage_perms.can_add_subpage())
- self.assertTrue(christmas_page_perms.can_add_subpage())
- self.assertTrue(unpub_perms.can_add_subpage())
- self.assertTrue(someone_elses_event_perms.can_add_subpage())
-
- self.assertFalse(homepage_perms.can_edit())
- self.assertTrue(christmas_page_perms.can_edit())
- self.assertTrue(unpub_perms.can_edit())
- self.assertFalse(someone_elses_event_perms.can_edit()) # basic 'add' permission doesn't allow editing pages owned by someone else
-
- self.assertFalse(homepage_perms.can_delete())
- self.assertFalse(christmas_page_perms.can_delete()) # cannot delete because it is published
- self.assertTrue(unpub_perms.can_delete())
- self.assertFalse(someone_elses_event_perms.can_delete())
-
- self.assertFalse(homepage_perms.can_publish())
- self.assertFalse(christmas_page_perms.can_publish())
- self.assertFalse(unpub_perms.can_publish())
-
- self.assertFalse(homepage_perms.can_unpublish())
- self.assertFalse(christmas_page_perms.can_unpublish())
- self.assertFalse(unpub_perms.can_unpublish())
-
- self.assertFalse(homepage_perms.can_publish_subpage())
- self.assertFalse(christmas_page_perms.can_publish_subpage())
- self.assertFalse(unpub_perms.can_publish_subpage())
-
- self.assertFalse(homepage_perms.can_reorder_children())
- self.assertFalse(christmas_page_perms.can_reorder_children())
- self.assertFalse(unpub_perms.can_reorder_children())
-
- self.assertFalse(homepage_perms.can_move())
- self.assertFalse(christmas_page_perms.can_move()) # cannot move because this would involve unpublishing from its current location
- self.assertTrue(unpub_perms.can_move())
- self.assertFalse(someone_elses_event_perms.can_move())
-
- self.assertFalse(christmas_page_perms.can_move_to(unpublished_event_page)) # cannot move because this would involve unpublishing from its current location
- self.assertTrue(unpub_perms.can_move_to(christmas_page))
- self.assertFalse(unpub_perms.can_move_to(homepage)) # no permission to create pages at destination
- self.assertFalse(unpub_perms.can_move_to(unpublished_event_page)) # cannot make page a child of itself
-
-
- def test_publisher_page_permissions(self):
- event_moderator = User.objects.get(username='eventmoderator')
- homepage = Page.objects.get(url_path='/home/')
- christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
- unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
-
- homepage_perms = homepage.permissions_for_user(event_moderator)
- christmas_page_perms = christmas_page.permissions_for_user(event_moderator)
- unpub_perms = unpublished_event_page.permissions_for_user(event_moderator)
-
- self.assertFalse(homepage_perms.can_add_subpage())
- self.assertTrue(christmas_page_perms.can_add_subpage())
- self.assertTrue(unpub_perms.can_add_subpage())
-
- self.assertFalse(homepage_perms.can_edit())
- self.assertTrue(christmas_page_perms.can_edit())
- self.assertTrue(unpub_perms.can_edit())
-
- self.assertFalse(homepage_perms.can_delete())
- self.assertTrue(christmas_page_perms.can_delete()) # cannot delete because it is published
- self.assertTrue(unpub_perms.can_delete())
-
- self.assertFalse(homepage_perms.can_publish())
- self.assertTrue(christmas_page_perms.can_publish())
- self.assertTrue(unpub_perms.can_publish())
-
- self.assertFalse(homepage_perms.can_unpublish())
- self.assertTrue(christmas_page_perms.can_unpublish())
- self.assertFalse(unpub_perms.can_unpublish()) # cannot unpublish a page that isn't published
-
- self.assertFalse(homepage_perms.can_publish_subpage())
- self.assertTrue(christmas_page_perms.can_publish_subpage())
- self.assertTrue(unpub_perms.can_publish_subpage())
-
- self.assertFalse(homepage_perms.can_reorder_children())
- self.assertTrue(christmas_page_perms.can_reorder_children())
- self.assertTrue(unpub_perms.can_reorder_children())
-
- self.assertFalse(homepage_perms.can_move())
- self.assertTrue(christmas_page_perms.can_move())
- self.assertTrue(unpub_perms.can_move())
-
- self.assertTrue(christmas_page_perms.can_move_to(unpublished_event_page))
- self.assertTrue(unpub_perms.can_move_to(christmas_page))
- self.assertFalse(unpub_perms.can_move_to(homepage)) # no permission to create pages at destination
- self.assertFalse(unpub_perms.can_move_to(unpublished_event_page)) # cannot make page a child of itself
-
- def test_inactive_user_has_no_permissions(self):
- user = User.objects.get(username='inactiveuser')
- christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
- unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
-
- christmas_page_perms = christmas_page.permissions_for_user(user)
- unpub_perms = unpublished_event_page.permissions_for_user(user)
-
- self.assertFalse(unpub_perms.can_add_subpage())
- self.assertFalse(unpub_perms.can_edit())
- self.assertFalse(unpub_perms.can_delete())
- self.assertFalse(unpub_perms.can_publish())
- self.assertFalse(christmas_page_perms.can_unpublish())
- self.assertFalse(unpub_perms.can_publish_subpage())
- self.assertFalse(unpub_perms.can_reorder_children())
- self.assertFalse(unpub_perms.can_move())
- self.assertFalse(unpub_perms.can_move_to(christmas_page))
-
- def test_superuser_has_full_permissions(self):
- user = User.objects.get(username='superuser')
- homepage = Page.objects.get(url_path='/home/')
- root = Page.objects.get(url_path='/')
- unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
-
- homepage_perms = homepage.permissions_for_user(user)
- root_perms = root.permissions_for_user(user)
- unpub_perms = unpublished_event_page.permissions_for_user(user)
-
- self.assertTrue(homepage_perms.can_add_subpage())
- self.assertTrue(root_perms.can_add_subpage())
-
- self.assertTrue(homepage_perms.can_edit())
- self.assertFalse(root_perms.can_edit()) # root is not a real editable page, even to superusers
-
- self.assertTrue(homepage_perms.can_delete())
- self.assertFalse(root_perms.can_delete())
-
- self.assertTrue(homepage_perms.can_publish())
- self.assertFalse(root_perms.can_publish())
-
- self.assertTrue(homepage_perms.can_unpublish())
- self.assertFalse(root_perms.can_unpublish())
- self.assertFalse(unpub_perms.can_unpublish())
-
- self.assertTrue(homepage_perms.can_publish_subpage())
- self.assertTrue(root_perms.can_publish_subpage())
-
- self.assertTrue(homepage_perms.can_reorder_children())
- self.assertTrue(root_perms.can_reorder_children())
-
- self.assertTrue(homepage_perms.can_move())
- self.assertFalse(root_perms.can_move())
-
- self.assertTrue(homepage_perms.can_move_to(root))
- self.assertFalse(homepage_perms.can_move_to(unpublished_event_page))
-
- def test_editable_pages_for_user_with_add_permission(self):
- event_editor = User.objects.get(username='eventeditor')
- homepage = Page.objects.get(url_path='/home/')
- christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
- unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
- someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
-
- editable_pages = UserPagePermissionsProxy(event_editor).editable_pages()
-
- self.assertFalse(editable_pages.filter(id=homepage.id).exists())
- self.assertTrue(editable_pages.filter(id=christmas_page.id).exists())
- self.assertTrue(editable_pages.filter(id=unpublished_event_page.id).exists())
- self.assertFalse(editable_pages.filter(id=someone_elses_event_page.id).exists())
-
- def test_editable_pages_for_user_with_edit_permission(self):
- event_moderator = User.objects.get(username='eventmoderator')
- homepage = Page.objects.get(url_path='/home/')
- christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
- unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
- someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
-
- editable_pages = UserPagePermissionsProxy(event_moderator).editable_pages()
-
- self.assertFalse(editable_pages.filter(id=homepage.id).exists())
- self.assertTrue(editable_pages.filter(id=christmas_page.id).exists())
- self.assertTrue(editable_pages.filter(id=unpublished_event_page.id).exists())
- self.assertTrue(editable_pages.filter(id=someone_elses_event_page.id).exists())
-
- def test_editable_pages_for_inactive_user(self):
- user = User.objects.get(username='inactiveuser')
- homepage = Page.objects.get(url_path='/home/')
- christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
- unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
- someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
-
- editable_pages = UserPagePermissionsProxy(user).editable_pages()
-
- self.assertFalse(editable_pages.filter(id=homepage.id).exists())
- self.assertFalse(editable_pages.filter(id=christmas_page.id).exists())
- self.assertFalse(editable_pages.filter(id=unpublished_event_page.id).exists())
- self.assertFalse(editable_pages.filter(id=someone_elses_event_page.id).exists())
-
- def test_editable_pages_for_superuser(self):
- user = User.objects.get(username='superuser')
- homepage = Page.objects.get(url_path='/home/')
- christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
- unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
- someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
-
- editable_pages = UserPagePermissionsProxy(user).editable_pages()
-
- self.assertTrue(editable_pages.filter(id=homepage.id).exists())
- self.assertTrue(editable_pages.filter(id=christmas_page.id).exists())
- self.assertTrue(editable_pages.filter(id=unpublished_event_page.id).exists())
- self.assertTrue(editable_pages.filter(id=someone_elses_event_page.id).exists())
-
-
-class TestPageQuerySet(TestCase):
- fixtures = ['test.json']
-
- def test_live(self):
- pages = Page.objects.live()
-
- # All pages must be live
- for page in pages:
- self.assertTrue(page.live)
-
- # Check that the homepage is in the results
- homepage = Page.objects.get(url_path='/home/')
- self.assertTrue(pages.filter(id=homepage.id).exists())
-
- def test_not_live(self):
- pages = Page.objects.not_live()
-
- # All pages must not be live
- for page in pages:
- self.assertFalse(page.live)
-
- # Check that "someone elses event" is in the results
- event = Page.objects.get(url_path='/home/events/someone-elses-event/')
- self.assertTrue(pages.filter(id=event.id).exists())
-
- def test_page(self):
- homepage = Page.objects.get(url_path='/home/')
- pages = Page.objects.page(homepage)
-
- # Should only select the homepage
- self.assertEqual(pages.count(), 1)
- self.assertEqual(pages.first(), homepage)
-
- def test_not_page(self):
- homepage = Page.objects.get(url_path='/home/')
- pages = Page.objects.not_page(homepage)
-
- # Should select everything except for the homepage
- self.assertEqual(pages.count(), Page.objects.all().count() - 1)
- for page in pages:
- self.assertNotEqual(page, homepage)
-
- def test_descendant_of(self):
- events_index = Page.objects.get(url_path='/home/events/')
- pages = Page.objects.descendant_of(events_index)
-
- # Check that all pages descend from events index
- for page in pages:
- self.assertTrue(page.get_ancestors().filter(id=events_index.id).exists())
-
- def test_descendant_of_inclusive(self):
- events_index = Page.objects.get(url_path='/home/events/')
- pages = Page.objects.descendant_of(events_index, inclusive=True)
-
- # Check that all pages descend from events index, includes event index
- for page in pages:
- self.assertTrue(page == events_index or page.get_ancestors().filter(id=events_index.id).exists())
-
- # Check that event index was included
- self.assertTrue(pages.filter(id=events_index.id).exists())
-
- def test_not_descendant_of(self):
- homepage = Page.objects.get(url_path='/home/')
- events_index = Page.objects.get(url_path='/home/events/')
- pages = Page.objects.not_descendant_of(events_index)
-
- # Check that no pages descend from events_index
- for page in pages:
- self.assertFalse(page.get_ancestors().filter(id=events_index.id).exists())
-
- # As this is not inclusive, events index should be in the results
- self.assertTrue(pages.filter(id=events_index.id).exists())
-
- def test_not_descendant_of_inclusive(self):
- homepage = Page.objects.get(url_path='/home/')
- events_index = Page.objects.get(url_path='/home/events/')
- pages = Page.objects.not_descendant_of(events_index, inclusive=True)
-
- # Check that all pages descend from homepage but not events index
- for page in pages:
- self.assertFalse(page.get_ancestors().filter(id=events_index.id).exists())
-
- # As this is inclusive, events index should not be in the results
- self.assertFalse(pages.filter(id=events_index.id).exists())
-
- def test_child_of(self):
- homepage = Page.objects.get(url_path='/home/')
- pages = Page.objects.child_of(homepage)
-
- # Check that all pages are children of homepage
- for page in pages:
- self.assertEqual(page.get_parent(), homepage)
-
- def test_not_child_of(self):
- events_index = Page.objects.get(url_path='/home/events/')
- pages = Page.objects.not_child_of(events_index)
-
- # Check that all pages are not children of events_index
- for page in pages:
- self.assertNotEqual(page.get_parent(), events_index)
-
- def test_ancestor_of(self):
- root_page = Page.objects.get(id=1)
- homepage = Page.objects.get(url_path='/home/')
- events_index = Page.objects.get(url_path='/home/events/')
- pages = Page.objects.ancestor_of(events_index)
-
- self.assertEqual(pages.count(), 2)
- self.assertEqual(pages[0], root_page)
- self.assertEqual(pages[1], homepage)
-
- def test_ancestor_of_inclusive(self):
- root_page = Page.objects.get(id=1)
- homepage = Page.objects.get(url_path='/home/')
- events_index = Page.objects.get(url_path='/home/events/')
- pages = Page.objects.ancestor_of(events_index, inclusive=True)
-
- self.assertEqual(pages.count(), 3)
- self.assertEqual(pages[0], root_page)
- self.assertEqual(pages[1], homepage)
- self.assertEqual(pages[2], events_index)
-
- def test_not_ancestor_of(self):
- root_page = Page.objects.get(id=1)
- homepage = Page.objects.get(url_path='/home/')
- events_index = Page.objects.get(url_path='/home/events/')
- pages = Page.objects.not_ancestor_of(events_index)
-
- # Test that none of the ancestors are in pages
- for page in pages:
- self.assertNotEqual(page, root_page)
- self.assertNotEqual(page, homepage)
-
- # Test that events index is in pages
- self.assertTrue(pages.filter(id=events_index.id).exists())
-
- def test_not_ancestor_of_inclusive(self):
- root_page = Page.objects.get(id=1)
- homepage = Page.objects.get(url_path='/home/')
- events_index = Page.objects.get(url_path='/home/events/')
- pages = Page.objects.not_ancestor_of(events_index, inclusive=True)
-
- # Test that none of the ancestors or the events_index are in pages
- for page in pages:
- self.assertNotEqual(page, root_page)
- self.assertNotEqual(page, homepage)
- self.assertNotEqual(page, events_index)
-
- def test_parent_of(self):
- homepage = Page.objects.get(url_path='/home/')
- events_index = Page.objects.get(url_path='/home/events/')
- pages = Page.objects.parent_of(events_index)
-
- # Pages must only contain homepage
- self.assertEqual(pages.count(), 1)
- self.assertEqual(pages[0], homepage)
-
- def test_not_parent_of(self):
- homepage = Page.objects.get(url_path='/home/')
- events_index = Page.objects.get(url_path='/home/events/')
- pages = Page.objects.not_parent_of(events_index)
-
- # Pages must not contain homepage
- for page in pages:
- self.assertNotEqual(page, homepage)
-
- # Test that events index is in pages
- self.assertTrue(pages.filter(id=events_index.id).exists())
-
- def test_sibling_of(self):
- events_index = Page.objects.get(url_path='/home/events/')
- event = Page.objects.get(url_path='/home/events/christmas/')
- pages = Page.objects.sibling_of(event)
-
- # Check that all pages are children of events_index
- for page in pages:
- self.assertEqual(page.get_parent(), events_index)
-
- # Check that the event is not included
- self.assertFalse(pages.filter(id=event.id).exists())
-
- def test_sibling_of_inclusive(self):
- events_index = Page.objects.get(url_path='/home/events/')
- event = Page.objects.get(url_path='/home/events/christmas/')
- pages = Page.objects.sibling_of(event, inclusive=True)
-
- # Check that all pages are children of events_index
- for page in pages:
- self.assertEqual(page.get_parent(), events_index)
-
- # Check that the event is included
- self.assertTrue(pages.filter(id=event.id).exists())
-
- def test_not_sibling_of(self):
- events_index = Page.objects.get(url_path='/home/events/')
- event = Page.objects.get(url_path='/home/events/christmas/')
- pages = Page.objects.not_sibling_of(event)
-
- # Check that all pages are not children of events_index
- for page in pages:
- if page != event:
- self.assertNotEqual(page.get_parent(), events_index)
-
- # Check that the event is included
- self.assertTrue(pages.filter(id=event.id).exists())
-
- # Test that events index is in pages
- self.assertTrue(pages.filter(id=events_index.id).exists())
-
- def test_not_sibling_of_inclusive(self):
- events_index = Page.objects.get(url_path='/home/events/')
- event = Page.objects.get(url_path='/home/events/christmas/')
- pages = Page.objects.not_sibling_of(event, inclusive=True)
-
- # Check that all pages are not children of events_index
- for page in pages:
- self.assertNotEqual(page.get_parent(), events_index)
-
- # Check that the event is not included
- self.assertFalse(pages.filter(id=event.id).exists())
-
- # Test that events index is in pages
- self.assertTrue(pages.filter(id=events_index.id).exists())
-
- def test_type(self):
- pages = Page.objects.type(EventPage)
-
- # Check that all objects are EventPages
- for page in pages:
- self.assertIsInstance(page.specific, EventPage)
-
- # Check that "someone elses event" is in the results
- event = Page.objects.get(url_path='/home/events/someone-elses-event/')
- self.assertTrue(pages.filter(id=event.id).exists())
-
- def test_not_type(self):
- pages = Page.objects.not_type(EventPage)
-
- # Check that no objects are EventPages
- for page in pages:
- self.assertNotIsInstance(page.specific, EventPage)
-
- # Check that the homepage is in the results
- homepage = Page.objects.get(url_path='/home/')
- self.assertTrue(pages.filter(id=homepage.id).exists())
-
-
-class TestMovePage(TestCase):
- fixtures = ['test.json']
-
- def test_move_page(self):
- about_us_page = SimplePage.objects.get(url_path='/home/about-us/')
- events_index = EventIndex.objects.get(url_path='/home/events/')
-
- events_index.move(about_us_page, pos='last-child')
-
- # re-fetch events index to confirm that db fields have been updated
- events_index = EventIndex.objects.get(id=events_index.id)
- self.assertEqual(events_index.url_path, '/home/about-us/events/')
- self.assertEqual(events_index.depth, 4)
- self.assertEqual(events_index.get_parent().id, about_us_page.id)
-
- # children of events_index should also have been updated
- christmas = events_index.get_children().get(slug='christmas')
- self.assertEqual(christmas.depth, 5)
- self.assertEqual(christmas.url_path, '/home/about-us/events/christmas/')
-
-
-class TestIssue7(TestCase):
- """
- This tests for an issue where if a site root page was moved, all the page
- urls in that site would change to None.
-
- The issue was caused by the 'wagtail_site_root_paths' cache variable not being
- cleared when a site root page was moved. Which left all the child pages
- thinking that they are no longer in the site and return None as their url.
-
- Fix: d6cce69a397d08d5ee81a8cbc1977ab2c9db2682
- Discussion: https://github.com/torchbox/wagtail/issues/7
- """
-
- fixtures = ['test.json']
-
- def test_issue7(self):
- # Get homepage, root page and site
- root_page = Page.objects.get(id=1)
- homepage = Page.objects.get(url_path='/home/')
- default_site = Site.objects.get(is_default_site=True)
-
- # Create a new homepage under current homepage
- new_homepage = SimplePage(title="New Homepage", slug="new-homepage")
- homepage.add_child(instance=new_homepage)
-
- # Set new homepage as the site root page
- default_site.root_page = new_homepage
- default_site.save()
-
- # Warm up the cache by getting the url
- _ = homepage.url
-
- # Move new homepage to root
- new_homepage.move(root_page, pos='last-child')
-
- # Get fresh instance of new_homepage
- new_homepage = Page.objects.get(id=new_homepage.id)
-
- # Check url
- self.assertEqual(new_homepage.url, '/')
-
-
-class TestIssue157(TestCase):
- """
- This tests for an issue where if a site root pages slug was changed, all the page
- urls in that site would change to None.
-
- The issue was caused by the 'wagtail_site_root_paths' cache variable not being
- cleared when a site root page was changed. Which left all the child pages
- thinking that they are no longer in the site and return None as their url.
-
- Fix: d6cce69a397d08d5ee81a8cbc1977ab2c9db2682
- Discussion: https://github.com/torchbox/wagtail/issues/157
- """
-
- fixtures = ['test.json']
-
- def test_issue157(self):
- # Get homepage
- homepage = Page.objects.get(url_path='/home/')
-
- # Warm up the cache by getting the url
- _ = homepage.url
-
- # Change homepage title and slug
- homepage.title = "New home"
- homepage.slug = "new-home"
- homepage.save()
-
- # Get fresh instance of homepage
- homepage = Page.objects.get(id=homepage.id)
-
- # Check url
- self.assertEqual(homepage.url, '/')
-
-
-class TestFixTreeCommand(TestCase):
- fixtures = ['test.json']
-
- def run_command(self):
- management.call_command('fixtree', interactive=False, stdout=StringIO())
-
- def test_fixes_numchild(self):
- # Get homepage and save old value
- homepage = Page.objects.get(url_path='/home/')
- old_numchild = homepage.numchild
-
- # Break it
- homepage.numchild = 12345
- homepage.save()
-
- # Check that its broken
- self.assertEqual(Page.objects.get(url_path='/home/').numchild, 12345)
-
- # Call command
- self.run_command()
-
- # Check if its fixed
- self.assertEqual(Page.objects.get(url_path='/home/').numchild, old_numchild)
-
- def test_fixes_depth(self):
- # Get homepage and save old value
- homepage = Page.objects.get(url_path='/home/')
- old_depth = homepage.depth
-
- # Break it
- homepage.depth = 12345
- homepage.save()
-
- # Check that its broken
- self.assertEqual(Page.objects.get(url_path='/home/').depth, 12345)
-
- # Call command
- self.run_command()
-
- # Check if its fixed
- self.assertEqual(Page.objects.get(url_path='/home/').depth, old_depth)
-
-
-class TestMovePagesCommand(TestCase):
- fixtures = ['test.json']
-
- def run_command(self, from_, to):
- management.call_command('move_pages', str(from_), str(to), interactive=False, stdout=StringIO())
-
- def test_move_pages(self):
- # Get pages
- events_index = Page.objects.get(url_path='/home/events/')
- about_us = Page.objects.get(url_path='/home/about-us/')
- page_ids = events_index.get_children().values_list('id', flat=True)
-
- # Move all events into "about us"
- self.run_command(events_index.id, about_us.id)
-
- # Check that all pages moved
- for page_id in page_ids:
- self.assertEqual(Page.objects.get(id=page_id).get_parent(), about_us)
-
-
-class TestReplaceTextCommand(TestCase):
- fixtures = ['test.json']
-
- def run_command(self, from_text, to_text):
- management.call_command('replace_text', from_text, to_text, interactive=False, stdout=StringIO())
-
- def test_replace_text(self):
- # Check that the christmas page is definitely about christmas
- self.assertEqual(Page.objects.get(url_path='/home/events/christmas/').title, "Christmas")
-
- # Make it about easter
- self.run_command("Christmas", "Easter")
-
- # Check that its now about easter
- self.assertEqual(Page.objects.get(url_path='/home/events/christmas/').title, "Easter")
diff --git a/wagtail/wagtailcore/tests/__init__.py b/wagtail/wagtailcore/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/wagtail/wagtailcore/tests/test_management_commands.py b/wagtail/wagtailcore/tests/test_management_commands.py
new file mode 100644
index 000000000..787b808b5
--- /dev/null
+++ b/wagtail/wagtailcore/tests/test_management_commands.py
@@ -0,0 +1,89 @@
+from StringIO import StringIO
+
+from django.test import TestCase, Client
+from django.http import HttpRequest, Http404
+from django.core import management
+from django.contrib.auth.models import User
+
+from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy
+from wagtail.tests.models import EventPage, EventIndex, SimplePage
+
+
+class TestFixTreeCommand(TestCase):
+ fixtures = ['test.json']
+
+ def run_command(self):
+ management.call_command('fixtree', interactive=False, stdout=StringIO())
+
+ def test_fixes_numchild(self):
+ # Get homepage and save old value
+ homepage = Page.objects.get(url_path='/home/')
+ old_numchild = homepage.numchild
+
+ # Break it
+ homepage.numchild = 12345
+ homepage.save()
+
+ # Check that its broken
+ self.assertEqual(Page.objects.get(url_path='/home/').numchild, 12345)
+
+ # Call command
+ self.run_command()
+
+ # Check if its fixed
+ self.assertEqual(Page.objects.get(url_path='/home/').numchild, old_numchild)
+
+ def test_fixes_depth(self):
+ # Get homepage and save old value
+ homepage = Page.objects.get(url_path='/home/')
+ old_depth = homepage.depth
+
+ # Break it
+ homepage.depth = 12345
+ homepage.save()
+
+ # Check that its broken
+ self.assertEqual(Page.objects.get(url_path='/home/').depth, 12345)
+
+ # Call command
+ self.run_command()
+
+ # Check if its fixed
+ self.assertEqual(Page.objects.get(url_path='/home/').depth, old_depth)
+
+
+class TestMovePagesCommand(TestCase):
+ fixtures = ['test.json']
+
+ def run_command(self, from_, to):
+ management.call_command('move_pages', str(from_), str(to), interactive=False, stdout=StringIO())
+
+ def test_move_pages(self):
+ # Get pages
+ events_index = Page.objects.get(url_path='/home/events/')
+ about_us = Page.objects.get(url_path='/home/about-us/')
+ page_ids = events_index.get_children().values_list('id', flat=True)
+
+ # Move all events into "about us"
+ self.run_command(events_index.id, about_us.id)
+
+ # Check that all pages moved
+ for page_id in page_ids:
+ self.assertEqual(Page.objects.get(id=page_id).get_parent(), about_us)
+
+
+class TestReplaceTextCommand(TestCase):
+ fixtures = ['test.json']
+
+ def run_command(self, from_text, to_text):
+ management.call_command('replace_text', from_text, to_text, interactive=False, stdout=StringIO())
+
+ def test_replace_text(self):
+ # Check that the christmas page is definitely about christmas
+ self.assertEqual(Page.objects.get(url_path='/home/events/christmas/').title, "Christmas")
+
+ # Make it about easter
+ self.run_command("Christmas", "Easter")
+
+ # Check that its now about easter
+ self.assertEqual(Page.objects.get(url_path='/home/events/christmas/').title, "Easter")
diff --git a/wagtail/wagtailcore/tests/test_page_model.py b/wagtail/wagtailcore/tests/test_page_model.py
new file mode 100644
index 000000000..1bae57952
--- /dev/null
+++ b/wagtail/wagtailcore/tests/test_page_model.py
@@ -0,0 +1,226 @@
+from StringIO import StringIO
+
+from django.test import TestCase, Client
+from django.http import HttpRequest, Http404
+from django.core import management
+from django.contrib.auth.models import User
+
+from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy
+from wagtail.tests.models import EventPage, EventIndex, SimplePage
+
+
+class TestRouting(TestCase):
+ fixtures = ['test.json']
+
+ def test_find_site_for_request(self):
+ default_site = Site.objects.get(is_default_site=True)
+ events_page = Page.objects.get(url_path='/home/events/')
+ events_site = Site.objects.create(hostname='events.example.com', root_page=events_page)
+
+ # requests without a Host: header should be directed to the default site
+ request = HttpRequest()
+ request.path = '/'
+ self.assertEqual(Site.find_for_request(request), default_site)
+
+ # requests with a known Host: header should be directed to the specific site
+ request = HttpRequest()
+ request.path = '/'
+ request.META['HTTP_HOST'] = 'events.example.com'
+ self.assertEqual(Site.find_for_request(request), events_site)
+
+ # requests with an unrecognised Host: header should be directed to the default site
+ request = HttpRequest()
+ request.path = '/'
+ request.META['HTTP_HOST'] = 'unknown.example.com'
+ self.assertEqual(Site.find_for_request(request), default_site)
+
+ def test_urls(self):
+ default_site = Site.objects.get(is_default_site=True)
+ homepage = Page.objects.get(url_path='/home/')
+ christmas_page = Page.objects.get(url_path='/home/events/christmas/')
+
+ # Basic installation only has one site configured, so page.url will return local URLs
+ self.assertEqual(homepage.full_url, 'http://localhost/')
+ self.assertEqual(homepage.url, '/')
+ self.assertEqual(homepage.relative_url(default_site), '/')
+
+ self.assertEqual(christmas_page.full_url, 'http://localhost/events/christmas/')
+ self.assertEqual(christmas_page.url, '/events/christmas/')
+ self.assertEqual(christmas_page.relative_url(default_site), '/events/christmas/')
+
+ def test_urls_with_multiple_sites(self):
+ events_page = Page.objects.get(url_path='/home/events/')
+ events_site = Site.objects.create(hostname='events.example.com', root_page=events_page)
+
+ default_site = Site.objects.get(is_default_site=True)
+ homepage = Page.objects.get(url_path='/home/')
+ christmas_page = Page.objects.get(url_path='/home/events/christmas/')
+
+ # with multiple sites, page.url will return full URLs to ensure that
+ # they work across sites
+ self.assertEqual(homepage.full_url, 'http://localhost/')
+ self.assertEqual(homepage.url, 'http://localhost/')
+ self.assertEqual(homepage.relative_url(default_site), '/')
+ self.assertEqual(homepage.relative_url(events_site), 'http://localhost/')
+
+ self.assertEqual(christmas_page.full_url, 'http://events.example.com/christmas/')
+ self.assertEqual(christmas_page.url, 'http://events.example.com/christmas/')
+ self.assertEqual(christmas_page.relative_url(default_site), 'http://events.example.com/christmas/')
+ self.assertEqual(christmas_page.relative_url(events_site), '/christmas/')
+
+ def test_request_routing(self):
+ homepage = Page.objects.get(url_path='/home/')
+ christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
+
+ request = HttpRequest()
+ request.path = '/events/christmas/'
+ response = homepage.route(request, ['events', 'christmas'])
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context_data['self'], christmas_page)
+ used_template = response.resolve_template(response.template_name)
+ self.assertEqual(used_template.name, 'tests/event_page.html')
+
+ def test_route_to_unknown_page_returns_404(self):
+ homepage = Page.objects.get(url_path='/home/')
+
+ request = HttpRequest()
+ request.path = '/events/quinquagesima/'
+ with self.assertRaises(Http404):
+ homepage.route(request, ['events', 'quinquagesima'])
+
+ def test_route_to_unpublished_page_returns_404(self):
+ homepage = Page.objects.get(url_path='/home/')
+
+ request = HttpRequest()
+ request.path = '/events/tentative-unpublished-event/'
+ with self.assertRaises(Http404):
+ homepage.route(request, ['events', 'tentative-unpublished-event'])
+
+
+class TestServeView(TestCase):
+ fixtures = ['test.json']
+
+ def setUp(self):
+ # Explicitly clear the cache of site root paths. Normally this would be kept
+ # in sync by the Site.save logic, but this is bypassed when the database is
+ # rolled back between tests using transactions.
+ from django.core.cache import cache
+ cache.delete('wagtail_site_root_paths')
+
+ def test_serve(self):
+ response = self.client.get('/events/christmas/')
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.templates[0].name, 'tests/event_page.html')
+ christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
+ self.assertEqual(response.context['self'], christmas_page)
+
+ self.assertContains(response, '
Christmas
')
+ self.assertContains(response, '
Event
')
+
+ def test_serve_unknown_page_returns_404(self):
+ response = self.client.get('/events/quinquagesima/')
+ self.assertEqual(response.status_code, 404)
+
+ def test_serve_unpublished_page_returns_404(self):
+ response = self.client.get('/events/tentative-unpublished-event/')
+ self.assertEqual(response.status_code, 404)
+
+ def test_serve_with_multiple_sites(self):
+ events_page = Page.objects.get(url_path='/home/events/')
+ Site.objects.create(hostname='events.example.com', root_page=events_page)
+
+ response = self.client.get('/christmas/', HTTP_HOST='events.example.com')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.templates[0].name, 'tests/event_page.html')
+ christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
+ self.assertEqual(response.context['self'], christmas_page)
+
+ self.assertContains(response, '
Christmas
')
+ self.assertContains(response, '
Event
')
+
+ # same request to the default host should return a 404
+ c = Client()
+ response = c.get('/christmas/', HTTP_HOST='localhost')
+ self.assertEqual(response.status_code, 404)
+
+ def test_serve_with_custom_context(self):
+ response = self.client.get('/events/')
+ self.assertEqual(response.status_code, 200)
+
+ # should render the whole page
+ self.assertContains(response, '
Events
')
+
+ # response should contain data from the custom 'events' context variable
+ self.assertContains(response, '
Christmas')
+
+ def test_ajax_response(self):
+ response = self.client.get('/events/', HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+ self.assertEqual(response.status_code, 200)
+
+ # should only render the content of includes/event_listing.html, not the whole page
+ self.assertNotContains(response, '
Events
')
+ self.assertContains(response, '
Christmas')
+
+
+class TestStaticSitePaths(TestCase):
+ def setUp(self):
+ self.root_page = Page.objects.get(id=1)
+
+ # For simple tests
+ self.home_page = self.root_page.add_child(instance=SimplePage(title="Homepage", slug="home"))
+ self.about_page = self.home_page.add_child(instance=SimplePage(title="About us", slug="about"))
+ self.contact_page = self.home_page.add_child(instance=SimplePage(title="Contact", slug="contact"))
+
+ # For custom tests
+ self.event_index = self.root_page.add_child(instance=EventIndex(title="Events", slug="events"))
+ for i in range(20):
+ self.event_index.add_child(instance=EventPage(title="Event " + str(i), slug="event" + str(i)))
+
+ def test_local_static_site_paths(self):
+ paths = list(self.about_page.get_static_site_paths())
+
+ self.assertEqual(paths, ['/'])
+
+ def test_child_static_site_paths(self):
+ paths = list(self.home_page.get_static_site_paths())
+
+ self.assertEqual(paths, ['/', '/about/', '/contact/'])
+
+ def test_custom_static_site_paths(self):
+ paths = list(self.event_index.get_static_site_paths())
+
+ # Event index path
+ expected_paths = ['/']
+
+ # One path for each page of results
+ expected_paths.extend(['/' + str(i + 1) + '/' for i in range(5)])
+
+ # One path for each event page
+ expected_paths.extend(['/event' + str(i) + '/' for i in range(20)])
+
+ paths.sort()
+ expected_paths.sort()
+ self.assertEqual(paths, expected_paths)
+
+
+class TestMovePage(TestCase):
+ fixtures = ['test.json']
+
+ def test_move_page(self):
+ about_us_page = SimplePage.objects.get(url_path='/home/about-us/')
+ events_index = EventIndex.objects.get(url_path='/home/events/')
+
+ events_index.move(about_us_page, pos='last-child')
+
+ # re-fetch events index to confirm that db fields have been updated
+ events_index = EventIndex.objects.get(id=events_index.id)
+ self.assertEqual(events_index.url_path, '/home/about-us/events/')
+ self.assertEqual(events_index.depth, 4)
+ self.assertEqual(events_index.get_parent().id, about_us_page.id)
+
+ # children of events_index should also have been updated
+ christmas = events_index.get_children().get(slug='christmas')
+ self.assertEqual(christmas.depth, 5)
+ self.assertEqual(christmas.url_path, '/home/about-us/events/christmas/')
diff --git a/wagtail/wagtailcore/tests/test_page_permissions.py b/wagtail/wagtailcore/tests/test_page_permissions.py
new file mode 100644
index 000000000..dbe35e39b
--- /dev/null
+++ b/wagtail/wagtailcore/tests/test_page_permissions.py
@@ -0,0 +1,226 @@
+from StringIO import StringIO
+
+from django.test import TestCase, Client
+from django.http import HttpRequest, Http404
+from django.core import management
+from django.contrib.auth.models import User
+
+from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy
+from wagtail.tests.models import EventPage, EventIndex, SimplePage
+
+
+class TestPagePermission(TestCase):
+ fixtures = ['test.json']
+
+ def test_nonpublisher_page_permissions(self):
+ event_editor = User.objects.get(username='eventeditor')
+ homepage = Page.objects.get(url_path='/home/')
+ christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
+ unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
+ someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
+
+ homepage_perms = homepage.permissions_for_user(event_editor)
+ christmas_page_perms = christmas_page.permissions_for_user(event_editor)
+ unpub_perms = unpublished_event_page.permissions_for_user(event_editor)
+ someone_elses_event_perms = someone_elses_event_page.permissions_for_user(event_editor)
+
+ self.assertFalse(homepage_perms.can_add_subpage())
+ self.assertTrue(christmas_page_perms.can_add_subpage())
+ self.assertTrue(unpub_perms.can_add_subpage())
+ self.assertTrue(someone_elses_event_perms.can_add_subpage())
+
+ self.assertFalse(homepage_perms.can_edit())
+ self.assertTrue(christmas_page_perms.can_edit())
+ self.assertTrue(unpub_perms.can_edit())
+ self.assertFalse(someone_elses_event_perms.can_edit()) # basic 'add' permission doesn't allow editing pages owned by someone else
+
+ self.assertFalse(homepage_perms.can_delete())
+ self.assertFalse(christmas_page_perms.can_delete()) # cannot delete because it is published
+ self.assertTrue(unpub_perms.can_delete())
+ self.assertFalse(someone_elses_event_perms.can_delete())
+
+ self.assertFalse(homepage_perms.can_publish())
+ self.assertFalse(christmas_page_perms.can_publish())
+ self.assertFalse(unpub_perms.can_publish())
+
+ self.assertFalse(homepage_perms.can_unpublish())
+ self.assertFalse(christmas_page_perms.can_unpublish())
+ self.assertFalse(unpub_perms.can_unpublish())
+
+ self.assertFalse(homepage_perms.can_publish_subpage())
+ self.assertFalse(christmas_page_perms.can_publish_subpage())
+ self.assertFalse(unpub_perms.can_publish_subpage())
+
+ self.assertFalse(homepage_perms.can_reorder_children())
+ self.assertFalse(christmas_page_perms.can_reorder_children())
+ self.assertFalse(unpub_perms.can_reorder_children())
+
+ self.assertFalse(homepage_perms.can_move())
+ self.assertFalse(christmas_page_perms.can_move()) # cannot move because this would involve unpublishing from its current location
+ self.assertTrue(unpub_perms.can_move())
+ self.assertFalse(someone_elses_event_perms.can_move())
+
+ self.assertFalse(christmas_page_perms.can_move_to(unpublished_event_page)) # cannot move because this would involve unpublishing from its current location
+ self.assertTrue(unpub_perms.can_move_to(christmas_page))
+ self.assertFalse(unpub_perms.can_move_to(homepage)) # no permission to create pages at destination
+ self.assertFalse(unpub_perms.can_move_to(unpublished_event_page)) # cannot make page a child of itself
+
+
+ def test_publisher_page_permissions(self):
+ event_moderator = User.objects.get(username='eventmoderator')
+ homepage = Page.objects.get(url_path='/home/')
+ christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
+ unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
+
+ homepage_perms = homepage.permissions_for_user(event_moderator)
+ christmas_page_perms = christmas_page.permissions_for_user(event_moderator)
+ unpub_perms = unpublished_event_page.permissions_for_user(event_moderator)
+
+ self.assertFalse(homepage_perms.can_add_subpage())
+ self.assertTrue(christmas_page_perms.can_add_subpage())
+ self.assertTrue(unpub_perms.can_add_subpage())
+
+ self.assertFalse(homepage_perms.can_edit())
+ self.assertTrue(christmas_page_perms.can_edit())
+ self.assertTrue(unpub_perms.can_edit())
+
+ self.assertFalse(homepage_perms.can_delete())
+ self.assertTrue(christmas_page_perms.can_delete()) # cannot delete because it is published
+ self.assertTrue(unpub_perms.can_delete())
+
+ self.assertFalse(homepage_perms.can_publish())
+ self.assertTrue(christmas_page_perms.can_publish())
+ self.assertTrue(unpub_perms.can_publish())
+
+ self.assertFalse(homepage_perms.can_unpublish())
+ self.assertTrue(christmas_page_perms.can_unpublish())
+ self.assertFalse(unpub_perms.can_unpublish()) # cannot unpublish a page that isn't published
+
+ self.assertFalse(homepage_perms.can_publish_subpage())
+ self.assertTrue(christmas_page_perms.can_publish_subpage())
+ self.assertTrue(unpub_perms.can_publish_subpage())
+
+ self.assertFalse(homepage_perms.can_reorder_children())
+ self.assertTrue(christmas_page_perms.can_reorder_children())
+ self.assertTrue(unpub_perms.can_reorder_children())
+
+ self.assertFalse(homepage_perms.can_move())
+ self.assertTrue(christmas_page_perms.can_move())
+ self.assertTrue(unpub_perms.can_move())
+
+ self.assertTrue(christmas_page_perms.can_move_to(unpublished_event_page))
+ self.assertTrue(unpub_perms.can_move_to(christmas_page))
+ self.assertFalse(unpub_perms.can_move_to(homepage)) # no permission to create pages at destination
+ self.assertFalse(unpub_perms.can_move_to(unpublished_event_page)) # cannot make page a child of itself
+
+ def test_inactive_user_has_no_permissions(self):
+ user = User.objects.get(username='inactiveuser')
+ christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
+ unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
+
+ christmas_page_perms = christmas_page.permissions_for_user(user)
+ unpub_perms = unpublished_event_page.permissions_for_user(user)
+
+ self.assertFalse(unpub_perms.can_add_subpage())
+ self.assertFalse(unpub_perms.can_edit())
+ self.assertFalse(unpub_perms.can_delete())
+ self.assertFalse(unpub_perms.can_publish())
+ self.assertFalse(christmas_page_perms.can_unpublish())
+ self.assertFalse(unpub_perms.can_publish_subpage())
+ self.assertFalse(unpub_perms.can_reorder_children())
+ self.assertFalse(unpub_perms.can_move())
+ self.assertFalse(unpub_perms.can_move_to(christmas_page))
+
+ def test_superuser_has_full_permissions(self):
+ user = User.objects.get(username='superuser')
+ homepage = Page.objects.get(url_path='/home/')
+ root = Page.objects.get(url_path='/')
+ unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
+
+ homepage_perms = homepage.permissions_for_user(user)
+ root_perms = root.permissions_for_user(user)
+ unpub_perms = unpublished_event_page.permissions_for_user(user)
+
+ self.assertTrue(homepage_perms.can_add_subpage())
+ self.assertTrue(root_perms.can_add_subpage())
+
+ self.assertTrue(homepage_perms.can_edit())
+ self.assertFalse(root_perms.can_edit()) # root is not a real editable page, even to superusers
+
+ self.assertTrue(homepage_perms.can_delete())
+ self.assertFalse(root_perms.can_delete())
+
+ self.assertTrue(homepage_perms.can_publish())
+ self.assertFalse(root_perms.can_publish())
+
+ self.assertTrue(homepage_perms.can_unpublish())
+ self.assertFalse(root_perms.can_unpublish())
+ self.assertFalse(unpub_perms.can_unpublish())
+
+ self.assertTrue(homepage_perms.can_publish_subpage())
+ self.assertTrue(root_perms.can_publish_subpage())
+
+ self.assertTrue(homepage_perms.can_reorder_children())
+ self.assertTrue(root_perms.can_reorder_children())
+
+ self.assertTrue(homepage_perms.can_move())
+ self.assertFalse(root_perms.can_move())
+
+ self.assertTrue(homepage_perms.can_move_to(root))
+ self.assertFalse(homepage_perms.can_move_to(unpublished_event_page))
+
+ def test_editable_pages_for_user_with_add_permission(self):
+ event_editor = User.objects.get(username='eventeditor')
+ homepage = Page.objects.get(url_path='/home/')
+ christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
+ unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
+ someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
+
+ editable_pages = UserPagePermissionsProxy(event_editor).editable_pages()
+
+ self.assertFalse(editable_pages.filter(id=homepage.id).exists())
+ self.assertTrue(editable_pages.filter(id=christmas_page.id).exists())
+ self.assertTrue(editable_pages.filter(id=unpublished_event_page.id).exists())
+ self.assertFalse(editable_pages.filter(id=someone_elses_event_page.id).exists())
+
+ def test_editable_pages_for_user_with_edit_permission(self):
+ event_moderator = User.objects.get(username='eventmoderator')
+ homepage = Page.objects.get(url_path='/home/')
+ christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
+ unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
+ someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
+
+ editable_pages = UserPagePermissionsProxy(event_moderator).editable_pages()
+
+ self.assertFalse(editable_pages.filter(id=homepage.id).exists())
+ self.assertTrue(editable_pages.filter(id=christmas_page.id).exists())
+ self.assertTrue(editable_pages.filter(id=unpublished_event_page.id).exists())
+ self.assertTrue(editable_pages.filter(id=someone_elses_event_page.id).exists())
+
+ def test_editable_pages_for_inactive_user(self):
+ user = User.objects.get(username='inactiveuser')
+ homepage = Page.objects.get(url_path='/home/')
+ christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
+ unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
+ someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
+
+ editable_pages = UserPagePermissionsProxy(user).editable_pages()
+
+ self.assertFalse(editable_pages.filter(id=homepage.id).exists())
+ self.assertFalse(editable_pages.filter(id=christmas_page.id).exists())
+ self.assertFalse(editable_pages.filter(id=unpublished_event_page.id).exists())
+ self.assertFalse(editable_pages.filter(id=someone_elses_event_page.id).exists())
+
+ def test_editable_pages_for_superuser(self):
+ user = User.objects.get(username='superuser')
+ homepage = Page.objects.get(url_path='/home/')
+ christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
+ unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
+ someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
+
+ editable_pages = UserPagePermissionsProxy(user).editable_pages()
+
+ self.assertTrue(editable_pages.filter(id=homepage.id).exists())
+ self.assertTrue(editable_pages.filter(id=christmas_page.id).exists())
+ self.assertTrue(editable_pages.filter(id=unpublished_event_page.id).exists())
+ self.assertTrue(editable_pages.filter(id=someone_elses_event_page.id).exists())
diff --git a/wagtail/wagtailcore/tests/test_page_queryset.py b/wagtail/wagtailcore/tests/test_page_queryset.py
new file mode 100644
index 000000000..06f2c3e21
--- /dev/null
+++ b/wagtail/wagtailcore/tests/test_page_queryset.py
@@ -0,0 +1,256 @@
+from StringIO import StringIO
+
+from django.test import TestCase, Client
+from django.http import HttpRequest, Http404
+from django.core import management
+from django.contrib.auth.models import User
+
+from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy
+from wagtail.tests.models import EventPage, EventIndex, SimplePage
+
+
+class TestPageQuerySet(TestCase):
+ fixtures = ['test.json']
+
+ def test_live(self):
+ pages = Page.objects.live()
+
+ # All pages must be live
+ for page in pages:
+ self.assertTrue(page.live)
+
+ # Check that the homepage is in the results
+ homepage = Page.objects.get(url_path='/home/')
+ self.assertTrue(pages.filter(id=homepage.id).exists())
+
+ def test_not_live(self):
+ pages = Page.objects.not_live()
+
+ # All pages must not be live
+ for page in pages:
+ self.assertFalse(page.live)
+
+ # Check that "someone elses event" is in the results
+ event = Page.objects.get(url_path='/home/events/someone-elses-event/')
+ self.assertTrue(pages.filter(id=event.id).exists())
+
+ def test_page(self):
+ homepage = Page.objects.get(url_path='/home/')
+ pages = Page.objects.page(homepage)
+
+ # Should only select the homepage
+ self.assertEqual(pages.count(), 1)
+ self.assertEqual(pages.first(), homepage)
+
+ def test_not_page(self):
+ homepage = Page.objects.get(url_path='/home/')
+ pages = Page.objects.not_page(homepage)
+
+ # Should select everything except for the homepage
+ self.assertEqual(pages.count(), Page.objects.all().count() - 1)
+ for page in pages:
+ self.assertNotEqual(page, homepage)
+
+ def test_descendant_of(self):
+ events_index = Page.objects.get(url_path='/home/events/')
+ pages = Page.objects.descendant_of(events_index)
+
+ # Check that all pages descend from events index
+ for page in pages:
+ self.assertTrue(page.get_ancestors().filter(id=events_index.id).exists())
+
+ def test_descendant_of_inclusive(self):
+ events_index = Page.objects.get(url_path='/home/events/')
+ pages = Page.objects.descendant_of(events_index, inclusive=True)
+
+ # Check that all pages descend from events index, includes event index
+ for page in pages:
+ self.assertTrue(page == events_index or page.get_ancestors().filter(id=events_index.id).exists())
+
+ # Check that event index was included
+ self.assertTrue(pages.filter(id=events_index.id).exists())
+
+ def test_not_descendant_of(self):
+ homepage = Page.objects.get(url_path='/home/')
+ events_index = Page.objects.get(url_path='/home/events/')
+ pages = Page.objects.not_descendant_of(events_index)
+
+ # Check that no pages descend from events_index
+ for page in pages:
+ self.assertFalse(page.get_ancestors().filter(id=events_index.id).exists())
+
+ # As this is not inclusive, events index should be in the results
+ self.assertTrue(pages.filter(id=events_index.id).exists())
+
+ def test_not_descendant_of_inclusive(self):
+ homepage = Page.objects.get(url_path='/home/')
+ events_index = Page.objects.get(url_path='/home/events/')
+ pages = Page.objects.not_descendant_of(events_index, inclusive=True)
+
+ # Check that all pages descend from homepage but not events index
+ for page in pages:
+ self.assertFalse(page.get_ancestors().filter(id=events_index.id).exists())
+
+ # As this is inclusive, events index should not be in the results
+ self.assertFalse(pages.filter(id=events_index.id).exists())
+
+ def test_child_of(self):
+ homepage = Page.objects.get(url_path='/home/')
+ pages = Page.objects.child_of(homepage)
+
+ # Check that all pages are children of homepage
+ for page in pages:
+ self.assertEqual(page.get_parent(), homepage)
+
+ def test_not_child_of(self):
+ events_index = Page.objects.get(url_path='/home/events/')
+ pages = Page.objects.not_child_of(events_index)
+
+ # Check that all pages are not children of events_index
+ for page in pages:
+ self.assertNotEqual(page.get_parent(), events_index)
+
+ def test_ancestor_of(self):
+ root_page = Page.objects.get(id=1)
+ homepage = Page.objects.get(url_path='/home/')
+ events_index = Page.objects.get(url_path='/home/events/')
+ pages = Page.objects.ancestor_of(events_index)
+
+ self.assertEqual(pages.count(), 2)
+ self.assertEqual(pages[0], root_page)
+ self.assertEqual(pages[1], homepage)
+
+ def test_ancestor_of_inclusive(self):
+ root_page = Page.objects.get(id=1)
+ homepage = Page.objects.get(url_path='/home/')
+ events_index = Page.objects.get(url_path='/home/events/')
+ pages = Page.objects.ancestor_of(events_index, inclusive=True)
+
+ self.assertEqual(pages.count(), 3)
+ self.assertEqual(pages[0], root_page)
+ self.assertEqual(pages[1], homepage)
+ self.assertEqual(pages[2], events_index)
+
+ def test_not_ancestor_of(self):
+ root_page = Page.objects.get(id=1)
+ homepage = Page.objects.get(url_path='/home/')
+ events_index = Page.objects.get(url_path='/home/events/')
+ pages = Page.objects.not_ancestor_of(events_index)
+
+ # Test that none of the ancestors are in pages
+ for page in pages:
+ self.assertNotEqual(page, root_page)
+ self.assertNotEqual(page, homepage)
+
+ # Test that events index is in pages
+ self.assertTrue(pages.filter(id=events_index.id).exists())
+
+ def test_not_ancestor_of_inclusive(self):
+ root_page = Page.objects.get(id=1)
+ homepage = Page.objects.get(url_path='/home/')
+ events_index = Page.objects.get(url_path='/home/events/')
+ pages = Page.objects.not_ancestor_of(events_index, inclusive=True)
+
+ # Test that none of the ancestors or the events_index are in pages
+ for page in pages:
+ self.assertNotEqual(page, root_page)
+ self.assertNotEqual(page, homepage)
+ self.assertNotEqual(page, events_index)
+
+ def test_parent_of(self):
+ homepage = Page.objects.get(url_path='/home/')
+ events_index = Page.objects.get(url_path='/home/events/')
+ pages = Page.objects.parent_of(events_index)
+
+ # Pages must only contain homepage
+ self.assertEqual(pages.count(), 1)
+ self.assertEqual(pages[0], homepage)
+
+ def test_not_parent_of(self):
+ homepage = Page.objects.get(url_path='/home/')
+ events_index = Page.objects.get(url_path='/home/events/')
+ pages = Page.objects.not_parent_of(events_index)
+
+ # Pages must not contain homepage
+ for page in pages:
+ self.assertNotEqual(page, homepage)
+
+ # Test that events index is in pages
+ self.assertTrue(pages.filter(id=events_index.id).exists())
+
+ def test_sibling_of(self):
+ events_index = Page.objects.get(url_path='/home/events/')
+ event = Page.objects.get(url_path='/home/events/christmas/')
+ pages = Page.objects.sibling_of(event)
+
+ # Check that all pages are children of events_index
+ for page in pages:
+ self.assertEqual(page.get_parent(), events_index)
+
+ # Check that the event is not included
+ self.assertFalse(pages.filter(id=event.id).exists())
+
+ def test_sibling_of_inclusive(self):
+ events_index = Page.objects.get(url_path='/home/events/')
+ event = Page.objects.get(url_path='/home/events/christmas/')
+ pages = Page.objects.sibling_of(event, inclusive=True)
+
+ # Check that all pages are children of events_index
+ for page in pages:
+ self.assertEqual(page.get_parent(), events_index)
+
+ # Check that the event is included
+ self.assertTrue(pages.filter(id=event.id).exists())
+
+ def test_not_sibling_of(self):
+ events_index = Page.objects.get(url_path='/home/events/')
+ event = Page.objects.get(url_path='/home/events/christmas/')
+ pages = Page.objects.not_sibling_of(event)
+
+ # Check that all pages are not children of events_index
+ for page in pages:
+ if page != event:
+ self.assertNotEqual(page.get_parent(), events_index)
+
+ # Check that the event is included
+ self.assertTrue(pages.filter(id=event.id).exists())
+
+ # Test that events index is in pages
+ self.assertTrue(pages.filter(id=events_index.id).exists())
+
+ def test_not_sibling_of_inclusive(self):
+ events_index = Page.objects.get(url_path='/home/events/')
+ event = Page.objects.get(url_path='/home/events/christmas/')
+ pages = Page.objects.not_sibling_of(event, inclusive=True)
+
+ # Check that all pages are not children of events_index
+ for page in pages:
+ self.assertNotEqual(page.get_parent(), events_index)
+
+ # Check that the event is not included
+ self.assertFalse(pages.filter(id=event.id).exists())
+
+ # Test that events index is in pages
+ self.assertTrue(pages.filter(id=events_index.id).exists())
+
+ def test_type(self):
+ pages = Page.objects.type(EventPage)
+
+ # Check that all objects are EventPages
+ for page in pages:
+ self.assertIsInstance(page.specific, EventPage)
+
+ # Check that "someone elses event" is in the results
+ event = Page.objects.get(url_path='/home/events/someone-elses-event/')
+ self.assertTrue(pages.filter(id=event.id).exists())
+
+ def test_not_type(self):
+ pages = Page.objects.not_type(EventPage)
+
+ # Check that no objects are EventPages
+ for page in pages:
+ self.assertNotIsInstance(page.specific, EventPage)
+
+ # Check that the homepage is in the results
+ homepage = Page.objects.get(url_path='/home/')
+ self.assertTrue(pages.filter(id=homepage.id).exists())
diff --git a/wagtail/wagtailcore/tests/test_whitelist.py b/wagtail/wagtailcore/tests/test_whitelist.py
new file mode 100644
index 000000000..28b63d9fb
--- /dev/null
+++ b/wagtail/wagtailcore/tests/test_whitelist.py
@@ -0,0 +1,136 @@
+from bs4 import BeautifulSoup, NavigableString
+
+from django.test import TestCase
+from wagtail.wagtailcore.whitelist import (
+ check_url,
+ attribute_rule,
+ allow_without_attributes,
+ Whitelister
+)
+
+class TestCheckUrl(TestCase):
+ def test_allowed_url_schemes(self):
+ for url_scheme in ['', 'http', 'https', 'ftp', 'mailto', 'tel']:
+ url = url_scheme + "://www.example.com"
+ self.assertTrue(bool(check_url(url)))
+
+ def test_disallowed_url_scheme(self):
+ self.assertFalse(bool(check_url("invalid://url")))
+
+
+class TestAttributeRule(TestCase):
+ def setUp(self):
+ self.soup = BeautifulSoup('
baz')
+
+ def test_no_rule_for_attr(self):
+ """
+ Test that attribute_rule() drops attributes for
+ which no rule has been defined.
+ """
+ tag = self.soup.b
+ fn = attribute_rule({'snowman': 'barbecue'})
+ fn(tag)
+ self.assertEqual(str(tag), '
baz')
+
+ def test_rule_true_for_attr(self):
+ """
+ Test that attribute_rule() does not change atrributes
+ when the corresponding rule returns True
+ """
+ tag = self.soup.b
+ fn = attribute_rule({'foo': True})
+ fn(tag)
+ self.assertEqual(str(tag), '
baz')
+
+ def test_rule_false_for_attr(self):
+ """
+ Test that attribute_rule() drops atrributes
+ when the corresponding rule returns False
+ """
+ tag = self.soup.b
+ fn = attribute_rule({'foo': False})
+ fn(tag)
+ self.assertEqual(str(tag), '
baz')
+
+ def test_callable_called_on_attr(self):
+ """
+ Test that when the rule returns a callable,
+ attribute_rule() replaces the attribute with
+ the result of calling the callable on the attribute.
+ """
+ tag = self.soup.b
+ fn = attribute_rule({'foo': len})
+ fn(tag)
+ self.assertEqual(str(tag), '
baz')
+
+ def test_callable_returns_None(self):
+ """
+ Test that when the rule returns a callable,
+ attribute_rule() replaces the attribute with
+ the result of calling the callable on the attribute.
+ """
+ tag = self.soup.b
+ fn = attribute_rule({'foo': lambda x: None})
+ fn(tag)
+ self.assertEqual(str(tag), '
baz')
+
+ def test_allow_without_attributes(self):
+ """
+ Test that attribute_rule() with will drop all
+ attributes.
+ """
+ soup = BeautifulSoup('
')
+ tag = soup.b
+ allow_without_attributes(tag)
+ self.assertEqual(str(tag), '
')
+
+
+class TestWhitelister(TestCase):
+ def test_clean_unknown_node(self):
+ """
+ Unknown node should remove a node from the parent document
+ """
+ soup = BeautifulSoup('
bazquux')
+ tag = soup.foo
+ Whitelister.clean_unknown_node('', soup.bar)
+ self.assertEqual(str(tag), '
quux')
+
+ def test_clean_tag_node_cleans_nested_recognised_node(self):
+ """
+
tags are allowed without attributes. This remains true
+ when tags are nested.
+ """
+ soup = BeautifulSoup('foo')
+ tag = soup.b
+ Whitelister.clean_tag_node(tag, tag)
+ self.assertEqual(str(tag), 'foo')
+
+ def test_clean_tag_node_disallows_nested_unrecognised_node(self):
+ """
+ tags should be removed, even when nested.
+ """
+ soup = BeautifulSoup('bar')
+ tag = soup.b
+ Whitelister.clean_tag_node(tag, tag)
+ self.assertEqual(str(tag), 'bar')
+
+ def test_clean_string_node_does_nothing(self):
+ soup = BeautifulSoup('bar')
+ string = soup.b.string
+ Whitelister.clean_string_node(string, string)
+ self.assertEqual(str(string), 'bar')
+
+ def test_clean_node_does_not_change_navigable_strings(self):
+ soup = BeautifulSoup('bar')
+ string = soup.b.string
+ Whitelister.clean_node(string, string)
+ self.assertEqual(str(string), 'bar')
+
+ def test_clean(self):
+ """
+ Whitelister.clean should remove disallowed tags and attributes from
+ a string
+ """
+ string = 'snowman Yorkshire'
+ cleaned_string = Whitelister.clean(string)
+ self.assertEqual(cleaned_string, 'snowman Yorkshire')
diff --git a/wagtail/wagtailcore/tests/tests.py b/wagtail/wagtailcore/tests/tests.py
new file mode 100644
index 000000000..d10474bba
--- /dev/null
+++ b/wagtail/wagtailcore/tests/tests.py
@@ -0,0 +1,99 @@
+from StringIO import StringIO
+
+from django.test import TestCase, Client
+from django.http import HttpRequest, Http404
+from django.core import management
+from django.contrib.auth.models import User
+
+from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy
+from wagtail.tests.models import EventPage, EventIndex, SimplePage
+
+
+class TestPageUrlTags(TestCase):
+ fixtures = ['test.json']
+
+ def test_pageurl_tag(self):
+ response = self.client.get('/events/')
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, 'Christmas')
+
+ def test_slugurl_tag(self):
+ response = self.client.get('/events/christmas/')
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, 'Back to events index')
+
+
+class TestIssue7(TestCase):
+ """
+ This tests for an issue where if a site root page was moved, all the page
+ urls in that site would change to None.
+
+ The issue was caused by the 'wagtail_site_root_paths' cache variable not being
+ cleared when a site root page was moved. Which left all the child pages
+ thinking that they are no longer in the site and return None as their url.
+
+ Fix: d6cce69a397d08d5ee81a8cbc1977ab2c9db2682
+ Discussion: https://github.com/torchbox/wagtail/issues/7
+ """
+
+ fixtures = ['test.json']
+
+ def test_issue7(self):
+ # Get homepage, root page and site
+ root_page = Page.objects.get(id=1)
+ homepage = Page.objects.get(url_path='/home/')
+ default_site = Site.objects.get(is_default_site=True)
+
+ # Create a new homepage under current homepage
+ new_homepage = SimplePage(title="New Homepage", slug="new-homepage")
+ homepage.add_child(instance=new_homepage)
+
+ # Set new homepage as the site root page
+ default_site.root_page = new_homepage
+ default_site.save()
+
+ # Warm up the cache by getting the url
+ _ = homepage.url
+
+ # Move new homepage to root
+ new_homepage.move(root_page, pos='last-child')
+
+ # Get fresh instance of new_homepage
+ new_homepage = Page.objects.get(id=new_homepage.id)
+
+ # Check url
+ self.assertEqual(new_homepage.url, '/')
+
+
+class TestIssue157(TestCase):
+ """
+ This tests for an issue where if a site root pages slug was changed, all the page
+ urls in that site would change to None.
+
+ The issue was caused by the 'wagtail_site_root_paths' cache variable not being
+ cleared when a site root page was changed. Which left all the child pages
+ thinking that they are no longer in the site and return None as their url.
+
+ Fix: d6cce69a397d08d5ee81a8cbc1977ab2c9db2682
+ Discussion: https://github.com/torchbox/wagtail/issues/157
+ """
+
+ fixtures = ['test.json']
+
+ def test_issue157(self):
+ # Get homepage
+ homepage = Page.objects.get(url_path='/home/')
+
+ # Warm up the cache by getting the url
+ _ = homepage.url
+
+ # Change homepage title and slug
+ homepage.title = "New home"
+ homepage.slug = "new-home"
+ homepage.save()
+
+ # Get fresh instance of homepage
+ homepage = Page.objects.get(id=homepage.id)
+
+ # Check url
+ self.assertEqual(homepage.url, '/')
diff --git a/wagtail/wagtailcore/whitelist.py b/wagtail/wagtailcore/whitelist.py
index 508682cda..a3d377bd6 100644
--- a/wagtail/wagtailcore/whitelist.py
+++ b/wagtail/wagtailcore/whitelist.py
@@ -89,7 +89,10 @@ class Whitelister(object):
cls.clean_string_node(doc, node)
elif isinstance(node, Tag):
cls.clean_tag_node(doc, node)
- else:
+ # This branch is here in case node is a BeautifulSoup object that does
+ # not inherit from NavigableString or Tag. I can't find any examples
+ # of such a thing at the moment, so this branch is untested.
+ else: # pragma: no cover
cls.clean_unknown_node(doc, node)
@classmethod
diff --git a/wagtail/wagtaildocs/tests.py b/wagtail/wagtaildocs/tests.py
index d068f9a27..3353ce39e 100644
--- a/wagtail/wagtaildocs/tests.py
+++ b/wagtail/wagtaildocs/tests.py
@@ -1,6 +1,6 @@
from django.test import TestCase
from wagtail.wagtaildocs import models
-from wagtail.tests.utils import login
+from wagtail.tests.utils import WagtailTestUtils
from django.contrib.auth.models import User, Group, Permission
from django.core.urlresolvers import reverse
from django.core.files.base import ContentFile
@@ -39,15 +39,17 @@ class TestDocumentPermissions(TestCase):
## ===== ADMIN VIEWS =====
-class TestDocumentIndexView(TestCase):
+class TestDocumentIndexView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get(reverse('wagtaildocs_index'), params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtaildocs/documents/index.html')
def test_search(self):
response = self.get({'q': "Hello"})
@@ -67,20 +69,24 @@ class TestDocumentIndexView(TestCase):
self.assertEqual(response.status_code, 200)
-class TestDocumentAddView(TestCase):
+class TestDocumentAddView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get(reverse('wagtaildocs_add_document'), params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtaildocs/documents/add.html')
+
+ # TODO: Test posting
-class TestDocumentEditView(TestCase):
+class TestDocumentEditView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
# Create a document to edit
self.document = models.Document.objects.create(title="Test document")
@@ -88,13 +94,17 @@ class TestDocumentEditView(TestCase):
def get(self, params={}):
return self.client.get(reverse('wagtaildocs_edit_document', args=(self.document.id,)), params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtaildocs/documents/edit.html')
+
+ # TODO: Test posting
-class TestDocumentDeleteView(TestCase):
+class TestDocumentDeleteView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
# Create a document to delete
self.document = models.Document.objects.create(title="Test document")
@@ -102,19 +112,26 @@ class TestDocumentDeleteView(TestCase):
def get(self, params={}):
return self.client.get(reverse('wagtaildocs_delete_document', args=(self.document.id,)), params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtaildocs/documents/confirm_delete.html')
+
+ # TODO: Test posting
-class TestDocumentChooserView(TestCase):
+class TestDocumentChooserView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get(reverse('wagtaildocs_chooser'), params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtaildocs/chooser/chooser.html')
+ self.assertTemplateUsed(response, 'wagtaildocs/chooser/chooser.js')
def test_search(self):
response = self.get({'q': "Hello"})
@@ -128,9 +145,9 @@ class TestDocumentChooserView(TestCase):
self.assertEqual(response.status_code, 200)
-class TestDocumentChooserChosenView(TestCase):
+class TestDocumentChooserChosenView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
# Create a document to choose
self.document = models.Document.objects.create(title="Test document")
@@ -138,19 +155,28 @@ class TestDocumentChooserChosenView(TestCase):
def get(self, params={}):
return self.client.get(reverse('wagtaildocs_document_chosen', args=(self.document.id,)), params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtaildocs/chooser/document_chosen.js')
+
+ # TODO: Test posting
-class TestDocumentChooserUploadView(TestCase):
+class TestDocumentChooserUploadView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get(reverse('wagtaildocs_chooser_upload'), params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtaildocs/chooser/chooser.html')
+ self.assertTemplateUsed(response, 'wagtaildocs/chooser/chooser.js')
+
+ # TODO: Test document upload with chooser
class TestDocumentFilenameProperties(TestCase):
diff --git a/wagtail/wagtailembeds/embeds.py b/wagtail/wagtailembeds/embeds.py
index f8ab35f16..03029a23b 100644
--- a/wagtail/wagtailembeds/embeds.py
+++ b/wagtail/wagtailembeds/embeds.py
@@ -16,7 +16,6 @@ import json
class EmbedNotFoundException(Exception): pass
-
class EmbedlyException(Exception): pass
class AccessDeniedEmbedlyException(EmbedlyException): pass
@@ -52,7 +51,7 @@ def embedly(url, max_width=None, key=None):
key = settings.EMBEDLY_KEY
# Get embedly client
- client = Embedly(key=settings.EMBEDLY_KEY)
+ client = Embedly(key=key)
# Call embedly
if max_width is not None:
diff --git a/wagtail/wagtailembeds/tests.py b/wagtail/wagtailembeds/tests.py
index b1eefdb17..e10c3f4c0 100644
--- a/wagtail/wagtailembeds/tests.py
+++ b/wagtail/wagtailembeds/tests.py
@@ -1,7 +1,27 @@
+from mock import patch
+import urllib2
+
+try:
+ import embedly
+ no_embedly = False
+except ImportError:
+ no_embedly = True
+
from django.test import TestCase
from django.test.client import Client
-from wagtail.tests.utils import login
+
+from wagtail.tests.utils import WagtailTestUtils
+from wagtail.tests.utils import unittest
+
from wagtail.wagtailembeds import get_embed
+from wagtail.wagtailembeds.embeds import (
+ EmbedNotFoundException,
+ EmbedlyException,
+ AccessDeniedEmbedlyException,
+)
+from wagtail.wagtailembeds.embeds import embedly as wagtail_embedly
+from wagtail.wagtailembeds.embeds import oembed as wagtail_oembed
+
class TestEmbeds(TestCase):
@@ -63,13 +83,168 @@ class TestEmbeds(TestCase):
self.assertEqual(embed.width, None)
-class TestChooser(TestCase):
+class TestChooser(TestCase, WagtailTestUtils):
def setUp(self):
# login
- login(self.client)
+ self.login()
def test_chooser(self):
r = self.client.get('/admin/embeds/chooser/')
self.assertEqual(r.status_code, 200)
# TODO: Test submitting
+
+class TestEmbedly(TestCase):
+ @unittest.skipIf(no_embedly, "Embedly is not installed")
+ def test_embedly_oembed_called_with_correct_arguments(self):
+ with patch('embedly.Embedly.oembed') as oembed:
+ oembed.return_value = {'type': 'photo',
+ 'url': 'http://www.example.com'}
+
+ wagtail_embedly('http://www.example.com', key='foo')
+ oembed.assert_called_with('http://www.example.com', better=False)
+
+ wagtail_embedly('http://www.example.com', max_width=100, key='foo')
+ oembed.assert_called_with('http://www.example.com', maxwidth=100, better=False)
+
+ @unittest.skipIf(no_embedly, "Embedly is not installed")
+ def test_embedly_401(self):
+ with patch('embedly.Embedly.oembed') as oembed:
+ oembed.return_value = {'type': 'photo',
+ 'url': 'http://www.example.com',
+ 'error': True,
+ 'error_code': 401}
+ self.assertRaises(AccessDeniedEmbedlyException,
+ wagtail_embedly, 'http://www.example.com', key='foo')
+
+ @unittest.skipIf(no_embedly, "Embedly is not installed")
+ def test_embedly_403(self):
+ with patch('embedly.Embedly.oembed') as oembed:
+ oembed.return_value = {'type': 'photo',
+ 'url': 'http://www.example.com',
+ 'error': True,
+ 'error_code': 403}
+ self.assertRaises(AccessDeniedEmbedlyException,
+ wagtail_embedly, 'http://www.example.com', key='foo')
+
+ @unittest.skipIf(no_embedly, "Embedly is not installed")
+ def test_embedly_404(self):
+ with patch('embedly.Embedly.oembed') as oembed:
+ oembed.return_value = {'type': 'photo',
+ 'url': 'http://www.example.com',
+ 'error': True,
+ 'error_code': 404}
+ self.assertRaises(EmbedNotFoundException,
+ wagtail_embedly, 'http://www.example.com', key='foo')
+
+ @unittest.skipIf(no_embedly, "Embedly is not installed")
+ def test_embedly_other_error(self):
+ with patch('embedly.Embedly.oembed') as oembed:
+ oembed.return_value = {'type': 'photo',
+ 'url': 'http://www.example.com',
+ 'error': True,
+ 'error_code': 999}
+ self.assertRaises(EmbedlyException, wagtail_embedly,
+ 'http://www.example.com', key='foo')
+
+ @unittest.skipIf(no_embedly, "Embedly is not installed")
+ def test_embedly_html_conversion(self):
+ with patch('embedly.Embedly.oembed') as oembed:
+ oembed.return_value = {'type': 'photo',
+ 'url': 'http://www.example.com'}
+ result = wagtail_embedly('http://www.example.com', key='foo')
+ self.assertEqual(result['html'], '
')
+
+ oembed.return_value = {'type': 'something else',
+ 'html': 'bar'}
+ result = wagtail_embedly('http://www.example.com', key='foo')
+ self.assertEqual(result['html'], 'bar')
+
+ @unittest.skipIf(no_embedly, "Embedly is not installed")
+ def test_embedly_return_value(self):
+ with patch('embedly.Embedly.oembed') as oembed:
+ oembed.return_value = {'type': 'something else',
+ 'html': 'bar'}
+ result = wagtail_embedly('http://www.example.com', key='foo')
+ self.assertEqual(result, {
+ 'title': '',
+ 'author_name': '',
+ 'provider_name': '',
+ 'type': 'something else',
+ 'thumbnail_url': None,
+ 'width': None,
+ 'height': None,
+ 'html': 'bar'})
+
+ oembed.return_value = {'type': 'something else',
+ 'author_name': 'Alice',
+ 'provider_name': 'Bob',
+ 'title': 'foo',
+ 'thumbnail_url': 'http://www.example.com',
+ 'width': 100,
+ 'height': 100,
+ 'html': 'bar'}
+ result = wagtail_embedly('http://www.example.com', key='foo')
+ self.assertEqual(result, {'type': 'something else',
+ 'author_name': 'Alice',
+ 'provider_name': 'Bob',
+ 'title': 'foo',
+ 'thumbnail_url': 'http://www.example.com',
+ 'width': 100,
+ 'height': 100,
+ 'html': 'bar'})
+
+
+class TestOembed(TestCase):
+ def setUp(self):
+ class DummyResponse(object):
+ def read(self):
+ return "foo"
+ self.dummy_response = DummyResponse()
+
+ def test_oembed_invalid_provider(self):
+ self.assertRaises(EmbedNotFoundException, wagtail_oembed, "foo")
+
+ def test_oembed_invalid_request(self):
+ config = {'side_effect': urllib2.URLError('foo')}
+ with patch.object(urllib2, 'urlopen', **config) as urlopen:
+ self.assertRaises(EmbedNotFoundException, wagtail_oembed,
+ "http://www.youtube.com/watch/")
+
+ @patch('urllib2.urlopen')
+ @patch('json.loads')
+ def test_oembed_photo_request(self, loads, urlopen) :
+ urlopen.return_value = self.dummy_response
+ loads.return_value = {'type': 'photo',
+ 'url': 'http://www.example.com'}
+ result = wagtail_oembed("http://www.youtube.com/watch/")
+ self.assertEqual(result['type'], 'photo')
+ self.assertEqual(result['html'], '
')
+ loads.assert_called_with("foo")
+
+ @patch('urllib2.urlopen')
+ @patch('json.loads')
+ def test_oembed_return_values(self, loads, urlopen):
+ urlopen.return_value = self.dummy_response
+ loads.return_value = {
+ 'type': 'something',
+ 'url': 'http://www.example.com',
+ 'title': 'test_title',
+ 'author_name': 'test_author',
+ 'provider_name': 'test_provider_name',
+ 'thumbnail_url': 'test_thumbail_url',
+ 'width': 'test_width',
+ 'height': 'test_height',
+ 'html': 'test_html'
+ }
+ result = wagtail_oembed("http://www.youtube.com/watch/")
+ self.assertEqual(result, {
+ 'type': 'something',
+ 'title': 'test_title',
+ 'author_name': 'test_author',
+ 'provider_name': 'test_provider_name',
+ 'thumbnail_url': 'test_thumbail_url',
+ 'width': 'test_width',
+ 'height': 'test_height',
+ 'html': 'test_html'
+ })
diff --git a/wagtail/wagtailimages/backends/base.py b/wagtail/wagtailimages/backends/base.py
index 25b7e882d..ce07420e3 100644
--- a/wagtail/wagtailimages/backends/base.py
+++ b/wagtail/wagtailimages/backends/base.py
@@ -1,6 +1,7 @@
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
+
class InvalidImageBackendError(ImproperlyConfigured):
pass
@@ -8,23 +9,22 @@ class InvalidImageBackendError(ImproperlyConfigured):
class BaseImageBackend(object):
def __init__(self, params):
self.quality = getattr(settings, 'IMAGE_COMPRESSION_QUALITY', 85)
-
+
def open_image(self, input_file):
"""
- Open an image and return the backend specific image object to pass
- to other methods. The object return has to have a size attribute
+ Open an image and return the backend specific image object to pass
+ to other methods. The object return has to have a size attribute
which is a tuple with the width and height of the image and a format
attribute with the format of the image.
"""
raise NotImplementedError('subclasses of BaseImageBackend must provide an open_image() method')
-
-
+
def save_image(self, image, output):
"""
Save the image to the output
"""
raise NotImplementedError('subclasses of BaseImageBackend must provide a save_image() method')
-
+
def resize(self, image, size):
"""
resize image to the requested size, using highest quality settings
@@ -32,11 +32,9 @@ class BaseImageBackend(object):
"""
raise NotImplementedError('subclasses of BaseImageBackend must provide an resize() method')
-
def crop_to_centre(self, image, size):
raise NotImplementedError('subclasses of BaseImageBackend must provide a crop_to_centre() method')
-
def resize_to_max(self, image, size):
"""
Resize image down to fit within the given dimensions, preserving aspect ratio.
@@ -58,10 +56,8 @@ class BaseImageBackend(object):
final_size = (target_width, int(original_height * horz_scale))
else:
final_size = (int(original_width * vert_scale), target_height)
-
- return self.resize(image, final_size)
-
+ return self.resize(image, final_size)
def resize_to_min(self, image, size):
"""
@@ -87,7 +83,6 @@ class BaseImageBackend(object):
return self.resize(image, final_size)
-
def resize_to_width(self, image, target_width):
"""
Resize image down to the given width, preserving aspect ratio.
@@ -104,7 +99,6 @@ class BaseImageBackend(object):
return self.resize(image, final_size)
-
def resize_to_height(self, image, target_height):
"""
Resize image down to the given height, preserving aspect ratio.
@@ -121,7 +115,6 @@ class BaseImageBackend(object):
return self.resize(image, final_size)
-
def resize_to_fill(self, image, size):
"""
Resize down and crop image to fill the given dimensions. Most suitable for thumbnails.
@@ -130,3 +123,8 @@ class BaseImageBackend(object):
"""
resized_image = self.resize_to_min(image, size)
return self.crop_to_centre(resized_image, size)
+
+
+ def no_operation(self, image, param):
+ """Return the image unchanged"""
+ return image
diff --git a/wagtail/wagtailimages/backends/pillow.py b/wagtail/wagtailimages/backends/pillow.py
index 05d12d291..96976c277 100644
--- a/wagtail/wagtailimages/backends/pillow.py
+++ b/wagtail/wagtailimages/backends/pillow.py
@@ -1,6 +1,9 @@
-from base import BaseImageBackend
+from __future__ import absolute_import
+
+from .base import BaseImageBackend
import PIL.Image
+
class PillowBackend(BaseImageBackend):
def __init__(self, params):
super(PillowBackend, self).__init__(params)
@@ -32,4 +35,4 @@ class PillowBackend(BaseImageBackend):
top = (original_height - final_height) / 2
return image.crop(
(left, top, left + final_width, top + final_height)
- )
\ No newline at end of file
+ )
diff --git a/wagtail/wagtailimages/backends/wand.py b/wagtail/wagtailimages/backends/wand.py
index 36e352715..91f2d255a 100644
--- a/wagtail/wagtailimages/backends/wand.py
+++ b/wagtail/wagtailimages/backends/wand.py
@@ -1,8 +1,9 @@
from __future__ import absolute_import
from .base import BaseImageBackend
-
from wand.image import Image
+from wand.api import library
+
class WandBackend(BaseImageBackend):
def __init__(self, params):
@@ -10,6 +11,7 @@ class WandBackend(BaseImageBackend):
def open_image(self, input_file):
image = Image(file=input_file)
+ image.wand = library.MagickCoalesceImages(image.wand)
return image
def save_image(self, image, output, format):
@@ -18,8 +20,9 @@ class WandBackend(BaseImageBackend):
image.save(file=output)
def resize(self, image, size):
- image.resize(size[0], size[1])
- return image
+ new_image = image.clone()
+ new_image.resize(size[0], size[1])
+ return new_image
def crop_to_centre(self, image, size):
(original_width, original_height) = image.size
@@ -34,7 +37,9 @@ class WandBackend(BaseImageBackend):
left = (original_width - final_width) / 2
top = (original_height - final_height) / 2
- image.crop(
+
+ new_image = image.clone()
+ new_image.crop(
left=left, top=top, right=left + final_width, bottom=top + final_height
)
- return image
+ return new_image
diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py
index 24fd1f0bf..8137971a7 100644
--- a/wagtail/wagtailimages/models.py
+++ b/wagtail/wagtailimages/models.py
@@ -1,5 +1,6 @@
import StringIO
import os.path
+import re
from taggit.managers import TaggableManager
@@ -150,6 +151,7 @@ class Filter(models.Model):
'width': 'resize_to_width',
'height': 'resize_to_height',
'fill': 'resize_to_fill',
+ 'original': 'no_operation',
}
def __init__(self, *args, **kwargs):
@@ -157,22 +159,34 @@ class Filter(models.Model):
self.method = None # will be populated when needed, by parsing the spec string
def _parse_spec_string(self):
- # parse the spec string, which is formatted as (method)-(arg),
- # and save the results to self.method_name and self.method_arg
- try:
- (method_name_simple, method_arg_string) = self.spec.split('-')
- self.method_name = Filter.OPERATION_NAMES[method_name_simple]
+ # parse the spec string and save the results to
+ # self.method_name and self.method_arg. There are various possible
+ # formats to match against:
+ # 'original'
+ # 'width-200'
+ # 'max-320x200'
- if method_name_simple in ('max', 'min', 'fill'):
- # method_arg_string is in the form 640x480
- (width, height) = [int(i) for i in method_arg_string.split('x')]
- self.method_arg = (width, height)
- else:
- # method_arg_string is a single number
- self.method_arg = int(method_arg_string)
+ if self.spec == 'original':
+ self.method_name = Filter.OPERATION_NAMES['original']
+ self.method_arg = None
+ return
- except (ValueError, KeyError):
- raise ValueError("Invalid image filter spec: %r" % self.spec)
+ match = re.match(r'(width|height)-(\d+)$', self.spec)
+ if match:
+ self.method_name = Filter.OPERATION_NAMES[match.group(1)]
+ self.method_arg = int(match.group(2))
+ return
+
+ match = re.match(r'(max|min|fill)-(\d+)x(\d+)$', self.spec)
+ if match:
+ self.method_name = Filter.OPERATION_NAMES[match.group(1)]
+ width = int(match.group(2))
+ height = int(match.group(3))
+ self.method_arg = (width, height)
+ return
+
+ # Spec is not one of our recognised patterns
+ raise ValueError("Invalid image filter spec: %r" % self.spec)
def process_image(self, input_file, backend_name='default'):
"""
@@ -180,27 +194,25 @@ class Filter(models.Model):
generate an output image with this filter applied, returning it
as another django.core.files.File object
"""
-
backend = get_image_backend(backend_name)
-
+
if not self.method:
self._parse_spec_string()
-
+
# If file is closed, open it
input_file.open('rb')
image = backend.open_image(input_file)
file_format = image.format
-
+
method = getattr(backend, self.method_name)
image = method(image, self.method_arg)
output = StringIO.StringIO()
backend.save_image(image, output, file_format)
-
+
# and then close the input file
input_file.close()
-
# generate new filename derived from old one, inserting the filter spec string before the extension
input_filename_parts = os.path.basename(input_file.name).split('.')
@@ -210,7 +222,6 @@ class Filter(models.Model):
output_filename = '.'.join(output_filename_parts)
output_file = File(output, name=output_filename)
-
return output_file
diff --git a/wagtail/wagtailimages/templates/wagtailimages/chooser/chooser.js b/wagtail/wagtailimages/templates/wagtailimages/chooser/chooser.js
index 18ff4c5c2..237b960f6 100644
--- a/wagtail/wagtailimages/templates/wagtailimages/chooser/chooser.js
+++ b/wagtail/wagtailimages/templates/wagtailimages/chooser/chooser.js
@@ -1,4 +1,6 @@
function(modal) {
+ var searchUrl = $('form.image-search', modal.body).attr('action');
+
function ajaxifyLinks (context) {
$('.listing a', context).click(function() {
modal.loadUrl(this.href);
@@ -12,7 +14,6 @@ function(modal) {
});
}
- var searchUrl = $('form.image-search', modal.body).attr('action');
function search() {
$.ajax({
url: searchUrl,
@@ -24,8 +25,8 @@ function(modal) {
});
return false;
}
- function setPage(page) {
+ function setPage(page) {
if($('#id_q').val().length){
dataObj = {q: $('#id_q').val(), p: page};
}else{
diff --git a/wagtail/wagtailimages/tests.py b/wagtail/wagtailimages/tests.py
index 00d1245e6..b31e33745 100644
--- a/wagtail/wagtailimages/tests.py
+++ b/wagtail/wagtailimages/tests.py
@@ -4,7 +4,7 @@ from django.contrib.auth.models import User, Group, Permission
from django.core.urlresolvers import reverse
from django.core.files.uploadedfile import SimpleUploadedFile
-from wagtail.tests.utils import login, unittest
+from wagtail.tests.utils import unittest, WagtailTestUtils
from wagtail.wagtailimages.models import get_image_model
from wagtail.wagtailimages.templatetags import image_tags
@@ -84,10 +84,10 @@ class TestRenditions(TestCase):
# default backend should be pillow
backend = get_image_backend()
self.assertTrue(isinstance(backend, PillowBackend))
-
+
def test_minification(self):
rendition = self.image.get_rendition('width-400')
-
+
# Check size
self.assertEqual(rendition.width, 400)
self.assertEqual(rendition.height, 300)
@@ -107,6 +107,13 @@ class TestRenditions(TestCase):
self.assertEqual(rendition.width, 160)
self.assertEqual(rendition.height, 120)
+ def test_resize_to_original(self):
+ rendition = self.image.get_rendition('original')
+
+ # Check size
+ self.assertEqual(rendition.width, 640)
+ self.assertEqual(rendition.height, 480)
+
def test_cache(self):
# Get two renditions with the same filter
first_rendition = self.image.get_rendition('width-400')
@@ -114,7 +121,7 @@ class TestRenditions(TestCase):
# Check that they are the same object
self.assertEqual(first_rendition, second_rendition)
-
+
class TestRenditionsWand(TestCase):
def setUp(self):
@@ -134,18 +141,18 @@ class TestRenditionsWand(TestCase):
def test_minification(self):
rendition = self.image.get_rendition('width-400')
-
+
# Check size
self.assertEqual(rendition.width, 400)
self.assertEqual(rendition.height, 300)
-
+
def test_resize_to_max(self):
rendition = self.image.get_rendition('max-100x100')
-
+
# Check size
self.assertEqual(rendition.width, 100)
self.assertEqual(rendition.height, 75)
-
+
def test_resize_to_min(self):
rendition = self.image.get_rendition('min-120x120')
@@ -153,6 +160,13 @@ class TestRenditionsWand(TestCase):
self.assertEqual(rendition.width, 160)
self.assertEqual(rendition.height, 120)
+ def test_resize_to_original(self):
+ rendition = self.image.get_rendition('original')
+
+ # Check size
+ self.assertEqual(rendition.width, 640)
+ self.assertEqual(rendition.height, 480)
+
def test_cache(self):
# Get two renditions with the same filter
first_rendition = self.image.get_rendition('width-400')
@@ -160,7 +174,7 @@ class TestRenditionsWand(TestCase):
# Check that they are the same object
self.assertEqual(first_rendition, second_rendition)
-
+
class TestImageTag(TestCase):
def setUp(self):
@@ -187,15 +201,17 @@ class TestImageTag(TestCase):
## ===== ADMIN VIEWS =====
-class TestImageIndexView(TestCase):
+class TestImageIndexView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get(reverse('wagtailimages_index'), params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailimages/images/index.html')
def test_search(self):
response = self.get({'q': "Hello"})
@@ -215,9 +231,9 @@ class TestImageIndexView(TestCase):
self.assertEqual(response.status_code, 200)
-class TestImageAddView(TestCase):
+class TestImageAddView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get(reverse('wagtailimages_add_image'), params)
@@ -225,8 +241,10 @@ class TestImageAddView(TestCase):
def post(self, post_data={}):
return self.client.post(reverse('wagtailimages_add_image'), post_data)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailimages/images/add.html')
def test_add(self):
response = self.post({
@@ -236,6 +254,7 @@ class TestImageAddView(TestCase):
# Should redirect back to index
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailimages_index'))
# Check that the image was created
images = Image.objects.filter(title="Test image")
@@ -247,9 +266,9 @@ class TestImageAddView(TestCase):
self.assertEqual(image.height, 480)
-class TestImageEditView(TestCase):
+class TestImageEditView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
# Create an image to edit
self.image = Image.objects.create(
@@ -263,8 +282,10 @@ class TestImageEditView(TestCase):
def post(self, post_data={}):
return self.client.post(reverse('wagtailimages_edit_image', args=(self.image.id,)), post_data)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailimages/images/edit.html')
def test_edit(self):
response = self.post({
@@ -273,15 +294,16 @@ class TestImageEditView(TestCase):
# Should redirect back to index
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailimages_index'))
# Check that the image was edited
image = Image.objects.get(id=self.image.id)
self.assertEqual(image.title, "Edited")
-class TestImageDeleteView(TestCase):
+class TestImageDeleteView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
# Create an image to edit
self.image = Image.objects.create(
@@ -295,8 +317,10 @@ class TestImageDeleteView(TestCase):
def post(self, post_data={}):
return self.client.post(reverse('wagtailimages_delete_image', args=(self.image.id,)), post_data)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailimages/images/confirm_delete.html')
def test_delete(self):
response = self.post({
@@ -305,21 +329,25 @@ class TestImageDeleteView(TestCase):
# Should redirect back to index
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailimages_index'))
# Check that the image was deleted
images = Image.objects.filter(title="Test image")
self.assertEqual(images.count(), 0)
-class TestImageChooserView(TestCase):
+class TestImageChooserView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get(reverse('wagtailimages_chooser'), params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailimages/chooser/chooser.html')
+ self.assertTemplateUsed(response, 'wagtailimages/chooser/chooser.js')
def test_search(self):
response = self.get({'q': "Hello"})
@@ -333,9 +361,9 @@ class TestImageChooserView(TestCase):
self.assertEqual(response.status_code, 200)
-class TestImageChooserChosenView(TestCase):
+class TestImageChooserChosenView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
# Create an image to edit
self.image = Image.objects.create(
@@ -346,16 +374,25 @@ class TestImageChooserChosenView(TestCase):
def get(self, params={}):
return self.client.get(reverse('wagtailimages_image_chosen', args=(self.image.id,)), params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailimages/chooser/image_chosen.js')
+
+ # TODO: Test posting
-class TestImageChooserUploadView(TestCase):
+class TestImageChooserUploadView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get(reverse('wagtailimages_chooser_upload'), params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailimages/chooser/chooser.html')
+ self.assertTemplateUsed(response, 'wagtailimages/chooser/chooser.js')
+
+ # TODO: Test uploading through chooser
diff --git a/wagtail/wagtailredirects/tests.py b/wagtail/wagtailredirects/tests.py
index a14a853fa..29ad7ec0b 100644
--- a/wagtail/wagtailredirects/tests.py
+++ b/wagtail/wagtailredirects/tests.py
@@ -1,7 +1,7 @@
from django.test import TestCase
from django.test.client import Client
from wagtail.wagtailredirects import models
-from wagtail.tests.utils import login
+from wagtail.tests.utils import WagtailTestUtils
from django.core.urlresolvers import reverse
@@ -66,15 +66,17 @@ class TestRedirects(TestCase):
self.assertTrue(r.has_header('Location'))
-class TestRedirectsIndexView(TestCase):
+class TestRedirectsIndexView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get(reverse('wagtailredirects_index'), params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailredirects/index.html')
def test_search(self):
response = self.get({'q': "Hello"})
@@ -88,9 +90,9 @@ class TestRedirectsIndexView(TestCase):
self.assertEqual(response.status_code, 200)
-class TestRedirectsAddView(TestCase):
+class TestRedirectsAddView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get(reverse('wagtailredirects_add_redirect'), params)
@@ -98,8 +100,10 @@ class TestRedirectsAddView(TestCase):
def post(self, post_data={}):
return self.client.post(reverse('wagtailredirects_add_redirect'), post_data)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailredirects/add.html')
def test_add(self):
response = self.post({
@@ -110,6 +114,7 @@ class TestRedirectsAddView(TestCase):
# Should redirect back to index
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailredirects_index'))
# Check that the redirect was created
redirects = models.Redirect.objects.filter(old_path='/test')
@@ -127,14 +132,14 @@ class TestRedirectsAddView(TestCase):
self.assertEqual(response.status_code, 200)
-class TestRedirectsEditView(TestCase):
+class TestRedirectsEditView(TestCase, WagtailTestUtils):
def setUp(self):
# Create a redirect to edit
self.redirect = models.Redirect(old_path='/test', redirect_link='http://www.test.com/')
self.redirect.save()
# Login
- login(self.client)
+ self.login()
def get(self, params={}, redirect_id=None):
return self.client.get(reverse('wagtailredirects_edit_redirect', args=(redirect_id or self.redirect.id, )), params)
@@ -142,8 +147,10 @@ class TestRedirectsEditView(TestCase):
def post(self, post_data={}, redirect_id=None):
return self.client.post(reverse('wagtailredirects_edit_redirect', args=(redirect_id or self.redirect.id, )), post_data)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailredirects/edit.html')
def test_nonexistant_redirect(self):
self.assertEqual(self.get(redirect_id=100000).status_code, 404)
@@ -157,6 +164,7 @@ class TestRedirectsEditView(TestCase):
# Should redirect back to index
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailredirects_index'))
# Check that the redirect was edited
redirects = models.Redirect.objects.filter(old_path='/test')
@@ -173,14 +181,14 @@ class TestRedirectsEditView(TestCase):
# Should not redirect to index
self.assertEqual(response.status_code, 200)
-class TestRedirectsDeleteView(TestCase):
+class TestRedirectsDeleteView(TestCase, WagtailTestUtils):
def setUp(self):
# Create a redirect to edit
self.redirect = models.Redirect(old_path='/test', redirect_link='http://www.test.com/')
self.redirect.save()
# Login
- login(self.client)
+ self.login()
def get(self, params={}, redirect_id=None):
return self.client.get(reverse('wagtailredirects_delete_redirect', args=(redirect_id or self.redirect.id, )), params)
@@ -188,8 +196,10 @@ class TestRedirectsDeleteView(TestCase):
def post(self, post_data={}, redirect_id=None):
return self.client.post(reverse('wagtailredirects_delete_redirect', args=(redirect_id or self.redirect.id, )), post_data)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailredirects/confirm_delete.html')
def test_nonexistant_redirect(self):
self.assertEqual(self.get(redirect_id=100000).status_code, 404)
@@ -201,6 +211,7 @@ class TestRedirectsDeleteView(TestCase):
# Should redirect back to index
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailredirects_index'))
# Check that the redirect was deleted
redirects = models.Redirect.objects.filter(old_path='/test')
diff --git a/wagtail/wagtailsearch/forms.py b/wagtail/wagtailsearch/forms.py
index 03e4ecd45..9485e4619 100644
--- a/wagtail/wagtailsearch/forms.py
+++ b/wagtail/wagtailsearch/forms.py
@@ -31,6 +31,8 @@ EditorsPickFormSetBase = inlineformset_factory(models.Query, models.EditorsPick,
class EditorsPickFormSet(EditorsPickFormSetBase):
+ minimum_forms = 1
+ minimum_forms_message = _("Please specify at least one recommendation for this search term.")
def add_fields(self, form, *args, **kwargs):
super(EditorsPickFormSet, self).add_fields(form, *args, **kwargs)
@@ -40,3 +42,20 @@ class EditorsPickFormSet(EditorsPickFormSetBase):
# Remove query field
del form.fields['query']
+
+ def clean(self):
+ # Editors pick must have at least one recommended page to be valid
+ # Check there is at least one non-deleted form.
+ non_deleted_forms = self.total_form_count()
+ non_empty_forms = 0
+ for i in xrange(0, self.total_form_count()):
+ form = self.forms[i]
+ if self.can_delete and self._should_delete_form(form):
+ non_deleted_forms -= 1
+ if not (form.instance.id is None and not form.has_changed()):
+ non_empty_forms += 1
+ if (
+ non_deleted_forms < self.minimum_forms
+ or non_empty_forms < self.minimum_forms
+ ):
+ raise forms.ValidationError(self.minimum_forms_message)
diff --git a/wagtail/wagtailsearch/models.py b/wagtail/wagtailsearch/models.py
index 809e139ed..6b07872f5 100644
--- a/wagtail/wagtailsearch/models.py
+++ b/wagtail/wagtailsearch/models.py
@@ -5,9 +5,10 @@ from indexed import Indexed
import datetime
import string
+MAX_QUERY_STRING_LENGTH = 255
class Query(models.Model):
- query_string = models.CharField(max_length=255, unique=True)
+ query_string = models.CharField(max_length=MAX_QUERY_STRING_LENGTH, unique=True)
def save(self, *args, **kwargs):
# Normalise query string
@@ -48,6 +49,9 @@ class Query(models.Model):
@staticmethod
def normalise_query_string(query_string):
+ # Truncate query string
+ if len(query_string) > MAX_QUERY_STRING_LENGTH:
+ query_string = query_string[:MAX_QUERY_STRING_LENGTH]
# Convert query_string to lowercase
query_string = query_string.lower()
diff --git a/wagtail/wagtailsearch/templates/wagtailsearch/editorspicks/add.html b/wagtail/wagtailsearch/templates/wagtailsearch/editorspicks/add.html
index 3c927d96c..f443d1a6a 100644
--- a/wagtail/wagtailsearch/templates/wagtailsearch/editorspicks/add.html
+++ b/wagtail/wagtailsearch/templates/wagtailsearch/editorspicks/add.html
@@ -6,7 +6,7 @@
{% include "wagtailadmin/shared/header.html" with title=add_str icon="pick" %}
- {% blocktrans %}s
+ {% blocktrans %}
Editors picks are a means of recommending specific pages that might not organically come high up in search results. E.g recommending your primary donation page to a user searching with a less common term like "giving".
{% endblocktrans %}
{% blocktrans %}
diff --git a/wagtail/wagtailsearch/tests/test_editorspicks.py b/wagtail/wagtailsearch/tests/test_editorspicks.py
index e0b49153f..7ee974fd0 100644
--- a/wagtail/wagtailsearch/tests/test_editorspicks.py
+++ b/wagtail/wagtailsearch/tests/test_editorspicks.py
@@ -1,5 +1,5 @@
from django.test import TestCase
-from wagtail.tests.utils import login
+from wagtail.tests.utils import unittest, WagtailTestUtils
from wagtail.wagtailsearch import models
@@ -45,15 +45,17 @@ class TestEditorsPicks(TestCase):
self.assertEqual(models.Query.get("root page").editors_picks.last().description, "Last editors pick")
-class TestEditorsPicksIndexView(TestCase):
+class TestEditorsPicksIndexView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get('/admin/search/editorspicks/', params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/index.html')
def test_search(self):
response = self.get({'q': "Hello"})
@@ -67,20 +69,24 @@ class TestEditorsPicksIndexView(TestCase):
self.assertEqual(response.status_code, 200)
-class TestEditorsPicksAddView(TestCase):
+class TestEditorsPicksAddView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get('/admin/search/editorspicks/add/', params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/add.html')
+
+ # TODO: Test posting
-class TestEditorsPicksEditView(TestCase):
+class TestEditorsPicksEditView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
# Create an editors pick to edit
self.query = models.Query.get("Hello")
@@ -89,13 +95,17 @@ class TestEditorsPicksEditView(TestCase):
def get(self, params={}):
return self.client.get('/admin/search/editorspicks/' + str(self.query.id) + '/', params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/edit.html')
+
+ # TODO: Test posting
-class TestEditorsPicksDeleteView(TestCase):
+class TestEditorsPicksDeleteView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
# Create an editors pick to delete
self.query = models.Query.get("Hello")
@@ -104,5 +114,9 @@ class TestEditorsPicksDeleteView(TestCase):
def get(self, params={}):
return self.client.get('/admin/search/editorspicks/' + str(self.query.id) + '/delete/', params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/confirm_delete.html')
+
+ # TODO: Test posting
diff --git a/wagtail/wagtailsearch/tests/test_frontend.py b/wagtail/wagtailsearch/tests/test_frontend.py
index 82189cfc9..8081c968d 100644
--- a/wagtail/wagtailsearch/tests/test_frontend.py
+++ b/wagtail/wagtailsearch/tests/test_frontend.py
@@ -5,8 +5,10 @@ class TestSearchView(TestCase):
def get(self, params={}):
return self.client.get('/search/', params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailsearch/search_results.html')
def test_search(self):
response = self.get({'q': "Hello"})
@@ -24,8 +26,10 @@ class TestSuggestionsView(TestCase):
def get(self, params={}):
return self.client.get('/search/suggest/', params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ # TODO: Check that a valid JSON document was returned
def test_search(self):
response = self.get({'q': "Hello"})
diff --git a/wagtail/wagtailsearch/tests/test_queries.py b/wagtail/wagtailsearch/tests/test_queries.py
index 7086d46f4..5c341d7d6 100644
--- a/wagtail/wagtailsearch/tests/test_queries.py
+++ b/wagtail/wagtailsearch/tests/test_queries.py
@@ -1,7 +1,7 @@
from django.test import TestCase
from django.core import management
from wagtail.wagtailsearch import models
-from wagtail.tests.utils import login, unittest
+from wagtail.tests.utils import unittest, WagtailTestUtils
from StringIO import StringIO
@@ -53,6 +53,16 @@ class TestQueryStringNormalisation(TestCase):
for query in queries:
self.assertNotEqual(self.query, models.Query.get(query))
+ def test_truncation(self):
+ test_querystring = 'a' * 1000
+ result = models.Query.normalise_query_string(test_querystring)
+ self.assertEqual(len(result), 255)
+
+ def test_no_truncation(self):
+ test_querystring = 'a' * 10
+ result = models.Query.normalise_query_string(test_querystring)
+ self.assertEqual(len(result), 10)
+
class TestQueryPopularity(TestCase):
def test_query_popularity(self):
@@ -139,15 +149,18 @@ class TestGarbageCollectCommand(TestCase):
# TODO: Test that this command is acctually doing its job
-class TestQueryChooserView(TestCase):
+class TestQueryChooserView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get('/admin/search/queries/chooser/', params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailsearch/queries/chooser/chooser.html')
+ self.assertTemplateUsed(response, 'wagtailsearch/queries/chooser/chooser.js')
def test_search(self):
response = self.get({'q': "Hello"})
diff --git a/wagtail/wagtailsearch/views/editorspicks.py b/wagtail/wagtailsearch/views/editorspicks.py
index 97332818e..bb39c02ba 100644
--- a/wagtail/wagtailsearch/views/editorspicks.py
+++ b/wagtail/wagtailsearch/views/editorspicks.py
@@ -45,12 +45,12 @@ def index(request):
def save_editorspicks(query, new_query, editors_pick_formset):
- # Set sort_order
- for i, form in enumerate(editors_pick_formset.ordered_forms):
- form.instance.sort_order = i
-
# Save
if editors_pick_formset.is_valid():
+ # Set sort_order
+ for i, form in enumerate(editors_pick_formset.ordered_forms):
+ form.instance.sort_order = i
+
editors_pick_formset.save()
# If query was changed, move all editors picks to the new query
@@ -72,10 +72,14 @@ def add(request):
# Save editors picks
editors_pick_formset = forms.EditorsPickFormSet(request.POST, instance=query)
-
if save_editorspicks(query, query, editors_pick_formset):
messages.success(request, _("Editor's picks for '{0}' created.").format(query))
return redirect('wagtailsearch_editorspicks_index')
+ else:
+ if len(editors_pick_formset.non_form_errors()):
+ messages.error(request, " ".join(error for error in editors_pick_formset.non_form_errors())) # formset level error (e.g. no forms submitted)
+ else:
+ messages.error(request, _("Recommendations have not been created due to errors")) # specific errors will be displayed within form fields
else:
editors_pick_formset = forms.EditorsPickFormSet()
else:
@@ -95,15 +99,22 @@ def edit(request, query_id):
if request.POST:
# Get query
query_form = forms.QueryForm(request.POST)
+ # and the recommendations
+ editors_pick_formset = forms.EditorsPickFormSet(request.POST, instance=query)
+
if query_form.is_valid():
new_query = models.Query.get(query_form['query_string'].value())
# Save editors picks
- editors_pick_formset = forms.EditorsPickFormSet(request.POST, instance=query)
-
if save_editorspicks(query, new_query, editors_pick_formset):
messages.success(request, _("Editor's picks for '{0}' updated.").format(new_query))
return redirect('wagtailsearch_editorspicks_index')
+ else:
+ if len(editors_pick_formset.non_form_errors()):
+ messages.error(request, " ".join(error for error in editors_pick_formset.non_form_errors())) # formset level error (e.g. no forms submitted)
+ else:
+ messages.error(request, _("Recommendations have not been saved due to errors")) # specific errors will be displayed within form fields
+
else:
query_form = forms.QueryForm(initial=dict(query_string=query.query_string))
editors_pick_formset = forms.EditorsPickFormSet(instance=query)
diff --git a/wagtail/wagtailsnippets/models.py b/wagtail/wagtailsnippets/models.py
index b929f06aa..8af626655 100644
--- a/wagtail/wagtailsnippets/models.py
+++ b/wagtail/wagtailsnippets/models.py
@@ -19,3 +19,4 @@ def get_snippet_content_types():
def register_snippet(model):
if model not in SNIPPET_MODELS:
SNIPPET_MODELS.append(model)
+ SNIPPET_MODELS.sort(key=lambda x: x._meta.verbose_name)
diff --git a/wagtail/wagtailsnippets/tests.py b/wagtail/wagtailsnippets/tests.py
index 727e5e52d..9e2587794 100644
--- a/wagtail/wagtailsnippets/tests.py
+++ b/wagtail/wagtailsnippets/tests.py
@@ -2,45 +2,50 @@ from django.test import TestCase
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
-from wagtail.tests.utils import login, unittest
-from wagtail.tests.models import Advert
+from wagtail.tests.utils import unittest, WagtailTestUtils
+from wagtail.tests.models import Advert, AlphaSnippet, ZuluSnippet
+from wagtail.wagtailsnippets.models import register_snippet, SNIPPET_MODELS
from wagtail.wagtailsnippets.views.snippets import get_content_type_from_url_params, get_snippet_edit_handler
from wagtail.wagtailsnippets.edit_handlers import SnippetChooserPanel
-class TestSnippetIndexView(TestCase):
+class TestSnippetIndexView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get(reverse('wagtailsnippets_index'), params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailsnippets/snippets/index.html')
def test_displays_snippet(self):
self.assertContains(self.get(), "Adverts")
-class TestSnippetListView(TestCase):
+class TestSnippetListView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get(reverse('wagtailsnippets_list',
args=('tests', 'advert')),
params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailsnippets/snippets/type_index.html')
def test_displays_add_button(self):
self.assertContains(self.get(), "Add advert")
-class TestSnippetCreateView(TestCase):
+class TestSnippetCreateView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get(reverse('wagtailsnippets_create',
@@ -52,8 +57,10 @@ class TestSnippetCreateView(TestCase):
args=('tests', 'advert')),
post_data)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailsnippets/snippets/create.html')
def test_create_invalid(self):
response = self.post(post_data={'foo': 'bar'})
@@ -64,20 +71,21 @@ class TestSnippetCreateView(TestCase):
response = self.post(post_data={'text': 'test_advert',
'url': 'http://www.example.com/'})
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailsnippets_list', args=('tests', 'advert')))
snippets = Advert.objects.filter(text='test_advert')
self.assertEqual(snippets.count(), 1)
self.assertEqual(snippets.first().url, 'http://www.example.com/')
-class TestSnippetEditView(TestCase):
+class TestSnippetEditView(TestCase, WagtailTestUtils):
def setUp(self):
self.test_snippet = Advert()
self.test_snippet.text = 'test_advert'
self.test_snippet.url = 'http://www.example.com/'
self.test_snippet.save()
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get(reverse('wagtailsnippets_edit',
@@ -89,8 +97,10 @@ class TestSnippetEditView(TestCase):
args=('tests', 'advert', self.test_snippet.id)),
post_data)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailsnippets/snippets/edit.html')
def test_non_existant_model(self):
response = self.client.get(reverse('wagtailsnippets_edit',
@@ -111,20 +121,21 @@ class TestSnippetEditView(TestCase):
response = self.post(post_data={'text': 'edited_test_advert',
'url': 'http://www.example.com/edited'})
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailsnippets_list', args=('tests', 'advert')))
snippets = Advert.objects.filter(text='edited_test_advert')
self.assertEqual(snippets.count(), 1)
self.assertEqual(snippets.first().url, 'http://www.example.com/edited')
-class TestSnippetDelete(TestCase):
+class TestSnippetDelete(TestCase, WagtailTestUtils):
def setUp(self):
self.test_snippet = Advert()
self.test_snippet.text = 'test_advert'
self.test_snippet.url = 'http://www.example.com/'
self.test_snippet.save()
- login(self.client)
+ self.login()
def test_delete_get(self):
response = self.client.get(reverse('wagtailsnippets_delete', args=('tests', 'advert', self.test_snippet.id, )))
@@ -136,6 +147,7 @@ class TestSnippetDelete(TestCase):
# Should be redirected to explorer page
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailsnippets_list', args=('tests', 'advert')))
# Check that the page is gone
self.assertEqual(Advert.objects.filter(text='test_advert').count(), 0)
@@ -168,3 +180,16 @@ class TestSnippetChooserPanel(TestCase):
def test_render_js(self):
self.assertTrue("createSnippetChooser(fixPrefix('id_text'), 'contenttypes/contenttype');"
in self.snippet_chooser_panel.render_js())
+
+
+class TestSnippetOrdering(TestCase):
+ def setUp(self):
+ register_snippet(ZuluSnippet)
+ register_snippet(AlphaSnippet)
+
+ def test_snippets_ordering(self):
+ # Ensure AlphaSnippet is before ZuluSnippet
+ # Cannot check first and last position as other snippets
+ # may get registered elsewhere during test
+ self.assertLess(SNIPPET_MODELS.index(AlphaSnippet),
+ SNIPPET_MODELS.index(ZuluSnippet))
diff --git a/wagtail/wagtailusers/tests.py b/wagtail/wagtailusers/tests.py
index bfe0fe4ef..6de97f202 100644
--- a/wagtail/wagtailusers/tests.py
+++ b/wagtail/wagtailusers/tests.py
@@ -1,18 +1,20 @@
from django.test import TestCase
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
-from wagtail.tests.utils import login
+from wagtail.tests.utils import WagtailTestUtils
-class TestUserIndexView(TestCase):
+class TestUserIndexView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get(reverse('wagtailusers_index'), params)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailusers/index.html')
def test_search(self):
response = self.get({'q': "Hello"})
@@ -26,9 +28,9 @@ class TestUserIndexView(TestCase):
self.assertEqual(response.status_code, 200)
-class TestUserCreateView(TestCase):
+class TestUserCreateView(TestCase, WagtailTestUtils):
def setUp(self):
- login(self.client)
+ self.login()
def get(self, params={}):
return self.client.get(reverse('wagtailusers_create'), params)
@@ -36,8 +38,10 @@ class TestUserCreateView(TestCase):
def post(self, post_data={}):
return self.client.post(reverse('wagtailusers_create'), post_data)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailusers/create.html')
def test_create(self):
response = self.post({
@@ -51,6 +55,7 @@ class TestUserCreateView(TestCase):
# Should redirect back to index
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailusers_index'))
# Check that the user was created
users = User.objects.filter(username='testuser')
@@ -58,13 +63,13 @@ class TestUserCreateView(TestCase):
self.assertEqual(users.first().email, 'test@user.com')
-class TestUserEditView(TestCase):
+class TestUserEditView(TestCase, WagtailTestUtils):
def setUp(self):
# Create a user to edit
self.test_user = User.objects.create_user(username='testuser', email='testuser@email.com', password='password')
# Login
- login(self.client)
+ self.login()
def get(self, params={}, user_id=None):
return self.client.get(reverse('wagtailusers_edit', args=(user_id or self.test_user.id, )), params)
@@ -72,8 +77,10 @@ class TestUserEditView(TestCase):
def post(self, post_data={}, user_id=None):
return self.client.post(reverse('wagtailusers_edit', args=(user_id or self.test_user.id, )), post_data)
- def test_status_code(self):
- self.assertEqual(self.get().status_code, 200)
+ def test_simple(self):
+ response = self.get()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'wagtailusers/edit.html')
def test_nonexistant_redirect(self):
self.assertEqual(self.get(user_id=100000).status_code, 404)
@@ -90,6 +97,7 @@ class TestUserEditView(TestCase):
# Should redirect back to index
self.assertEqual(response.status_code, 302)
+ self.assertURLEqual(response.url, reverse('wagtailusers_index'))
# Check that the user was edited
user = User.objects.get(id=self.test_user.id)