From d1bad6fadf4cc67d48c16ca50cc252afa3a87d5d Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 11:18:09 +0100 Subject: [PATCH 01/29] Split up wagtailadmin tests --- wagtail/wagtailadmin/tests/__init__.py | 0 .../{tests.py => tests/test_pages_views.py} | 28 ---------------- wagtail/wagtailadmin/tests/tests.py | 33 +++++++++++++++++++ 3 files changed, 33 insertions(+), 28 deletions(-) create mode 100644 wagtail/wagtailadmin/tests/__init__.py rename wagtail/wagtailadmin/{tests.py => tests/test_pages_views.py} (89%) create mode 100644 wagtail/wagtailadmin/tests/tests.py diff --git a/wagtail/wagtailadmin/tests/__init__.py b/wagtail/wagtailadmin/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtail/wagtailadmin/tests.py b/wagtail/wagtailadmin/tests/test_pages_views.py similarity index 89% rename from wagtail/wagtailadmin/tests.py rename to wagtail/wagtailadmin/tests/test_pages_views.py index c6bf6c8e6..1c546b5ad 100644 --- a/wagtail/wagtailadmin/tests.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -5,16 +5,6 @@ from wagtail.wagtailcore.models import Page from django.core.urlresolvers import reverse -class TestHome(TestCase): - def setUp(self): - # Login - login(self.client) - - def test_status_code(self): - response = self.client.get(reverse('wagtailadmin_home')) - self.assertEqual(response.status_code, 200) - - class TestPageExplorer(TestCase): def setUp(self): # Find root page @@ -281,21 +271,3 @@ class TestPageMove(TestCase): def test_page_set_page_position(self): response = self.client.get(reverse('wagtailadmin_pages_set_page_position', args=(self.test_page.id, ))) self.assertEqual(response.status_code, 200) - - -class TestEditorHooks(TestCase): - def setUp(self): - self.homepage = Page.objects.get(id=2) - login(self.client) - - def test_editor_css_and_js_hooks_on_add(self): - response = self.client.get(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.homepage.id))) - self.assertEqual(response.status_code, 200) - self.assertContains(response, '') - self.assertContains(response, '') - - def test_editor_css_and_js_hooks_on_edit(self): - response = self.client.get(reverse('wagtailadmin_pages_edit', args=(self.homepage.id, ))) - self.assertEqual(response.status_code, 200) - self.assertContains(response, '') - self.assertContains(response, '') diff --git a/wagtail/wagtailadmin/tests/tests.py b/wagtail/wagtailadmin/tests/tests.py new file mode 100644 index 000000000..3e5db6204 --- /dev/null +++ b/wagtail/wagtailadmin/tests/tests.py @@ -0,0 +1,33 @@ +from django.test import TestCase +from wagtail.tests.models import SimplePage, EventPage +from wagtail.tests.utils import login, unittest +from wagtail.wagtailcore.models import Page +from django.core.urlresolvers import reverse + + +class TestHome(TestCase): + def setUp(self): + # Login + login(self.client) + + def test_status_code(self): + response = self.client.get(reverse('wagtailadmin_home')) + self.assertEqual(response.status_code, 200) + + +class TestEditorHooks(TestCase): + def setUp(self): + self.homepage = Page.objects.get(id=2) + login(self.client) + + def test_editor_css_and_js_hooks_on_add(self): + response = self.client.get(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.homepage.id))) + self.assertEqual(response.status_code, 200) + self.assertContains(response, '') + self.assertContains(response, '') + + def test_editor_css_and_js_hooks_on_edit(self): + response = self.client.get(reverse('wagtailadmin_pages_edit', args=(self.homepage.id, ))) + self.assertEqual(response.status_code, 200) + self.assertContains(response, '') + self.assertContains(response, '') From 90a08fffe5f39a2675208ce33ba52b9f73d07082 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 12:43:28 +0100 Subject: [PATCH 02/29] Added wagtailadmin account management tests These test the following: - Users can login and logout - Logged in users are redirected to dashboard (not implemented yet #25) - Account section works and users can change their password - The password reset functionality works --- .../tests/test_account_management.py | 320 ++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 wagtail/wagtailadmin/tests/test_account_management.py diff --git a/wagtail/wagtailadmin/tests/test_account_management.py b/wagtail/wagtailadmin/tests/test_account_management.py new file mode 100644 index 000000000..95d54d7d9 --- /dev/null +++ b/wagtail/wagtailadmin/tests/test_account_management.py @@ -0,0 +1,320 @@ +from django.test import TestCase +from wagtail.tests.utils import login, unittest +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): + """ + This tests that users can login and logout of the admin interface + """ + def setUp(self): + login(self.client) + + def test_login_view(self): + """ + This tests that the login view responds with a login page + """ + # Logout so we can test the login view + self.client.logout() + + # Get login page + response = self.client.get(reverse('wagtailadmin_login')) + + # Check that the user recieved a login page + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/login.html') + + def test_login_view_post(self): + """ + This posts user credentials to the login view and checks that + the user was logged in successfully + """ + # Logout so we can test the login view + self.client.logout() + + # Post credentials to the login page + post_data = { + 'username': 'test', + 'password': 'password', + } + response = self.client.post(reverse('wagtailadmin_login'), post_data) + + # Check that the user was redirected to the dashboard + self.assertEqual(response.status_code, 302) + + # 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 + redirected to the admin dashboard if they try to access the login + page + """ + # Get login page + response = self.client.get(reverse('wagtailadmin_login')) + + # Check that the user was redirected to the dashboard + self.assertEqual(response.status_code, 302) + + def test_logout(self): + """ + This tests that the user can logout + """ + # Get logout page page + response = self.client.get(reverse('wagtailadmin_logout')) + + # Check that the user was redirected to the login page + self.assertEqual(response.status_code, 302) + + # Check that the user was logged out + self.assertFalse('_auth_user_id' in self.client.session) + + +class TestAccountSection(TestCase): + """ + This tests that the accounts section is working + """ + def setUp(self): + login(self.client) + + def test_account_view(self): + """ + This tests that the login view responds with a login page + """ + # Get account page + response = self.client.get(reverse('wagtailadmin_account')) + + # Check that the user recieved an account page + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/account/account.html') + + def test_change_password_view(self): + """ + This tests that the change password view responds with a change password page + """ + # Get change password page + response = self.client.get(reverse('wagtailadmin_account_change_password')) + + # Check that the user recieved a change password page + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/account/change_password.html') + + def test_change_password_view_post(self): + """ + This posts a new password to the change password view and checks + that the users password was changed + """ + # Post new password to change password page + post_data = { + 'new_password1': 'newpassword', + 'new_password2': 'newpassword', + } + response = self.client.post(reverse('wagtailadmin_account_change_password'), post_data) + + # Check that the user was redirected + self.assertEqual(response.status_code, 302) + + # Check that the password was changed + self.assertTrue(User.objects.get(username='test').check_password('newpassword')) + + def test_change_password_view_post_password_mismatch(self): + """ + This posts a two passwords that don't match to the password change + view and checks that a validation error was raised + """ + # Post new password to change password page + post_data = { + 'new_password1': 'newpassword', + 'new_password2': 'badpassword', + } + response = self.client.post(reverse('wagtailadmin_account_change_password'), post_data) + + # Check that the user wasn't redirected + self.assertEqual(response.status_code, 200) + + # Check that a validation error was raised + self.assertTrue('new_password2' in response.context['form'].errors.keys()) + self.assertTrue("The two password fields didn't match." in response.context['form'].errors['new_password2']) + + # Check that the password was not changed + self.assertTrue(User.objects.get(username='test').check_password('password')) + + +class TestPasswordReset(TestCase): + """ + This tests that the password reset is working + """ + def setUp(self): + # Create a user + User.objects.create_superuser(username='test', email='test@email.com', password='password') + + def test_password_reset_view(self): + """ + This tests that the password reset view returns a password reset page + """ + # Get password reset page + response = self.client.get(reverse('password_reset')) + + # Check that the user recieved a password reset page + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/account/password_reset/form.html') + + def test_password_reset_view_post(self): + """ + This posts an email address to the password reset view and + checks that a password reset email was sent + """ + # Post email address to password reset view + post_data = { + 'email': 'test@email.com', + } + response = self.client.post(reverse('password_reset'), post_data) + + # Check that the user was redirected + self.assertEqual(response.status_code, 302) + + # Check that a password reset email was sent to the user + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, ['test@email.com']) + self.assertEqual(mail.outbox[0].subject, "Password reset") + + def test_password_reset_view_post_unknown_email(self): + """ + This posts an unknown email address to the password reset view and + checks that the password reset form raises a validation error + """ + post_data = { + 'email': 'unknown@email.com', + } + response = self.client.post(reverse('password_reset'), post_data) + + # Check that the user wasn't redirected + self.assertEqual(response.status_code, 200) + + # Check that a validation error was raised + self.assertTrue('__all__' in response.context['form'].errors.keys()) + self.assertTrue("This email address is not recognised." in response.context['form'].errors['__all__']) + + # Check that an email was not sent + self.assertEqual(len(mail.outbox), 0) + + def test_password_reset_view_post_invalid_email(self): + """ + This posts an incalid email address to the password reset view and + checks that the password reset form raises a validation error + """ + post_data = { + 'email': 'Hello world!', + } + response = self.client.post(reverse('password_reset'), post_data) + + # Check that the user wasn't redirected + self.assertEqual(response.status_code, 200) + + # Check that a validation error was raised + self.assertTrue('email' in response.context['form'].errors.keys()) + self.assertTrue("Enter a valid email address." in response.context['form'].errors['email']) + + # Check that an email was not sent + self.assertEqual(len(mail.outbox), 0) + + def setup_password_reset_confirm_tests(self): + from django.utils.encoding import force_bytes + from django.utils.http import urlsafe_base64_encode + + # Get user + self.user = User.objects.get(username='test') + + # Generate a password reset token + self.password_reset_token = PasswordResetTokenGenerator().make_token(self.user) + + # Generate a password reset uid + self.password_reset_uid = urlsafe_base64_encode(force_bytes(self.user.pk)) + + # Create url_args + self.url_kwargs = dict(uidb64=self.password_reset_uid, token=self.password_reset_token) + + def test_password_reset_confirm_view(self): + """ + This tests that the password reset confirm view returns a password reset confirm page + """ + self.setup_password_reset_confirm_tests() + + # Get password reset confirm page + response = self.client.get(reverse('password_reset_confirm', kwargs=self.url_kwargs)) + + # Check that the user recieved a password confirm done page + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/account/password_reset/confirm.html') + + def test_password_reset_confirm_view_post(self): + """ + This posts a new password to the password reset confirm view and checks + that the users password was changed + """ + self.setup_password_reset_confirm_tests() + + # Post new password to change password page + post_data = { + 'new_password1': 'newpassword', + 'new_password2': 'newpassword', + } + response = self.client.post(reverse('password_reset_confirm', kwargs=self.url_kwargs), post_data) + + # Check that the user was redirected + self.assertEqual(response.status_code, 302) + + # Check that the password was changed + self.assertTrue(User.objects.get(username='test').check_password('newpassword')) + + def test_password_reset_confirm_view_post_password_mismatch(self): + """ + This posts a two passwords that don't match to the password reset + confirm view and checks that a validation error was raised + """ + self.setup_password_reset_confirm_tests() + + # Post new password to change password page + post_data = { + 'new_password1': 'newpassword', + 'new_password2': 'badpassword', + } + response = self.client.post(reverse('password_reset_confirm', kwargs=self.url_kwargs), post_data) + + # Check that the user wasn't redirected + self.assertEqual(response.status_code, 200) + + # Check that a validation error was raised + self.assertTrue('new_password2' in response.context['form'].errors.keys()) + self.assertTrue("The two password fields didn't match." in response.context['form'].errors['new_password2']) + + # Check that the password was not changed + self.assertTrue(User.objects.get(username='test').check_password('password')) + + def test_password_reset_done_view(self): + """ + This tests that the password reset done view returns a password reset done page + """ + # Get password reset done page + response = self.client.get(reverse('password_reset_done')) + + # Check that the user recieved a password reset done page + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/account/password_reset/done.html') + + def test_password_reset_complete_view(self): + """ + This tests that the password reset complete view returns a password reset complete page + """ + # Get password reset complete page + response = self.client.get(reverse('password_reset_complete')) + + # Check that the user recieved a password reset complete page + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/account/password_reset/complete.html') From 526cfba68285f89a85319aa4e627773a01ce482c Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 14:13:43 +0100 Subject: [PATCH 03/29] Added tests for unpublish view --- wagtail/tests/utils.py | 6 +- .../wagtailadmin/tests/test_pages_views.py | 68 +++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/wagtail/tests/utils.py b/wagtail/tests/utils.py index 7b6557a78..4774fc4da 100644 --- a/wagtail/tests/utils.py +++ b/wagtail/tests/utils.py @@ -1,4 +1,4 @@ -from django.contrib.auth.models import User +from django.contrib.auth.models import User, Permission # We need to make sure that we're using the same unittest library that Django uses internally # Otherwise, we get issues with the "SkipTest" and "ExpectedFailure" exceptions being recognised as errors @@ -14,7 +14,9 @@ except ImportError: def login(client): # Create a user - User.objects.create_superuser(username='test', email='test@email.com', password='password') + user = User.objects.create_superuser(username='test', email='test@email.com', password='password') # Login client.login(username='test', password='password') + + return user diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index 1c546b5ad..75d727f22 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -3,6 +3,7 @@ from wagtail.tests.models import SimplePage, EventPage from wagtail.tests.utils import login, unittest from wagtail.wagtailcore.models import Page from django.core.urlresolvers import reverse +from django.contrib.auth.models import Permission class TestPageExplorer(TestCase): @@ -271,3 +272,70 @@ class TestPageMove(TestCase): def test_page_set_page_position(self): response = self.client.get(reverse('wagtailadmin_pages_set_page_position', args=(self.test_page.id, ))) self.assertEqual(response.status_code, 200) + + +class TestPageUnpublish(TestCase): + def setUp(self): + self.user = login(self.client) + + # Create a page to unpublish + 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) + + def test_unpublish_view(self): + """ + This tests that the unpublish view responds with an unpublish confirm page + """ + # Get unpublish page + response = self.client.get(reverse('wagtailadmin_pages_unpublish', args=(self.page.id, ))) + + # Check that the user recieved an unpublish confirm page + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/confirm_unpublish.html') + + def test_unpublish_view_invalid_page_id(self): + """ + This tests that the unpublish view returns an error if the page id is invalid + """ + # Get unpublish page + response = self.client.get(reverse('wagtailadmin_pages_unpublish', args=(12345, ))) + + # Check that the user recieved a 404 response + self.assertEqual(response.status_code, 404) + + def test_unpublish_view_bad_permissions(self): + """ + This tests that the unpublish view doesn't allow users without unpublish permissions + """ + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Get unpublish page + response = self.client.get(reverse('wagtailadmin_pages_unpublish', args=(self.page.id, ))) + + # Check that the user recieved a 403 response + self.assertEqual(response.status_code, 403) + + def test_unpublish_view_post(self): + """ + This posts to the unpublish view and checks that the page was unpublished + """ + # Post to the unpublish page + response = self.client.post(reverse('wagtailadmin_pages_unpublish', args=(self.page.id, )), { + 'foo': "Must post something or the view won't see this as a POST request", + }) + + # Check that the user was redirected + self.assertEqual(response.status_code, 302) + + # Check that the page was unpublished + self.assertFalse(SimplePage.objects.get(id=self.page.id).live) From d85612dac88e38b96bc5a1001f0e5aa78990b943 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 14:22:10 +0100 Subject: [PATCH 04/29] Added permission checks to page views --- .../wagtailadmin/tests/test_pages_views.py | 88 +++++++++++++++++-- 1 file changed, 79 insertions(+), 9 deletions(-) diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index 75d727f22..eaba42f7b 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -33,12 +33,26 @@ class TestPageCreation(TestCase): self.root_page = Page.objects.get(id=2) # Login - login(self.client) + self.user = login(self.client) def test_add_subpage(self): response = self.client.get(reverse('wagtailadmin_pages_add_subpage', args=(self.root_page.id, ))) self.assertEqual(response.status_code, 200) + def test_add_subpage_bad_permissions(self): + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Get add subpage page + response = self.client.get(reverse('wagtailadmin_pages_add_subpage', args=(self.root_page.id, ))) + + # Check that the user recieved a 403 response + self.assertEqual(response.status_code, 403) + def test_add_subpage_nonexistantparent(self): response = self.client.get(reverse('wagtailadmin_pages_add_subpage', args=(100000, ))) self.assertEqual(response.status_code, 404) @@ -47,6 +61,20 @@ class TestPageCreation(TestCase): response = self.client.get(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.root_page.id))) self.assertEqual(response.status_code, 200) + def test_create_simplepage_bad_permissions(self): + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Get page + response = self.client.get(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.root_page.id, ))) + + # Check that the user recieved a 403 response + self.assertEqual(response.status_code, 403) + def test_create_simplepage_post(self): post_data = { 'title': "New page!", @@ -133,14 +161,28 @@ class TestPageEdit(TestCase): self.root_page.add_child(instance=self.event_page) # Login - login(self.client) + self.user = login(self.client) - def test_edit_page(self): + def test_page_edit(self): # Tests that the edit page loads response = self.client.get(reverse('wagtailadmin_pages_edit', args=(self.event_page.id, ))) self.assertEqual(response.status_code, 200) - def test_edit_post(self): + def test_page_edit_bad_permissions(self): + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Get edit page + response = self.client.get(reverse('wagtailadmin_pages_edit', args=(self.child_page.id, ))) + + # Check that the user recieved a 403 response + self.assertEqual(response.status_code, 403) + + def test_page_edit_post(self): # Tests simple editing post_data = { 'title': "I've been edited!", @@ -156,7 +198,7 @@ class TestPageEdit(TestCase): child_page_new = SimplePage.objects.get(id=self.child_page.id) self.assertTrue(child_page_new.has_unpublished_changes) - def test_edit_post_publish(self): + def test_page_edit_post_publish(self): # Tests publish from edit page post_data = { 'title': "I've been edited!", @@ -189,13 +231,27 @@ class TestPageDelete(TestCase): self.root_page.add_child(instance=self.child_page) # Login - login(self.client) + self.user = login(self.client) - def test_delete(self): + def test_page_delete(self): response = self.client.get(reverse('wagtailadmin_pages_delete', args=(self.child_page.id, ))) self.assertEqual(response.status_code, 200) - def test_delete_post(self): + def test_page_delete_bad_permissions(self): + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Get delete page + response = self.client.get(reverse('wagtailadmin_pages_delete', args=(self.child_page.id, ))) + + # Check that the user recieved a 403 response + self.assertEqual(response.status_code, 403) + + def test_page_delete_post(self): post_data = {'hello': 'world'} # For some reason, this test doesn't work without a bit of POST data response = self.client.post(reverse('wagtailadmin_pages_delete', args=(self.child_page.id, )), post_data) @@ -259,12 +315,26 @@ class TestPageMove(TestCase): self.section_a.add_child(instance=self.test_page) # Login - login(self.client) + self.user = login(self.client) def test_page_move(self): response = self.client.get(reverse('wagtailadmin_pages_move', args=(self.test_page.id, ))) self.assertEqual(response.status_code, 200) + def test_page_move_bad_permissions(self): + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Get move page + response = self.client.get(reverse('wagtailadmin_pages_move', args=(self.test_page.id, ))) + + # Check that the user recieved a 403 response + self.assertEqual(response.status_code, 403) + def test_page_move_confirm(self): response = self.client.get(reverse('wagtailadmin_pages_move_confirm', args=(self.test_page.id, self.section_b.id))) self.assertEqual(response.status_code, 200) From dcca37264b1eef7e8954e3c6275aecedcad93621 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 14:40:03 +0100 Subject: [PATCH 05/29] Added tests for approve/reject moderation views --- .../wagtailadmin/tests/test_pages_views.py | 71 ++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index eaba42f7b..1c3abf555 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -1,9 +1,10 @@ from django.test import TestCase from wagtail.tests.models import SimplePage, EventPage from wagtail.tests.utils import login, unittest -from wagtail.wagtailcore.models import Page +from wagtail.wagtailcore.models import Page, PageRevision from django.core.urlresolvers import reverse -from django.contrib.auth.models import Permission +from django.contrib.auth.models import User, Permission +from django.core import mail class TestPageExplorer(TestCase): @@ -409,3 +410,69 @@ class TestPageUnpublish(TestCase): # Check that the page was unpublished self.assertFalse(SimplePage.objects.get(id=self.page.id).live) + + +class TestApproveRejectModeration(TestCase): + def setUp(self): + self.submitter = User.objects.create_superuser( + username='submitter', + email='submitter@email.com', + password='password', + ) + + self.user = login(self.client) + + # Create a page and submit it for moderation + root_page = Page.objects.get(id=2) + self.page = SimplePage( + title="Hello world!", + slug='hello-world', + live=False, + ) + root_page.add_child(instance=self.page) + + self.page.save_revision(user=self.submitter, submitted_for_moderation=True) + self.revision = self.page.get_latest_revision() + + def test_approve_moderation_view(self): + """ + This posts to the approve moderation view and checks that the page was approved + """ + # Post + response = self.client.post(reverse('wagtailadmin_pages_approve_moderation', args=(self.revision.id, )), { + 'foo': "Must post something or the view won't see this as a POST request", + }) + + # Check that the user was redirected + self.assertEqual(response.status_code, 302) + + # Page must be live + self.assertTrue(Page.objects.get(id=self.page.id).live) + + # Submitter must recieve an approved email + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, ['submitter@email.com']) + self.assertEqual(mail.outbox[0].subject, 'The page "Hello world!" has been approved') + + def test_reject_moderation_view(self): + """ + This posts to the reject moderation view and checks that the page was rejected + """ + # Post + response = self.client.post(reverse('wagtailadmin_pages_reject_moderation', args=(self.revision.id, )), { + 'foo': "Must post something or the view won't see this as a POST request", + }) + + # Check that the user was redirected + self.assertEqual(response.status_code, 302) + + # Page must not be live + self.assertFalse(Page.objects.get(id=self.page.id).live) + + # Revision must no longer be submitted for moderation + self.assertFalse(PageRevision.objects.get(id=self.revision.id).submitted_for_moderation) + + # Submitter must recieve a rejected email + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, ['submitter@email.com']) + self.assertEqual(mail.outbox[0].subject, 'The page "Hello world!" has been rejected') From 046f17e64f4bb2688b8efb224e9686775700b8f7 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 14:44:51 +0100 Subject: [PATCH 06/29] Added error tests for approve/moderation views --- .../wagtailadmin/tests/test_pages_views.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index 1c3abf555..23dae25d8 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -454,6 +454,37 @@ class TestApproveRejectModeration(TestCase): self.assertEqual(mail.outbox[0].to, ['submitter@email.com']) self.assertEqual(mail.outbox[0].subject, 'The page "Hello world!" has been approved') + def test_approve_moderation_view_bad_revision_id(self): + """ + This tests that the approve moderation view handles invalid revision ids correctly + """ + # Post + response = self.client.post(reverse('wagtailadmin_pages_approve_moderation', args=(12345, )), { + 'foo': "Must post something or the view won't see this as a POST request", + }) + + # Check that the user recieved a 404 response + self.assertEqual(response.status_code, 404) + + def test_approve_moderation_view_bad_permissions(self): + """ + This tests that the approve moderation view doesn't allow users without moderation permissions + """ + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Post + response = self.client.post(reverse('wagtailadmin_pages_approve_moderation', args=(self.revision.id, )), { + 'foo': "Must post something or the view won't see this as a POST request", + }) + + # Check that the user recieved a 403 response + self.assertEqual(response.status_code, 403) + def test_reject_moderation_view(self): """ This posts to the reject moderation view and checks that the page was rejected @@ -476,3 +507,34 @@ class TestApproveRejectModeration(TestCase): self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].to, ['submitter@email.com']) self.assertEqual(mail.outbox[0].subject, 'The page "Hello world!" has been rejected') + + def test_reject_moderation_view_bad_revision_id(self): + """ + This tests that the reject moderation view handles invalid revision ids correctly + """ + # Post + response = self.client.post(reverse('wagtailadmin_pages_reject_moderation', args=(12345, )), { + 'foo': "Must post something or the view won't see this as a POST request", + }) + + # Check that the user recieved a 404 response + self.assertEqual(response.status_code, 404) + + def test_reject_moderation_view_bad_permissions(self): + """ + This tests that the reject moderation view doesn't allow users without moderation permissions + """ + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Post + response = self.client.post(reverse('wagtailadmin_pages_reject_moderation', args=(self.revision.id, )), { + 'foo': "Must post something or the view won't see this as a POST request", + }) + + # Check that the user recieved a 403 response + self.assertEqual(response.status_code, 403) From 87838f887996e53a15130af60276a50c0bf67f95 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 15:01:37 +0100 Subject: [PATCH 07/29] Improvements to search view tests --- .../wagtailadmin/tests/test_pages_views.py | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index 23dae25d8..bc1a99e89 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -268,25 +268,36 @@ class TestPageSearch(TestCase): # Login login(self.client) - def get(self, params={}): - return self.client.get(reverse('wagtailadmin_pages_search'), params) + def get(self, params=None, **extra): + return self.client.get(reverse('wagtailadmin_pages_search'), params or {}, **extra) - def test_status_code(self): - self.assertEqual(self.get().status_code, 200) + def test_view(self): + response = self.get() + self.assertTemplateUsed(response, 'wagtailadmin/pages/search.html') + self.assertEqual(response.status_code, 200) def test_search(self): response = self.get({'q': "Hello"}) self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/search.html') + self.assertEqual(response.context['query_string'], "Hello") + + def test_ajax(self): + response = self.get({'q': "Hello"}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + self.assertTemplateNotUsed(response, 'wagtailadmin/pages/search.html') + self.assertTemplateUsed(response, 'wagtailadmin/pages/search_results.html') self.assertEqual(response.context['query_string'], "Hello") def test_pagination(self): pages = ['0', '1', '-1', '9999', 'Not a page'] for page in pages: - response = self.get({'p': page}) + response = self.get({'q': "Hello", 'p': page}) self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/search.html') def test_root_can_appear_in_search_results(self): - response = self.client.get('/admin/pages/search/?q=roo') + response = self.get({'q': "roo"}) self.assertEqual(response.status_code, 200) # 'pages' list in the response should contain root results = response.context['pages'] From 4e260fb9a192ba523bf17ebe214b2bcc8846892e Mon Sep 17 00:00:00 2001 From: Tom Talbot Date: Fri, 30 May 2014 15:08:12 +0100 Subject: [PATCH 08/29] Unit tests for snippets views --- wagtail/tests/models.py | 18 +++ wagtail/wagtailsnippets/tests.py | 152 +++++++++++++++++++--- wagtail/wagtailsnippets/views/snippets.py | 6 +- 3 files changed, 157 insertions(+), 19 deletions(-) diff --git a/wagtail/tests/models.py b/wagtail/tests/models.py index 844fb836f..bbf91d278 100644 --- a/wagtail/tests/models.py +++ b/wagtail/tests/models.py @@ -7,6 +7,7 @@ from wagtail.wagtailadmin.edit_handlers import FieldPanel, MultiFieldPanel, Inli from wagtail.wagtailimages.edit_handlers import ImageChooserPanel from wagtail.wagtaildocs.edit_handlers import DocumentChooserPanel from wagtail.wagtailforms.models import AbstractEmailForm, AbstractFormField +from wagtail.wagtailsnippets.models import register_snippet EVENT_AUDIENCE_CHOICES = ( @@ -252,3 +253,20 @@ FormPage.content_panels = [ FieldPanel('subject', classname="full"), ], "Email") ] + + +# Snippets + +class Advert(models.Model): + url = models.URLField(null=True, blank=True) + text = models.CharField(max_length=255) + + panels = [ + FieldPanel('url'), + FieldPanel('text'), + ] + + def __unicode__(self): + return self.text + +register_snippet(Advert) diff --git a/wagtail/wagtailsnippets/tests.py b/wagtail/wagtailsnippets/tests.py index 782fe9087..fd283f7f5 100644 --- a/wagtail/wagtailsnippets/tests.py +++ b/wagtail/wagtailsnippets/tests.py @@ -1,19 +1,139 @@ -""" -This file demonstrates writing tests using the unittest module. These will pass -when you run "manage.py test". - -Replace this with more appropriate tests for your application. -""" - -from wagtail.tests.utils import unittest - from django.test import TestCase +from django.core.urlresolvers import reverse +from django.contrib.auth.models import User + +from wagtail.tests.utils import login, unittest +from wagtail.tests.models import Advert -@unittest.skip("Need real tests") -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. - """ - self.assertEqual(1 + 1, 2) +class TestSnippetIndexView(TestCase): + def setUp(self): + login(self.client) + + 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_displays_snippet(self): + self.assertContains(self.get(), "Adverts") + + +class TestSnippetListView(TestCase): + def setUp(self): + login(self.client) + + 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_displays_add_button(self): + self.assertContains(self.get(), "Add advert") + + +class TestSnippetCreateView(TestCase): + def setUp(self): + login(self.client) + + def get(self, params={}): + return self.client.get(reverse('wagtailsnippets_create', + args=('tests', 'advert')), + params) + + def post(self, post_data={}): + return self.client.post(reverse('wagtailsnippets_create', + args=('tests', 'advert')), + post_data) + + def test_status_code(self): + self.assertEqual(self.get().status_code, 200) + + def test_create_invalid(self): + response = self.post(post_data={'foo': 'bar'}) + self.assertContains(response, "The snippet could not be created due to errors.") + self.assertContains(response, "This field is required.") + + def test_create(self): + response = self.post(post_data={'text': 'test_advert', + 'url': 'http://www.example.com/'}) + self.assertEqual(response.status_code, 302) + + snippets = Advert.objects.filter(text='test_advert') + self.assertEqual(snippets.count(), 1) + self.assertEqual(snippets.first().url, 'http://www.example.com/') + + +class TestSnippetEditView(TestCase): + 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) + + def get(self, params={}): + return self.client.get(reverse('wagtailsnippets_edit', + args=('tests', 'advert', self.test_snippet.id)), + params) + + def post(self, post_data={}): + return self.client.post(reverse('wagtailsnippets_edit', + args=('tests', 'advert', self.test_snippet.id)), + post_data) + + def test_status_code(self): + self.assertEqual(self.get().status_code, 200) + + def test_non_existant_model(self): + response = self.client.get(reverse('wagtailsnippets_edit', + args=('tests', 'foo', self.test_snippet.id))) + self.assertEqual(response.status_code, 404) + + def test_nonexistant_id(self): + response = self.client.get(reverse('wagtailsnippets_edit', + args=('tests', 'advert', 999999))) + self.assertEqual(response.status_code, 404) + + def test_edit_invalid(self): + response = self.post(post_data={'foo': 'bar'}) + self.assertContains(response, "The snippet could not be saved due to errors.") + self.assertContains(response, "This field is required.") + + def test_edit(self): + response = self.post(post_data={'text': 'edited_test_advert', + 'url': 'http://www.example.com/edited'}) + self.assertEqual(response.status_code, 302) + + 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): + 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) + + def test_delete_get(self): + response = self.client.get(reverse('wagtailsnippets_delete', args=('tests', 'advert', self.test_snippet.id, ))) + self.assertEqual(response.status_code, 200) + + def test_delete_post(self): + post_data = {'foo': 'bar'} # For some reason, this test doesn't work without a bit of POST data + response = self.client.post(reverse('wagtailsnippets_delete', args=('tests', 'advert', self.test_snippet.id, )), post_data) + + # Should be redirected to explorer page + self.assertEqual(response.status_code, 302) + + # Check that the page is gone + self.assertEqual(Advert.objects.filter(text='test_advert').count(), 0) diff --git a/wagtail/wagtailsnippets/views/snippets.py b/wagtail/wagtailsnippets/views/snippets.py index 763362750..ed0e310cb 100644 --- a/wagtail/wagtailsnippets/views/snippets.py +++ b/wagtail/wagtailsnippets/views/snippets.py @@ -125,7 +125,7 @@ def create(request, content_type_app_name, content_type_model_name): messages.success( request, _("{snippet_type} '{instance}' created.").format( - snippet_type=capfirst(get_snippet_type_name(content_type)[0]), + snippet_type=capfirst(get_snippet_type_name(content_type)[0]), instance=instance ) ) @@ -166,7 +166,7 @@ def edit(request, content_type_app_name, content_type_model_name, id): messages.success( request, _("{snippet_type} '{instance}' updated.").format( - snippet_type=capfirst(snippet_type_name), + snippet_type=capfirst(snippet_type_name), instance=instance ) ) @@ -202,7 +202,7 @@ def delete(request, content_type_app_name, content_type_model_name, id): messages.success( request, _("{snippet_type} '{instance}' deleted.").format( - snippet_type=capfirst(snippet_type_name), + snippet_type=capfirst(snippet_type_name), instance=instance ) ) From 890072aa5288e9a42e9f651d49580b2d3405301d Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 15:21:26 +0100 Subject: [PATCH 09/29] Added tests for fixtree command --- .../management/commands/fixtree.py | 22 ++++----- wagtail/wagtailcore/tests.py | 45 +++++++++++++++++++ 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/wagtail/wagtailcore/management/commands/fixtree.py b/wagtail/wagtailcore/management/commands/fixtree.py index 4bf989d1e..7fbed1e5b 100644 --- a/wagtail/wagtailcore/management/commands/fixtree.py +++ b/wagtail/wagtailcore/management/commands/fixtree.py @@ -12,15 +12,15 @@ class Command(NoArgsCommand): try: page.specific except ObjectDoesNotExist: - print "Page %d (%s) is missing a subclass record; deleting." % (page.id, page.title) + self.stdout.write("Page %d (%s) is missing a subclass record; deleting." % (page.id, page.title)) problems_found = True page.delete() (_, _, _, bad_depth, bad_numchild) = Page.find_problems() if bad_depth: - print "Incorrect depth value found for pages: %r" % bad_depth + self.stdout.write("Incorrect depth value found for pages: %r" % bad_depth) if bad_numchild: - print "Incorrect numchild value found for pages: %r" % bad_numchild + self.stdout.write("Incorrect numchild value found for pages: %r" % bad_numchild) if bad_depth or bad_numchild: Page.fix_tree(destructive=False) @@ -28,20 +28,20 @@ class Command(NoArgsCommand): remaining_problems = Page.find_problems() if any(remaining_problems): - print "Remaining problems (cannot fix automatically):" + self.stdout.write("Remaining problems (cannot fix automatically):") (bad_alpha, bad_path, orphans, bad_depth, bad_numchild) = remaining_problems if bad_alpha: - print "Invalid characters found in path for pages: %r" % bad_alpha + self.stdout.write("Invalid characters found in path for pages: %r" % bad_alpha) if bad_path: - print "Invalid path length found for pages: %r" % bad_path + self.stdout.write("Invalid path length found for pages: %r" % bad_path) if orphans: - print "Orphaned pages found: %r" % orphans + self.stdout.write("Orphaned pages found: %r" % orphans) if bad_depth: - print "Incorrect depth value found for pages: %r" % bad_depth + self.stdout.write("Incorrect depth value found for pages: %r" % bad_depth) if bad_numchild: - print "Incorrect numchild value found for pages: %r" % bad_numchild + self.stdout.write("Incorrect numchild value found for pages: %r" % bad_numchild) elif problems_found: - print "All problems fixed." + self.stdout.write("All problems fixed.") else: - print "No problems found." + self.stdout.write("No problems found.") diff --git a/wagtail/wagtailcore/tests.py b/wagtail/wagtailcore/tests.py index 1b12c1025..ce00002f6 100644 --- a/wagtail/wagtailcore/tests.py +++ b/wagtail/wagtailcore/tests.py @@ -1,5 +1,7 @@ from django.test import TestCase, Client from django.http import HttpRequest, Http404 +from django.core import management +from StringIO import StringIO from django.contrib.auth.models import User @@ -776,3 +778,46 @@ class TestIssue157(TestCase): # Check url self.assertEqual(homepage.url, '/') + + +class TestFixTreeCommand(TestCase): + fixtures = ['test.json'] + + def run_command(self): + management.call_command('fixtree', interactive=False, stdout=StringIO()) + + def test_fixes_numchild(self): + # Get homepage and save old value + homepage = Page.objects.get(url_path='/home/') + old_numchild = homepage.numchild + + # Break it + homepage.numchild = 12345 + homepage.save() + + # Check that its broken + self.assertEqual(Page.objects.get(url_path='/home/').numchild, 12345) + + # Call command + self.run_command() + + # Check if its fixed + self.assertEqual(Page.objects.get(url_path='/home/').numchild, old_numchild) + + def test_fixes_depth(self): + # Get homepage and save old value + homepage = Page.objects.get(url_path='/home/') + old_depth = homepage.depth + + # Break it + homepage.depth = 12345 + homepage.save() + + # Check that its broken + self.assertEqual(Page.objects.get(url_path='/home/').depth, 12345) + + # Call command + self.run_command() + + # Check if its fixed + self.assertEqual(Page.objects.get(url_path='/home/').depth, old_depth) From 9b0e56dc90eed67cca795c0b76b38b844a81ead5 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 15:43:34 +0100 Subject: [PATCH 10/29] Fixed invalid numchild values in test fixtures --- wagtail/tests/fixtures/test.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wagtail/tests/fixtures/test.json b/wagtail/tests/fixtures/test.json index 625c5618b..11b82d9f1 100644 --- a/wagtail/tests/fixtures/test.json +++ b/wagtail/tests/fixtures/test.json @@ -39,7 +39,7 @@ "model": "wagtailcore.page", "fields": { "title": "Events", - "numchild": 2, + "numchild": 3, "show_in_menus": true, "live": true, "depth": 3, @@ -62,7 +62,7 @@ "model": "wagtailcore.page", "fields": { "title": "Christmas", - "numchild": 1, + "numchild": 0, "show_in_menus": true, "live": true, "depth": 4, @@ -90,7 +90,7 @@ "model": "wagtailcore.page", "fields": { "title": "Tentative Unpublished Event", - "numchild": 1, + "numchild": 0, "show_in_menus": true, "live": false, "depth": 4, @@ -118,7 +118,7 @@ "model": "wagtailcore.page", "fields": { "title": "Someone Else's Event", - "numchild": 1, + "numchild": 0, "show_in_menus": true, "live": false, "depth": 4, From e9ce499d5a40287b82b1caea955a861d5241066e Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 15:43:54 +0100 Subject: [PATCH 11/29] Added test for move_pages command --- .../management/commands/move_pages.py | 4 ++-- wagtail/wagtailcore/tests.py | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/wagtail/wagtailcore/management/commands/move_pages.py b/wagtail/wagtailcore/management/commands/move_pages.py index 344604f06..86ea4016d 100644 --- a/wagtail/wagtailcore/management/commands/move_pages.py +++ b/wagtail/wagtailcore/management/commands/move_pages.py @@ -15,8 +15,8 @@ class Command(BaseCommand): pages = from_page.get_children() # Move the pages - print 'Moving ' + str(len(pages)) + ' pages from "' + from_page.title + '" to "' + to_page.title + '"' + self.stdout.write('Moving ' + str(len(pages)) + ' pages from "' + from_page.title + '" to "' + to_page.title + '"') for page in pages: page.move(to_page, pos='last-child') - print 'Done' + self.stdout.write('Done') diff --git a/wagtail/wagtailcore/tests.py b/wagtail/wagtailcore/tests.py index ce00002f6..8cbd7be9a 100644 --- a/wagtail/wagtailcore/tests.py +++ b/wagtail/wagtailcore/tests.py @@ -821,3 +821,23 @@ class TestFixTreeCommand(TestCase): # Check if its fixed self.assertEqual(Page.objects.get(url_path='/home/').depth, old_depth) + + +class TestMovePagesCommand(TestCase): + fixtures = ['test.json'] + + def run_command(self, from_, to): + management.call_command('move_pages', str(from_), str(to), interactive=False, stdout=StringIO()) + + def test_move_pages(self): + # Get pages + events_index = Page.objects.get(url_path='/home/events/') + about_us = Page.objects.get(url_path='/home/about-us/') + page_ids = events_index.get_children().values_list('id', flat=True) + + # Move all events into "about us" + self.run_command(events_index.id, about_us.id) + + # Check that all pages moved + for page_id in page_ids: + self.assertEqual(Page.objects.get(id=page_id).get_parent(), about_us) From 86969e9df3a4e5416e14c8a0a67a1d047d9ba782 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 15:49:21 +0100 Subject: [PATCH 12/29] Added test for replace_text command --- .../management/commands/replace_text.py | 2 +- wagtail/wagtailcore/tests.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/wagtail/wagtailcore/management/commands/replace_text.py b/wagtail/wagtailcore/management/commands/replace_text.py index 23788d370..d654a61d6 100644 --- a/wagtail/wagtailcore/management/commands/replace_text.py +++ b/wagtail/wagtailcore/management/commands/replace_text.py @@ -24,7 +24,7 @@ class Command(BaseCommand): revision.save(update_fields=['content_json']) for content_type in get_page_types(): - print "scanning %s" % content_type.name + self.stdout.write("scanning %s" % content_type.name) page_class = content_type.model_class() try: diff --git a/wagtail/wagtailcore/tests.py b/wagtail/wagtailcore/tests.py index 8cbd7be9a..df66c6b93 100644 --- a/wagtail/wagtailcore/tests.py +++ b/wagtail/wagtailcore/tests.py @@ -841,3 +841,20 @@ class TestMovePagesCommand(TestCase): # Check that all pages moved for page_id in page_ids: self.assertEqual(Page.objects.get(id=page_id).get_parent(), about_us) + + +class TestReplaceTextCommand(TestCase): + fixtures = ['test.json'] + + def run_command(self, from_text, to_text): + management.call_command('replace_text', from_text, to_text, interactive=False, stdout=StringIO()) + + def test_replace_text(self): + # Check that the christmas page is definitely about christmas + self.assertEqual(Page.objects.get(url_path='/home/events/christmas/').title, "Christmas") + + # Make it about easter + self.run_command("Christmas", "Easter") + + # Check that its now about easter + self.assertEqual(Page.objects.get(url_path='/home/events/christmas/').title, "Easter") From 2a92db9e5af1e2e9df0ecc4184f1be74668c4c27 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 15:55:04 +0100 Subject: [PATCH 13/29] Removed unneeded import --- wagtail/tests/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wagtail/tests/utils.py b/wagtail/tests/utils.py index 4774fc4da..6590e6dcc 100644 --- a/wagtail/tests/utils.py +++ b/wagtail/tests/utils.py @@ -1,4 +1,4 @@ -from django.contrib.auth.models import User, Permission +from django.contrib.auth.models import User # We need to make sure that we're using the same unittest library that Django uses internally # Otherwise, we get issues with the "SkipTest" and "ExpectedFailure" exceptions being recognised as errors From d057b1774f2cb159f6caa06ebf91817dea6ace92 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 16:07:23 +0100 Subject: [PATCH 14/29] Added tests for submitting pages to moderation --- .../wagtailadmin/tests/test_pages_views.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index bc1a99e89..9cad6ecdc 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -111,6 +111,36 @@ class TestPageCreation(TestCase): self.assertIsInstance(page, SimplePage) self.assertTrue(page.live) + def test_create_simplepage_post_submit(self): + # Create a moderator user for testing email + moderator = User.objects.create_superuser('moderator', 'moderator@email.com', 'password') + + # Submit + post_data = { + 'title': "New page!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-submit': "Submit", + } + response = self.client.post(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.root_page.id)), post_data) + + # Should be redirected to explorer page + self.assertEqual(response.status_code, 302) + + # Find the page and check it + page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific + self.assertEqual(page.title, post_data['title']) + self.assertIsInstance(page, SimplePage) + self.assertFalse(page.live) + + # The latest revision for the page should now be in moderation + self.assertTrue(page.get_latest_revision().submitted_for_moderation) + + # Check that the moderator got an email + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, ['moderator@email.com']) + self.assertEqual(mail.outbox[0].subject, 'The page "New page!" has been submitted for moderation') + def test_create_simplepage_post_existingslug(self): # This tests the existing slug checking on page save @@ -219,6 +249,34 @@ class TestPageEdit(TestCase): # The page shouldn't have "has_unpublished_changes" flag set self.assertFalse(child_page_new.has_unpublished_changes) + def test_page_edit_post_submit(self): + # Create a moderator user for testing email + moderator = User.objects.create_superuser('moderator', 'moderator@email.com', 'password') + + # Tests submitting from edit page + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-submit': "Submit", + } + response = self.client.post(reverse('wagtailadmin_pages_edit', args=(self.child_page.id, )), post_data) + + # Should be redirected to explorer page + self.assertEqual(response.status_code, 302) + + # The page should have "has_unpublished_changes" flag set + child_page_new = SimplePage.objects.get(id=self.child_page.id) + self.assertTrue(child_page_new.has_unpublished_changes) + + # The latest revision for the page should now be in moderation + self.assertTrue(child_page_new.get_latest_revision().submitted_for_moderation) + + # Check that the moderator got an email + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, ['moderator@email.com']) + self.assertEqual(mail.outbox[0].subject, 'The page "Hello world!" has been submitted for moderation') # Note: should this be "I've been edited!"? + class TestPageDelete(TestCase): def setUp(self): From c4399636dd726dd09acc026b5fbf77fe69c84969 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 16:16:57 +0100 Subject: [PATCH 15/29] Added test for preview on edit --- wagtail/wagtailadmin/tests/test_pages_views.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index 9cad6ecdc..23ece10bb 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -277,6 +277,21 @@ class TestPageEdit(TestCase): self.assertEqual(mail.outbox[0].to, ['moderator@email.com']) self.assertEqual(mail.outbox[0].subject, 'The page "Hello world!" has been submitted for moderation') # Note: should this be "I've been edited!"? + def test_preview_on_edit(self): + # Tests submitting from edit page + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-submit': "Submit", + } + response = self.client.post(reverse('wagtailadmin_pages_preview_on_edit', args=(self.child_page.id, )), post_data) + + # Check the response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'tests/simple_page.html') + self.assertContains(response, "I've been edited!") + class TestPageDelete(TestCase): def setUp(self): From 73242ad02b4b6354de69cb33f949ee2cdeee50c5 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 16:19:55 +0100 Subject: [PATCH 16/29] Added test for preview on create --- wagtail/wagtailadmin/tests/test_pages_views.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index 23ece10bb..a2531e63b 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -171,6 +171,20 @@ class TestPageCreation(TestCase): response = self.client.get(reverse('wagtailadmin_pages_create', args=('wagtailimages', 'image', self.root_page.id))) self.assertEqual(response.status_code, 404) + def test_preview_on_create(self): + post_data = { + 'title': "New page!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-submit': "Submit", + } + response = self.client.post(reverse('wagtailadmin_pages_preview_on_create', args=('tests', 'simplepage', self.root_page.id)), post_data) + + # Check the response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'tests/simple_page.html') + self.assertContains(response, "New page!") + class TestPageEdit(TestCase): def setUp(self): @@ -278,7 +292,6 @@ class TestPageEdit(TestCase): self.assertEqual(mail.outbox[0].subject, 'The page "Hello world!" has been submitted for moderation') # Note: should this be "I've been edited!"? def test_preview_on_edit(self): - # Tests submitting from edit page post_data = { 'title': "I've been edited!", 'content': "Some content", From c4107067096f62a0ee11f8a0ebecac356a7dfbc1 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 16:24:45 +0100 Subject: [PATCH 17/29] Added test for content_type_use --- wagtail/wagtailadmin/tests/test_pages_views.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index a2531e63b..5bcb0594f 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -635,3 +635,19 @@ class TestApproveRejectModeration(TestCase): # Check that the user recieved a 403 response self.assertEqual(response.status_code, 403) + + +class TestContentTypeUse(TestCase): + fixtures = ['test.json'] + + def setUp(self): + self.user = login(self.client) + + def test_content_type_use(self): + # Get use of event page + response = self.client.get(reverse('wagtailadmin_pages_type_use', args=('tests', 'eventpage'))) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/content_type_use.html') + self.assertContains(response, "Christmas") From 488acc6afa86aba35ecf37b6c1babd8c44390633 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 16:36:56 +0100 Subject: [PATCH 18/29] Added test for preview for moderation --- wagtail/wagtailadmin/tests/test_pages_views.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/wagtail/wagtailadmin/tests/test_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index 5bcb0594f..ccaa67dae 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -636,6 +636,14 @@ class TestApproveRejectModeration(TestCase): # Check that the user recieved a 403 response self.assertEqual(response.status_code, 403) + def test_preview_for_moderation(self): + response = self.client.get(reverse('wagtailadmin_pages_preview_for_moderation', args=(self.revision.id, ))) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'tests/simple_page.html') + self.assertContains(response, "Hello world!") + class TestContentTypeUse(TestCase): fixtures = ['test.json'] From 370442d13dbb7c309784c861c7841d317709194b Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 16:38:32 +0100 Subject: [PATCH 19/29] Added missing template --- wagtail/tests/templates/tests/simple_page.html | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 wagtail/tests/templates/tests/simple_page.html diff --git a/wagtail/tests/templates/tests/simple_page.html b/wagtail/tests/templates/tests/simple_page.html new file mode 100644 index 000000000..3413b3aa2 --- /dev/null +++ b/wagtail/tests/templates/tests/simple_page.html @@ -0,0 +1,11 @@ +{% load pageurl %} + + + + {{ self.title }} + + +

