Merge branch 'master' into feature/template-tags-rationalisation

Conflicts:
	docs/building_your_site/frontenddevelopers.rst
	wagtail/wagtailimages/templatetags/image_tags.py
pull/336/head
Karl Hobley 2014-06-24 09:57:54 +01:00
commit 3bef7bf302
36 zmienionych plików z 1068 dodań i 358 usunięć

Wyświetl plik

@ -12,7 +12,7 @@ services:
# Package installation
install:
- python setup.py install
- pip install psycopg2 pyelasticsearch elasticutils==0.8.2 wand embedly
- pip install psycopg2 elasticsearch wand embedly
- pip install coveralls
# Pre-test configuration
before_script:

Wyświetl plik

@ -3,15 +3,19 @@ Changelog
0.4 (xx.xx.20xx)
~~~~~~~~~~~~~~~~
* ElasticUtils/pyelasticsearch swapped for elasticsearch-py
* Added 'original' as a resizing rule supported by the 'image' tag
* Hallo.js updated to version 1.0.4
* Snippets are now ordered alphabetically
* 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
* Added styleguide
* 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
* Added 'construct_whitelister_element_rules' hook for customising the HTML whitelist used when saving rich text fields
* Fix: Animated GIFs are now coalesced before resizing
* Fix: Wand backend clones images before modifying them
* Fix: Admin breadcrumb now positioned correctly on mobile

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -189,17 +189,41 @@ The available resizing methods are:
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:
Wagtail provides two shorcuts to give greater control over the ``img`` element:
.. versionadded:: 0.4
**Adding attributes to the {% image %} tag**
Extra attributes can be specified with the syntax ``attribute="value"``:
.. code-block:: django
{% load wagtailimages_tags %}
...
{% image self.photo width-400 class="foo" id="bar" %}
No validation is performed on attributes add in this way by the developer. It's possible to add `src`, `width`, `height` and `alt` of your own that might conflict with those generated by the tag itself.
**Generating the image "as"**
Wagtail can assign the image data to another object using Django's ``as`` syntax:
.. code-block:: django
{% image self.photo width-400 as tmp_photo %}
<img src="{{ tmp_photo.src }}" width="{{ tmp_photo.width }}"
height="{{ tmp_photo.height }}" alt="{{ tmp_photo.alt }}" class="my-custom-class" />
.. versionadded:: 0.4
The ``attrs`` shortcut
-----------------------
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
<img {{ tmp_photo.attrs }} class="my-custom-class" />
.. _rich-text-filter:

Wyświetl plik

@ -546,6 +546,28 @@ Where ``'hook'`` is one of the following hook strings and ``function`` is a func
+ 'demo/css/vendor/font-awesome/css/font-awesome.min.css">')
hooks.register('insert_editor_css', editor_css)
.. _construct_whitelister_element_rules:
``construct_whitelister_element_rules``
.. versionadded:: 0.4
Customise the rules that define which HTML elements are allowed in rich text areas. By default only a limited set of HTML elements and attributes are whitelisted - all others are stripped out. The callables passed into this hook must return a dict, which maps element names to handler functions that will perform some kind of manipulation of the element. These handler functions receive the element as a `BeautifulSoup <http://www.crummy.com/software/BeautifulSoup/bs4/doc/>`_ Tag object.
The ``wagtail.wagtailcore.whitelist`` module provides a few helper functions to assist in defining these handlers: ``allow_without_attributes``, a handler which preserves the element but strips out all of its attributes, and ``attribute_rule`` which accepts a dict specifying how to handle each attribute, and returns a handler function. This dict will map attribute names to either True (indicating that the attribute should be kept), False (indicating that it should be dropped), or a callable (which takes the initial attribute value and returns either a final value for the attribute, or None to drop the attribute).
For example, the following hook function will add the ``<blockquote>`` element to the whitelist, and allow the ``target`` attribute on ``<a>`` elements:
.. code-block:: python
from wagtail.wagtailadmin import hooks
from wagtail.wagtailcore.whitelist import attribute_rule, check_url, allow_without_attributes
def whitelister_element_rules():
return {
'blockquote': allow_without_attributes,
'a': attribute_rule({'href': check_url, 'target': True}),
}
hooks.register('construct_whitelister_element_rules', whitelister_element_rules)
Image Formats in the Rich Text Editor
-------------------------------------

