mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-04-29 11:04:49 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
16ca198377
7 changed files with 191 additions and 6 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
--------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
136
wagtail/wagtailcore/tests/test_whitelist.py
Normal file
136
wagtail/wagtailcore/tests/test_whitelist.py
Normal 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>')
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Reference in a new issue