{{ self.title }}

+

Simple page

+ + From 85ef536f0566bdb9610bd993d4ef6bdb1a54b3a6 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 30 May 2014 16:45:27 +0100 Subject: [PATCH 20/29] Added test for send email task --- wagtail/wagtailadmin/tests/tests.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/wagtail/wagtailadmin/tests/tests.py b/wagtail/wagtailadmin/tests/tests.py index 3e5db6204..12aa28a0b 100644 --- a/wagtail/wagtailadmin/tests/tests.py +++ b/wagtail/wagtailadmin/tests/tests.py @@ -2,7 +2,9 @@ from django.test import TestCase from wagtail.tests.models import SimplePage, EventPage from wagtail.tests.utils import login, unittest 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): @@ -31,3 +33,14 @@ class TestEditorHooks(TestCase): self.assertEqual(response.status_code, 200) self.assertContains(response, '') self.assertContains(response, '') + + +class TestSendEmailTask(TestCase): + def test_send_email(self): + send_email_task("Test subject", "Test content", ["nobody@email.com"], "test@email.com") + + # Check that the email was sent + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].subject, "Test subject") + self.assertEqual(mail.outbox[0].body, "Test content") + self.assertEqual(mail.outbox[0].to, ["nobody@email.com"]) From 9ccdcc604e2b81ccb7506f241e502808cd7b7096 Mon Sep 17 00:00:00 2001 From: Neal Todd Date: Fri, 30 May 2014 17:24:37 +0100 Subject: [PATCH 21/29] Recache coverage badge --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index d967dcb1f..3f29a679b 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ .. image:: https://travis-ci.org/torchbox/wagtail.png?branch=master :target: https://travis-ci.org/torchbox/wagtail -.. image:: https://coveralls.io/repos/torchbox/wagtail/badge.png?branch=master +.. image:: https://coveralls.io/repos/torchbox/wagtail/badge.png?branch=master&zxcv :target: https://coveralls.io/r/torchbox/wagtail?branch=master .. image:: https://pypip.in/v/wagtail/badge.png?zxcv From 337f5d414648aa544fa9000a19a6d8d9241e03d4 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 2 Jun 2014 12:42:59 +0100 Subject: [PATCH 22/29] unittest2 no longer an 'essential' requirement for developers --- requirements-dev.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 008d24d2e..d35d6b087 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,7 +1,3 @@ -# Requirements essential for developing wagtail (not needed to run it) - -unittest2==0.5.1 - # For coverage and PEP8 linting coverage==3.7.1 flake8==2.1.0 From 13c1af3d970346e809e37fe74ed2101d6d95512a Mon Sep 17 00:00:00 2001 From: Tom Dyson Date: Mon, 2 Jun 2014 13:49:10 +0100 Subject: [PATCH 23/29] Don't be coy about our test coverage --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 3f29a679b..62b3c5cd5 100644 --- a/README.rst +++ b/README.rst @@ -24,7 +24,7 @@ Wagtail is a Django content management system built originally for the `Royal Co * Support for tree-based content organisation * Optional preview->submit->approve workflow * Fast out of the box. `Varnish `_-friendly if you need it -* Tests! But not enough; we're working hard to improve this +* Excellent test coverage Find out more at `wagtail.io `_. From 3fb8dc03e57b4457e1ff95b0f0549648018f4e62 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Mon, 2 Jun 2014 15:54:26 +0100 Subject: [PATCH 24/29] updates to FE docs --- .../building_your_site/frontenddevelopers.rst | 118 +++++++++++++----- 1 file changed, 84 insertions(+), 34 deletions(-) diff --git a/docs/building_your_site/frontenddevelopers.rst b/docs/building_your_site/frontenddevelopers.rst index d39bd8c33..8febd2923 100644 --- a/docs/building_your_site/frontenddevelopers.rst +++ b/docs/building_your_site/frontenddevelopers.rst @@ -4,51 +4,77 @@ For Front End developers .. note:: This documentation is currently being written. +.. contents:: + ======================== Overview ======================== +This page is aimed at non-Django-literate Front End developers. + Wagtail uses Django's templating language. For developers new to Django, start with Django's own template documentation: https://docs.djangoproject.com/en/dev/topics/templates/ Python programmers new to Django/Wagtail may prefer more technical documentation: https://docs.djangoproject.com/en/dev/ref/templates/api/ +You should be familiar with Django templating basics before continuing with this documentation. + ========================== -Displaying Pages +Templates ========================== -Template Location -~~~~~~~~~~~~~~~~~ +Every type of page or "content type" in Wagtail is defined in "The Model": a file called ``models.py``. The name of each "page model" is prefixing with ``class``. e.g If your site has a blog, you might have a ``BlogPage`` page model and another called ``BlogPageListing``. The names of the models are up to the developer. -For each of your ``Page``-derived models, Wagtail will look for a template in the following location, relative to your project root:: +For each type of page in ``models.py``, Wagtail assumes an HTML template file exists of (almost) the same name. The Front End developer may need to create these templates themselves. - project/ - app/ +To find a suitable template, Wagtail converts CamelCase names to underscore_case. So for a ``BlogPage``, a template file ``blog_page.html`` will be expected. The name of the template file can be overridden per model if necessary. + +Template files are assumed to exist here:: + + name_of_project/ + name_of_app/ templates/ - app/ - blog_index_page.html + name_of_app/ + blog_page.html models.py -Class names are converted from camel case to underscores. For example, the template for model class ``BlogIndexPage`` would be assumed to be ``blog_index_page.html``. For more information, see the Django documentation for the `application directories template loader`_. + +For more information, see the Django documentation for the `application directories template loader`_. .. _application directories template loader: https://docs.djangoproject.com/en/dev/ref/templates/api/ -Self -~~~~ +Page content +~~~~~~~~~~~~ -By default, the context passed to a model's template consists of two properties: ``self`` and ``request``. ``self`` is the model object being displayed. ``request`` is the normal Django request object. So, to include the title of a ``Page``, use ``{{ self.title }}``. +The data/content entered into each page is accessed/output through Django's ``{{ double-brace }}`` notation. Each field from the model must be accessed by prefixing ``self.``. e.g the page title ``{{ self.title }}`` or another field ``{{ self.author }}``. -======================== -Static files (css, js, images) -======================== +Additionally ``request.`` is available and contains Django's request object. +============== +Static assets +============== -Images -~~~~~~ +Static files e.g CSS, JS and images are typically stored here:: + + name_of_project/ + name_of_app/ + static/ + name_of_app/ + css/ + js/ + images/ + models.py -Images uploaded to Wagtail go into the image library and from there are added to pages via the :doc:`page editor interface `. +(The names "css", "js" etc aren't important, only their position within the tree.) + +Any file within the static folder should be inserted into your HTML using the ``{% static %}`` tag. More about it: :ref:`static_tag`. + +User images +~~~~~~~~~~~ + +Images uploaded to Wagtail by its users go into the image library and from there are added to pages via the :doc:`page editor interface `. Unlike other CMS, adding images to a page does not involve choosing a "version" of the image to use. Wagtail has no predefined image "formats" or "sizes". Instead the template developer defines image manipulation to occur *on the fly* when the image is requested, via a special syntax within the template. @@ -73,16 +99,21 @@ The syntax for displaying/manipulating an image is thus:: {% image [image] [method]-[dimension(s)] %} -For example:: +For example: + +.. code-block:: django + + {% load image %} + ... {% image self.photo width-400 %} {% image self.photo fill-80x80 %} -The ``image`` is the Django object refering to the image. If your page model defined a field called "photo" then ``image`` would probably be ``self.photo``. The ``method`` defines which resizing algorithm to use and ``dimension(s)`` provides height and/or width values (as ``[width|height]`` or ``[width]x[height]``) to refine that algorithm. +In the above syntax ``[image]`` is the Django object refering to the image. If your page model defined a field called "photo" then ``[image]`` would probably be ``self.photo``. The ``[method]`` defines which resizing algorithm to use and ``[dimension(s)]`` provides height and/or width values (as ``[width|height]`` or ``[width]x[height]``) to refine that algorithm. -Note that a space separates ``image`` and ``method``, but not ``method`` and ``dimensions``: a hyphen between ``method`` and ``dimensions`` is mandatory. Multiple dimensions must be separated by an ``x``. +Note that a space separates ``[image]`` and ``[method]``, but not ``[method]`` and ``[dimensions]``: a hyphen between ``[method]`` and ``[dimensions]`` is mandatory. Multiple dimensions must be separated by an ``x``. The available ``method`` s are: @@ -123,53 +154,63 @@ The available ``method`` s are: .. Note:: Wagtail *does not allow deforming or stretching images*. Image dimension ratios will always be kept. Wagtail also *does not support upscaling*. Small images forced to appear at larger sizes will "max out" at their their native dimensions. - -To request the "original" version of an image, it is suggested you rely on the lack of upscaling support by requesting an image much larger than it's maximum dimensions. e.g to insert an image who's dimensions are uncertain/unknown, at it's maximum size, try: ``{% image self.image width-10000 %}``. This assumes the image is unlikely to be larger than 10000px wide. +.. Note:: + Wagtail does not make the "original" version of an image explicitly available. To request it, it's suggested you rely on the lack of upscaling by requesting an image much larger than it's maximum dimensions. e.g to insert an image who's dimensions are uncertain/unknown at it's maximum size, try: ``{% image self.image width-10000 %}``. This assumes the image is unlikely to be larger than 10000px wide. .. _rich-text-filter: + Rich text (filter) ~~~~~~~~~~~~~~~~~~ -This filter is required for use with any ``RichTextField``. It will expand internal shorthand references to embeds and links made in the Wagtail editor into fully-baked HTML ready for display. **Note that the template tag loaded differs from the name of the filter.** +This filter is required for use with any field that generates raw HTML e.g ``RichTextField``. It will expand internal shorthand references to embeds and links made in the Wagtail editor into fully-baked HTML ready for display. .. code-block:: django {% load rich_text %} ... - {{ body|richtext }} + {{ self.body|richtext }} + +.. Note:: + Note that the template tag loaded differs from the name of the filter. Internal links (tag) ~~~~~~~~~~~~~~~~~~~~ **pageurl** -Takes a ``Page``-derived object and returns its URL as relative (``/foo/bar/``) if it's within the same site as the current page, or absolute (``http://example.com/foo/bar/``) if not. +Takes a Page object and returns a relative URL (``/foo/bar/``) if within the same site as the current page, or absolute (``http://example.com/foo/bar/``) if not. .. code-block:: django {% load pageurl %} ... - + **slugurl** -Takes a ``slug`` string and returns the URL for the ``Page``-derived object with that slug. Like ``pageurl``, will try to provide a relative link if possible, but will default to an absolute link if on a different site. +Takes any ``slug`` as defined in a page's "Promote" tab and returns the URL for the matching Page. Like ``pageurl``, will try to provide a relative link if possible, but will default to an absolute link if on a different site. This is most useful when creating shared page furniture e.g top level navigation or site-wide links. .. code-block:: django {% load slugurl %} ... - - + +.. _static_tag: Static files (tag) -~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~ +Used to load anything from your static files directory. Use of this tag avoids rewriting all static paths if hosting arrangements change, as they might between local and a live environments. -Misc -~~~~~~~~~~ +.. code-block:: django + + {% load static %} + ... + My image + +Notice that the full path name is not required and the path snippet you enter only need begin with the parent app's directory name. @@ -177,10 +218,19 @@ Misc Wagtail User Bar ======================== -This tag provides a Wagtail icon and flyout menu on the top-right of a page for a logged-in user with editing capabilities, with the option of editing the current Page-derived object or adding a new sibling object. +This tag provides a contextual flyout menu on the top-right of a page for logged-in users. The menu gives editors the ability to edit the current page or add another at the same level. Moderators are also given the ability to accept or reject a page previewed as part of content moderation. .. code-block:: django {% load wagtailuserbar %} ... {% wagtailuserbar %} + +By default the User Bar appears in the top right of the browser window, flush with the edge. If this conflicts with your design it can be moved with a css rule in your own CSS files e.g to move it down from the top: + +.. code-block:: css + + #wagtail-userbar{ + top:200px + } + From 4c8ae31b6f459d68707c2a9081735124612ce3c9 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Mon, 2 Jun 2014 15:56:16 +0100 Subject: [PATCH 25/29] removed under construction notice. I consider it complete-ish. Otherwise it'll never be removed --- docs/building_your_site/frontenddevelopers.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/building_your_site/frontenddevelopers.rst b/docs/building_your_site/frontenddevelopers.rst index 8febd2923..6232f5383 100644 --- a/docs/building_your_site/frontenddevelopers.rst +++ b/docs/building_your_site/frontenddevelopers.rst @@ -1,9 +1,6 @@ For Front End developers ======================== -.. note:: - This documentation is currently being written. - .. contents:: ======================== From e7e63e605e725ce8a21d564411dbb0ab5bf8b892 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Mon, 2 Jun 2014 16:15:49 +0100 Subject: [PATCH 26/29] added a note about the image tag 'as' syntax --- .../building_your_site/frontenddevelopers.rst | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/docs/building_your_site/frontenddevelopers.rst b/docs/building_your_site/frontenddevelopers.rst index 6232f5383..78c8cf78e 100644 --- a/docs/building_your_site/frontenddevelopers.rst +++ b/docs/building_your_site/frontenddevelopers.rst @@ -71,11 +71,11 @@ Any file within the static folder should be inserted into your HTML using the `` User images ~~~~~~~~~~~ -Images uploaded to Wagtail by its users go into the image library and from there are added to pages via the :doc:`page editor interface `. +Images uploaded to Wagtail by its users (as opposed to a developer's static files, above) go into the image library and from there are added to pages via the :doc:`page editor interface `. Unlike other CMS, adding images to a page does not involve choosing a "version" of the image to use. Wagtail has no predefined image "formats" or "sizes". Instead the template developer defines image manipulation to occur *on the fly* when the image is requested, via a special syntax within the template. -Images from the library **must** be requested using this syntax, but images in your codebase can be added via conventional means e.g ``img`` tags. Only images from the library can be manipulated on the fly. +Images from the library **must** be requested using this syntax, but a developer's static images can be added via conventional means e.g ``img`` tags. Only images from the library can be manipulated on the fly. Read more about the image manipulation syntax here :ref:`image_tag`. @@ -92,7 +92,9 @@ In addition to Django's standard tags and filters, Wagtail provides some of it's Images (tag) ~~~~~~~~~~~~ -The syntax for displaying/manipulating an image is thus:: +The ``image`` tag inserts an XHTML-compatible ``img`` element into the page, setting its ``src``, ``width``, ``height`` and ``alt``. See also :ref:`image_tag_alt`. + +The syntax for the tag is thus:: {% image [image] [method]-[dimension(s)] %} @@ -154,6 +156,24 @@ The available ``method`` s are: .. Note:: Wagtail does not make the "original" version of an image explicitly available. To request it, it's suggested you rely on the lack of upscaling by requesting an image much larger than it's maximum dimensions. e.g to insert an image who's dimensions are uncertain/unknown at it's maximum size, try: ``{% image self.image width-10000 %}``. This assumes the image is unlikely to be larger than 10000px wide. + +.. _image_tag_alt: + +More control over the ``img`` tag +--------------------------------- + +In some cases greater control over the ``img`` tag is required, for example to add a custom ``class``. Rather than generating the ``img`` element for you, Wagtail can assign the relevant data to another object using Django's ``as`` syntax: + +.. code-block:: django + + {% load image %} + ... + {% image self.photo width-400 as tmp_photo %} + + {{ tmp_photo.alt }} + + .. _rich-text-filter: Rich text (filter) From 09f1300f89a121852ee05fa8a82a5a75d2247008 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Mon, 2 Jun 2014 16:26:46 +0100 Subject: [PATCH 27/29] wording tweak --- docs/building_your_site/frontenddevelopers.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/building_your_site/frontenddevelopers.rst b/docs/building_your_site/frontenddevelopers.rst index 78c8cf78e..3d3117b84 100644 --- a/docs/building_your_site/frontenddevelopers.rst +++ b/docs/building_your_site/frontenddevelopers.rst @@ -21,11 +21,11 @@ You should be familiar with Django templating basics before continuing with this Templates ========================== -Every type of page or "content type" in Wagtail is defined in "The Model": a file called ``models.py``. The name of each "page model" is prefixing with ``class``. e.g If your site has a blog, you might have a ``BlogPage`` page model and another called ``BlogPageListing``. The names of the models are up to the developer. +Every type of page or "content type" in Wagtail is defined as a "model" in a file called ``models.py``. If your site has a blog, you might have a ``BlogPage`` model and another called ``BlogPageListing``. The names of the models are up to the Django developer. -For each type of page in ``models.py``, Wagtail assumes an HTML template file exists of (almost) the same name. The Front End developer may need to create these templates themselves. +For each page model in ``models.py``, Wagtail assumes an HTML template file exists of (almost) the same name. The Front End developer may need to create these templates themselves by refering to ``models.py`` to infer template names from the models defined therein. -To find a suitable template, Wagtail converts CamelCase names to underscore_case. So for a ``BlogPage``, a template file ``blog_page.html`` will be expected. The name of the template file can be overridden per model if necessary. +To find a suitable template, Wagtail converts CamelCase names to underscore_case. So for a ``BlogPage``, a template ``blog_page.html`` will be expected. The name of the template file can be overridden per model if necessary. Template files are assumed to exist here:: From 1207342f203e12a92809cf7ba748e4dbe9c6e242 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Mon, 2 Jun 2014 16:34:45 +0100 Subject: [PATCH 28/29] wording tweak --- docs/building_your_site/frontenddevelopers.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/building_your_site/frontenddevelopers.rst b/docs/building_your_site/frontenddevelopers.rst index 3d3117b84..d009a1af8 100644 --- a/docs/building_your_site/frontenddevelopers.rst +++ b/docs/building_your_site/frontenddevelopers.rst @@ -179,7 +179,9 @@ In some cases greater control over the ``img`` tag is required, for example to a Rich text (filter) ~~~~~~~~~~~~~~~~~~ -This filter is required for use with any field that generates raw HTML e.g ``RichTextField``. It will expand internal shorthand references to embeds and links made in the Wagtail editor into fully-baked HTML ready for display. +This filter takes a chunk of HTML content and renders it as safe HTML in the page. Importantly it also expands internal shorthand references to embedded images and links made in the Wagtail editor into fully-baked HTML ready for display. + +Only fields using ``RichTextField`` need this applied in the template. .. code-block:: django @@ -193,7 +195,8 @@ This filter is required for use with any field that generates raw HTML e.g ``Ric Internal links (tag) ~~~~~~~~~~~~~~~~~~~~ -**pageurl** +pageurl +-------- Takes a Page object and returns a relative URL (``/foo/bar/``) if within the same site as the current page, or absolute (``http://example.com/foo/bar/``) if not. @@ -203,7 +206,8 @@ Takes a Page object and returns a relative URL (``/foo/bar/``) if within the sam ... -**slugurl** +slugurl +-------- Takes any ``slug`` as defined in a page's "Promote" tab and returns the URL for the matching Page. Like ``pageurl``, will try to provide a relative link if possible, but will default to an absolute link if on a different site. This is most useful when creating shared page furniture e.g top level navigation or site-wide links. From 6107fa0bcf5c4d899d53635f780d857fae71d689 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 3 Jun 2014 09:43:10 +0100 Subject: [PATCH 29/29] Removed unittest2 from .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cc1cb74e8..e23794d4f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ services: install: - python setup.py install - pip install psycopg2 pyelasticsearch elasticutils==0.8.2 wand - - pip install coveralls unittest2 + - pip install coveralls # Pre-test configuration before_script: - psql -c 'create database wagtaildemo;' -U postgres