Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Dave Cranwell 2014-06-13 17:09:46 +01:00
commit 16ca198377
7 changed files with 191 additions and 6 deletions

View file

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

View file

@ -371,8 +371,8 @@ Edit Handler API
~~~~~~~~~~~~~~~~
Hooks
-----
Admin Hooks
-----------
On loading, Wagtail will search for any app with the file ``wagtail_hooks.py`` and execute the contents. This provides a way to register your own functions to execute at certain points in Wagtail's execution, such as when a ``Page`` object is saved or when the main menu is constructed.
@ -547,6 +547,38 @@ Where ``'hook'`` is one of the following hook strings and ``function`` is a func
hooks.register('insert_editor_css', editor_css)
Image Formats in the Rich Text Editor
-------------------------------------
On loading, Wagtail will search for any app with the file ``image_formats.py`` and execute the contents. This provides a way to customize the formatting options shown to the editor when inserting images in the ``RichTextField`` editor.
As an example, add a "thumbnail" format:
.. code-block:: python
# image_formats.py
from wagtail.wagtailimages.formats import Format, register_image_format
register_image_format(Format('thumbnail', 'Thumbnail', 'richtext-image thumbnail', 'max-120x120'))
To begin, import the the ``Format`` class, ``register_image_format`` function, and optionally ``unregister_image_format`` function. To register a new ``Format``, call the ``register_image_format`` with the ``Format`` object as the argument. The ``Format`` takes the following init arguments:
``name``
The unique key used to identify the format. To unregister this format, call ``unregister_image_format`` with this string as the only argument.
``label``
The label used in the chooser form when inserting the image into the ``RichTextField``.
``classnames``
The string to assign to the ``class`` attribute of the generated ``<img>`` tag.
``filter_spec``
The string specification to create the image rendition. For more, see the :ref:`image_tag`.
To unregister, call ``unregister_image_format`` with the string of the ``name`` of the ``Format`` as the only argument.
Content Index Pages (CRUD)
--------------------------

View file

@ -40,7 +40,7 @@
<li class="actions preview">
{% trans 'Preview' as preview_label %}
{% if display_modes|length > 1 %}
<div class="dropdown dropup button match-width">
<div class="dropdown dropup dropdown-button match-width">
{% include "wagtailadmin/pages/_preview_button_on_create.html" with label=preview_label icon=1 %}
<div class="dropdown-toggle icon icon-arrow-up"></div>
<ul role="menu">

View file

