Merge branch 'master' into scheduled-publishing

Conflicts:
	wagtail/wagtailcore/models.py
pull/386/head
Karl Hobley 2014-06-27 16:54:35 +01:00
commit 1cc68807ea
13 zmienionych plików z 337 dodań i 167 usunięć

Wyświetl plik

@ -209,7 +209,6 @@ Methods:
* get_context
* get_template
* is_navigable
* get_other_siblings
* get_ancestors
* get_descendants
* get_siblings

Wyświetl plik

@ -91,6 +91,9 @@ In addition to Django's standard tags and filters, Wagtail provides some of its
Images (tag)
~~~~~~~~~~~~
.. versionchanged:: 0.4
The 'image_tags' tags library was renamed to 'wagtailimages_tags'
The ``image`` tag inserts an XHTML-compatible ``img`` element into the page, setting its ``src``, ``width``, ``height`` and ``alt``. See also :ref:`image_tag_alt`.
The syntax for the tag is thus::
@ -101,7 +104,7 @@ For example:
.. code-block:: django
{% load image %}
{% load wagtailimages_tags %}
...
{% image self.photo width-400 %}
@ -206,7 +209,7 @@ No validation is performed on attributes add in this way by the developer. It's
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 }}"
@ -228,13 +231,16 @@ You can also use the ``attrs`` property as a shorthand to output the ``src``, ``
Rich text (filter)
~~~~~~~~~~~~~~~~~~
.. versionchanged:: 0.4
The 'rich_text' tags library was renamed to 'wagtailcore_tags'
This filter takes a chunk of HTML content and renders it as safe HTML in the page. Importantly it also expands internal shorthand references to embedded images and links made in the Wagtail editor into fully-baked HTML ready for display.
Only fields using ``RichTextField`` need this applied in the template.
.. code-block:: django
{% load rich_text %}
{% load wagtailcore_tags %}
...
{{ self.body|richtext }}
@ -270,6 +276,9 @@ Wagtail embeds and images are included at their full width, which may overflow t
Internal links (tag)
~~~~~~~~~~~~~~~~~~~~
.. versionchanged:: 0.4
The 'pageurl' tags library was renamed to 'wagtailcore_tags'
pageurl
--------
@ -277,7 +286,7 @@ Takes a Page object and returns a relative URL (``/foo/bar/``) if within the sam
.. code-block:: django
{% load pageurl %}
{% load wagtailcore_tags %}
...
<a href="{% pageurl self.blog_page %}">
@ -288,7 +297,7 @@ Takes any ``slug`` as defined in a page's "Promote" tab and returns the URL for
.. code-block:: django
{% load slugurl %}
{% load wagtailcore_tags %}
...
<a href="{% slugurl self.your_slug %}">

Wyświetl plik