Wyświetl plik

@ -220,17 +220,14 @@ The default DB search backend uses Django's ``__icontains`` filter.
Elasticsearch Backend
`````````````````````
Prerequisites are the Elasticsearch service itself and, via pip, the `elasticutils`_ and `pyelasticsearch`_ packages:
Prerequisites are the Elasticsearch service itself and, via pip, the `elasticsearch-py`_ package:
.. code-block:: guess
pip install elasticutils==0.8.2 pyelasticsearch
pip install elasticsearch
.. note::
ElasticUtils 0.9+ is not supported.
.. note::
The dependency on elasticutils and pyelasticsearch is scheduled to be replaced by a dependency on `elasticsearch-py`_.
If you are using Elasticsearch < 1.0, install elasticsearch-py version 0.4.5: ```pip install elasticsearch==0.4.5```
The backend is configured in settings:
@ -246,7 +243,7 @@ The backend is configured in settings:
}
}
Other than ``BACKEND`` the keys are optional and default to the values shown. ``FORCE_NEW`` is used by elasticutils. In addition, any other keys are passed directly to the Elasticsearch constructor as case-sensitive keyword arguments (e.g. ``'max_retries': 1``).
Other than ``BACKEND`` the keys are optional and default to the values shown. ``FORCE_NEW`` is used by elasticsearch-py. In addition, any other keys are passed directly to the Elasticsearch constructor as case-sensitive keyword arguments (e.g. ``'max_retries': 1``).
If you prefer not to run an Elasticsearch server in development or production, there are many hosted services available, including `Searchly`_, who offer a free account suitable for testing and development. To use Searchly:
@ -256,8 +253,6 @@ If you prefer not to run an Elasticsearch server in development or production, t
- Configure ``URLS`` and ``INDEX`` in the Elasticsearch entry in ``WAGTAILSEARCH_BACKENDS``
- Run ``./manage.py update_index``
.. _elasticutils: http://elasticutils.readthedocs.org
.. _pyelasticsearch: http://pyelasticsearch.readthedocs.org
.. _elasticsearch-py: http://elasticsearch-py.readthedocs.org
.. _Searchly: http://www.searchly.com/
.. _dashboard.searchly.com/users/sign\_up: https://dashboard.searchly.com/users/sign_up

Wyświetl plik

@ -13,7 +13,7 @@ MEDIA_ROOT = os.path.join(WAGTAIL_ROOT, 'test-media')
if not settings.configured:
try:
import elasticutils
import elasticsearch
has_elasticsearch = True
except ImportError:
has_elasticsearch = False

Wyświetl plik

@ -303,6 +303,9 @@ class StandardChild(Page):
pass
class BusinessIndex(Page):
subpage_types = ['tests.BusinessChild', 'tests.BusinessSubIndex']
class BusinessSubIndex(Page):
subpage_types = ['tests.BusinessChild']
class BusinessChild(Page):

Wyświetl plik

@ -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))

Wyświetl plik

@ -1,4 +1,5 @@
from wagtail.wagtailadmin import hooks
from wagtail.wagtailcore.whitelist import attribute_rule, check_url, allow_without_attributes
def editor_css():
return """<link rel="stylesheet" href="/path/to/my/custom.css">"""
@ -8,3 +9,11 @@ hooks.register('insert_editor_css', editor_css)
def editor_js():
return """<script src="/path/to/my/custom.js"></script>"""
hooks.register('insert_editor_js', editor_js)
def whitelister_element_rules():
return {
'blockquote': allow_without_attributes,
'a': attribute_rule({'href': check_url, 'target': True}),
}
hooks.register('construct_whitelister_element_rules', whitelister_element_rules)

Wyświetl plik

@ -19,7 +19,7 @@ from django.utils.translation import ugettext as _
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

Wyświetl plik

@ -329,9 +329,9 @@ $(function() {
});
/* Set up behaviour of preview button */
$('.action-preview').click(function(e) {
$('.action-preview').click(function(e) {
e.preventDefault();
var previewWindow = window.open($(this).data('placeholder'), $(this).data('windowname'));
$.ajax({
@ -340,18 +340,9 @@ $(function() {
data: $('#page-edit-form').serialize(),
success: function(data, textStatus, request) {
if (request.getResponseHeader('X-Wagtail-Preview') == 'ok') {
var pdoc = previewWindow.document;
var frame = pdoc.getElementById('preview-frame');
frame = frame.contentWindow || frame.contentDocument.document || frame.contentDocument;
frame.document.open();
frame.document.write(data);
frame.document.close();
var hideTimeout = setTimeout(function(){
pdoc.getElementById('loading-spinner-wrapper').className += 'remove';
clearTimeout(hideTimeout);
}, 50) // just enough to give effect without adding discernible slowness
previewWindow.document.open();
previewWindow.document.write(data);
previewWindow.document.close();
} else {
previewWindow.close();
document.open();

Wyświetl plik

@ -11,6 +11,5 @@
<div id="loading-spinner-wrapper">
<div id="loading-spinner"></div>
</div>
<iframe id="preview-frame" src="{% url 'wagtailadmin_pages_preview_loading' %}"></iframe>
</body>
</html>

Wyświetl plik

@ -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()

Wyświetl plik

@ -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'))

Wyświetl plik

@ -1,10 +1,11 @@
from django.test import TestCase
from wagtail.tests.models import SimplePage, EventPage, StandardIndex, StandardChild, BusinessIndex, BusinessChild
from wagtail.tests.models import SimplePage, EventPage, StandardIndex, StandardChild, BusinessIndex, BusinessChild, BusinessSubIndex
from wagtail.tests.utils import unittest, WagtailTestUtils
from wagtail.wagtailcore.models import Page, PageRevision
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User, Permission
from django.core import mail
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)
@ -681,41 +771,87 @@ class TestSubpageBusinessRules(TestCase, WagtailTestUtils):
# Find root page
self.root_page = Page.objects.get(id=2)
# Add standard page
# Add standard page (allows subpages of any type)
self.standard_index = StandardIndex()
self.standard_index.title = "Standard Index"
self.standard_index.slug = "standard-index"
self.root_page.add_child(instance=self.standard_index)
# Add business page
# Add business page (allows BusinessChild and BusinessSubIndex as subpages)
self.business_index = BusinessIndex()
self.business_index.title = "Business Index"
self.business_index.slug = "business-index"
self.root_page.add_child(instance=self.business_index)
# Add business child
# Add business child (allows no subpages)
self.business_child = BusinessChild()
self.business_child.title = "Business Child"
self.business_child.slug = "business-child"
self.business_index.add_child(instance=self.business_child)
# Add business subindex (allows only BusinessChild as subpages)
self.business_subindex = BusinessSubIndex()
self.business_subindex.title = "Business Subindex"
self.business_subindex.slug = "business-subindex"
self.business_index.add_child(instance=self.business_subindex)
# Login
self.login()
def test_standard_subpage(self):
response = self.client.get(reverse('wagtailadmin_pages_add_subpage', args=(self.standard_index.id, )))
add_subpage_url = reverse('wagtailadmin_pages_add_subpage', args=(self.standard_index.id, ))
# explorer should contain a link to 'add child page'
response = self.client.get(reverse('wagtailadmin_explore', args=(self.standard_index.id, )))
self.assertEqual(response.status_code, 200)
self.assertContains(response, add_subpage_url)
# add_subpage should give us the full set of page types to choose
response = self.client.get(add_subpage_url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Standard Child')
self.assertContains(response, 'Business Child')
def test_business_subpage(self):
response = self.client.get(reverse('wagtailadmin_pages_add_subpage', args=(self.business_index.id, )))
add_subpage_url = reverse('wagtailadmin_pages_add_subpage', args=(self.business_index.id, ))
# explorer should contain a link to 'add child page'
response = self.client.get(reverse('wagtailadmin_explore', args=(self.business_index.id, )))
self.assertEqual(response.status_code, 200)
self.assertContains(response, add_subpage_url)
# add_subpage should give us a cut-down set of page types to choose
response = self.client.get(add_subpage_url)
self.assertEqual(response.status_code, 200)
self.assertNotContains(response, 'Standard Child')
self.assertContains(response, 'Business Child')
def test_business_child_subpage(self):
response = self.client.get(reverse('wagtailadmin_pages_add_subpage', args=(self.business_child.id, )))
add_subpage_url = reverse('wagtailadmin_pages_add_subpage', args=(self.business_child.id, ))
# explorer should not contain a link to 'add child page', as this page doesn't accept subpages
response = self.client.get(reverse('wagtailadmin_explore', args=(self.business_child.id, )))
self.assertEqual(response.status_code, 200)
self.assertNotContains(response, 'Standard Child')
self.assertEqual(0, len(response.context['page_types']))
self.assertNotContains(response, add_subpage_url)
# this also means that fetching add_subpage is blocked at the permission-check level
response = self.client.get(reverse('wagtailadmin_pages_add_subpage', args=(self.business_child.id, )))
self.assertEqual(response.status_code, 403)
def test_cannot_add_invalid_subpage_type(self):
# cannot add SimplePage as a child of BusinessIndex, as SimplePage is not present in subpage_types
response = self.client.get(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.business_index.id)))
self.assertEqual(response.status_code, 403)
# likewise for BusinessChild which has an empty subpage_types list
response = self.client.get(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.business_child.id)))
self.assertEqual(response.status_code, 403)
# but we can add a BusinessChild to BusinessIndex
response = self.client.get(reverse('wagtailadmin_pages_create', args=('tests', 'businesschild', self.business_index.id)))
self.assertEqual(response.status_code, 200)
def test_not_prompted_for_page_type_when_only_one_choice(self):
response = self.client.get(reverse('wagtailadmin_pages_add_subpage', args=(self.business_subindex.id, )))
# BusinessChild is the only valid subpage type of BusinessSubIndex, so redirect straight there
self.assertRedirects(response, reverse('wagtailadmin_pages_create', args=('tests', 'businesschild', self.business_subindex.id)))

Wyświetl plik

@ -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("<!-- Wagtail user bar embed code -->", 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)

Wyświetl plik

@ -50,7 +50,6 @@ urlpatterns += [
url(r'^pages/(\d+)/edit/preview/$', pages.preview_on_edit, name='wagtailadmin_pages_preview_on_edit'),
url(r'^pages/preview/$', pages.preview, name='wagtailadmin_pages_preview'),
url(r'^pages/preview_loading/$', pages.preview_loading, name='wagtailadmin_pages_preview_loading'),
url(r'^pages/(\d+)/view_draft/$', pages.view_draft, name='wagtailadmin_pages_view_draft'),
url(r'^pages/(\d+)/add_subpage/$', pages.add_subpage, name='wagtailadmin_pages_add_subpage'),

Wyświetl plik

@ -12,7 +12,7 @@ from wagtail.wagtailadmin.edit_handlers import TabbedInterface, ObjectList
from wagtail.wagtailadmin.forms import SearchForm
from wagtail.wagtailadmin import tasks, hooks
from wagtail.wagtailcore.models import Page, PageRevision, get_page_types
from wagtail.wagtailcore.models import Page, PageRevision
@permission_required('wagtailadmin.access_admin')
@ -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:
@ -59,6 +57,12 @@ def add_subpage(request, parent_page_id):
page_types = sorted(parent_page.clean_subpage_types(), key=lambda pagetype: pagetype.name.lower())
if len(page_types) == 1:
# Only one page type is available - redirect straight to the create form rather than
# making the user choose
content_type = page_types[0]
return redirect('wagtailadmin_pages_create', content_type.app_label, content_type.model, parent_page.id)
return render(request, 'wagtailadmin/pages/add_subpage.html', {
'parent_page': parent_page,
'page_types': page_types,
@ -111,15 +115,11 @@ def create(request, content_type_app_name, content_type_model_name, parent_page_
except ContentType.DoesNotExist:
raise Http404
page_class = content_type.model_class()
# page must be in the list of allowed subpage types for this parent ID
# == Restriction temporarily relaxed so that as superusers we can add index pages and things -
# == TODO: reinstate this for regular editors when we have distinct user types
#
# if page_class not in parent_page.clean_subpage_types():
# messages.error(request, "Sorry, you do not have access to create a page of type '%s' here." % content_type.name)
# return redirect('wagtailadmin_pages_select_type')
if content_type not in parent_page.clean_subpage_types():
raise PermissionDenied
page_class = content_type.model_class()
page = page_class(owner=request.user)
edit_handler_class = get_page_edit_handler(page_class)
@ -179,6 +179,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 +265,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
})
@ -420,12 +422,6 @@ def preview(request):
"""
return render(request, 'wagtailadmin/pages/preview.html')
def preview_loading(request):
"""
This page is blank, but must be real HTML so its DOM can be written to once the preview of the page has rendered
"""
return HttpResponse("<html><head><title></title></head><body></body></html>")
@permission_required('wagtailadmin.access_admin')
def unpublish(request, page_id):
page = get_object_or_404(Page, id=page_id)

Wyświetl plik

@ -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):
@ -843,6 +843,8 @@ class PagePermissionTester(object):
def can_add_subpage(self):
if not self.user.is_active:
return False
if not self.page.specific_class.clean_subpage_types(): # this page model has an empty subpage_types list, so no subpages are allowed
return False
return self.user.is_superuser or ('add' in self.permissions)
def can_edit(self):
@ -897,10 +899,13 @@ class PagePermissionTester(object):
"""
Niggly special case for creating and publishing a page in one go.
Differs from can_publish in that we want to be able to publish subpages of root, but not
to be able to publish root itself
to be able to publish root itself. (Also, can_publish_subpage returns false if the page
does not allow subpages at all.)
"""
if not self.user.is_active:
return False
if not self.page.specific_class.clean_subpage_types(): # this page model has an empty subpage_types list, so no subpages are allowed
return False
return self.user.is_superuser or ('publish' in self.permissions)

Wyświetl plik

@ -13,6 +13,8 @@ from wagtail.wagtaildocs.models import Document
from wagtail.wagtailimages.models import get_image_model
from wagtail.wagtailimages.formats import get_image_format
from wagtail.wagtailadmin import hooks
# Define a set of 'embed handlers' and 'link handlers'. These handle the translation
# of 'special' HTML elements in rich text - ones which we do not want to include
@ -158,6 +160,18 @@ LINK_HANDLERS = {
# Prepare a whitelisting engine with custom behaviour:
# rewrite any elements with a data-embedtype or data-linktype attribute
class DbWhitelister(Whitelister):
has_loaded_custom_whitelist_rules = False
@classmethod
def clean(cls, html):
if not cls.has_loaded_custom_whitelist_rules:
for fn in hooks.get_hooks('construct_whitelister_element_rules'):
cls.element_rules = dict(
cls.element_rules.items() + fn().items())
cls.has_loaded_custom_whitelist_rules = True
return super(DbWhitelister, cls).clean(html)
@classmethod
def clean_tag_node(cls, doc, tag):
if 'data-embedtype' in tag.attrs:

Wyświetl plik

@ -0,0 +1,50 @@
from django.test import TestCase
from wagtail.wagtailcore.rich_text import DbWhitelister
from wagtail.wagtailcore.whitelist import Whitelister
from bs4 import BeautifulSoup
class TestDbWhitelister(TestCase):
def assertHtmlEqual(self, str1, str2):
"""
Assert that two HTML strings are equal at the DOM level
(necessary because we can't guarantee the order that attributes are output in)
"""
self.assertEqual(BeautifulSoup(str1), BeautifulSoup(str2))
def test_page_link_is_rewritten(self):
input_html = '<p>Look at the <a data-linktype="page" data-id="2" href="/">lovely homepage</a> of my <a href="http://wagtail.io/">Wagtail</a> site</p>'
output_html = DbWhitelister.clean(input_html)
expected = '<p>Look at the <a linktype="page" id="2">lovely homepage</a> of my <a href="http://wagtail.io/">Wagtail</a> site</p>'
self.assertHtmlEqual(expected, output_html)
def test_document_link_is_rewritten(self):
input_html = '<p>Look at our <a data-linktype="document" data-id="1" href="/documents/1/brochure.pdf">horribly oversized brochure</a></p>'
output_html = DbWhitelister.clean(input_html)
expected = '<p>Look at our <a linktype="document" id="1">horribly oversized brochure</a></p>'
self.assertHtmlEqual(expected, output_html)
def test_image_embed_is_rewritten(self):
input_html = '<p>OMG look at this picture of a kitten: <figure data-embedtype="image" data-id="5" data-format="image-with-caption" data-alt="A cute kitten" class="fancy-image"><img src="/media/images/kitten.jpg" width="320" height="200" alt="A cute kitten" /><figcaption>A kitten, yesterday.</figcaption></figure></p>'
output_html = DbWhitelister.clean(input_html)
expected = '<p>OMG look at this picture of a kitten: <embed embedtype="image" id="5" format="image-with-caption" alt="A cute kitten" /></p>'
self.assertHtmlEqual(expected, output_html)
def test_media_embed_is_rewritten(self):
input_html = '<p>OMG look at this video of a kitten: <iframe data-embedtype="media" data-url="https://www.youtube.com/watch?v=dQw4w9WgXcQ" width="640" height="480" src="//www.youtube.com/embed/dQw4w9WgXcQ" frameborder="0" allowfullscreen></iframe></p>'
output_html = DbWhitelister.clean(input_html)
expected = '<p>OMG look at this video of a kitten: <embed embedtype="media" url="https://www.youtube.com/watch?v=dQw4w9WgXcQ" /></p>'
self.assertHtmlEqual(expected, output_html)
def test_whitelist_hooks(self):
# wagtail.tests.wagtail_hooks overrides the whitelist to permit <blockquote> and <a target="...">
input_html = '<blockquote>I would put a tax on all people who <a href="https://twitter.com/DMReporter/status/432914941201223680/photo/1" target="_blank" tea="darjeeling">stand in water</a>.</blockquote><p>- <character>Gumby</character></p>'
output_html = DbWhitelister.clean(input_html)
expected = '<blockquote>I would put a tax on all people who <a href="https://twitter.com/DMReporter/status/432914941201223680/photo/1" target="_blank">stand in water</a>.</blockquote><p>- Gumby</p>'
self.assertHtmlEqual(expected, output_html)
# check that the base Whitelister class is unaffected by these custom whitelist rules
input_html = '<blockquote>I would put a tax on all people who <a href="https://twitter.com/DMReporter/status/432914941201223680/photo/1" target="_blank" tea="darjeeling">stand in water</a>.</blockquote><p>- <character>Gumby</character></p>'
output_html = Whitelister.clean(input_html)
expected = 'I would put a tax on all people who <a href="https://twitter.com/DMReporter/status/432914941201223680/photo/1">stand in water</a>.<p>- Gumby</p>'
self.assertHtmlEqual(expected, output_html)

Wyświetl plik

@ -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 *

Wyświetl plik

@ -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('_')

Wyświetl plik

@ -65,7 +65,8 @@ class Whitelister(object):
'h6': allow_without_attributes,
'hr': allow_without_attributes,
'i': allow_without_attributes,
'img': attribute_rule({'src': check_url, 'width': True, 'height': True, 'alt': True}),
'img': attribute_rule({'src': check_url, 'width': True, 'height': True,
'alt': True}),
'li': allow_without_attributes,
'ol': allow_without_attributes,
'p': allow_without_attributes,
@ -77,7 +78,8 @@ class Whitelister(object):
@classmethod
def clean(cls, html):
"""Clean up an HTML string to contain just the allowed elements / attributes"""
"""Clean up an HTML string to contain just the allowed elements /
attributes"""
doc = BeautifulSoup(html, 'lxml')
cls.clean_node(doc, doc)
return unicode(doc)

Wyświetl plik

@ -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):

Wyświetl plik

@ -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(
'<img src="%s" width="%d" height="%d" alt="%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('<img %s %s>' % (self.attrs, extra_attributes_string))
else:
return mark_safe('<img %s>' % self.attrs)
class Meta:
abstract = True

Wyświetl plik

@ -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)

Wyświetl plik

@ -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 %}<img {{ test_img.attrs }} />')
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")

Wyświetl plik

@ -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')

Wyświetl plik

@ -1,6 +1,9 @@
from __future__ import absolute_import
from django.db import models
from elasticutils import get_es, S
from elasticsearch import Elasticsearch, NotFoundError, RequestError
from elasticsearch.helpers import bulk
from wagtail.wagtailsearch.backends.base import BaseSearch
from wagtail.wagtailsearch.indexed import Indexed
@ -9,16 +12,141 @@ import string
class ElasticSearchResults(object):
def __init__(self, model, query, prefetch_related=[]):
def __init__(self, backend, model, query_string, fields=None, filters={}, prefetch_related=[]):
self.backend = backend
self.model = model
self.query = query
self.count = query.count()
self.query_string = query_string
self.fields = fields
self.filters = filters
self.prefetch_related = prefetch_related
def _get_filters(self):
# Filters
filters = []
# Filter by content type
filters.append({
'prefix': {
'content_type': self.model.indexed_get_content_type()
}
})
# Extra filters
if self.filters:
for key, value in self.filters.items():
if '__' in key:
field, lookup = key.split('__')
else:
field = key
lookup = None
if lookup is None:
if value is None:
filters.append({
'missing': {
'field': field,
}
})
else:
filters.append({
'term': {
field: value
}
})
if lookup in ['startswith', 'prefix']:
filters.append({
'prefix': {
field: value
}
})
if lookup in ['gt', 'gte', 'lt', 'lte']:
filters.append({
'range': {
field: {
lookup: value,
}
}
})
if lookup == 'range':
lower, upper = value
filters.append({
'range': {
field: {
'gte': lower,
'lte': upper,
}
}
})
return filters
def _get_query(self):
# Query
query = {
'query_string': {
'query': self.query_string,
}
}
# Fields
if self.fields:
query['query_string']['fields'] = self.fields
# Filters
filters = self._get_filters()
return {
'filtered': {
'query': query,
'filter': {
'and': filters,
}
}
}
def _get_results_pks(self, offset=0, limit=None):
query = self._get_query()
query['from'] = offset
if limit is not None:
query['size'] = limit
hits = self.backend.es.search(
index=self.backend.es_index,
body=dict(query=query),
_source=False,
fields='pk',
)
pks = [hit['fields']['pk'] for hit in hits['hits']['hits']]
# ElasticSearch 1.x likes to pack pks into lists, unpack them if this has happened
return [pk[0] if isinstance(pk, list) else pk for pk in pks]
def _get_count(self):
query = self._get_query()
# Elasticsearch 1.x
count = self.backend.es.count(
index=self.backend.es_index,
body=dict(query=query),
)
# ElasticSearch 0.90.x fallback
if not count['_shards']['successful'] and "No query registered for [query]]" in count['_shards']['failures'][0]['reason']:
count = self.backend.es.count(
index=self.backend.es_index,
body=query,
)
return count['count']
def __getitem__(self, key):
if isinstance(key, slice):
# Get primary keys
pk_list_unclean = [result._source["pk"] for result in self.query[key]]
pk_list_unclean = self._get_results_pks(key.start, key.stop - key.start)
# Remove duplicate keys (and preserve order)
seen_pks = set()
@ -45,11 +173,11 @@ class ElasticSearchResults(object):
return results_sorted
else:
# Return a single item
pk = self.query[key]._source["pk"]
pk = self._get_results_pks(key, key + 1)[0]
return self.model.objects.get(pk=pk)
def __len__(self):
return self.count
return self._get_count()
class ElasticSearch(BaseSearch):
@ -64,22 +192,17 @@ class ElasticSearch(BaseSearch):
# Get ElasticSearch interface
# Any remaining params are passed into the ElasticSearch constructor
self.es = get_es(
self.es = Elasticsearch(
urls=self.es_urls,
timeout=self.es_timeout,
force_new=self.es_force_new,
**params)
self.s = S().es(
urls=self.es_urls,
timeout=self.es_timeout,
force_new=self.es_force_new,
**params).indexes(self.es_index)
def reset_index(self):
# Delete old index
try:
self.es.delete_index(self.es_index)
except:
self.es.indices.delete(self.es_index)
except NotFoundError:
pass
# Settings
@ -128,7 +251,7 @@ class ElasticSearch(BaseSearch):
}
# Create new index
self.es.create_index(self.es_index, INDEX_SETTINGS)
self.es.indices.create(self.es_index, INDEX_SETTINGS)
def add_type(self, model):
# Get type name
@ -144,14 +267,14 @@ class ElasticSearch(BaseSearch):
}.items() + indexed_fields.items())
# Put mapping
self.es.put_mapping(self.es_index, content_type, {
self.es.indices.put_mapping(index=self.es_index, doc_type=content_type, body={
content_type: {
"properties": fields,
}
})
def refresh_index(self):
self.es.refresh(self.es_index)
self.es.indices.refresh(self.es_index)
def add(self, obj):
# Make sure the object can be indexed
@ -183,24 +306,33 @@ class ElasticSearch(BaseSearch):
type_set[obj_type].append(obj.indexed_build_document())
# Loop through each type and bulk add them
results = []
for type_name, type_objects in type_set.items():
results.append((type_name, len(type_objects)))
self.es.bulk_index(self.es_index, type_name, type_objects)
return results
# Get list of actions
actions = []
for obj in type_objects:
action = {
'_index': self.es_index,
'_type': type_name,
'_id': obj['id'],
}
action.update(obj)
actions.append(action)
bulk(self.es, actions)
def delete(self, obj):
# Object must be a decendant of Indexed and be a django model
if not isinstance(obj, Indexed) or not isinstance(obj, models.Model):
return
# Get ID for document
doc_id = obj.indexed_get_document_id()
# Delete document
try:
self.es.delete(self.es_index, obj.indexed_get_content_type(), doc_id)
except:
self.es.delete(
self.es_index,
obj.indexed_get_content_type(),
obj.indexed_get_document_id(),
)
except NotFoundError:
pass # Document doesn't exist, ignore this exception
def search(self, query_string, model, fields=None, filters={}, prefetch_related=[]):
@ -215,27 +347,5 @@ class ElasticSearch(BaseSearch):
if not query_string:
return []
# Query
if fields:
query = self.s.query_raw({
"query_string": {
"query": query_string,
"fields": fields,
}
})
else:
query = self.s.query_raw({
"query_string": {
"query": query_string,
}
})
# Filter results by this content type
query = query.filter(content_type__prefix=model.indexed_get_content_type())
# Extra filters
if filters:
query = query.filter(**filters)
# Return search results
return ElasticSearchResults(model, query, prefetch_related=prefetch_related)
return ElasticSearchResults(self, model, query_string, fields=fields, filters=filters, prefetch_related=prefetch_related)

Wyświetl plik

@ -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())

Wyświetl plik

@ -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):

Wyświetl plik

@ -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)

Wyświetl plik

@ -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)