diff --git a/CHANGELOG.txt b/CHANGELOG.txt index ddd41d76e..19eec21e7 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -9,6 +9,11 @@ Changelog * Removed the "More" section from the admin menu * Added pagination to page listings in admin * Support for setting a subpage_types property on page models, to define which page types are allowed as subpages + * Added a new datetime picker widget + * Added styleguide (mainly for wagtail developers) + * Aesthetic improvements to preview experience + * 'image' tag now accepts extra keyword arguments to be output as attributes on the img tag + * Added an 'attrs' property to image rendition objects to output src, width, height and alt attributes all in one go * Fix: Animated GIFs are now coalesced before resizing * Fix: Wand backend clones images before modifying them * Fix: Admin breadcrumb now positioned correctly on mobile diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index a3f9c7221..fcba8d2d7 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -1,8 +1,8 @@ Original Authors ================ -* Matthew Westcott matthew.westcott@torchbox.com -* David Cranwell david.cranwell@torchbox.com +* Matthew Westcott matthew.westcott@torchbox.com twitter: @gasmanic +* David Cranwell david.cranwell@torchbox.com twitter: @davecranwell * Karl Hobley karl.hobley@torchbox.com * Helen Chapman helen.chapman@torchbox.com diff --git a/README.rst b/README.rst index 62b3c5cd5..0e68b8c6b 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&zxcv +.. image:: https://coveralls.io/repos/torchbox/wagtail/badge.png?branch=master&zxcv1 :target: https://coveralls.io/r/torchbox/wagtail?branch=master .. image:: https://pypip.in/v/wagtail/badge.png?zxcv diff --git a/docs/building_your_site/frontenddevelopers.rst b/docs/building_your_site/frontenddevelopers.rst index f0054eaab..a8cf6a25e 100644 --- a/docs/building_your_site/frontenddevelopers.rst +++ b/docs/building_your_site/frontenddevelopers.rst @@ -197,6 +197,11 @@ In some cases greater control over the ``img`` tag is required, for example to a {{ tmp_photo.alt }} +You can also use the ``attrs`` property as a shorthand to output the ``src``, ``width``, ``height`` and ``alt`` attributes in one go: + +.. code-block:: django + + .. _rich-text-filter: diff --git a/wagtail/tests/utils.py b/wagtail/tests/utils.py index 0b13e5932..02327799b 100644 --- a/wagtail/tests/utils.py +++ b/wagtail/tests/utils.py @@ -24,21 +24,3 @@ class WagtailTestUtils(object): self.client.login(username='test', password='password') return user - - # From: https://github.com/django/django/blob/255449c1ee61c14778658caae8c430fa4d76afd6/django/contrib/auth/tests/test_views.py#L70-L85 - def assertURLEqual(self, url, expected, parse_qs=False): - """ - Given two URLs, make sure all their components (the ones given by - urlparse) are equal, only comparing components that are present in both - URLs. - If `parse_qs` is True, then the querystrings are parsed with QueryDict. - This is useful if you don't want the order of parameters to matter. - Otherwise, the query strings are compared as-is. - """ - fields = ParseResult._fields - - for attr, x, y in zip(fields, urlparse(url), urlparse(expected)): - if parse_qs and attr == 'query': - x, y = QueryDict(x), QueryDict(y) - if x and y and x != y: - self.fail("%r != %r (%s doesn't match)" % (url, expected, attr)) diff --git a/wagtail/wagtailadmin/edit_handlers.py b/wagtail/wagtailadmin/edit_handlers.py index 81ebbf137..7bbead9b1 100644 --- a/wagtail/wagtailadmin/edit_handlers.py +++ b/wagtail/wagtailadmin/edit_handlers.py @@ -17,7 +17,7 @@ from django.core.urlresolvers import reverse from django.utils.translation import ugettext_lazy from wagtail.wagtailcore.models import Page -from wagtail.wagtailcore.util import camelcase_to_underscore +from wagtail.wagtailcore.utils import camelcase_to_underscore from wagtail.wagtailcore.fields import RichTextArea diff --git a/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py b/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py index 2d85c355e..9f03c7b63 100644 --- a/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py +++ b/wagtail/wagtailadmin/templatetags/wagtailadmin_tags.py @@ -6,7 +6,7 @@ from wagtail.wagtailadmin import hooks from wagtail.wagtailadmin.menu import MenuItem from wagtail.wagtailcore.models import get_navigation_menu_items, UserPagePermissionsProxy -from wagtail.wagtailcore.util import camelcase_to_underscore +from wagtail.wagtailcore.utils import camelcase_to_underscore register = template.Library() diff --git a/wagtail/wagtailadmin/tests/test_account_management.py b/wagtail/wagtailadmin/tests/test_account_management.py index 3553b8fba..566d68723 100644 --- a/wagtail/wagtailadmin/tests/test_account_management.py +++ b/wagtail/wagtailadmin/tests/test_account_management.py @@ -43,8 +43,7 @@ class TestAuthentication(TestCase, WagtailTestUtils): response = self.client.post(reverse('wagtailadmin_login'), post_data) # Check that the user was redirected to the dashboard - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, reverse('wagtailadmin_home')) + self.assertRedirects(response, reverse('wagtailadmin_home')) # Check that the user was logged in self.assertTrue('_auth_user_id' in self.client.session) @@ -60,8 +59,7 @@ class TestAuthentication(TestCase, WagtailTestUtils): response = self.client.get(reverse('wagtailadmin_login')) # Check that the user was redirected to the dashboard - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, reverse('wagtailadmin_home')) + self.assertRedirects(response, reverse('wagtailadmin_home')) def test_logout(self): """ @@ -71,8 +69,7 @@ class TestAuthentication(TestCase, WagtailTestUtils): 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')) + self.assertRedirects(response, reverse('wagtailadmin_login')) # Check that the user was logged out self.assertFalse('_auth_user_id' in self.client.session) @@ -89,8 +86,7 @@ class TestAuthentication(TestCase, WagtailTestUtils): 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')) + self.assertRedirects(response, reverse('wagtailadmin_login') + '?next=' + reverse('wagtailadmin_home')) def test_not_logged_in_redirect_default_settings(self): """ @@ -109,7 +105,7 @@ class TestAuthentication(TestCase, WagtailTestUtils): # 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')) + self.assertRedirects(response, reverse('wagtailadmin_login') + '?next=' + reverse('wagtailadmin_home')) class TestAccountSection(TestCase, WagtailTestUtils): @@ -154,8 +150,7 @@ class TestAccountSection(TestCase, WagtailTestUtils): response = self.client.post(reverse('wagtailadmin_account_change_password'), post_data) # Check that the user was redirected to the account page - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, reverse('wagtailadmin_account')) + self.assertRedirects(response, reverse('wagtailadmin_account')) # Check that the password was changed self.assertTrue(User.objects.get(username='test').check_password('newpassword')) @@ -214,8 +209,7 @@ class TestPasswordReset(TestCase, WagtailTestUtils): response = self.client.post(reverse('password_reset'), post_data) # Check that the user was redirected to the done page - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, reverse('password_reset_done')) + self.assertRedirects(response, reverse('password_reset_done')) # Check that a password reset email was sent to the user self.assertEqual(len(mail.outbox), 1) @@ -306,8 +300,7 @@ class TestPasswordReset(TestCase, WagtailTestUtils): response = self.client.post(reverse('password_reset_confirm', kwargs=self.url_kwargs), post_data) # Check that the user was redirected to the complete page - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, reverse('password_reset_complete')) + self.assertRedirects(response, 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_pages_views.py b/wagtail/wagtailadmin/tests/test_pages_views.py index 09e6b6f5c..6c1f490dd 100644 --- a/wagtail/wagtailadmin/tests/test_pages_views.py +++ b/wagtail/wagtailadmin/tests/test_pages_views.py @@ -5,6 +5,7 @@ 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 +from django.core.paginator import Paginator class TestPageExplorer(TestCase, WagtailTestUtils): @@ -13,9 +14,10 @@ class TestPageExplorer(TestCase, WagtailTestUtils): self.root_page = Page.objects.get(id=2) # Add child page - self.child_page = SimplePage() - self.child_page.title = "Hello world!" - self.child_page.slug = "hello-world" + self.child_page = SimplePage( + title="Hello world!", + slug="hello-world", + ) self.root_page.add_child(instance=self.child_page) # Login @@ -24,9 +26,81 @@ class TestPageExplorer(TestCase, WagtailTestUtils): def test_explore(self): response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, ))) self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') self.assertEqual(self.root_page, response.context['parent_page']) self.assertTrue(response.context['pages'].paginator.object_list.filter(id=self.child_page.id).exists()) + def test_explore_root(self): + response = self.client.get(reverse('wagtailadmin_explore_root')) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + self.assertEqual(Page.objects.get(id=1), response.context['parent_page']) + self.assertTrue(response.context['pages'].paginator.object_list.filter(id=self.root_page.id).exists()) + + def test_ordering(self): + response = self.client.get(reverse('wagtailadmin_explore_root'), {'ordering': 'content_type'}) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + self.assertEqual(response.context['ordering'], 'content_type') + + def test_invalid_ordering(self): + response = self.client.get(reverse('wagtailadmin_explore_root'), {'ordering': 'invalid_order'}) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + self.assertEqual(response.context['ordering'], 'title') + + def test_reordering(self): + response = self.client.get(reverse('wagtailadmin_explore_root'), {'ordering': 'ord'}) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + self.assertEqual(response.context['ordering'], 'ord') + + # Pages must not be paginated + self.assertNotIsInstance(response.context['pages'], Paginator) + + def make_pages(self): + for i in range(150): + self.root_page.add_child(instance=SimplePage( + title="Page " + str(i), + slug="page-" + str(i), + )) + + def test_pagination(self): + self.make_pages() + + response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, )), {'p': 2}) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + + # Check that we got the correct page + self.assertEqual(response.context['pages'].number, 2) + + def test_pagination_invalid(self): + self.make_pages() + + response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, )), {'p': 'Hello World!'}) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + + # Check that we got page one + self.assertEqual(response.context['pages'].number, 1) + + def test_pagination_out_of_range(self): + self.make_pages() + + response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, )), {'p': 99999}) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + + # Check that we got the last page + self.assertEqual(response.context['pages'].number, response.context['pages'].paginator.num_pages) + class TestPageCreation(TestCase, WagtailTestUtils): def setUp(self): @@ -85,8 +159,7 @@ class TestPageCreation(TestCase, WagtailTestUtils): 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) - self.assertURLEqual(response.url, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + self.assertRedirects(response, 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,8 +177,7 @@ class TestPageCreation(TestCase, WagtailTestUtils): 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) - self.assertURLEqual(response.url, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + self.assertRedirects(response, 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 @@ -127,8 +199,7 @@ class TestPageCreation(TestCase, WagtailTestUtils): 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) - self.assertURLEqual(response.url, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + self.assertRedirects(response, 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 @@ -144,7 +215,7 @@ class TestPageCreation(TestCase, WagtailTestUtils): 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): + def test_create_simplepage_post_existing_slug(self): # This tests the existing slug checking on page save # Create a page @@ -165,6 +236,9 @@ class TestPageCreation(TestCase, WagtailTestUtils): # Should not be redirected (as the save should fail) self.assertEqual(response.status_code, 200) + # Check that a form error was raised + self.assertFormError(response, 'form', 'slug', "This slug is already in use") + def test_create_nonexistantparent(self): response = self.client.get(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', 100000))) self.assertEqual(response.status_code, 404) @@ -245,8 +319,7 @@ class TestPageEdit(TestCase, WagtailTestUtils): 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) - self.assertURLEqual(response.url, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + self.assertRedirects(response, 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) @@ -263,8 +336,7 @@ class TestPageEdit(TestCase, WagtailTestUtils): 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) - self.assertURLEqual(response.url, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + self.assertRedirects(response, 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) @@ -287,8 +359,7 @@ class TestPageEdit(TestCase, WagtailTestUtils): 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) - self.assertURLEqual(response.url, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + self.assertRedirects(response, 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) @@ -302,6 +373,29 @@ class TestPageEdit(TestCase, WagtailTestUtils): 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_page_edit_post_existing_slug(self): + # This tests the existing slug checking on page edit + + # Create a page + self.child_page = SimplePage() + self.child_page.title = "Hello world 2" + self.child_page.slug = "hello-world2" + self.root_page.add_child(instance=self.child_page) + + # Attempt to change the slug to one thats already in use + post_data = { + 'title': "Hello world 2", + 'slug': 'hello-world', + 'action-submit': "Submit", + } + response = self.client.post(reverse('wagtailadmin_pages_edit', args=(self.child_page.id, )), post_data) + + # Should not be redirected (as the save should fail) + self.assertEqual(response.status_code, 200) + + # Check that a form error was raised + self.assertFormError(response, 'form', 'slug', "This slug is already in use") + def test_preview_on_edit(self): post_data = { 'title': "I've been edited!", @@ -354,8 +448,7 @@ class TestPageDelete(TestCase, WagtailTestUtils): response = self.client.post(reverse('wagtailadmin_pages_delete', args=(self.child_page.id, )), post_data) # Should be redirected to explorer page - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + self.assertRedirects(response, 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) @@ -514,9 +607,8 @@ class TestPageUnpublish(TestCase, WagtailTestUtils): 'foo': "Must post something or the view won't see this as a POST request", }) - # 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, ))) + # Should be redirected to explorer page + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) # Check that the page was unpublished self.assertFalse(SimplePage.objects.get(id=self.page.id).live) @@ -554,8 +646,7 @@ class TestApproveRejectModeration(TestCase, WagtailTestUtils): }) # Check that the user was redirected to the dashboard - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, reverse('wagtailadmin_home')) + self.assertRedirects(response, reverse('wagtailadmin_home')) # Page must be live self.assertTrue(Page.objects.get(id=self.page.id).live) @@ -606,8 +697,7 @@ class TestApproveRejectModeration(TestCase, WagtailTestUtils): }) # Check that the user was redirected to the dashboard - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, reverse('wagtailadmin_home')) + self.assertRedirects(response, reverse('wagtailadmin_home')) # Page must not be live self.assertFalse(Page.objects.get(id=self.page.id).live) diff --git a/wagtail/wagtailadmin/tests/test_userbar.py b/wagtail/wagtailadmin/tests/test_userbar.py new file mode 100644 index 000000000..acdb81690 --- /dev/null +++ b/wagtail/wagtailadmin/tests/test_userbar.py @@ -0,0 +1,82 @@ +from django.test import TestCase +from django.test.client import RequestFactory +from django.core.urlresolvers import reverse +from django.template import Template, Context +from django.contrib.auth.models import User, AnonymousUser + +from wagtail.tests.utils import WagtailTestUtils +from wagtail.wagtailcore.models import Page + + +class TestUserbarTag(TestCase): + def setUp(self): + self.user = User.objects.create_superuser(username='test', email='test@email.com', password='password') + self.homepage = Page.objects.get(id=2) + + def dummy_request(self, user=None): + request = RequestFactory().get('/') + request.user = user or AnonymousUser() + return request + + def test_userbar_tag(self): + template = Template("{% load wagtailuserbar %}{% wagtailuserbar %}") + content = template.render(Context({ + 'self': self.homepage, + 'request': self.dummy_request(self.user), + })) + + self.assertIn("", content) + + def test_userbar_tag_anonymous_user(self): + template = Template("{% load wagtailuserbar %}{% wagtailuserbar %}") + content = template.render(Context({ + 'self': self.homepage, + 'request': self.dummy_request(), + })) + + # Make sure nothing was rendered + self.assertEqual(content, '') + + +class TestUserbarFrontend(TestCase, WagtailTestUtils): + def setUp(self): + self.login() + self.homepage = Page.objects.get(id=2) + + def test_userbar_frontend(self): + response = self.client.get(reverse('wagtailadmin_userbar_frontend', args=(self.homepage.id, ))) + + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/userbar/base.html') + + def test_userbar_frontend_anonymous_user_cannot_see(self): + # Logout + self.client.logout() + + response = self.client.get(reverse('wagtailadmin_userbar_frontend', args=(self.homepage.id, ))) + + # Check that the user recieved a forbidden message + self.assertEqual(response.status_code, 403) + + +class TestUserbarModeration(TestCase, WagtailTestUtils): + def setUp(self): + self.login() + self.homepage = Page.objects.get(id=2) + self.homepage.save_revision() + self.revision = self.homepage.get_latest_revision() + + def test_userbar_moderation(self): + response = self.client.get(reverse('wagtailadmin_userbar_moderation', args=(self.revision.id, ))) + + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/userbar/base.html') + + def test_userbar_moderation_anonymous_user_cannot_see(self): + # Logout + self.client.logout() + + response = self.client.get(reverse('wagtailadmin_userbar_moderation', args=(self.revision.id, ))) + + # Check that the user recieved a forbidden message + self.assertEqual(response.status_code, 403) diff --git a/wagtail/wagtailadmin/views/pages.py b/wagtail/wagtailadmin/views/pages.py index c3eb24e9e..bfca64463 100644 --- a/wagtail/wagtailadmin/views/pages.py +++ b/wagtail/wagtailadmin/views/pages.py @@ -25,16 +25,14 @@ def index(request, parent_page_id=None): pages = parent_page.get_children().prefetch_related('content_type') # Get page ordering - if 'ordering' in request.GET: - ordering = request.GET['ordering'] - - if ordering in ['title', '-title', 'content_type', '-content_type', 'live', '-live']: - pages = pages.order_by(ordering) - else: + ordering = request.GET.get('ordering', 'title') + if ordering not in ['title', '-title', 'content_type', '-content_type', 'live', '-live', 'ord']: ordering = 'title' # Pagination if ordering != 'ord': + pages = pages.order_by(ordering) + p = request.GET.get('p', 1) paginator = Paginator(pages, 50) try: @@ -179,6 +177,7 @@ def create(request, content_type_app_name, content_type_model_name, parent_page_ 'parent_page': parent_page, 'edit_handler': edit_handler, 'display_modes': page.get_page_modes(), + 'form': form, # Used in unit tests }) @@ -264,6 +263,7 @@ def edit(request, page_id): 'edit_handler': edit_handler, 'errors_debug': errors_debug, 'display_modes': page.get_page_modes(), + 'form': form, # Used in unit tests }) diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 5c3f9d1c5..1f0c98d83 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -18,7 +18,7 @@ from django.utils.translation import ugettext_lazy as _ from treebeard.mp_tree import MP_Node -from wagtail.wagtailcore.util import camelcase_to_underscore +from wagtail.wagtailcore.utils import camelcase_to_underscore from wagtail.wagtailcore.query import PageQuerySet from wagtail.wagtailsearch import Indexed, get_search_backend @@ -126,56 +126,56 @@ def get_navigable_page_content_type_ids(): class PageManager(models.Manager): - def get_query_set(self): + def get_queryset(self): return PageQuerySet(self.model).order_by('path') def live(self): - return self.get_query_set().live() + return self.get_queryset().live() def not_live(self): - return self.get_query_set().not_live() + return self.get_queryset().not_live() def page(self, other): - return self.get_query_set().page(other) + return self.get_queryset().page(other) def not_page(self, other): - return self.get_query_set().not_page(other) + return self.get_queryset().not_page(other) def descendant_of(self, other, inclusive=False): - return self.get_query_set().descendant_of(other, inclusive) + return self.get_queryset().descendant_of(other, inclusive) def not_descendant_of(self, other, inclusive=False): - return self.get_query_set().not_descendant_of(other, inclusive) + return self.get_queryset().not_descendant_of(other, inclusive) def child_of(self, other): - return self.get_query_set().child_of(other) + return self.get_queryset().child_of(other) def not_child_of(self, other): - return self.get_query_set().not_child_of(other) + return self.get_queryset().not_child_of(other) def ancestor_of(self, other, inclusive=False): - return self.get_query_set().ancestor_of(other, inclusive) + return self.get_queryset().ancestor_of(other, inclusive) def not_ancestor_of(self, other, inclusive=False): - return self.get_query_set().not_ancestor_of(other, inclusive) + return self.get_queryset().not_ancestor_of(other, inclusive) def parent_of(self, other): - return self.get_query_set().parent_of(other) + return self.get_queryset().parent_of(other) def not_parent_of(self, other): - return self.get_query_set().not_parent_of(other) + return self.get_queryset().not_parent_of(other) def sibling_of(self, other, inclusive=False): - return self.get_query_set().sibling_of(other, inclusive) + return self.get_queryset().sibling_of(other, inclusive) def not_sibling_of(self, other, inclusive=False): - return self.get_query_set().not_sibling_of(other, inclusive) + return self.get_queryset().not_sibling_of(other, inclusive) def type(self, model): - return self.get_query_set().type(model) + return self.get_queryset().type(model) def not_type(self, model): - return self.get_query_set().not_type(model) + return self.get_queryset().not_type(model) class PageBase(models.base.ModelBase): @@ -697,8 +697,8 @@ class Orderable(models.Model): class SubmittedRevisionsManager(models.Manager): - def get_query_set(self): - return super(SubmittedRevisionsManager, self).get_query_set().filter(submitted_for_moderation=True) + def get_queryset(self): + return super(SubmittedRevisionsManager, self).get_queryset().filter(submitted_for_moderation=True) class PageRevision(models.Model): diff --git a/wagtail/wagtailcore/util.py b/wagtail/wagtailcore/util.py index c5ad7ea8d..842fe24fa 100644 --- a/wagtail/wagtailcore/util.py +++ b/wagtail/wagtailcore/util.py @@ -1,6 +1,7 @@ -import re +import warnings +warnings.warn( + "The wagtail.wagtailcore.util module has been renamed. " + "Use wagtail.wagtailcore.utils instead.", DeprecationWarning) -def camelcase_to_underscore(str): - # http://djangosnippets.org/snippets/585/ - return re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', '_\\1', str).lower().strip('_') +from .utils import * diff --git a/wagtail/wagtailcore/utils.py b/wagtail/wagtailcore/utils.py new file mode 100644 index 000000000..c5ad7ea8d --- /dev/null +++ b/wagtail/wagtailcore/utils.py @@ -0,0 +1,6 @@ +import re + + +def camelcase_to_underscore(str): + # http://djangosnippets.org/snippets/585/ + return re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', '_\\1', str).lower().strip('_') diff --git a/wagtail/wagtaildocs/tests.py b/wagtail/wagtaildocs/tests.py index 3353ce39e..a8420406b 100644 --- a/wagtail/wagtaildocs/tests.py +++ b/wagtail/wagtaildocs/tests.py @@ -43,29 +43,61 @@ class TestDocumentIndexView(TestCase, WagtailTestUtils): def setUp(self): self.login() - def get(self, params={}): - return self.client.get(reverse('wagtaildocs_index'), params) - def test_simple(self): - response = self.get() + response = self.client.get(reverse('wagtaildocs_index')) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'wagtaildocs/documents/index.html') def test_search(self): - response = self.get({'q': "Hello"}) + response = self.client.get(reverse('wagtaildocs_index'), {'q': "Hello"}) self.assertEqual(response.status_code, 200) self.assertEqual(response.context['query_string'], "Hello") + def make_docs(self): + for i in range(50): + document = models.Document(title="Test " + str(i)) + document.save() + def test_pagination(self): - pages = ['0', '1', '-1', '9999', 'Not a page'] - for page in pages: - response = self.get({'p': page}) - self.assertEqual(response.status_code, 200) + self.make_docs() + + response = self.client.get(reverse('wagtaildocs_index'), {'p': 2}) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtaildocs/documents/index.html') + + # Check that we got the correct page + self.assertEqual(response.context['documents'].number, 2) + + def test_pagination_invalid(self): + self.make_docs() + + response = self.client.get(reverse('wagtaildocs_index'), {'p': 'Hello World!'}) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtaildocs/documents/index.html') + + # Check that we got page one + self.assertEqual(response.context['documents'].number, 1) + + def test_pagination_out_of_range(self): + self.make_docs() + + response = self.client.get(reverse('wagtaildocs_index'), {'p': 99999}) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtaildocs/documents/index.html') + + # Check that we got the last page + self.assertEqual(response.context['documents'].number, response.context['documents'].paginator.num_pages) def test_ordering(self): orderings = ['title', '-created_at'] for ordering in orderings: - response = self.get({'ordering': ordering}) + response = self.client.get(reverse('wagtaildocs_index'), {'ordering': ordering}) self.assertEqual(response.status_code, 200) @@ -73,33 +105,63 @@ class TestDocumentAddView(TestCase, WagtailTestUtils): def setUp(self): self.login() - def get(self, params={}): - return self.client.get(reverse('wagtaildocs_add_document'), params) - def test_simple(self): - response = self.get() + response = self.client.get(reverse('wagtaildocs_add_document')) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'wagtaildocs/documents/add.html') - # TODO: Test posting + def test_post(self): + # Build a fake file + fake_file = ContentFile("A boring example document") + fake_file.name = 'test.txt' + + # Submit + post_data = { + 'title': "Test document", + 'file': fake_file, + } + response = self.client.post(reverse('wagtaildocs_add_document'), post_data) + + # User should be redirected back to the index + self.assertRedirects(response, reverse('wagtaildocs_index')) + + # Document should be created + self.assertTrue(models.Document.objects.filter(title="Test document").exists()) class TestDocumentEditView(TestCase, WagtailTestUtils): def setUp(self): self.login() - # Create a document to edit - self.document = models.Document.objects.create(title="Test document") + # Build a fake file + fake_file = ContentFile("A boring example document") + fake_file.name = 'test.txt' - def get(self, params={}): - return self.client.get(reverse('wagtaildocs_edit_document', args=(self.document.id,)), params) + # Create a document to edit + self.document = models.Document.objects.create(title="Test document", file=fake_file) def test_simple(self): - response = self.get() + response = self.client.get(reverse('wagtaildocs_edit_document', args=(self.document.id,))) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'wagtaildocs/documents/edit.html') - # TODO: Test posting + def test_post(self): + # Build a fake file + fake_file = ContentFile("A boring example document") + fake_file.name = 'test.txt' + + # Submit title change + post_data = { + 'title': "Test document changed!", + 'file': fake_file, + } + response = self.client.post(reverse('wagtaildocs_edit_document', args=(self.document.id,)), post_data) + + # User should be redirected back to the index + self.assertRedirects(response, reverse('wagtaildocs_index')) + + # Document title should be changed + self.assertEqual(models.Document.objects.get(id=self.document.id).title, "Test document changed!") class TestDocumentDeleteView(TestCase, WagtailTestUtils): @@ -109,40 +171,80 @@ class TestDocumentDeleteView(TestCase, WagtailTestUtils): # Create a document to delete self.document = models.Document.objects.create(title="Test document") - def get(self, params={}): - return self.client.get(reverse('wagtaildocs_delete_document', args=(self.document.id,)), params) - def test_simple(self): - response = self.get() + response = self.client.get(reverse('wagtaildocs_delete_document', args=(self.document.id,))) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'wagtaildocs/documents/confirm_delete.html') - # TODO: Test posting + def test_delete(self): + # Submit title change + post_data = { + 'foo': 'bar' + } + response = self.client.post(reverse('wagtaildocs_delete_document', args=(self.document.id,)), post_data) + + # User should be redirected back to the index + self.assertRedirects(response, reverse('wagtaildocs_index')) + + # Document should be deleted + self.assertFalse(models.Document.objects.filter(id=self.document.id).exists()) class TestDocumentChooserView(TestCase, WagtailTestUtils): def setUp(self): self.login() - def get(self, params={}): - return self.client.get(reverse('wagtaildocs_chooser'), params) - def test_simple(self): - response = self.get() + response = self.client.get(reverse('wagtaildocs_chooser')) 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"}) + response = self.client.get(reverse('wagtaildocs_chooser'), {'q': "Hello"}) self.assertEqual(response.status_code, 200) self.assertEqual(response.context['query_string'], "Hello") + def make_docs(self): + for i in range(50): + document = models.Document(title="Test " + str(i)) + document.save() + def test_pagination(self): - pages = ['0', '1', '-1', '9999', 'Not a page'] - for page in pages: - response = self.get({'p': page}) - self.assertEqual(response.status_code, 200) + self.make_docs() + + response = self.client.get(reverse('wagtaildocs_chooser'), {'p': 2}) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtaildocs/documents/list.html') + + # Check that we got the correct page + self.assertEqual(response.context['documents'].number, 2) + + def test_pagination_invalid(self): + self.make_docs() + + response = self.client.get(reverse('wagtaildocs_chooser'), {'p': 'Hello World!'}) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtaildocs/documents/list.html') + + # Check that we got page one + self.assertEqual(response.context['documents'].number, 1) + + def test_pagination_out_of_range(self): + self.make_docs() + + response = self.client.get(reverse('wagtaildocs_chooser'), {'p': 99999}) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtaildocs/documents/list.html') + + # Check that we got the last page + self.assertEqual(response.context['documents'].number, response.context['documents'].paginator.num_pages) class TestDocumentChooserChosenView(TestCase, WagtailTestUtils): @@ -152,31 +254,40 @@ class TestDocumentChooserChosenView(TestCase, WagtailTestUtils): # Create a document to choose self.document = models.Document.objects.create(title="Test document") - def get(self, params={}): - return self.client.get(reverse('wagtaildocs_document_chosen', args=(self.document.id,)), params) - def test_simple(self): - response = self.get() + response = self.client.get(reverse('wagtaildocs_document_chosen', args=(self.document.id,))) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'wagtaildocs/chooser/document_chosen.js') - # TODO: Test posting - class TestDocumentChooserUploadView(TestCase, WagtailTestUtils): def setUp(self): self.login() - def get(self, params={}): - return self.client.get(reverse('wagtaildocs_chooser_upload'), params) - def test_simple(self): - response = self.get() + response = self.client.get(reverse('wagtaildocs_chooser_upload')) 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 + def test_post(self): + # Build a fake file + fake_file = ContentFile("A boring example document") + fake_file.name = 'test.txt' + + # Submit + post_data = { + 'title': "Test document", + 'file': fake_file, + } + response = self.client.post(reverse('wagtaildocs_chooser_upload'), post_data) + + # Check that the response is a javascript file saying the document was chosen + self.assertTemplateUsed(response, 'wagtaildocs/chooser/document_chosen.js') + self.assertContains(response, "modal.respond('documentChosen'") + + # Document should be created + self.assertTrue(models.Document.objects.filter(title="Test document").exists()) class TestDocumentFilenameProperties(TestCase): diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py index 8137971a7..3ff2f9782 100644 --- a/wagtail/wagtailimages/models.py +++ b/wagtail/wagtailimages/models.py @@ -10,7 +10,7 @@ from django.db import models from django.db.models.signals import pre_delete from django.dispatch.dispatcher import receiver from django.utils.safestring import mark_safe -from django.utils.html import escape +from django.utils.html import escape, format_html_join from django.conf import settings from django.utils.translation import ugettext_lazy as _ @@ -68,8 +68,8 @@ class AbstractImage(models.Model, TagSearchable): except ObjectDoesNotExist: file_field = self.file - # If we have a backend attribute then pass it to process - # image - else pass 'default' + # If we have a backend attribute then pass it to process + # image - else pass 'default' backend_name = getattr(self, 'backend', 'default') generated_image_file = filter.process_image(file_field.file, backend_name=backend_name) @@ -236,11 +236,19 @@ class AbstractRendition(models.Model): def url(self): return self.file.url - def img_tag(self): + @property + def attrs(self): return mark_safe( - '%s' % (escape(self.url), self.width, self.height, escape(self.image.title)) + 'src="%s" width="%d" height="%d" alt="%s"' % (escape(self.url), self.width, self.height, escape(self.image.title)) ) + def img_tag(self, extra_attributes=None): + if extra_attributes: + extra_attributes_string = format_html_join(' ', '{0}="{1}"', extra_attributes.items()) + return mark_safe('' % (self.attrs, extra_attributes_string)) + else: + return mark_safe('' % self.attrs) + class Meta: abstract = True diff --git a/wagtail/wagtailimages/templatetags/image_tags.py b/wagtail/wagtailimages/templatetags/image_tags.py index e59d9cd14..5c7273417 100644 --- a/wagtail/wagtailimages/templatetags/image_tags.py +++ b/wagtail/wagtailimages/templatetags/image_tags.py @@ -7,32 +7,36 @@ register = template.Library() # Local cache of filters, avoid hitting the DB filters = {} + @register.tag(name="image") def image(parser, token): - args = token.split_contents() + bits = token.split_contents()[1:] + image_var = bits[0] + filter_spec = bits[1] + bits = bits[2:] - if len(args) == 3: - # token is of the form {% image self.photo max-320x200 %} - tag_name, image_var, filter_spec = args - return ImageNode(image_var, filter_spec) - - elif len(args) == 5: + if len(bits) == 2 and bits[0] == 'as': # token is of the form {% image self.photo max-320x200 as img %} - tag_name, image_var, filter_spec, as_token, out_var = args - - if as_token != 'as': - raise template.TemplateSyntaxError("'image' tag should be of the form {%% image self.photo max-320x200 %%} or {%% image self.photo max-320x200 as img %%}") - - return ImageNode(image_var, filter_spec, out_var) - + return ImageNode(image_var, filter_spec, output_var_name=bits[1]) else: - raise template.TemplateSyntaxError("'image' tag should be of the form {%% image self.photo max-320x200 %%} or {%% image self.photo max-320x200 as img %%}") + # token is of the form {% image self.photo max-320x200 %} - all additional tokens + # should be kwargs, which become attributes + attrs = {} + for bit in bits: + try: + name, value = bit.split('=') + except ValueError: + raise template.TemplateSyntaxError("'image' tag should be of the form {% image self.photo max-320x200 [ custom-attr=\"value\" ... ] %} or {% image self.photo max-320x200 as img %}") + attrs[name] = parser.compile_filter(value) # setup to resolve context variables as value + + return ImageNode(image_var, filter_spec, attrs=attrs) class ImageNode(template.Node): - def __init__(self, image_var_name, filter_spec, output_var_name=None): + def __init__(self, image_var_name, filter_spec, output_var_name=None, attrs={}): self.image_var = template.Variable(image_var_name) self.output_var_name = output_var_name + self.attrs = attrs if filter_spec not in filters: filters[filter_spec], _ = Filter.objects.get_or_create(spec=filter_spec) @@ -66,4 +70,7 @@ class ImageNode(template.Node): return '' else: # render the rendition's image tag now - return rendition.img_tag() + resolved_attrs = {} + for key in self.attrs: + resolved_attrs[key] = self.attrs[key].resolve(context) + return rendition.img_tag(resolved_attrs) diff --git a/wagtail/wagtailimages/tests.py b/wagtail/wagtailimages/tests.py index b31e33745..2fbac118c 100644 --- a/wagtail/wagtailimages/tests.py +++ b/wagtail/wagtailimages/tests.py @@ -197,6 +197,32 @@ class TestImageTag(TestCase): self.assertTrue('height="300"' in result) self.assertTrue('alt="Test image"' in result) + def render_image_tag_as(self, image, filter_spec): + temp = template.Template('{% load image_tags %}{% image image_obj ' + filter_spec + ' as test_img %}') + context = template.Context({'image_obj': image}) + return temp.render(context) + + def test_image_tag_attrs(self): + result = self.render_image_tag_as(self.image, 'width-400') + + # Check that all the required HTML attributes are set + self.assertTrue('width="400"' in result) + self.assertTrue('height="300"' in result) + self.assertTrue('alt="Test image"' in result) + + def render_image_tag_with_extra_attributes(self, image, title): + temp = template.Template('{% load image_tags %}{% image image_obj width-400 class="photo" title=title|lower %}') + context = template.Context({'image_obj': image, 'title': title}) + return temp.render(context) + + def test_image_tag_with_extra_attributes(self): + result = self.render_image_tag_with_extra_attributes(self.image, 'My Wonderful Title') + + # Check that all the required HTML attributes are set + self.assertTrue('width="400"' in result) + self.assertTrue('height="300"' in result) + self.assertTrue('class="photo"' in result) + self.assertTrue('title="my wonderful title"' in result) ## ===== ADMIN VIEWS ===== @@ -253,8 +279,7 @@ class TestImageAddView(TestCase, WagtailTestUtils): }) # Should redirect back to index - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, reverse('wagtailimages_index')) + self.assertRedirects(response, reverse('wagtailimages_index')) # Check that the image was created images = Image.objects.filter(title="Test image") @@ -293,8 +318,7 @@ class TestImageEditView(TestCase, WagtailTestUtils): }) # Should redirect back to index - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, reverse('wagtailimages_index')) + self.assertRedirects(response, reverse('wagtailimages_index')) # Check that the image was edited image = Image.objects.get(id=self.image.id) @@ -328,8 +352,7 @@ class TestImageDeleteView(TestCase, WagtailTestUtils): }) # Should redirect back to index - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, reverse('wagtailimages_index')) + self.assertRedirects(response, reverse('wagtailimages_index')) # Check that the image was deleted images = Image.objects.filter(title="Test image") diff --git a/wagtail/wagtailredirects/tests.py b/wagtail/wagtailredirects/tests.py index 29ad7ec0b..9aab1e98a 100644 --- a/wagtail/wagtailredirects/tests.py +++ b/wagtail/wagtailredirects/tests.py @@ -113,8 +113,7 @@ class TestRedirectsAddView(TestCase, WagtailTestUtils): }) # Should redirect back to index - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, reverse('wagtailredirects_index')) + self.assertRedirects(response, reverse('wagtailredirects_index')) # Check that the redirect was created redirects = models.Redirect.objects.filter(old_path='/test') @@ -163,8 +162,7 @@ class TestRedirectsEditView(TestCase, WagtailTestUtils): }) # Should redirect back to index - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, reverse('wagtailredirects_index')) + self.assertRedirects(response, reverse('wagtailredirects_index')) # Check that the redirect was edited redirects = models.Redirect.objects.filter(old_path='/test') @@ -210,8 +208,7 @@ class TestRedirectsDeleteView(TestCase, WagtailTestUtils): }) # Should redirect back to index - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, reverse('wagtailredirects_index')) + self.assertRedirects(response, reverse('wagtailredirects_index')) # Check that the redirect was deleted redirects = models.Redirect.objects.filter(old_path='/test') diff --git a/wagtail/wagtailsearch/tests/test_editorspicks.py b/wagtail/wagtailsearch/tests/test_editorspicks.py index 7ee974fd0..99debab40 100644 --- a/wagtail/wagtailsearch/tests/test_editorspicks.py +++ b/wagtail/wagtailsearch/tests/test_editorspicks.py @@ -1,4 +1,6 @@ from django.test import TestCase +from django.core.urlresolvers import reverse + from wagtail.tests.utils import unittest, WagtailTestUtils from wagtail.wagtailsearch import models @@ -49,39 +51,104 @@ class TestEditorsPicksIndexView(TestCase, WagtailTestUtils): def setUp(self): self.login() - def get(self, params={}): - return self.client.get('/admin/search/editorspicks/', params) - def test_simple(self): - response = self.get() + response = self.client.get(reverse('wagtailsearch_editorspicks_index')) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/index.html') def test_search(self): - response = self.get({'q': "Hello"}) + response = self.client.get(reverse('wagtailsearch_editorspicks_index'), {'q': "Hello"}) self.assertEqual(response.status_code, 200) self.assertEqual(response.context['query_string'], "Hello") + def make_editors_picks(self): + for i in range(50): + models.EditorsPick.objects.create( + query=models.Query.get("query " + str(i)), + page_id=1, + sort_order=0, + description="First editors pick", + ) + def test_pagination(self): - pages = ['0', '1', '-1', '9999', 'Not a page'] - for page in pages: - response = self.get({'p': page}) - self.assertEqual(response.status_code, 200) + self.make_editors_picks() + + response = self.client.get(reverse('wagtailsearch_editorspicks_index'), {'p': 2}) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/index.html') + + # Check that we got the correct page + self.assertEqual(response.context['queries'].number, 2) + + def test_pagination_invalid(self): + self.make_editors_picks() + + response = self.client.get(reverse('wagtailsearch_editorspicks_index'), {'p': 'Hello World!'}) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/index.html') + + # Check that we got page one + self.assertEqual(response.context['queries'].number, 1) + + def test_pagination_out_of_range(self): + self.make_editors_picks() + + response = self.client.get(reverse('wagtailsearch_editorspicks_index'), {'p': 99999}) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/index.html') + + # Check that we got the last page + self.assertEqual(response.context['queries'].number, response.context['queries'].paginator.num_pages) class TestEditorsPicksAddView(TestCase, WagtailTestUtils): def setUp(self): self.login() - def get(self, params={}): - return self.client.get('/admin/search/editorspicks/add/', params) - def test_simple(self): - response = self.get() + response = self.client.get(reverse('wagtailsearch_editorspicks_add')) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/add.html') - # TODO: Test posting + def test_post(self): + # Submit + post_data = { + 'query_string': "test", + 'editors_picks-TOTAL_FORMS': 1, + 'editors_picks-INITIAL_FORMS': 0, + 'editors_picks-MAX_NUM_FORMS': 1000, + 'editors_picks-0-DELETE': '', + 'editors_picks-0-ORDER': 0, + 'editors_picks-0-page': 1, + 'editors_picks-0-description': "Hello", + } + response = self.client.post(reverse('wagtailsearch_editorspicks_add'), post_data) + + # User should be redirected back to the index + self.assertRedirects(response, reverse('wagtailsearch_editorspicks_index')) + + # Check that the editors pick was created + self.assertTrue(models.Query.get('test').editors_picks.filter(page_id=1).exists()) + + def test_post_without_recommendations(self): + # Submit + post_data = { + 'query_string': "test", + 'editors_picks-TOTAL_FORMS': 0, + 'editors_picks-INITIAL_FORMS': 0, + 'editors_picks-MAX_NUM_FORMS': 1000, + } + response = self.client.post(reverse('wagtailsearch_editorspicks_add'), post_data) + + # User should be given an error + self.assertEqual(response.status_code, 200) + self.assertFormsetError(response, 'editors_pick_formset', None, None, "Please specify at least one recommendation for this search term.") class TestEditorsPicksEditView(TestCase, WagtailTestUtils): @@ -90,17 +157,123 @@ class TestEditorsPicksEditView(TestCase, WagtailTestUtils): # Create an editors pick to edit self.query = models.Query.get("Hello") - self.query.editors_picks.create(page_id=1, description="Root page") - - def get(self, params={}): - return self.client.get('/admin/search/editorspicks/' + str(self.query.id) + '/', params) + self.editors_pick = self.query.editors_picks.create(page_id=1, description="Root page") + self.editors_pick_2 = self.query.editors_picks.create(page_id=2, description="Homepage") def test_simple(self): - response = self.get() + response = self.client.get(reverse('wagtailsearch_editorspicks_edit', args=(self.query.id, ))) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/edit.html') - # TODO: Test posting + def test_post(self): + # Submit + post_data = { + 'query_string': "Hello", + 'editors_picks-TOTAL_FORMS': 2, + 'editors_picks-INITIAL_FORMS': 2, + 'editors_picks-MAX_NUM_FORMS': 1000, + 'editors_picks-0-id': self.editors_pick.id, + 'editors_picks-0-DELETE': '', + 'editors_picks-0-ORDER': 0, + 'editors_picks-0-page': 1, + 'editors_picks-0-description': "Description has changed", # Change + 'editors_picks-1-id': self.editors_pick_2.id, + 'editors_picks-1-DELETE': '', + 'editors_picks-1-ORDER': 1, + 'editors_picks-1-page': 2, + 'editors_picks-1-description': "Homepage", + } + response = self.client.post(reverse('wagtailsearch_editorspicks_edit', args=(self.query.id, )), post_data) + + # User should be redirected back to the index + self.assertRedirects(response, reverse('wagtailsearch_editorspicks_index')) + + # Check that the editors pick description was edited + self.assertEqual(models.EditorsPick.objects.get(id=self.editors_pick.id).description, "Description has changed") + + def test_post_reorder(self): + # Check order before reordering + self.assertEqual(models.Query.get("Hello").editors_picks.all()[0], self.editors_pick) + self.assertEqual(models.Query.get("Hello").editors_picks.all()[1], self.editors_pick_2) + + # Submit + post_data = { + 'query_string': "Hello", + 'editors_picks-TOTAL_FORMS': 2, + 'editors_picks-INITIAL_FORMS': 2, + 'editors_picks-MAX_NUM_FORMS': 1000, + 'editors_picks-0-id': self.editors_pick.id, + 'editors_picks-0-DELETE': '', + 'editors_picks-0-ORDER': 1, # Change + 'editors_picks-0-page': 1, + 'editors_picks-0-description': "Root page", + 'editors_picks-1-id': self.editors_pick_2.id, + 'editors_picks-1-DELETE': '', + 'editors_picks-1-ORDER': 0, # Change + 'editors_picks-1-page': 2, + 'editors_picks-1-description': "Homepage", + } + response = self.client.post(reverse('wagtailsearch_editorspicks_edit', args=(self.query.id, )), post_data) + + # User should be redirected back to the index + self.assertRedirects(response, reverse('wagtailsearch_editorspicks_index')) + + # Check that the recommendations were reordered + self.assertEqual(models.Query.get("Hello").editors_picks.all()[0], self.editors_pick_2) + self.assertEqual(models.Query.get("Hello").editors_picks.all()[1], self.editors_pick) + + def test_post_delete_recommendation(self): + # Submit + post_data = { + 'query_string': "Hello", + 'editors_picks-TOTAL_FORMS': 2, + 'editors_picks-INITIAL_FORMS': 2, + 'editors_picks-MAX_NUM_FORMS': 1000, + 'editors_picks-0-id': self.editors_pick.id, + 'editors_picks-0-DELETE': '', + 'editors_picks-0-ORDER': 0, + 'editors_picks-0-page': 1, + 'editors_picks-0-description': "Root page", + 'editors_picks-1-id': self.editors_pick_2.id, + 'editors_picks-1-DELETE': 1, + 'editors_picks-1-ORDER': 1, + 'editors_picks-1-page': 2, + 'editors_picks-1-description': "Homepage", + } + response = self.client.post(reverse('wagtailsearch_editorspicks_edit', args=(self.query.id, )), post_data) + + # User should be redirected back to the index + self.assertRedirects(response, reverse('wagtailsearch_editorspicks_index')) + + # Check that the recommendation was deleted + self.assertFalse(models.EditorsPick.objects.filter(id=self.editors_pick_2.id).exists()) + + # The other recommendation should still exist + self.assertTrue(models.EditorsPick.objects.filter(id=self.editors_pick.id).exists()) + + def test_post_without_recommendations(self): + # Submit + post_data = { + 'query_string': "Hello", + 'editors_picks-TOTAL_FORMS': 2, + 'editors_picks-INITIAL_FORMS': 2, + 'editors_picks-MAX_NUM_FORMS': 1000, + 'editors_picks-0-id': self.editors_pick.id, + 'editors_picks-0-DELETE': 1, + 'editors_picks-0-ORDER': 0, + 'editors_picks-0-page': 1, + 'editors_picks-0-description': "Description has changed", # Change + 'editors_picks-1-id': self.editors_pick_2.id, + 'editors_picks-1-DELETE': 1, + 'editors_picks-1-ORDER': 1, + 'editors_picks-1-page': 2, + 'editors_picks-1-description': "Homepage", + } + response = self.client.post(reverse('wagtailsearch_editorspicks_edit', args=(self.query.id, )), post_data) + + # User should be given an error + self.assertEqual(response.status_code, 200) + self.assertFormsetError(response, 'editors_pick_formset', None, None, "Please specify at least one recommendation for this search term.") class TestEditorsPicksDeleteView(TestCase, WagtailTestUtils): @@ -109,14 +282,26 @@ class TestEditorsPicksDeleteView(TestCase, WagtailTestUtils): # Create an editors pick to delete self.query = models.Query.get("Hello") - self.query.editors_picks.create(page_id=1, description="Root page") - - def get(self, params={}): - return self.client.get('/admin/search/editorspicks/' + str(self.query.id) + '/delete/', params) + self.editors_pick = self.query.editors_picks.create(page_id=1, description="Root page") + self.editors_pick_2 = self.query.editors_picks.create(page_id=2, description="Homepage") def test_simple(self): - response = self.get() + response = self.client.get(reverse('wagtailsearch_editorspicks_delete', args=(self.query.id, ))) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/confirm_delete.html') - # TODO: Test posting + def test_post(self): + # Submit + post_data = { + 'foo': 'bar', + } + response = self.client.post(reverse('wagtailsearch_editorspicks_delete', args=(self.query.id, )), post_data) + + # User should be redirected back to the index + self.assertRedirects(response, reverse('wagtailsearch_editorspicks_index')) + + # Check that both recommendations were deleted + self.assertFalse(models.EditorsPick.objects.filter(id=self.editors_pick_2.id).exists()) + + # The other recommendation should still exist + self.assertFalse(models.EditorsPick.objects.filter(id=self.editors_pick.id).exists()) diff --git a/wagtail/wagtailsearch/tests/test_queries.py b/wagtail/wagtailsearch/tests/test_queries.py index 5c341d7d6..70854c785 100644 --- a/wagtail/wagtailsearch/tests/test_queries.py +++ b/wagtail/wagtailsearch/tests/test_queries.py @@ -102,45 +102,6 @@ class TestQueryPopularity(TestCase): self.assertEqual(popular_queries[1], models.Query.get("popular query")) self.assertEqual(popular_queries[2], models.Query.get("little popular query")) - @unittest.expectedFailure # Time based popularity isn't implemented yet - def test_query_popularity_over_time(self): - today = timezone.now().date() - two_days_ago = today - datetime.timedelta(days=2) - a_week_ago = today - datetime.timedelta(days=7) - a_month_ago = today - datetime.timedelta(days=30) - - # Add 10 hits to a query that was very popular query a month ago - for i in range(10): - models.Query.get("old popular query").add_hit(date=a_month_ago) - - # Add 5 hits to a query that is was popular 2 days ago - for i in range(5): - models.Query.get("new popular query").add_hit(date=two_days_ago) - - # Get most popular queries - popular_queries = models.Query.get_most_popular() - - # Old popular query should be most popular - self.assertEqual(popular_queries.count(), 2) - self.assertEqual(popular_queries[0], models.Query.get("old popular query")) - self.assertEqual(popular_queries[1], models.Query.get("new popular query")) - - # Get most popular queries for past week - past_week_popular_queries = models.Query.get_most_popular(date_since=a_week_ago) - - # Only new popular query should be in this list - self.assertEqual(past_week_popular_queries.count(), 1) - self.assertEqual(past_week_popular_queries[0], models.Query.get("new popular query")) - - # Old popular query gets a couple more hits! - for i in range(2): - models.Query.get("old popular query").add_hit() - - # Old popular query should now be in the most popular queries - self.assertEqual(past_week_popular_queries.count(), 2) - self.assertEqual(past_week_popular_queries[0], models.Query.get("new popular query")) - self.assertEqual(past_week_popular_queries[1], models.Query.get("old popular query")) - class TestGarbageCollectCommand(TestCase): def test_garbage_collect_command(self): diff --git a/wagtail/wagtailsnippets/tests.py b/wagtail/wagtailsnippets/tests.py index 9e2587794..1b1e012de 100644 --- a/wagtail/wagtailsnippets/tests.py +++ b/wagtail/wagtailsnippets/tests.py @@ -70,8 +70,7 @@ class TestSnippetCreateView(TestCase, WagtailTestUtils): def test_create(self): 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'))) + self.assertRedirects(response, reverse('wagtailsnippets_list', args=('tests', 'advert'))) snippets = Advert.objects.filter(text='test_advert') self.assertEqual(snippets.count(), 1) @@ -120,8 +119,7 @@ class TestSnippetEditView(TestCase, WagtailTestUtils): 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) - self.assertURLEqual(response.url, reverse('wagtailsnippets_list', args=('tests', 'advert'))) + self.assertRedirects(response, reverse('wagtailsnippets_list', args=('tests', 'advert'))) snippets = Advert.objects.filter(text='edited_test_advert') self.assertEqual(snippets.count(), 1) @@ -146,8 +144,7 @@ class TestSnippetDelete(TestCase, WagtailTestUtils): 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) - self.assertURLEqual(response.url, reverse('wagtailsnippets_list', args=('tests', 'advert'))) + self.assertRedirects(response, 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 6de97f202..a360fe468 100644 --- a/wagtail/wagtailusers/tests.py +++ b/wagtail/wagtailusers/tests.py @@ -54,8 +54,7 @@ class TestUserCreateView(TestCase, WagtailTestUtils): }) # Should redirect back to index - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, reverse('wagtailusers_index')) + self.assertRedirects(response, reverse('wagtailusers_index')) # Check that the user was created users = User.objects.filter(username='testuser') @@ -96,8 +95,7 @@ class TestUserEditView(TestCase, WagtailTestUtils): }) # Should redirect back to index - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, reverse('wagtailusers_index')) + self.assertRedirects(response, reverse('wagtailusers_index')) # Check that the user was edited user = User.objects.get(id=self.test_user.id)