mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-05-22 22:05:50 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
7237abf185
12 changed files with 2529 additions and 1907 deletions
|
|
@ -2,7 +2,8 @@ Changelog
|
|||
=========
|
||||
|
||||
0.4 (xx.xx.20xx)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~
|
||||
* Added 'original' as a resizing rule supported by the 'image' tag
|
||||
|
||||
0.3.1 (03.06.2014)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ The ``image`` tag inserts an XHTML-compatible ``img`` element into the page, set
|
|||
|
||||
The syntax for the tag is thus::
|
||||
|
||||
{% image [image] [method]-[dimension(s)] %}
|
||||
{% image [image] [resize-rule] %}
|
||||
|
||||
For example:
|
||||
|
||||
|
|
@ -108,16 +108,20 @@ For example:
|
|||
<!-- or a square thumbnail: -->
|
||||
{% image self.photo fill-80x80 %}
|
||||
|
||||
In the above syntax ``[image]`` is the Django object refering to the image. If your page model defined a field called "photo" then ``[image]`` would probably be ``self.photo``. The ``[method]`` defines which resizing algorithm to use and ``[dimension(s)]`` provides height and/or width values (as ``[width|height]`` or ``[width]x[height]``) to refine that algorithm.
|
||||
In the above syntax ``[image]`` is the Django object refering to the image. If your page model defined a field called "photo" then ``[image]`` would probably be ``self.photo``. The ``[resize-rule]`` defines how the image is to be resized when inserted into the page; various resizing methods are supported, to cater for different usage cases (e.g. lead images that span the whole width of the page, or thumbnails to be cropped to a fixed size).
|
||||
|
||||
Note that a space separates ``[image]`` and ``[method]``, but not ``[method]`` and ``[dimensions]``: a hyphen between ``[method]`` and ``[dimensions]`` is mandatory. Multiple dimensions must be separated by an ``x``.
|
||||
Note that a space separates ``[image]`` and ``[resize-rule]``, but the resize rule must not contain spaces.
|
||||
|
||||
The available ``method`` s are:
|
||||
The available resizing methods are:
|
||||
|
||||
.. glossary::
|
||||
``max``
|
||||
(takes two dimensions)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo max-1000x500 %}
|
||||
|
||||
Fit **within** the given dimensions.
|
||||
|
||||
The longest edge will be reduced to the equivalent dimension size defined. e.g A portrait image of width 1000, height 2000, treated with the ``max`` dimensions ``1000x500`` (landscape) would result in the image shrunk so the *height* was 500 pixels and the width 250.
|
||||
|
|
@ -125,6 +129,10 @@ The available ``method`` s are:
|
|||
``min``
|
||||
(takes two dimensions)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo min-500x200 %}
|
||||
|
||||
**Cover** the given dimensions.
|
||||
|
||||
This may result in an image slightly **larger** than the dimensions you specify. e.g A square image of width 2000, height 2000, treated with the ``min`` dimensions ``500x200`` (landscape) would have it's height and width changed to 500, i.e matching the width required, but greater than the height.
|
||||
|
|
@ -132,27 +140,45 @@ The available ``method`` s are:
|
|||
``width``
|
||||
(takes one dimension)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo width-640 %}
|
||||
|
||||
Reduces the width of the image to the dimension specified.
|
||||
|
||||
``height``
|
||||
(takes one dimension)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo height-480 %}
|
||||
|
||||
Resize the height of the image to the dimension specified..
|
||||
|
||||
``fill``
|
||||
(takes two dimensions)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo fill-200x200 %}
|
||||
|
||||
Resize and **crop** to fill the **exact** dimensions.
|
||||
|
||||
This can be particularly useful for websites requiring square thumbnails of arbitrary images. For example, a landscape image of width 2000, height 1000, treated with ``fill`` dimensions ``200x200`` would have its height reduced to 200, then its width (ordinarily 400) cropped to 200.
|
||||
|
||||
**The crop always aligns on the centre of the image.**
|
||||
|
||||
.. Note::
|
||||
Wagtail does not allow deforming or stretching images. Image dimension ratios will always be kept. Wagtail also *does not support upscaling*. Small images forced to appear at larger sizes will "max out" at their their native dimensions.
|
||||
``original``
|
||||
(takes no dimensions)
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% image self.photo original %}
|
||||
|
||||
Leaves the image at its original size - no resizing is performed.
|
||||
|
||||
.. Note::
|
||||
Wagtail does not make the "original" version of an image explicitly available. To request it, you could rely on the lack of upscaling by requesting an image larger than its maximum dimensions. e.g to insert an image whose dimensions are unknown at its maximum size, try: ``{% image self.image width-10000 %}``. This assumes the image is unlikely to be larger than 10000px wide.
|
||||
Wagtail does not allow deforming or stretching images. Image dimension ratios will always be kept. Wagtail also *does not support upscaling*. Small images forced to appear at larger sizes will "max out" at their their native dimensions.
|
||||
|
||||
|
||||
.. _image_tag_alt:
|
||||
|
|
@ -190,6 +216,32 @@ Only fields using ``RichTextField`` need this applied in the template.
|
|||
.. Note::
|
||||
Note that the template tag loaded differs from the name of the filter.
|
||||
|
||||
Responsive Embeds
|
||||
-----------------
|
||||
|
||||
Wagtail embeds and images are included at their full width, which may overflow the bounds of the content container you've defined in your templates. To make images and embeds responsive -- meaning they'll resize to fit their container -- include the following CSS.
|
||||
|
||||
.. code-block:: css
|
||||
|
||||
.rich-text img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.responsive-object {
|
||||
position: relative;
|
||||
}
|
||||
.responsive-object iframe,
|
||||
.responsive-object object,
|
||||
.responsive-object embed {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
Internal links (tag)
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ You can explicitly link ``Page``-derived models together using the ``Page`` mode
|
|||
Snippets
|
||||
--------
|
||||
|
||||
Snippets are not subclasses, so you must include the model class directly. A chooser is provided which takes the field name snippet class.
|
||||
Snippets are vanilla Django models you create yourself without a Wagtail-provided base class. So using them as a field in a page requires specifying your own ``appname.modelname``. A chooser, ``SnippetChooserPanel``, is provided which takes the field name and snippet class.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
@ -248,6 +248,12 @@ Full-Width Input
|
|||
Use ``classname="full"`` to make a field (input element) stretch the full width of the Wagtail page editor. This will not work if the field is encapsulated in a ``MultiFieldPanel``, which places its child fields into a formset.
|
||||
|
||||
|
||||
Titles
|
||||
------
|
||||
|
||||
Use ``classname="title"`` to make Page's built-in title field stand out with more vertical padding.
|
||||
|
||||
|
||||
Required Fields
|
||||
---------------
|
||||
|
||||
|
|
@ -264,19 +270,11 @@ Without a panel definition, a default form field (without label) will be used to
|
|||
.. _Django model field reference (editable): https://docs.djangoproject.com/en/dev/ref/models/fields/#editable
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
MultiFieldPanel
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The ``MultiFieldPanel`` groups a list of child fields into a fieldset, which can also be collapsed into a heading bar to save space.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
BOOK_FIELD_COLLECTION = [
|
||||
|
|
@ -294,8 +292,7 @@ MultiFieldPanel
|
|||
# ...
|
||||
]
|
||||
|
||||
|
||||
|
||||
By default, ``MultiFieldPanel`` s are expanded and not collapsible. Adding the classname ``collapsible`` will enable the collapse control. Adding both ``collapsible`` and ``collapsed`` to the classname parameter will load the editor page with the ``MultiFieldPanel`` collapsed under its heading.
|
||||
|
||||
|
||||
.. _inline_panels:
|
||||
|
|
@ -303,7 +300,55 @@ MultiFieldPanel
|
|||
Inline Panels and Model Clusters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``django-modelcluster`` module allows for streamlined relation of extra models to a Wagtail page.
|
||||
The ``django-modelcluster`` module allows for streamlined relation of extra models to a Wagtail page. For instance, you can create objects related through a ``ForeignKey`` relationship on the fly and save them to a draft revision of a ``Page`` object. Normally, your related objects "cluster" would need to be created beforehand (or asynchronously) before linking them to a Page.
|
||||
|
||||
Let's look at the example of adding related links to a ``Page``-derived model. We want to be able to add as many as we like, assign an order, and do all of this without leaving the page editing screen.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from wagtail.wagtailcore.models import Orderable, Page
|
||||
from modelcluster.fields import ParentalKey
|
||||
|
||||
# The abstract model for related links, complete with panels
|
||||
class RelatedLink(models.Model):
|
||||
title = models.CharField(max_length=255)
|
||||
link_external = models.URLField("External link", blank=True)
|
||||
|
||||
panels = [
|
||||
FieldPanel('title'),
|
||||
FieldPanel('link_external'),
|
||||
]
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
# The real model which combines the abstract model, an
|
||||
# Orderable helper class, and what amounts to a ForeignKey link
|
||||
# to the model we want to add related links to (BookPage)
|
||||
class BookPageRelatedLinks(Orderable, RelatedLink):
|
||||
page = ParentalKey('demo.BookPage', related_name='related_links')
|
||||
|
||||
class BookPage( Page ):
|
||||
# ...
|
||||
|
||||
BookPage.content_panels = [
|
||||
# ...
|
||||
InlinePanel( BookPage, 'related_links', label="Related Links" ),
|
||||
]
|
||||
|
||||
The ``RelatedLink`` class is a vanilla Django abstract model. The ``BookPageRelatedLinks`` model extends it with capability for being ordered in the Wagtail interface via the ``Orderable`` class as well as adding a ``page`` property which links the model to the ``BookPage`` model we're adding the related links objects to. Finally, in the panel definitions for ``BookPage``, we'll add an ``InlinePanel`` to provide an interface for it all. Let's look again at the parameters that ``InlinePanel`` accepts:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
InlinePanel( base_model, relation_name, panels=None, label='', help_text='' )
|
||||
|
||||
``base_model`` is the model you're extending with the cluster. The ``relation_name`` is the ``related_name`` label given to the cluster's ``ParentalKey`` relation. You can add the ``panels`` manually or make them part of the cluster model. Finally, ``label`` and ``help_text`` provide a heading and caption, respectively, for the Wagtail editor.
|
||||
|
||||
For another example of using model clusters, see :ref:`tagging`
|
||||
|
||||
For more on ``django-modelcluster``, visit `the django-modelcluster github project page`_ ).
|
||||
|
||||
.. _the django-modelcluster github page: https://github.com/torchbox/django-modelcluster
|
||||
|
||||
|
||||
.. _extending_wysiwyg:
|
||||
|
|
@ -311,12 +356,205 @@ The ``django-modelcluster`` module allows for streamlined relation of extra mode
|
|||
Extending the WYSIWYG Editor (hallo.js)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Adding hallo.js plugins:
|
||||
https://github.com/torchbox/wagtail/commit/1ecc215759142e6cafdacb185bbfd3f8e9cd3185
|
||||
To inject javascript into the Wagtail page editor, see the :ref:`insert_editor_js` hook. Once you have the hook in place and your hallo.js plugin loads into the Wagtail page editor, use the following Javascript to register the plugin with hallo.js.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
registerHalloPlugin(name, opts);
|
||||
|
||||
hallo.js plugin names are prefixed with the ``"IKS."`` namespace, but the ``name`` you pass into ``registerHalloPlugin()`` should be without the prefix. ``opts`` is an object passed into the plugin.
|
||||
|
||||
For information on developing custom hallo.js plugins, see the project's page: https://github.com/bergie/hallo
|
||||
|
||||
|
||||
Edit Handler API
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Registering functions with a Wagtail hook follows the following pattern:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from wagtail.wagtailadmin import hooks
|
||||
|
||||
hooks.register('hook', function)
|
||||
|
||||
Where ``'hook'`` is one of the following hook strings and ``function`` is a function you've defined to handle the hook.
|
||||
|
||||
.. _construct_wagtail_edit_bird:
|
||||
|
||||
``construct_wagtail_edit_bird``
|
||||
Add or remove items from the wagtail userbar. Add, edit, and moderation tools are provided by default. The callable passed into the hook must take the ``request`` object and a list of menu objects, ``items``. The menu item objects must have a ``render`` method which can take a ``request`` object and return the HTML string representing the menu item. See the userbar templates and menu item classes for more information.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from wagtail.wagtailadmin import hooks
|
||||
|
||||
class UserbarPuppyLinkItem(object):
|
||||
def render(self, request):
|
||||
return '<li><a href="http://cuteoverload.com/tag/puppehs/" ' \
|
||||
+ 'target="_parent" class="action icon icon-wagtail">Puppies!</a></li>'
|
||||
|
||||
def add_puppy_link_item(request, items):
|
||||
return items.append( UserbarPuppyLinkItem() )
|
||||
|
||||
hooks.register('construct_wagtail_edit_bird', add_puppy_link_item)
|
||||
|
||||
.. _construct_homepage_panels:
|
||||
|
||||
``construct_homepage_panels``
|
||||
Add or remove panels from the Wagtail admin homepage. The callable passed into this hook should take a ``request`` object and a list of ``panels``, objects which have a ``render()`` method returning a string. The objects also have an ``order`` property, an integer used for ordering the panels. The default panels use integers between ``100`` and ``300``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from wagtail.wagtailadmin import hooks
|
||||
|
||||
class WelcomePanel(object):
|
||||
order = 50
|
||||
|
||||
def render(self):
|
||||
return mark_safe("""
|
||||
<section class="panel summary nice-padding">
|
||||
<h3>No, but seriously -- welcome to the admin homepage.</h3>
|
||||
</section>
|
||||
""")
|
||||
|
||||
def add_another_welcome_panel(request, panels):
|
||||
return panels.append( WelcomePanel() )
|
||||
|
||||
hooks.register('construct_homepage_panels', add_another_welcome_panel)
|
||||
|
||||
.. _after_create_page:
|
||||
|
||||
``after_create_page``
|
||||
Do something with a ``Page`` object after it has been saved to the database (as a published page or a revision). The callable passed to this hook should take a ``request`` object and a ``page`` object. The function does not have to return anything, but if an object with a ``status_code`` property is returned, Wagtail will use it as a response object. By default, Wagtail will instead redirect to the Explorer page for the new page's parent.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.http import HttpResponse
|
||||
|
||||
from wagtail.wagtailadmin import hooks
|
||||
|
||||
def do_after_page_create(request, page):
|
||||
return HttpResponse("Congrats on making content!", content_type="text/plain")
|
||||
hooks.register('after_create_page', do_after_page_create)
|
||||
|
||||
.. _after_edit_page:
|
||||
|
||||
``after_edit_page``
|
||||
Do something with a ``Page`` object after it has been updated. Uses the same behavior as ``after_create_page``.
|
||||
|
||||
.. _after_delete_page:
|
||||
|
||||
``after_delete_page``
|
||||
Do something after a ``Page`` object is deleted. Uses the same behavior as ``after_create_page``.
|
||||
|
||||
.. _register_admin_urls:
|
||||
|
||||
``register_admin_urls``
|
||||
Register additional admin page URLs. The callable fed into this hook should return a list of Django URL patterns which define the structure of the pages and endpoints of your extension to the Wagtail admin. For more about vanilla Django URLconfs and views, see `url dispatcher`_.
|
||||
|
||||
.. _url dispatcher: https://docs.djangoproject.com/en/dev/topics/http/urls/
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.conf.urls import url
|
||||
|
||||
from wagtail.wagtailadmin import hooks
|
||||
|
||||
def admin_view( request ):
|
||||
return HttpResponse( \
|
||||
"I have approximate knowledge of many things!", \
|
||||
content_type="text/plain")
|
||||
|
||||
def urlconf_time():
|
||||
return [
|
||||
url(r'^how_did_you_almost_know_my_name/$', admin_view, name='frank' ),
|
||||
]
|
||||
hooks.register('register_admin_urls', urlconf_time)
|
||||
|
||||
.. _construct_main_menu:
|
||||
|
||||
``construct_main_menu``
|
||||
Add, remove, or alter ``MenuItem`` objects from the Wagtail admin menu. The callable passed to this hook must take a ``request`` object and a list of ``menu_items``; it must return a list of menu items. New items can be constructed from the ``MenuItem`` class by passing in: a ``label`` which will be the text in the menu item, the URL of the admin page you want the menu item to link to (usually by calling ``reverse()`` on the admin view you've set up), CSS class ``name`` applied to the wrapping ``<li>`` of the menu item as ``"menu-{name}"``, CSS ``classnames`` which are used to give the link an icon, and an ``order`` integer which determine's the item's place in the menu.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from wagtail.wagtailadmin import hooks
|
||||
from wagtail.wagtailadmin.menu import MenuItem
|
||||
|
||||
def construct_main_menu(request, menu_items):
|
||||
menu_items.append(
|
||||
MenuItem( 'Frank', reverse('frank'), classnames='icon icon-folder-inverse', order=10000)
|
||||
)
|
||||
hooks.register('construct_main_menu', construct_main_menu)
|
||||
|
||||
|
||||
.. _insert_editor_js:
|
||||
|
||||
``insert_editor_js``
|
||||
Add additional Javascript files or code snippets to the page editor. Output must be compatible with ``compress``, as local static includes or string.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.utils.html import format_html, format_html_join
|
||||
from django.conf import settings
|
||||
|
||||
from wagtail.wagtailadmin import hooks
|
||||
|
||||
def editor_js():
|
||||
js_files = [
|
||||
'demo/js/hallo-plugins/hallo-demo-plugin.js',
|
||||
]
|
||||
js_includes = format_html_join('\n', '<script src="{0}{1}"></script>',
|
||||
((settings.STATIC_URL, filename) for filename in js_files)
|
||||
)
|
||||
return js_includes + format_html(
|
||||
"""
|
||||
<script>
|
||||
registerHalloPlugin('demoeditor');
|
||||
</script>
|
||||
"""
|
||||
)
|
||||
hooks.register('insert_editor_js', editor_js)
|
||||
|
||||
.. _insert_editor_css:
|
||||
|
||||
``insert_editor_css``
|
||||
Add additional CSS or SCSS files or snippets to the page editor. Output must be compatible with ``compress``, as local static includes or string.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.utils.html import format_html
|
||||
from django.conf import settings
|
||||
|
||||
from wagtail.wagtailadmin import hooks
|
||||
|
||||
def editor_css():
|
||||
return format_html('<link rel="stylesheet" href="' \
|
||||
+ settings.STATIC_URL \
|
||||
+ 'demo/css/vendor/font-awesome/css/font-awesome.min.css">')
|
||||
hooks.register('insert_editor_css', editor_css)
|
||||
|
||||
|
||||
Content Index Pages (CRUD)
|
||||
--------------------------
|
||||
|
||||
|
||||
Custom Choosers
|
||||
---------------
|
||||
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
|
|
|
|||
|
|
@ -118,6 +118,8 @@ Will return::
|
|||
tauntaun kennel bed and breakfast
|
||||
|
||||
|
||||
.. _tagging:
|
||||
|
||||
Tagging
|
||||
-------
|
||||
|
||||
|
|
|
|||
|
|
@ -255,18 +255,42 @@ FormPage.content_panels = [
|
|||
]
|
||||
|
||||
|
||||
# Snippets
|
||||
|
||||
# Snippets
|
||||
|
||||
class Advert(models.Model):
|
||||
url = models.URLField(null=True, blank=True)
|
||||
text = models.CharField(max_length=255)
|
||||
url = models.URLField(null=True, blank=True)
|
||||
text = models.CharField(max_length=255)
|
||||
|
||||
panels = [
|
||||
FieldPanel('url'),
|
||||
FieldPanel('text'),
|
||||
]
|
||||
panels = [
|
||||
FieldPanel('url'),
|
||||
FieldPanel('text'),
|
||||
]
|
||||
|
||||
def __unicode__(self):
|
||||
return self.text
|
||||
|
||||
def __unicode__(self):
|
||||
return self.text
|
||||
|
||||
register_snippet(Advert)
|
||||
|
||||
|
||||
# AlphaSnippet and ZuluSnippet are for testing ordering of
|
||||
# snippets when registering. They are named as such to ensure
|
||||
# thier ordering is clear. They are registered during testing
|
||||
# to ensure specific [in]correct register ordering
|
||||
|
||||
# AlphaSnippet is registered during TestSnippetOrdering
|
||||
class AlphaSnippet(models.Model):
|
||||
text = models.CharField(max_length=255)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.text
|
||||
|
||||
|
||||
# ZuluSnippet is registered during TestSnippetOrdering
|
||||
class ZuluSnippet(models.Model):
|
||||
text = models.CharField(max_length=255)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.text
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -52,7 +52,7 @@ def embedly(url, max_width=None, key=None):
|
|||
key = settings.EMBEDLY_KEY
|
||||
|
||||
# Get embedly client
|
||||
client = Embedly(key=settings.EMBEDLY_KEY)
|
||||
client = Embedly(key=key)
|
||||
|
||||
# Call embedly
|
||||
if max_width is not None:
|
||||
|
|
|
|||
|
|
@ -130,3 +130,8 @@ class BaseImageBackend(object):
|
|||
"""
|
||||
resized_image = self.resize_to_min(image, size)
|
||||
return self.crop_to_centre(resized_image, size)
|
||||
|
||||
|
||||
def no_operation(self, image, param):
|
||||
"""Return the image unchanged"""
|
||||
return image
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import StringIO
|
||||
import os.path
|
||||
import re
|
||||
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
|
|
@ -150,6 +151,7 @@ class Filter(models.Model):
|
|||
'width': 'resize_to_width',
|
||||
'height': 'resize_to_height',
|
||||
'fill': 'resize_to_fill',
|
||||
'original': 'no_operation',
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
@ -157,22 +159,34 @@ class Filter(models.Model):
|
|||
self.method = None # will be populated when needed, by parsing the spec string
|
||||
|
||||
def _parse_spec_string(self):
|
||||
# parse the spec string, which is formatted as (method)-(arg),
|
||||
# and save the results to self.method_name and self.method_arg
|
||||
try:
|
||||
(method_name_simple, method_arg_string) = self.spec.split('-')
|
||||
self.method_name = Filter.OPERATION_NAMES[method_name_simple]
|
||||
# parse the spec string and save the results to
|
||||
# self.method_name and self.method_arg. There are various possible
|
||||
# formats to match against:
|
||||
# 'original'
|
||||
# 'width-200'
|
||||
# 'max-320x200'
|
||||
|
||||
if method_name_simple in ('max', 'min', 'fill'):
|
||||
# method_arg_string is in the form 640x480
|
||||
(width, height) = [int(i) for i in method_arg_string.split('x')]
|
||||
self.method_arg = (width, height)
|
||||
else:
|
||||
# method_arg_string is a single number
|
||||
self.method_arg = int(method_arg_string)
|
||||
if self.spec == 'original':
|
||||
self.method_name = Filter.OPERATION_NAMES['original']
|
||||
self.method_arg = None
|
||||
return
|
||||
|
||||
except (ValueError, KeyError):
|
||||
raise ValueError("Invalid image filter spec: %r" % self.spec)
|
||||
match = re.match(r'(width|height)-(\d+)$', self.spec)
|
||||
if match:
|
||||
self.method_name = Filter.OPERATION_NAMES[match.group(1)]
|
||||
self.method_arg = int(match.group(2))
|
||||
return
|
||||
|
||||
match = re.match(r'(max|min|fill)-(\d+)x(\d+)$', self.spec)
|
||||
if match:
|
||||
self.method_name = Filter.OPERATION_NAMES[match.group(1)]
|
||||
width = int(match.group(2))
|
||||
height = int(match.group(3))
|
||||
self.method_arg = (width, height)
|
||||
return
|
||||
|
||||
# Spec is not one of our recognised patterns
|
||||
raise ValueError("Invalid image filter spec: %r" % self.spec)
|
||||
|
||||
def process_image(self, input_file, backend_name='default'):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -107,6 +107,13 @@ class TestRenditions(TestCase):
|
|||
self.assertEqual(rendition.width, 160)
|
||||
self.assertEqual(rendition.height, 120)
|
||||
|
||||
def test_resize_to_original(self):
|
||||
rendition = self.image.get_rendition('original')
|
||||
|
||||
# Check size
|
||||
self.assertEqual(rendition.width, 640)
|
||||
self.assertEqual(rendition.height, 480)
|
||||
|
||||
def test_cache(self):
|
||||
# Get two renditions with the same filter
|
||||
first_rendition = self.image.get_rendition('width-400')
|
||||
|
|
@ -153,6 +160,13 @@ class TestRenditionsWand(TestCase):
|
|||
self.assertEqual(rendition.width, 160)
|
||||
self.assertEqual(rendition.height, 120)
|
||||
|
||||
def test_resize_to_original(self):
|
||||
rendition = self.image.get_rendition('original')
|
||||
|
||||
# Check size
|
||||
self.assertEqual(rendition.width, 640)
|
||||
self.assertEqual(rendition.height, 480)
|
||||
|
||||
def test_cache(self):
|
||||
# Get two renditions with the same filter
|
||||
first_rendition = self.image.get_rendition('width-400')
|
||||
|
|
|
|||
|
|
@ -19,3 +19,4 @@ def get_snippet_content_types():
|
|||
def register_snippet(model):
|
||||
if model not in SNIPPET_MODELS:
|
||||
SNIPPET_MODELS.append(model)
|
||||
SNIPPET_MODELS.sort(key=lambda x: x._meta.verbose_name)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ from django.core.urlresolvers import reverse
|
|||
from django.contrib.auth.models import User
|
||||
|
||||
from wagtail.tests.utils import login, unittest
|
||||
from wagtail.tests.models import Advert
|
||||
from wagtail.tests.models import Advert, AlphaSnippet, ZuluSnippet
|
||||
from wagtail.wagtailsnippets.models import register_snippet, SNIPPET_MODELS
|
||||
|
||||
from wagtail.wagtailsnippets.views.snippets import get_content_type_from_url_params, get_snippet_edit_handler
|
||||
from wagtail.wagtailsnippets.edit_handlers import SnippetChooserPanel
|
||||
|
|
@ -168,3 +169,16 @@ class TestSnippetChooserPanel(TestCase):
|
|||
def test_render_js(self):
|
||||
self.assertTrue("createSnippetChooser(fixPrefix('id_text'), 'contenttypes/contenttype');"
|
||||
in self.snippet_chooser_panel.render_js())
|
||||
|
||||
|
||||
class TestSnippetOrdering(TestCase):
|
||||
def setUp(self):
|
||||
register_snippet(ZuluSnippet)
|
||||
register_snippet(AlphaSnippet)
|
||||
|
||||
def test_snippets_ordering(self):
|
||||
# Ensure AlphaSnippet is before ZuluSnippet
|
||||
# Cannot check first and last position as other snippets
|
||||
# may get registered elsewhere during test
|
||||
self.assertLess(SNIPPET_MODELS.index(AlphaSnippet),
|
||||
SNIPPET_MODELS.index(ZuluSnippet))
|
||||
|
|
|
|||
Loading…
Reference in a new issue