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..09e6b6f5c 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.models import SimplePage, EventPage, StandardIndex, StandardChild, BusinessIndex, BusinessChild
+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
@@ -185,8 +188,13 @@ class TestPageCreation(TestCase):
self.assertTemplateUsed(response, 'tests/simple_page.html')
self.assertContains(response, "New page!")
+ # Check that the treebeard attributes were set correctly on the page object
+ self.assertEqual(response.context['self'].depth, self.root_page.depth + 1)
+ self.assertTrue(response.context['self'].path.startswith(self.root_page.path))
+ self.assertEqual(response.context['self'].get_parent(), self.root_page)
-class TestPageEdit(TestCase):
+
+class TestPageEdit(TestCase, WagtailTestUtils):
def setUp(self):
# Find root page
self.root_page = Page.objects.get(id=2)
@@ -206,7 +214,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 +246,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 +264,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 +288,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 +317,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 +329,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 +355,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 +402,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 +425,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 +454,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 +514,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 +530,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 +553,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 +605,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 +660,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
@@ -659,3 +674,48 @@ class TestContentTypeUse(TestCase):
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'wagtailadmin/pages/content_type_use.html')
self.assertContains(response, "Christmas")
+
+
+class TestSubpageBusinessRules(TestCase, WagtailTestUtils):
+ def setUp(self):
+ # Find root page
+ self.root_page = Page.objects.get(id=2)
+
+ # Add standard page
+ self.standard_index = StandardIndex()
+ self.standard_index.title = "Standard Index"
+ self.standard_index.slug = "standard-index"
+ self.root_page.add_child(instance=self.standard_index)
+
+ # Add business page
+ self.business_index = BusinessIndex()
+ self.business_index.title = "Business Index"
+ self.business_index.slug = "business-index"
+ self.root_page.add_child(instance=self.business_index)
+
+ # Add business child
+ self.business_child = BusinessChild()
+ self.business_child.title = "Business Child"
+ self.business_child.slug = "business-child"
+ self.business_index.add_child(instance=self.business_child)
+
+ # Login
+ self.login()
+
+ def test_standard_subpage(self):
+ response = self.client.get(reverse('wagtailadmin_pages_add_subpage', args=(self.standard_index.id, )))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, 'Standard Child')
+ self.assertContains(response, 'Business Child')
+
+ def test_business_subpage(self):
+ response = self.client.get(reverse('wagtailadmin_pages_add_subpage', args=(self.business_index.id, )))
+ self.assertEqual(response.status_code, 200)
+ self.assertNotContains(response, 'Standard Child')
+ self.assertContains(response, 'Business Child')
+
+ def test_business_child_subpage(self):
+ response = self.client.get(reverse('wagtailadmin_pages_add_subpage', args=(self.business_child.id, )))
+ self.assertEqual(response.status_code, 200)
+ self.assertNotContains(response, 'Standard Child')
+ self.assertEqual(0, len(response.context['page_types']))
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/wagtailadmin/views/pages.py b/wagtail/wagtailadmin/views/pages.py
index 69e6b7f4f..916c373eb 100644
--- a/wagtail/wagtailadmin/views/pages.py
+++ b/wagtail/wagtailadmin/views/pages.py
@@ -57,13 +57,11 @@ def add_subpage(request, parent_page_id):
if not parent_page.permissions_for_user(request.user).can_add_subpage():
raise PermissionDenied
- page_types = sorted([ContentType.objects.get_for_model(model_class) for model_class in parent_page.clean_subpage_types()], key=lambda pagetype: pagetype.name.lower())
- all_page_types = sorted(get_page_types(), key=lambda pagetype: pagetype.name.lower())
+ page_types = sorted(parent_page.clean_subpage_types(), key=lambda pagetype: pagetype.name.lower())
return render(request, 'wagtailadmin/pages/add_subpage.html', {
'parent_page': parent_page,
'page_types': page_types,
- 'all_page_types': all_page_types,
})
@@ -364,6 +362,10 @@ def preview_on_create(request, content_type_app_name, content_type_model_name, p
parent_page = get_object_or_404(Page, id=parent_page_id).specific
page.set_url_path(parent_page)
+ # Set treebeard attributes
+ page.depth = parent_page.depth + 1
+ page.path = Page._get_children_path_interval(parent_page.path)[1]
+
# This view will generally be invoked as an AJAX request; as such, in the case of
# an error Django will return a plaintext response. This isn't what we want, since
# we will be writing the response back to an HTML page regardless of success or
diff --git a/wagtail/wagtailadmin/views/userbar.py b/wagtail/wagtailadmin/views/userbar.py
index 6e6ef3492..41776f684 100644
--- a/wagtail/wagtailadmin/views/userbar.py
+++ b/wagtail/wagtailadmin/views/userbar.py
@@ -1,9 +1,13 @@
from django.shortcuts import render
+from django.contrib.auth.decorators import permission_required
from wagtail.wagtailadmin.userbar import EditPageItem, AddPageItem, ApproveModerationEditPageItem, RejectModerationEditPageItem
from wagtail.wagtailadmin import hooks
from wagtail.wagtailcore.models import Page, PageRevision
+
+
+@permission_required('wagtailadmin.access_admin')
def for_frontend(request, page_id):
items = [
EditPageItem(Page.objects.get(id=page_id)),
@@ -24,6 +28,8 @@ def for_frontend(request, page_id):
'items': rendered_items,
})
+
+@permission_required('wagtailadmin.access_admin')
def for_moderation(request, revision_id):
items = [
EditPageItem(PageRevision.objects.get(id=revision_id).page),
@@ -44,4 +50,4 @@ def for_moderation(request, revision_id):
# Render the edit bird
return render(request, 'wagtailadmin/userbar/base.html', {
'items': rendered_items,
- })
\ No newline at end of file
+ })
diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py
index 1864f5c88..e2b728005 100644
--- a/wagtail/wagtailcore/models.py
+++ b/wagtail/wagtailcore/models.py
@@ -1,7 +1,6 @@
-import sys
-import os
from StringIO import StringIO
from urlparse import urlparse
+import warnings
from modelcluster.models import ClusterableModel
@@ -101,30 +100,29 @@ def get_page_types():
return _PAGE_CONTENT_TYPES
-LEAF_PAGE_MODEL_CLASSES = []
-_LEAF_PAGE_CONTENT_TYPE_IDS = []
-
-
def get_leaf_page_content_type_ids():
- global _LEAF_PAGE_CONTENT_TYPE_IDS
- if len(_LEAF_PAGE_CONTENT_TYPE_IDS) != len(LEAF_PAGE_MODEL_CLASSES):
- _LEAF_PAGE_CONTENT_TYPE_IDS = [
- ContentType.objects.get_for_model(cls).id for cls in LEAF_PAGE_MODEL_CLASSES
- ]
- return _LEAF_PAGE_CONTENT_TYPE_IDS
-
-
-NAVIGABLE_PAGE_MODEL_CLASSES = []
-_NAVIGABLE_PAGE_CONTENT_TYPE_IDS = []
-
+ warnings.warn("""
+ get_leaf_page_content_type_ids is deprecated, as it treats pages without an explicit subpage_types
+ setting as 'leaf' pages. Code that calls get_leaf_page_content_type_ids must be rewritten to avoid
+ this incorrect assumption.
+ """, DeprecationWarning)
+ return [
+ content_type.id
+ for content_type in get_page_types()
+ if not getattr(content_type.model_class(), 'subpage_types', None)
+ ]
def get_navigable_page_content_type_ids():
- global _NAVIGABLE_PAGE_CONTENT_TYPE_IDS
- if len(_NAVIGABLE_PAGE_CONTENT_TYPE_IDS) != len(NAVIGABLE_PAGE_MODEL_CLASSES):
- _NAVIGABLE_PAGE_CONTENT_TYPE_IDS = [
- ContentType.objects.get_for_model(cls).id for cls in NAVIGABLE_PAGE_MODEL_CLASSES
- ]
- return _NAVIGABLE_PAGE_CONTENT_TYPE_IDS
+ warnings.warn("""
+ get_navigable_page_content_type_ids is deprecated, as it treats pages without an explicit subpage_types
+ setting as 'leaf' pages. Code that calls get_navigable_page_content_type_ids must be rewritten to avoid
+ this incorrect assumption.
+ """, DeprecationWarning)
+ return [
+ content_type.id
+ for content_type in get_page_types()
+ if getattr(content_type.model_class(), 'subpage_types', None)
+ ]
class PageManager(models.Manager):
@@ -209,10 +207,6 @@ class PageBase(models.base.ModelBase):
if not cls.is_abstract:
# register this type in the list of page content types
PAGE_MODEL_CLASSES.append(cls)
- if cls.subpage_types:
- NAVIGABLE_PAGE_MODEL_CLASSES.append(cls)
- else:
- LEAF_PAGE_MODEL_CLASSES.append(cls)
class Page(MP_Node, ClusterableModel, Indexed):
@@ -259,9 +253,6 @@ class Page(MP_Node, ClusterableModel, Indexed):
def __unicode__(self):
return self.title
- # by default pages do not allow any kind of subpages
- subpage_types = []
-
is_abstract = True # don't offer Page in the list of page types a superuser can create
def set_url_path(self, parent):
@@ -394,32 +385,32 @@ 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):
"""
Return true if it's meaningful to browse subpages of this page -
- i.e. it currently has subpages, or its page type indicates that sub-pages are supported,
+ i.e. it currently has subpages,
or it's at the top level (this rule necessary for empty out-of-the-box sites to have working navigation)
"""
- return (not self.is_leaf()) or (self.content_type_id not in get_leaf_page_content_type_ids()) or self.depth == 2
+ return (not self.is_leaf()) or self.depth == 2
def get_other_siblings(self):
# get sibling pages excluding self
@@ -484,25 +475,30 @@ class Page(MP_Node, ClusterableModel, Indexed):
where required
"""
if cls._clean_subpage_types is None:
- res = []
- for page_type in cls.subpage_types:
- if isinstance(page_type, basestring):
- try:
- app_label, model_name = page_type.split(".")
- except ValueError:
- # If we can't split, assume a model in current app
- app_label = cls._meta.app_label
- model_name = page_type
+ subpage_types = getattr(cls, 'subpage_types', None)
+ if subpage_types is None:
+ # if subpage_types is not specified on the Page class, allow all page types as subpages
+ res = get_page_types()
+ else:
+ res = []
+ for page_type in cls.subpage_types:
+ if isinstance(page_type, basestring):
+ try:
+ app_label, model_name = page_type.split(".")
+ except ValueError:
+ # If we can't split, assume a model in current app
+ app_label = cls._meta.app_label
+ model_name = page_type
+
+ model = get_model(app_label, model_name)
+ if model:
+ res.append(ContentType.objects.get_for_model(model))
+ else:
+ raise NameError(_("name '{0}' (used in subpage_types list) is not defined.").format(page_type))
- model = get_model(app_label, model_name)
- if model:
- res.append(model)
else:
- raise NameError(_("name '{0}' (used in subpage_types list) is not defined.").format(page_type))
-
- else:
- # assume it's already a model class
- res.append(page_type)
+ # assume it's already a model class
+ res.append(ContentType.objects.get_for_model(page_type))
cls._clean_subpage_types = res
@@ -657,13 +653,8 @@ class Page(MP_Node, ClusterableModel, Indexed):
def get_navigation_menu_items():
# Get all pages that appear in the navigation menu: ones which have children,
- # or are a non-leaf type (indicating that they *could* have children),
# or are at the top-level (this rule required so that an empty site out-of-the-box has a working menu)
- navigable_content_type_ids = get_navigable_page_content_type_ids()
- if navigable_content_type_ids:
- pages = Page.objects.filter(Q(content_type__in=navigable_content_type_ids)|Q(depth=2)|Q(numchild__gt=0)).order_by('path')
- else:
- pages = Page.objects.filter(Q(depth=2)|Q(numchild__gt=0)).order_by('path')
+ pages = Page.objects.filter(Q(depth=2)|Q(numchild__gt=0)).order_by('path')
# Turn this into a tree structure:
# tree_node = (page, children)
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/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..892dde5bd 100644
--- a/wagtail/wagtailembeds/tests.py
+++ b/wagtail/wagtailembeds/tests.py
@@ -1,7 +1,24 @@
+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, unittest
+
from wagtail.wagtailembeds import get_embed
+from wagtail.wagtailembeds.embeds import (
+ EmbedNotFoundException,
+ EmbedlyException,
+ AccessDeniedEmbedlyException,
+)
+from wagtail.wagtailembeds.embeds import embedly as wagtail_embedly, oembed as wagtail_oembed
+
class TestEmbeds(TestCase):
@@ -63,13 +80,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/tests.py b/wagtail/wagtailsnippets/tests.py
index de5e60b9b..9e2587794 100644
--- a/wagtail/wagtailsnippets/tests.py
+++ b/wagtail/wagtailsnippets/tests.py
@@ -2,46 +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.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',
@@ -53,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'})
@@ -65,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',
@@ -90,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',
@@ -112,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, )))
@@ -137,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)
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)