@ -31,6 +31,7 @@
border-color: rgba(255,255,255,0.2);
border-style: solid;
border-width:1px 0 0 0;
overflow:hidden;
}
a{

Wyświetl plik

@ -18,6 +18,7 @@ from django.template.response import TemplateResponse
from django.utils import timezone
from django.utils.translation import ugettext
from django.utils.translation import ugettext_lazy as _
from django.utils.functional import cached_property
from treebeard.mp_tree import MP_Node
@ -336,7 +337,7 @@ class Page(MP_Node, ClusterableModel, Indexed):
cursor.execute(update_statement,
[new_url_path, len(old_url_path) + 1, self.path + '%', self.id])
@property
@cached_property
def specific(self):
"""
Return this page in its most specific subclassed form.
@ -350,7 +351,7 @@ class Page(MP_Node, ClusterableModel, Indexed):
else:
return content_type.get_object_for_this_type(id=self.id)
@property
@cached_property
def specific_class(self):
"""
return the class that this page would be if instantiated in its
@ -380,7 +381,7 @@ class Page(MP_Node, ClusterableModel, Indexed):
raise Http404
def save_revision(self, user=None, submitted_for_moderation=False, approved_go_live_at=None):
self.revisions.create(
return self.revisions.create(
content_json=self.to_json(),
user=user,
submitted_for_moderation=submitted_for_moderation,
@ -388,20 +389,15 @@ class Page(MP_Node, ClusterableModel, Indexed):
)
def get_latest_revision(self):
try:
revision = self.revisions.order_by('-created_at')[0]
except IndexError:
return False
return revision
return self.revisions.order_by('-created_at').first()
def get_latest_revision_as_page(self):
try:
revision = self.revisions.order_by('-created_at')[0]
except IndexError:
return self.specific
latest_revision = self.get_latest_revision()
return revision.as_page_object()
if latest_revision:
return latest_revision.as_page_object()
else:
return self.specific
def get_context(self, request, *args, **kwargs):
return {
@ -431,6 +427,10 @@ class Page(MP_Node, ClusterableModel, Indexed):
return (not self.is_leaf()) or self.depth == 2
def get_other_siblings(self):
warnings.warn(
"The 'Page.get_other_siblings()' method has been replaced. "
"Use 'Page.get_siblings(inclusive=False)' instead.", DeprecationWarning)
# get sibling pages excluding self
return self.get_siblings().exclude(id=self.id)
@ -801,6 +801,9 @@ class PageRevision(models.Model):
self.submitted_for_moderation = False
page.revisions.update(submitted_for_moderation=False)
def __unicode__(self):
return '"' + unicode(self.page) + '" at ' + unicode(self.created_at)
PAGE_PERMISSION_TYPE_CHOICES = [
('add', 'Add'),
@ -860,29 +863,20 @@ class UserPagePermissionsProxy(object):
if self.user.is_superuser:
return Page.objects.all()
# Translate each of the user's permission rules into a Q-expression
q_expressions = []
for perm in self.permissions:
if perm.permission_type == 'add':
# user has edit permission on any subpage of perm.page
# (including perm.page itself) that is owned by them
q_expressions.append(
Q(path__startswith=perm.page.path, owner=self.user)
)
elif perm.permission_type == 'edit':
# user has edit permission on any subpage of perm.page
# (including perm.page itself) regardless of owner
q_expressions.append(
Q(path__startswith=perm.page.path)
)
editable_pages = Page.objects.none()
for perm in self.permissions.filter(permission_type='add'):
# user has edit permission on any subpage of perm.page
# (including perm.page itself) that is owned by them
editable_pages |= Page.objects.descendant_of(perm.page, inclusive=True).filter(owner=self.user)
for perm in self.permissions.filter(permission_type='edit'):
# user has edit permission on any subpage of perm.page
# (including perm.page itself) regardless of owner
editable_pages |= Page.objects.descendant_of(perm.page, inclusive=True)
return editable_pages
if q_expressions:
all_rules = q_expressions[0]
for expr in q_expressions[1:]:
all_rules = all_rules | expr
return Page.objects.filter(all_rules)
else:
return Page.objects.none()
def can_edit_pages(self):
"""Return True if the user has permission to edit any pages"""

Wyświetl plik

@ -1,24 +1,8 @@
from django import template
import warnings
from wagtail.wagtailcore.models import Page
register = template.Library()
warnings.warn(
"The pageurl tag library has been moved to wagtailcore_tags. "
"Use {% load wagtailcore_tags %} instead.", DeprecationWarning)
@register.simple_tag(takes_context=True)
def pageurl(context, page):
"""
Outputs a page's URL as relative (/foo/bar/) if it's within the same site as the
current page, or absolute (http://example.com/foo/bar/) if not.
"""
return page.relative_url(context['request'].site)
@register.simple_tag(takes_context=True)
def slugurl(context, slug):
"""Returns the URL for the page that has the given slug."""
page = Page.objects.filter(slug=slug).first()
if page:
return page.relative_url(context['request'].site)
else:
return None
from wagtail.wagtailcore.templatetags.wagtailcore_tags import register, pageurl

Wyświetl plik

@ -1,11 +1,8 @@
from django import template
from django.utils.safestring import mark_safe
import warnings
from wagtail.wagtailcore.rich_text import expand_db_html
register = template.Library()
warnings.warn(
"The rich_text tag library has been moved to wagtailcore_tags. "
"Use {% load wagtailcore_tags %} instead.", DeprecationWarning)
@register.filter
def richtext(value):
return mark_safe('<div class="rich-text">' + expand_db_html(value) + '</div>')
from wagtail.wagtailcore.templatetags.wagtailcore_tags import register, richtext

Wyświetl plik

@ -0,0 +1,32 @@
from django import template
from django.utils.safestring import mark_safe
from wagtail.wagtailcore.models import Page
from wagtail.wagtailcore.rich_text import expand_db_html
register = template.Library()
@register.simple_tag(takes_context=True)
def pageurl(context, page):
"""
Outputs a page's URL as relative (/foo/bar/) if it's within the same site as the
current page, or absolute (http://example.com/foo/bar/) if not.
"""
return page.relative_url(context['request'].site)
@register.simple_tag(takes_context=True)
def slugurl(context, slug):
"""Returns the URL for the page that has the given slug."""
page = Page.objects.filter(slug=slug).first()
if page:
return page.relative_url(context['request'].site)
else:
return None
@register.filter
def richtext(value):
return mark_safe('<div class="rich-text">' + expand_db_html(value) + '</div>')

Wyświetl plik

@ -1,24 +1,8 @@
from django import template
from django.utils.safestring import mark_safe
import warnings
from wagtail.wagtailembeds import get_embed
warnings.warn(
"The embed_filters tag library has been moved to wagtailcore_tags. "
"Use {% load wagtailembeds_tags %} instead.", DeprecationWarning)
register = template.Library()
@register.filter
def embed(url, max_width=None):
embed = get_embed(url, max_width=max_width)
try:
if embed is not None:
return mark_safe(embed.html)
else:
return ''
except:
return ''
@register.filter
def embedly(url, max_width=None):
return embed(url, max_width)
from wagtail.wagtailembeds.templatetags.wagtailembeds_tags import register, embed, embedly

Wyświetl plik

@ -0,0 +1,30 @@
import warnings
from django import template
from django.utils.safestring import mark_safe
from wagtail.wagtailembeds import get_embed
register = template.Library()
@register.filter
def embed(url, max_width=None):
embed = get_embed(url, max_width=max_width)
try:
if embed is not None:
return mark_safe(embed.html)
else:
return ''
except:
return ''
@register.filter
def embedly(url, max_width=None):
warnings.warn(
"The 'embedly' filter has been renamed. "
"Use 'embed' instead.", DeprecationWarning)
return embed(url, max_width)

Wyświetl plik

@ -7,6 +7,7 @@ try:
except ImportError:
no_embedly = True
from django import template
from django.test import TestCase
from wagtail.tests.utils import WagtailTestUtils, unittest
@ -18,7 +19,8 @@ from wagtail.wagtailembeds.embeds import (
AccessDeniedEmbedlyException,
)
from wagtail.wagtailembeds.embeds import embedly as wagtail_embedly, oembed as wagtail_oembed
from wagtail.wagtailembeds.templatetags.embed_filters import embed as embed_filter
from wagtail.wagtailembeds.templatetags.embed_filters import embedly as embedly_filter
class TestEmbeds(TestCase):
@ -258,3 +260,81 @@ class TestOembed(TestCase):
'height': 'test_height',
'html': 'test_html'
})
class TestEmbedFilter(TestCase):
def setUp(self):
class DummyResponse(object):
def read(self):
return "foo"
self.dummy_response = DummyResponse()
@patch('urllib2.urlopen')
@patch('json.loads')
def test_valid_embed(self, loads, urlopen):
urlopen.return_value = self.dummy_response
loads.return_value = {'type': 'photo',
'url': 'http://www.example.com'}
result = embed_filter('http://www.youtube.com/watch/')
self.assertEqual(result, '<img src="http://www.example.com" />')
@patch('urllib2.urlopen')
@patch('json.loads')
def test_render_filter(self, loads, urlopen):
urlopen.return_value = self.dummy_response
loads.return_value = {'type': 'photo',
'url': 'http://www.example.com'}
temp = template.Template('{% load embed_filters %}{{ "http://www.youtube.com/watch/"|embed }}')
context = template.Context()
result = temp.render(context)
self.assertEqual(result, '<img src="http://www.example.com" />')
@patch('urllib2.urlopen')
@patch('json.loads')
def test_render_filter_nonexistent_type(self, loads, urlopen):
urlopen.return_value = self.dummy_response
loads.return_value = {'type': 'foo',
'url': 'http://www.example.com'}
temp = template.Template('{% load embed_filters %}{{ "http://www.youtube.com/watch/"|embed }}')
context = template.Context()
result = temp.render(context)
self.assertEqual(result, '')
class TestEmbedlyFilter(TestEmbedFilter):
def setUp(self):
class DummyResponse(object):
def read(self):
return "foo"
self.dummy_response = DummyResponse()
@patch('urllib2.urlopen')
@patch('json.loads')
def test_valid_embed(self, loads, urlopen):
urlopen.return_value = self.dummy_response
loads.return_value = {'type': 'photo',
'url': 'http://www.example.com'}
result = embedly_filter('http://www.youtube.com/watch/')
self.assertEqual(result, '<img src="http://www.example.com" />')
@patch('urllib2.urlopen')
@patch('json.loads')
def test_render_filter(self, loads, urlopen):
urlopen.return_value = self.dummy_response
loads.return_value = {'type': 'photo',
'url': 'http://www.example.com'}
temp = template.Template('{% load embed_filters %}{{ "http://www.youtube.com/watch/"|embedly }}')
context = template.Context()
result = temp.render(context)
self.assertEqual(result, '<img src="http://www.example.com" />')
@patch('urllib2.urlopen')
@patch('json.loads')
def test_render_filter_nonexistent_type(self, loads, urlopen):
urlopen.return_value = self.dummy_response
loads.return_value = {'type': 'foo',
'url': 'http://www.example.com'}
temp = template.Template('{% load embed_filters %}{{ "http://www.youtube.com/watch/"|embedly }}')
context = template.Context()
result = temp.render(context)
self.assertEqual(result, '')

Wyświetl plik

@ -1,76 +1,8 @@
from django import template
import warnings
from wagtail.wagtailimages.models import Filter
register = template.Library()
# Local cache of filters, avoid hitting the DB
filters = {}
warnings.warn(
"The image_tags tag library has been moved to wagtailcore_tags. "
"Use {% load wagtailimages_tags %} instead.", DeprecationWarning)
@register.tag(name="image")
def image(parser, token):
bits = token.split_contents()[1:]
image_var = bits[0]
filter_spec = bits[1]
bits = bits[2:]
if len(bits) == 2 and bits[0] == 'as':
# token is of the form {% image self.photo max-320x200 as img %}
return ImageNode(image_var, filter_spec, output_var_name=bits[1])
else:
# 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, 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)
self.filter = filters[filter_spec]
def render(self, context):
try:
image = self.image_var.resolve(context)
except template.VariableDoesNotExist:
return ''
if not image:
return ''
try:
rendition = image.get_rendition(self.filter)
except IOError:
# It's fairly routine for people to pull down remote databases to their
# local dev versions without retrieving the corresponding image files.
# In such a case, we would get an IOError at the point where we try to
# create the resized version of a non-existent image. Since this is a
# bit catastrophic for a missing image, we'll substitute a dummy
# Rendition object so that we just output a broken link instead.
Rendition = image.renditions.model # pick up any custom Image / Rendition classes that may be in use
rendition = Rendition(image=image, width=0, height=0)
rendition.file.name = 'not-found'
if self.output_var_name:
# return the rendition object in the given variable
context[self.output_var_name] = rendition
return ''
else:
# render the rendition's image tag now
resolved_attrs = {}
for key in self.attrs:
resolved_attrs[key] = self.attrs[key].resolve(context)
return rendition.img_tag(resolved_attrs)
from wagtail.wagtailimages.templatetags.wagtailimages_tags import register, image

Wyświetl plik

@ -0,0 +1,76 @@
from django import template
from wagtail.wagtailimages.models import Filter
register = template.Library()
# Local cache of filters, avoid hitting the DB
filters = {}
@register.tag(name="image")
def image(parser, token):
bits = token.split_contents()[1:]
image_var = bits[0]
filter_spec = bits[1]
bits = bits[2:]
if len(bits) == 2 and bits[0] == 'as':
# token is of the form {% image self.photo max-320x200 as img %}
return ImageNode(image_var, filter_spec, output_var_name=bits[1])
else:
# 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, 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)
self.filter = filters[filter_spec]
def render(self, context):
try:
image = self.image_var.resolve(context)
except template.VariableDoesNotExist:
return ''
if not image:
return ''
try:
rendition = image.get_rendition(self.filter)
except IOError:
# It's fairly routine for people to pull down remote databases to their
# local dev versions without retrieving the corresponding image files.
# In such a case, we would get an IOError at the point where we try to
# create the resized version of a non-existent image. Since this is a
# bit catastrophic for a missing image, we'll substitute a dummy
# Rendition object so that we just output a broken link instead.
Rendition = image.renditions.model # pick up any custom Image / Rendition classes that may be in use
rendition = Rendition(image=image, width=0, height=0)
rendition.file.name = 'not-found'
if self.output_var_name:
# return the rendition object in the given variable
context[self.output_var_name] = rendition
return ''
else:
# render the rendition's image tag now
resolved_attrs = {}
for key in self.attrs:
resolved_attrs[key] = self.attrs[key].resolve(context)
return rendition.img_tag(resolved_attrs)

Wyświetl plik

@ -1,3 +1,5 @@
from mock import MagicMock
from django.test import TestCase
from django import template
from django.contrib.auth.models import User, Group, Permission
@ -6,7 +8,11 @@ from django.core.files.uploadedfile import SimpleUploadedFile
from wagtail.tests.utils import unittest, WagtailTestUtils
from wagtail.wagtailimages.models import get_image_model
from wagtail.wagtailimages.templatetags import image_tags
from wagtail.wagtailimages.formats import (
Format,
get_image_format,
register_image_format
)
from wagtail.wagtailimages.backends import get_image_backend
from wagtail.wagtailimages.backends.pillow import PillowBackend
@ -419,3 +425,49 @@ class TestImageChooserUploadView(TestCase, WagtailTestUtils):
self.assertTemplateUsed(response, 'wagtailimages/chooser/chooser.js')
# TODO: Test uploading through chooser
class TestFormat(TestCase):
def setUp(self):
# test format
self.format = Format(
'test name',
'test label',
'test classnames',
'test filter spec'
)
# test image
self.image = MagicMock()
self.image.id = 0
def test_editor_attributes(self):
result = self.format.editor_attributes(
self.image,
'test alt text'
)
self.assertEqual(result,
'data-embedtype="image" data-id="0" data-format="test name" data-alt="test alt text" ')
def test_image_to_editor_html(self):
result = self.format.image_to_editor_html(
self.image,
'test alt text'
)
self.assertRegexpMatches(
result,
'<img data-embedtype="image" data-id="0" data-format="test name" data-alt="test alt text" class="test classnames" src="[^"]+" width="1" height="1" alt="test alt text">',
)
def test_image_to_html_no_classnames(self):
self.format.classnames = None
result = self.format.image_to_html(self.image, 'test alt text')
self.assertRegexpMatches(
result,
'<img src="[^"]+" width="1" height="1" alt="test alt text">'
)
self.format.classnames = 'test classnames'
def test_get_image_format(self):
register_image_format(self.format)
result = get_image_format('test name')
self.assertEqual(result, self.format)