@ -0,0 +1,136 @@
from bs4 import BeautifulSoup, NavigableString
from django.test import TestCase
from wagtail.wagtailcore.whitelist import (
check_url,
attribute_rule,
allow_without_attributes,
Whitelister
)
class TestCheckUrl(TestCase):
def test_allowed_url_schemes(self):
for url_scheme in ['', 'http', 'https', 'ftp', 'mailto', 'tel']:
url = url_scheme + "://www.example.com"
self.assertTrue(bool(check_url(url)))
def test_disallowed_url_scheme(self):
self.assertFalse(bool(check_url("invalid://url")))
class TestAttributeRule(TestCase):
def setUp(self):
self.soup = BeautifulSoup('<b foo="bar">baz</b>')
def test_no_rule_for_attr(self):
"""
Test that attribute_rule() drops attributes for
which no rule has been defined.
"""
tag = self.soup.b
fn = attribute_rule({'snowman': 'barbecue'})
fn(tag)
self.assertEqual(str(tag), '<b>baz</b>')
def test_rule_true_for_attr(self):
"""
Test that attribute_rule() does not change atrributes
when the corresponding rule returns True
"""
tag = self.soup.b
fn = attribute_rule({'foo': True})
fn(tag)
self.assertEqual(str(tag), '<b foo="bar">baz</b>')
def test_rule_false_for_attr(self):
"""
Test that attribute_rule() drops atrributes
when the corresponding rule returns False
"""
tag = self.soup.b
fn = attribute_rule({'foo': False})
fn(tag)
self.assertEqual(str(tag), '<b>baz</b>')
def test_callable_called_on_attr(self):
"""
Test that when the rule returns a callable,
attribute_rule() replaces the attribute with
the result of calling the callable on the attribute.
"""
tag = self.soup.b
fn = attribute_rule({'foo': len})
fn(tag)
self.assertEqual(str(tag), '<b foo="3">baz</b>')
def test_callable_returns_None(self):
"""
Test that when the rule returns a callable,
attribute_rule() replaces the attribute with
the result of calling the callable on the attribute.
"""
tag = self.soup.b
fn = attribute_rule({'foo': lambda x: None})
fn(tag)
self.assertEqual(str(tag), '<b>baz</b>')
def test_allow_without_attributes(self):
"""
Test that attribute_rule() with will drop all
attributes.
"""
soup = BeautifulSoup('<b foo="bar" baz="quux" snowman="barbecue"></b>')
tag = soup.b
allow_without_attributes(tag)
self.assertEqual(str(tag), '<b></b>')
class TestWhitelister(TestCase):
def test_clean_unknown_node(self):
"""
Unknown node should remove a node from the parent document
"""
soup = BeautifulSoup('<foo><bar>baz</bar>quux</foo>')
tag = soup.foo
Whitelister.clean_unknown_node('', soup.bar)
self.assertEqual(str(tag), '<foo>quux</foo>')
def test_clean_tag_node_cleans_nested_recognised_node(self):
"""
<b> tags are allowed without attributes. This remains true
when tags are nested.
"""
soup = BeautifulSoup('<b><b class="delete me">foo</b></b>')
tag = soup.b
Whitelister.clean_tag_node(tag, tag)
self.assertEqual(str(tag), '<b><b>foo</b></b>')
def test_clean_tag_node_disallows_nested_unrecognised_node(self):
"""
<foo> tags should be removed, even when nested.
"""
soup = BeautifulSoup('<b><foo>bar</foo></b>')
tag = soup.b
Whitelister.clean_tag_node(tag, tag)
self.assertEqual(str(tag), '<b>bar</b>')
def test_clean_string_node_does_nothing(self):
soup = BeautifulSoup('<b>bar</b>')
string = soup.b.string
Whitelister.clean_string_node(string, string)
self.assertEqual(str(string), 'bar')
def test_clean_node_does_not_change_navigable_strings(self):
soup = BeautifulSoup('<b>bar</b>')
string = soup.b.string
Whitelister.clean_node(string, string)
self.assertEqual(str(string), 'bar')
def test_clean(self):
"""
Whitelister.clean should remove disallowed tags and attributes from
a string
"""
string = '<b foo="bar">snowman <barbecue>Yorkshire</barbecue></b>'
cleaned_string = Whitelister.clean(string)
self.assertEqual(cleaned_string, '<b>snowman Yorkshire</b>')

View file

@ -89,7 +89,10 @@ class Whitelister(object):
cls.clean_string_node(doc, node)
elif isinstance(node, Tag):
cls.clean_tag_node(doc, node)
else:
# This branch is here in case node is a BeautifulSoup object that does
# not inherit from NavigableString or Tag. I can't find any examples
# of such a thing at the moment, so this branch is untested.
else: # pragma: no cover
cls.clean_unknown_node(doc, node)
@classmethod

View file

@ -5,9 +5,10 @@ from indexed import Indexed
import datetime
import string
MAX_QUERY_STRING_LENGTH = 255
class Query(models.Model):
query_string = models.CharField(max_length=255, unique=True)
query_string = models.CharField(max_length=MAX_QUERY_STRING_LENGTH, unique=True)
def save(self, *args, **kwargs):
# Normalise query string
@ -48,6 +49,9 @@ class Query(models.Model):
@staticmethod
def normalise_query_string(query_string):
# Truncate query string
if len(query_string) > MAX_QUERY_STRING_LENGTH:
query_string = query_string[:MAX_QUERY_STRING_LENGTH]
# Convert query_string to lowercase
query_string = query_string.lower()

View file

@ -53,6 +53,16 @@ class TestQueryStringNormalisation(TestCase):
for query in queries:
self.assertNotEqual(self.query, models.Query.get(query))
def test_truncation(self):
test_querystring = 'a' * 1000
result = models.Query.normalise_query_string(test_querystring)
self.assertEqual(len(result), 255)
def test_no_truncation(self):
test_querystring = 'a' * 10
result = models.Query.normalise_query_string(test_querystring)
self.assertEqual(len(result), 10)
class TestQueryPopularity(TestCase):
def test_query_popularity(self):