mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-04-12 02:50:59 +00:00
Merge branch 'master' into feature/styleguide
This commit is contained in:
commit
b638cc01e9
62 changed files with 4525 additions and 3040 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:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,15 @@ Changelog
|
|||
=========
|
||||
|
||||
0.4 (xx.xx.20xx)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~
|
||||
* 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
|
||||
* Fix: Animated GIFs are now coalesced before resizing
|
||||
* Fix: Wand backend clones images before modifying them
|
||||
* Fix: Admin breadcrumb now positioned correctly on mobile
|
||||
|
||||
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,237 @@ 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
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
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.
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
--------------------------
|
||||
|
||||
|
||||
Custom Choosers
|
||||
---------------
|
||||
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ Wagtail instance available as the basis for your new site:
|
|||
- Install `Vagrant <http://www.vagrantup.com/>`_ 1.1+
|
||||
- Clone the demonstration site, create the Vagrant box and initialise Wagtail::
|
||||
|
||||
git clone git@github.com:torchbox/wagtaildemo.git
|
||||
git clone https://github.com/torchbox/wagtaildemo.git
|
||||
cd wagtaildemo
|
||||
vagrant up
|
||||
vagrant ssh
|
||||
|
|
|
|||
|
|
@ -118,6 +118,8 @@ Will return::
|
|||
tauntaun kennel bed and breakfast
|
||||
|
||||
|
||||
.. _tagging:
|
||||
|
||||
Tagging
|
||||
-------
|
||||
|
||||
|
|
|
|||
|
|
@ -224,10 +224,13 @@ Prerequisites are the Elasticsearch service itself and, via pip, the `elasticuti
|
|||
|
||||
.. code-block:: guess
|
||||
|
||||
pip install elasticutils pyelasticsearch
|
||||
pip install elasticutils==0.8.2 pyelasticsearch
|
||||
|
||||
.. note::
|
||||
The dependency on pyelasticsearch is scheduled to be replaced by a dependency on `elasticsearch-py`_.
|
||||
ElasticUtils 0.9+ is not supported.
|
||||
|
||||
.. note::
|
||||
The dependency on elasticutils and pyelasticsearch is scheduled to be replaced by a dependency on `elasticsearch-py`_.
|
||||
|
||||
The backend is configured in settings:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# For coverage and PEP8 linting
|
||||
coverage==3.7.1
|
||||
flake8==2.1.0
|
||||
mock==1.0.1
|
||||
|
|
|
|||
|
|
@ -102,7 +102,9 @@ if not settings.configured:
|
|||
),
|
||||
COMPRESS_ENABLED=False, # disable compression so that we can run tests on the content of the compress tag
|
||||
WAGTAILSEARCH_BACKENDS=WAGTAILSEARCH_BACKENDS,
|
||||
WAGTAIL_SITE_NAME='Test Site'
|
||||
WAGTAIL_SITE_NAME='Test Site',
|
||||
LOGIN_REDIRECT_URL='wagtailadmin_home',
|
||||
LOGIN_URL='wagtailadmin_login',
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.six.moves.urllib.parse import urlparse, ParseResult
|
||||
from django.http import QueryDict
|
||||
|
||||
# We need to make sure that we're using the same unittest library that Django uses internally
|
||||
# Otherwise, we get issues with the "SkipTest" and "ExpectedFailure" exceptions being recognised as errors
|
||||
|
|
@ -12,11 +15,30 @@ except ImportError:
|
|||
import unittest
|
||||
|
||||
|
||||
def login(client):
|
||||
# Create a user
|
||||
user = User.objects.create_superuser(username='test', email='test@email.com', password='password')
|
||||
class WagtailTestUtils(object):
|
||||
def login(self):
|
||||
# Create a user
|
||||
user = User.objects.create_superuser(username='test', email='test@email.com', password='password')
|
||||
|
||||
# Login
|
||||
client.login(username='test', password='password')
|
||||
# Login
|
||||
self.client.login(username='test', password='password')
|
||||
|
||||
return user
|
||||
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))
|
||||
|
|
|
|||
|
|
@ -16,9 +16,12 @@ function ModalWorkflow(opts) {
|
|||
/* remove any previous modals before continuing (closing doesn't remove them from the dom) */
|
||||
$('body > .modal').remove();
|
||||
|
||||
// set default contents of container
|
||||
var container = $('<div class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">\n <div class="modal-dialog">\n <div class="modal-content">\n <button type="button" class="close icon text-replace icon-cross" data-dismiss="modal" aria-hidden="true">×</button>\n <div class="modal-body"></div>\n </div><!-- /.modal-content -->\n </div><!-- /.modal-dialog -->\n</div>');
|
||||
|
||||
// add container to body and hide it, so content can be added to it before display
|
||||
$('body').append(container);
|
||||
container.modal();
|
||||
container.modal('hide');
|
||||
|
||||
self.body = container.find('.modal-body');
|
||||
|
||||
|
|
@ -49,15 +52,19 @@ function ModalWorkflow(opts) {
|
|||
|
||||
self.loadResponseText = function(responseText) {
|
||||
var response = eval('(' + responseText + ')');
|
||||
|
||||
self.loadBody(response);
|
||||
};
|
||||
|
||||
self.loadBody = function(body) {
|
||||
if (body.html) {
|
||||
self.body.html(body.html);
|
||||
self.loadBody = function(response) {
|
||||
if (response.html) {
|
||||
// if the response is html
|
||||
self.body.html(response.html);
|
||||
container.modal('show');
|
||||
}
|
||||
if (body.onload) {
|
||||
body.onload(self);
|
||||
if (response.onload) {
|
||||
// if the response is a function
|
||||
response.onload(self);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
65
wagtail/wagtailadmin/static/wagtailadmin/js/vendor/bootstrap-transition.js
vendored
Normal file
65
wagtail/wagtailadmin/static/wagtailadmin/js/vendor/bootstrap-transition.js
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/* ========================================================================
|
||||
* Bootstrap: transition.js v3.1.1
|
||||
* http://getbootstrap.com/javascript/#transitions
|
||||
* ========================================================================
|
||||
* Copyright 2011-2014 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
|
||||
+function () { 'use strict';
|
||||
|
||||
(function (o_o) {
|
||||
typeof define == 'function' && define.amd ? define(['jquery'], o_o) :
|
||||
typeof exports == 'object' ? o_o(require('jquery')) : o_o(jQuery)
|
||||
})(function ($) {
|
||||
|
||||
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
|
||||
// ============================================================
|
||||
|
||||
function transitionEnd() {
|
||||
var el = document.createElement('bootstrap')
|
||||
|
||||
var transEndEventNames = {
|
||||
WebkitTransition : 'webkitTransitionEnd',
|
||||
MozTransition : 'transitionend',
|
||||
OTransition : 'oTransitionEnd otransitionend',
|
||||
transition : 'transitionend'
|
||||
}
|
||||
|
||||
for (var name in transEndEventNames) {
|
||||
if (el.style[name] !== undefined) {
|
||||
return { end: transEndEventNames[name] }
|
||||
}
|
||||
}
|
||||
|
||||
return false // explicit for ie8 ( ._.)
|
||||
}
|
||||
|
||||
// http://blog.alexmaccaw.com/css-transitions
|
||||
$.fn.emulateTransitionEnd = function (duration) {
|
||||
var called = false
|
||||
var $el = this
|
||||
$(this).one('bsTransitionEnd', function () { called = true })
|
||||
var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
|
||||
setTimeout(callback, duration)
|
||||
return this
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$.support.transition = transitionEnd()
|
||||
|
||||
if (!$.support.transition) return
|
||||
|
||||
$.event.special.bsTransitionEnd = {
|
||||
bindType: $.support.transition.end,
|
||||
delegateType: $.support.transition.end,
|
||||
handle: function (e) {
|
||||
if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}();
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -650,6 +650,13 @@ ul.tagit li.tagit-choice-editable{
|
|||
}
|
||||
}
|
||||
|
||||
/* search-bars */
|
||||
.search-bar{
|
||||
.required label:after{
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Transitions */
|
||||
fieldset, input, textarea, select{
|
||||
@include transition(background-color 0.2s ease);
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ header{
|
|||
|
||||
/* necessary on mobile only to make way for hamburger menu */
|
||||
&.nice-padding{
|
||||
padding-left:4em;
|
||||
padding-left:$desktop-nice-padding;
|
||||
}
|
||||
|
||||
label{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
$zindex-modal-background: 500;
|
||||
|
||||
.fade {
|
||||
opacity: 0;
|
||||
@include transition(opacity .15s linear);
|
||||
|
||||
&.in {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Kill the scroll on the body
|
||||
.modal-open {
|
||||
overflow: hidden;
|
||||
|
|
@ -70,7 +80,7 @@ $zindex-modal-background: 500;
|
|||
background-color: black;
|
||||
// Fade for backdrop
|
||||
&.fade { opacity:0; }
|
||||
&.in { opacity:0.7; }
|
||||
&.in { opacity:0.5; }
|
||||
}
|
||||
|
||||
.modal .close{
|
||||
|
|
|
|||
|
|
@ -431,13 +431,13 @@ footer{
|
|||
.breadcrumb{
|
||||
@include unlist();
|
||||
}
|
||||
|
||||
.breadcrumb{
|
||||
@include clearfix();
|
||||
overflow:hidden;
|
||||
background:$color-teal-darker;
|
||||
padding-top:1.4em;
|
||||
font-size:0.85em;
|
||||
|
||||
|
||||
li {
|
||||
display: block;
|
||||
float: left;
|
||||
|
|
@ -469,7 +469,7 @@ footer{
|
|||
content:"n";
|
||||
padding-left:20px;
|
||||
font-size:2em;
|
||||
color:$color-teal;
|
||||
color:$color-teal-darker;
|
||||
line-height:0.9em;
|
||||
}
|
||||
}
|
||||
|
|
@ -740,6 +740,19 @@ footer, .logo{
|
|||
margin-left:50px;
|
||||
}
|
||||
|
||||
.breadcrumb{
|
||||
padding-top:0;
|
||||
background:$color-teal-darker;
|
||||
|
||||
li {
|
||||
a, span{
|
||||
&:after{
|
||||
color:$color-teal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Z-indexes */
|
||||
.nav-main{
|
||||
li{
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ body{
|
|||
.wrapper{
|
||||
padding-left:$mobile-nice-padding;
|
||||
padding-right:$mobile-nice-padding;
|
||||
margin-left:0;
|
||||
max-width:none;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,8 @@
|
|||
margin-bottom:10em;
|
||||
}
|
||||
|
||||
.page-editor header {
|
||||
|
||||
}
|
||||
.page-editor .breadcrumb{
|
||||
margin-top:-1.8em;
|
||||
margin-top:-1.2em;
|
||||
margin-bottom:2em;
|
||||
}
|
||||
.page-editor .modal .breadcrumb{
|
||||
|
|
@ -336,6 +333,9 @@ footer .preview{
|
|||
}
|
||||
|
||||
@media screen and (min-width: $breakpoint-mobile){
|
||||
.page-editor .breadcrumb{
|
||||
margin-top:-1.8em;
|
||||
}
|
||||
.object{
|
||||
fieldset{
|
||||
max-width:1024px;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,6 @@
|
|||
{% block furniture %}
|
||||
<div class="content-wrapper">
|
||||
<h1>{% trans "Password change successful" %}</h1>
|
||||
<p><a href="{% url 'django.contrib.auth.views.login' %}" class="button button-primary">{% trans "Login" %}</a></p>
|
||||
<p><a href="{% url 'wagtailadmin_login' %}" class="button button-primary">{% trans "Login" %}</a></p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
<script src="{{ STATIC_URL }}wagtailadmin/js/vendor/jquery-ui-1.10.3.js"></script>
|
||||
<script src="{{ STATIC_URL }}wagtailadmin/js/vendor/jquery.timepicker.min.js"></script>
|
||||
<script src="{{ STATIC_URL }}wagtailadmin/js/vendor/jquery.autosize.js"></script>
|
||||
<script src="{{ STATIC_URL }}wagtailadmin/js/vendor/bootstrap-transition.js"></script>
|
||||
<script src="{{ STATIC_URL }}wagtailadmin/js/vendor/bootstrap-modal.js"></script>
|
||||
<script src="{{ STATIC_URL }}wagtailadmin/js/vendor/bootstrap-tab.js"></script>
|
||||
<script src="{{ STATIC_URL }}wagtailadmin/js/vendor/jquery.dlmenu.js"></script>
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@
|
|||
<td class="title" valign="top">
|
||||
<h2><a href="{% url 'wagtailadmin_pages_edit' revision.page.id %}" title="{% trans 'Edit this page' %}">{{ revision.page.title }}</a></h2>
|
||||
<ul class="actions">
|
||||
<li><a href="{% url 'wagtailadmin_pages_edit' revision.page.id %}" class="button button-small" target="_blank">{% trans "Edit" %}</a></li>
|
||||
<li><a href="{% url 'wagtailadmin_pages_edit' revision.page.id %}" class="button button-small">{% trans "Edit" %}</a></li>
|
||||
{% if revision.page.has_unpublished_changes %}
|
||||
<li><a href="{% url 'wagtailadmin_pages_view_draft' revision.page.id %}" class="button button-small" target="_blank">{% trans 'Draft' %}</a></li>
|
||||
<li><a href="{% url 'wagtailadmin_pages_view_draft' revision.page.id %}" class="button button-small">{% trans 'Draft' %}</a></li>
|
||||
{% endif %}
|
||||
{% if revision.page.live %}
|
||||
<li><a href="{{ revision.page.url }}" class="button button-small" target="_blank">{% trans 'Live' %}</a></li>
|
||||
<li><a href="{{ revision.page.url }}" class="button button-small">{% trans 'Live' %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form action="{% url 'django.contrib.auth.views.login' %}" method="post" autocomplete="off">
|
||||
<form action="{% url 'wagtailadmin_login' %}" method="post" autocomplete="off">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
<h1>{% trans "Sign in to Wagtail" %}</h1>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{% load wagtailadmin_tags %}
|
||||
<li class="{{ field.css_classes }} {{ field|fieldtype }} {{ li_classes }} {% if field.errors %}error{% endif %}">
|
||||
<li class="{% if field.field.required %}required{% endif %} {{ field.css_classes }} {{ field|fieldtype }} {{ li_classes }} {% if field.errors %}error{% endif %}">
|
||||
<div class="field">
|
||||
{{ field.label_tag }}
|
||||
<div class="field-content">
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
from django.test import TestCase
|
||||
from wagtail.tests.utils import login, unittest
|
||||
from wagtail.tests.utils import unittest, WagtailTestUtils
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.tokens import PasswordResetTokenGenerator
|
||||
from django.core import mail
|
||||
|
||||
|
||||
class TestAuthentication(TestCase):
|
||||
class TestAuthentication(TestCase, WagtailTestUtils):
|
||||
"""
|
||||
This tests that users can login and logout of the admin interface
|
||||
"""
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def test_login_view(self):
|
||||
"""
|
||||
|
|
@ -44,12 +44,12 @@ class TestAuthentication(TestCase):
|
|||
|
||||
# Check that the user was redirected to the dashboard
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, reverse('wagtailadmin_home'))
|
||||
|
||||
# Check that the user was logged in
|
||||
self.assertTrue('_auth_user_id' in self.client.session)
|
||||
self.assertEqual(self.client.session['_auth_user_id'], User.objects.get(username='test').id)
|
||||
|
||||
@unittest.expectedFailure # See: https://github.com/torchbox/wagtail/issues/25
|
||||
def test_already_logged_in_redirect(self):
|
||||
"""
|
||||
This tests that a user who is already logged in is automatically
|
||||
|
|
@ -61,27 +61,63 @@ class TestAuthentication(TestCase):
|
|||
|
||||
# Check that the user was redirected to the dashboard
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, reverse('wagtailadmin_home'))
|
||||
|
||||
def test_logout(self):
|
||||
"""
|
||||
This tests that the user can logout
|
||||
"""
|
||||
# Get logout page page
|
||||
# Get logout page
|
||||
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'))
|
||||
|
||||
# Check that the user was logged out
|
||||
self.assertFalse('_auth_user_id' in self.client.session)
|
||||
|
||||
def test_not_logged_in_redirect(self):
|
||||
"""
|
||||
This tests that a not logged in user is redirected to the
|
||||
login page
|
||||
"""
|
||||
# Logout
|
||||
self.client.logout()
|
||||
|
||||
class TestAccountSection(TestCase):
|
||||
# Get dashboard
|
||||
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'))
|
||||
|
||||
def test_not_logged_in_redirect_default_settings(self):
|
||||
"""
|
||||
This does the same as the above test but checks that it
|
||||
redirects to the correct place when the user has not set
|
||||
the LOGIN_URL setting correctly
|
||||
"""
|
||||
# Logout
|
||||
self.client.logout()
|
||||
|
||||
# Get dashboard with default LOGIN_URL setting
|
||||
with self.settings(LOGIN_URL='django.contrib.auth.views.login'):
|
||||
response = self.client.get(reverse('wagtailadmin_home'))
|
||||
|
||||
# Check that the user was redirected to the login page and that next was set correctly
|
||||
# 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'))
|
||||
|
||||
|
||||
class TestAccountSection(TestCase, WagtailTestUtils):
|
||||
"""
|
||||
This tests that the accounts section is working
|
||||
"""
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def test_account_view(self):
|
||||
"""
|
||||
|
|
@ -117,8 +153,9 @@ class TestAccountSection(TestCase):
|
|||
}
|
||||
response = self.client.post(reverse('wagtailadmin_account_change_password'), post_data)
|
||||
|
||||
# Check that the user was redirected
|
||||
# Check that the user was redirected to the account page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, reverse('wagtailadmin_account'))
|
||||
|
||||
# Check that the password was changed
|
||||
self.assertTrue(User.objects.get(username='test').check_password('newpassword'))
|
||||
|
|
@ -146,7 +183,7 @@ class TestAccountSection(TestCase):
|
|||
self.assertTrue(User.objects.get(username='test').check_password('password'))
|
||||
|
||||
|
||||
class TestPasswordReset(TestCase):
|
||||
class TestPasswordReset(TestCase, WagtailTestUtils):
|
||||
"""
|
||||
This tests that the password reset is working
|
||||
"""
|
||||
|
|
@ -176,8 +213,9 @@ class TestPasswordReset(TestCase):
|
|||
}
|
||||
response = self.client.post(reverse('password_reset'), post_data)
|
||||
|
||||
# Check that the user was redirected
|
||||
# Check that the user was redirected to the done page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, reverse('password_reset_done'))
|
||||
|
||||
# Check that a password reset email was sent to the user
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
|
|
@ -267,8 +305,9 @@ class TestPasswordReset(TestCase):
|
|||
}
|
||||
response = self.client.post(reverse('password_reset_confirm', kwargs=self.url_kwargs), post_data)
|
||||
|
||||
# Check that the user was redirected
|
||||
# Check that the user was redirected to the complete page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, reverse('password_reset_complete'))
|
||||
|
||||
# Check that the password was changed
|
||||
self.assertTrue(User.objects.get(username='test').check_password('newpassword'))
|
||||
|
|
|
|||
130
wagtail/wagtailadmin/tests/test_page_chooser.py
Normal file
130
wagtail/wagtailadmin/tests/test_page_chooser.py
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
from django.test import TestCase
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from wagtail.wagtailcore.models import Page
|
||||
from wagtail.tests.models import SimplePage
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
|
||||
|
||||
class TestChooserBrowse(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.root_page = Page.objects.get(id=2)
|
||||
|
||||
# Add child page
|
||||
self.child_page = SimplePage()
|
||||
self.child_page.title = "foobarbaz"
|
||||
self.child_page.slug = "foobarbaz"
|
||||
self.root_page.add_child(instance=self.child_page)
|
||||
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailadmin_choose_page'), params)
|
||||
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailadmin/chooser/browse.html')
|
||||
|
||||
def test_search(self):
|
||||
response = self.get({'q': "foobarbaz"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "There is one match")
|
||||
self.assertContains(response, "foobarbaz")
|
||||
|
||||
def test_search_no_results(self):
|
||||
response = self.get({'q': "quux"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "There are 0 matches")
|
||||
|
||||
def test_get_invalid(self):
|
||||
response = self.get({'page_type': 'foo.bar'})
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
|
||||
class TestChooserBrowseChild(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.root_page = Page.objects.get(id=2)
|
||||
|
||||
# Add child page
|
||||
self.child_page = SimplePage()
|
||||
self.child_page.title = "foobarbaz"
|
||||
self.child_page.slug = "foobarbaz"
|
||||
self.root_page.add_child(instance=self.child_page)
|
||||
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailadmin_choose_page_child',
|
||||
args=(self.root_page.id,)), params)
|
||||
|
||||
def get_invalid(self, params={}):
|
||||
return self.client.get(reverse('wagtailadmin_choose_page_child',
|
||||
args=(9999999,)), params)
|
||||
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailadmin/chooser/browse.html')
|
||||
|
||||
def test_search(self):
|
||||
response = self.get({'q': "foobarbaz"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "There is one match")
|
||||
self.assertContains(response, "foobarbaz")
|
||||
|
||||
def test_search_no_results(self):
|
||||
response = self.get({'q': "quux"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "There are 0 matches")
|
||||
|
||||
def test_get_invalid(self):
|
||||
self.assertEqual(self.get_invalid().status_code, 404)
|
||||
|
||||
|
||||
class TestChooserExternalLink(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailadmin_choose_page_external_link'), params)
|
||||
|
||||
def post(self, post_data={}):
|
||||
return self.client.post(reverse('wagtailadmin_choose_page_external_link'), post_data)
|
||||
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailadmin/chooser/external_link.html')
|
||||
|
||||
def test_get_with_param(self):
|
||||
self.assertEqual(self.get({'prompt_for_link_text': 'foo'}).status_code, 200)
|
||||
|
||||
def test_create_link(self):
|
||||
request = self.post({'url': 'http://www.example.com'})
|
||||
self.assertContains(request, "'url': 'http://www.example.com/',")
|
||||
self.assertContains(request, "'title': 'http://www.example.com/'")
|
||||
|
||||
|
||||
class TestChooserEmailLink(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailadmin_choose_page_email_link'), params)
|
||||
|
||||
def post(self, post_data={}):
|
||||
return self.client.post(reverse('wagtailadmin_choose_page_email_link'), post_data)
|
||||
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailadmin/chooser/email_link.html')
|
||||
|
||||
def test_get_with_param(self):
|
||||
self.assertEqual(self.get({'prompt_for_link_text': 'foo'}).status_code, 200)
|
||||
|
||||
def test_create_link(self):
|
||||
request = self.post({'email_address': 'example@example.com'})
|
||||
self.assertContains(request, "'url': 'mailto:example@example.com',")
|
||||
self.assertContains(request, "'title': 'example@example.com'")
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
from django.test import TestCase
|
||||
from wagtail.tests.models import SimplePage, EventPage
|
||||
from wagtail.tests.utils import login, unittest
|
||||
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
|
||||
|
||||
|
||||
class TestPageExplorer(TestCase):
|
||||
class TestPageExplorer(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
# Find root page
|
||||
self.root_page = Page.objects.get(id=2)
|
||||
|
|
@ -19,7 +19,7 @@ class TestPageExplorer(TestCase):
|
|||
self.root_page.add_child(instance=self.child_page)
|
||||
|
||||
# Login
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def test_explore(self):
|
||||
response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, )))
|
||||
|
|
@ -28,13 +28,13 @@ class TestPageExplorer(TestCase):
|
|||
self.assertTrue(response.context['pages'].paginator.object_list.filter(id=self.child_page.id).exists())
|
||||
|
||||
|
||||
class TestPageCreation(TestCase):
|
||||
class TestPageCreation(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
# Find root page
|
||||
self.root_page = Page.objects.get(id=2)
|
||||
|
||||
# Login
|
||||
self.user = login(self.client)
|
||||
self.user = self.login()
|
||||
|
||||
def test_add_subpage(self):
|
||||
response = self.client.get(reverse('wagtailadmin_pages_add_subpage', args=(self.root_page.id, )))
|
||||
|
|
@ -86,6 +86,7 @@ class TestPageCreation(TestCase):
|
|||
|
||||
# Should be redirected to explorer page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, 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,6 +105,7 @@ class TestPageCreation(TestCase):
|
|||
|
||||
# Should be redirected to explorer page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, 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
|
||||
|
|
@ -126,6 +128,7 @@ class TestPageCreation(TestCase):
|
|||
|
||||
# Should be redirected to explorer page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, 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
|
||||
|
|
@ -186,7 +189,7 @@ class TestPageCreation(TestCase):
|
|||
self.assertContains(response, "New page!")
|
||||
|
||||
|
||||
class TestPageEdit(TestCase):
|
||||
class TestPageEdit(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
# Find root page
|
||||
self.root_page = Page.objects.get(id=2)
|
||||
|
|
@ -206,7 +209,7 @@ class TestPageEdit(TestCase):
|
|||
self.root_page.add_child(instance=self.event_page)
|
||||
|
||||
# Login
|
||||
self.user = login(self.client)
|
||||
self.user = self.login()
|
||||
|
||||
def test_page_edit(self):
|
||||
# Tests that the edit page loads
|
||||
|
|
@ -238,6 +241,7 @@ class TestPageEdit(TestCase):
|
|||
|
||||
# Should be redirected to explorer page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, 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)
|
||||
|
|
@ -255,6 +259,7 @@ class TestPageEdit(TestCase):
|
|||
|
||||
# Should be redirected to explorer page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, 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)
|
||||
|
|
@ -278,6 +283,7 @@ class TestPageEdit(TestCase):
|
|||
|
||||
# Should be redirected to explorer page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, 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)
|
||||
|
|
@ -306,7 +312,7 @@ class TestPageEdit(TestCase):
|
|||
self.assertContains(response, "I've been edited!")
|
||||
|
||||
|
||||
class TestPageDelete(TestCase):
|
||||
class TestPageDelete(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
# Find root page
|
||||
self.root_page = Page.objects.get(id=2)
|
||||
|
|
@ -318,7 +324,7 @@ class TestPageDelete(TestCase):
|
|||
self.root_page.add_child(instance=self.child_page)
|
||||
|
||||
# Login
|
||||
self.user = login(self.client)
|
||||
self.user = self.login()
|
||||
|
||||
def test_page_delete(self):
|
||||
response = self.client.get(reverse('wagtailadmin_pages_delete', args=(self.child_page.id, )))
|
||||
|
|
@ -344,15 +350,16 @@ class TestPageDelete(TestCase):
|
|||
|
||||
# Should be redirected to explorer page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, 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)
|
||||
|
||||
|
||||
class TestPageSearch(TestCase):
|
||||
class TestPageSearch(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
# Login
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params=None, **extra):
|
||||
return self.client.get(reverse('wagtailadmin_pages_search'), params or {}, **extra)
|
||||
|
|
@ -390,7 +397,7 @@ class TestPageSearch(TestCase):
|
|||
self.assertTrue(any([r.slug == 'root' for r in results]))
|
||||
|
||||
|
||||
class TestPageMove(TestCase):
|
||||
class TestPageMove(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
# Find root page
|
||||
self.root_page = Page.objects.get(id=2)
|
||||
|
|
@ -413,7 +420,7 @@ class TestPageMove(TestCase):
|
|||
self.section_a.add_child(instance=self.test_page)
|
||||
|
||||
# Login
|
||||
self.user = login(self.client)
|
||||
self.user = self.login()
|
||||
|
||||
def test_page_move(self):
|
||||
response = self.client.get(reverse('wagtailadmin_pages_move', args=(self.test_page.id, )))
|
||||
|
|
@ -442,18 +449,18 @@ class TestPageMove(TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TestPageUnpublish(TestCase):
|
||||
class TestPageUnpublish(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.user = login(self.client)
|
||||
self.user = self.login()
|
||||
|
||||
# Create a page to unpublish
|
||||
root_page = Page.objects.get(id=2)
|
||||
self.root_page = Page.objects.get(id=2)
|
||||
self.page = SimplePage(
|
||||
title="Hello world!",
|
||||
slug='hello-world',
|
||||
live=True,
|
||||
)
|
||||
root_page.add_child(instance=self.page)
|
||||
self.root_page.add_child(instance=self.page)
|
||||
|
||||
def test_unpublish_view(self):
|
||||
"""
|
||||
|
|
@ -502,14 +509,15 @@ class TestPageUnpublish(TestCase):
|
|||
'foo': "Must post something or the view won't see this as a POST request",
|
||||
})
|
||||
|
||||
# Check that the user was redirected
|
||||
# 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, )))
|
||||
|
||||
# Check that the page was unpublished
|
||||
self.assertFalse(SimplePage.objects.get(id=self.page.id).live)
|
||||
|
||||
|
||||
class TestApproveRejectModeration(TestCase):
|
||||
class TestApproveRejectModeration(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.submitter = User.objects.create_superuser(
|
||||
username='submitter',
|
||||
|
|
@ -517,7 +525,7 @@ class TestApproveRejectModeration(TestCase):
|
|||
password='password',
|
||||
)
|
||||
|
||||
self.user = login(self.client)
|
||||
self.user = self.login()
|
||||
|
||||
# Create a page and submit it for moderation
|
||||
root_page = Page.objects.get(id=2)
|
||||
|
|
@ -540,8 +548,9 @@ class TestApproveRejectModeration(TestCase):
|
|||
'foo': "Must post something or the view won't see this as a POST request",
|
||||
})
|
||||
|
||||
# Check that the user was redirected
|
||||
# Check that the user was redirected to the dashboard
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, reverse('wagtailadmin_home'))
|
||||
|
||||
# Page must be live
|
||||
self.assertTrue(Page.objects.get(id=self.page.id).live)
|
||||
|
|
@ -591,8 +600,9 @@ class TestApproveRejectModeration(TestCase):
|
|||
'foo': "Must post something or the view won't see this as a POST request",
|
||||
})
|
||||
|
||||
# Check that the user was redirected
|
||||
# Check that the user was redirected to the dashboard
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, reverse('wagtailadmin_home'))
|
||||
|
||||
# Page must not be live
|
||||
self.assertFalse(Page.objects.get(id=self.page.id).live)
|
||||
|
|
@ -645,11 +655,11 @@ class TestApproveRejectModeration(TestCase):
|
|||
self.assertContains(response, "Hello world!")
|
||||
|
||||
|
||||
class TestContentTypeUse(TestCase):
|
||||
class TestContentTypeUse(TestCase, WagtailTestUtils):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def setUp(self):
|
||||
self.user = login(self.client)
|
||||
self.user = self.login()
|
||||
|
||||
def test_content_type_use(self):
|
||||
# Get use of event page
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
from django.test import TestCase
|
||||
from wagtail.tests.models import SimplePage, EventPage
|
||||
from wagtail.tests.utils import login, unittest
|
||||
from wagtail.tests.utils import unittest, WagtailTestUtils
|
||||
from wagtail.wagtailcore.models import Page
|
||||
from wagtail.wagtailadmin.tasks import send_email_task
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core import mail
|
||||
|
||||
|
||||
class TestHome(TestCase):
|
||||
class TestHome(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
# Login
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def test_status_code(self):
|
||||
def test_simple(self):
|
||||
response = self.client.get(reverse('wagtailadmin_home'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TestEditorHooks(TestCase):
|
||||
class TestEditorHooks(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.homepage = Page.objects.get(id=2)
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def test_editor_css_and_js_hooks_on_add(self):
|
||||
response = self.client.get(reverse('wagtailadmin_pages_create', args=('tests', 'simplepage', self.homepage.id)))
|
||||
|
|
|
|||
|
|
@ -5,15 +5,8 @@ from wagtail.wagtailadmin.forms import LoginForm, PasswordResetForm
|
|||
from wagtail.wagtailadmin.views import account, chooser, home, pages, tags, userbar
|
||||
from wagtail.wagtailadmin import hooks
|
||||
|
||||
urlpatterns = [
|
||||
url(
|
||||
r'^login/$', 'django.contrib.auth.views.login', {
|
||||
'template_name': 'wagtailadmin/login.html',
|
||||
'authentication_form': LoginForm,
|
||||
'extra_context': {'show_password_reset': getattr(settings, 'WAGTAIL_PASSWORD_MANAGEMENT_ENABLED', True)},
|
||||
}, name='wagtailadmin_login'
|
||||
),
|
||||
|
||||
urlpatterns = [
|
||||
# Password reset
|
||||
url(
|
||||
r'^password_reset/$', 'django.contrib.auth.views.password_reset', {
|
||||
|
|
@ -81,6 +74,7 @@ urlpatterns += [
|
|||
|
||||
url(r'^tag-autocomplete/$', tags.autocomplete, name='wagtailadmin_tag_autocomplete'),
|
||||
|
||||
url(r'^login/$', account.login, name='wagtailadmin_login'),
|
||||
url(r'^account/$', account.account, name='wagtailadmin_account'),
|
||||
url(r'^account/change_password/$', account.change_password, name='wagtailadmin_account_change_password'),
|
||||
url(r'^logout/$', account.logout, name='wagtailadmin_logout'),
|
||||
|
|
@ -90,6 +84,13 @@ urlpatterns += [
|
|||
]
|
||||
|
||||
|
||||
# This is here to make sure that 'django.contrib.auth.views.login' is reversed correctly
|
||||
# It must be placed after 'wagtailadmin_login' to prevent this from being used
|
||||
urlpatterns += [
|
||||
url(r'^login/$', 'django.contrib.auth.views.login'),
|
||||
]
|
||||
|
||||
|
||||
# Import additional urlpatterns from any apps that define a register_admin_urls hook
|
||||
for fn in hooks.get_hooks('register_admin_urls'):
|
||||
urls = fn()
|
||||
|
|
|
|||
|
|
@ -3,8 +3,13 @@ from django.shortcuts import render, redirect
|
|||
from django.contrib import messages
|
||||
from django.contrib.auth.forms import SetPasswordForm
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.views import logout as auth_logout
|
||||
from django.contrib.auth.views import logout as auth_logout, login as auth_login
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
from django.views.decorators.cache import never_cache
|
||||
|
||||
from wagtail.wagtailadmin import forms
|
||||
|
||||
|
||||
@permission_required('wagtailadmin.access_admin')
|
||||
def account(request):
|
||||
|
|
@ -37,6 +42,21 @@ def change_password(request):
|
|||
})
|
||||
|
||||
|
||||
@sensitive_post_parameters()
|
||||
@never_cache
|
||||
def login(request):
|
||||
if request.user.is_authenticated():
|
||||
return redirect('wagtailadmin_home')
|
||||
else:
|
||||
return auth_login(request,
|
||||
template_name='wagtailadmin/login.html',
|
||||
authentication_form=forms.LoginForm,
|
||||
extra_context={
|
||||
'show_password_reset': getattr(settings, 'WAGTAIL_PASSWORD_MANAGEMENT_ENABLED', True),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def logout(request):
|
||||
response = auth_logout(request, next_page = 'wagtailadmin_login')
|
||||
|
||||
|
|
|
|||
|
|
@ -394,23 +394,23 @@ class Page(MP_Node, ClusterableModel, Indexed):
|
|||
|
||||
return revision.as_page_object()
|
||||
|
||||
def get_context(self, request):
|
||||
def get_context(self, request, *args, **kwargs):
|
||||
return {
|
||||
'self': self,
|
||||
'request': request,
|
||||
}
|
||||
|
||||
def get_template(self, request):
|
||||
def get_template(self, request, *args, **kwargs):
|
||||
if request.is_ajax():
|
||||
return self.ajax_template or self.template
|
||||
else:
|
||||
return self.template
|
||||
|
||||
def serve(self, request):
|
||||
def serve(self, request, *args, **kwargs):
|
||||
return TemplateResponse(
|
||||
request,
|
||||
self.get_template(request),
|
||||
self.get_context(request)
|
||||
self.get_template(request, *args, **kwargs),
|
||||
self.get_context(request, *args, **kwargs)
|
||||
)
|
||||
|
||||
def is_navigable(self):
|
||||
|
|
|
|||
|
|
@ -1,860 +0,0 @@
|
|||
from django.test import TestCase, Client
|
||||
from django.http import HttpRequest, Http404
|
||||
from django.core import management
|
||||
from StringIO import StringIO
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy
|
||||
from wagtail.tests.models import EventPage, EventIndex, SimplePage
|
||||
|
||||
|
||||
class TestRouting(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_find_site_for_request(self):
|
||||
default_site = Site.objects.get(is_default_site=True)
|
||||
events_page = Page.objects.get(url_path='/home/events/')
|
||||
events_site = Site.objects.create(hostname='events.example.com', root_page=events_page)
|
||||
|
||||
# requests without a Host: header should be directed to the default site
|
||||
request = HttpRequest()
|
||||
request.path = '/'
|
||||
self.assertEqual(Site.find_for_request(request), default_site)
|
||||
|
||||
# requests with a known Host: header should be directed to the specific site
|
||||
request = HttpRequest()
|
||||
request.path = '/'
|
||||
request.META['HTTP_HOST'] = 'events.example.com'
|
||||
self.assertEqual(Site.find_for_request(request), events_site)
|
||||
|
||||
# requests with an unrecognised Host: header should be directed to the default site
|
||||
request = HttpRequest()
|
||||
request.path = '/'
|
||||
request.META['HTTP_HOST'] = 'unknown.example.com'
|
||||
self.assertEqual(Site.find_for_request(request), default_site)
|
||||
|
||||
def test_urls(self):
|
||||
default_site = Site.objects.get(is_default_site=True)
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = Page.objects.get(url_path='/home/events/christmas/')
|
||||
|
||||
# Basic installation only has one site configured, so page.url will return local URLs
|
||||
self.assertEqual(homepage.full_url, 'http://localhost/')
|
||||
self.assertEqual(homepage.url, '/')
|
||||
self.assertEqual(homepage.relative_url(default_site), '/')
|
||||
|
||||
self.assertEqual(christmas_page.full_url, 'http://localhost/events/christmas/')
|
||||
self.assertEqual(christmas_page.url, '/events/christmas/')
|
||||
self.assertEqual(christmas_page.relative_url(default_site), '/events/christmas/')
|
||||
|
||||
def test_urls_with_multiple_sites(self):
|
||||
events_page = Page.objects.get(url_path='/home/events/')
|
||||
events_site = Site.objects.create(hostname='events.example.com', root_page=events_page)
|
||||
|
||||
default_site = Site.objects.get(is_default_site=True)
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = Page.objects.get(url_path='/home/events/christmas/')
|
||||
|
||||
# with multiple sites, page.url will return full URLs to ensure that
|
||||
# they work across sites
|
||||
self.assertEqual(homepage.full_url, 'http://localhost/')
|
||||
self.assertEqual(homepage.url, 'http://localhost/')
|
||||
self.assertEqual(homepage.relative_url(default_site), '/')
|
||||
self.assertEqual(homepage.relative_url(events_site), 'http://localhost/')
|
||||
|
||||
self.assertEqual(christmas_page.full_url, 'http://events.example.com/christmas/')
|
||||
self.assertEqual(christmas_page.url, 'http://events.example.com/christmas/')
|
||||
self.assertEqual(christmas_page.relative_url(default_site), 'http://events.example.com/christmas/')
|
||||
self.assertEqual(christmas_page.relative_url(events_site), '/christmas/')
|
||||
|
||||
def test_request_routing(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
|
||||
request = HttpRequest()
|
||||
request.path = '/events/christmas/'
|
||||
response = homepage.route(request, ['events', 'christmas'])
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.context_data['self'], christmas_page)
|
||||
used_template = response.resolve_template(response.template_name)
|
||||
self.assertEqual(used_template.name, 'tests/event_page.html')
|
||||
|
||||
def test_route_to_unknown_page_returns_404(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
|
||||
request = HttpRequest()
|
||||
request.path = '/events/quinquagesima/'
|
||||
with self.assertRaises(Http404):
|
||||
homepage.route(request, ['events', 'quinquagesima'])
|
||||
|
||||
def test_route_to_unpublished_page_returns_404(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
|
||||
request = HttpRequest()
|
||||
request.path = '/events/tentative-unpublished-event/'
|
||||
with self.assertRaises(Http404):
|
||||
homepage.route(request, ['events', 'tentative-unpublished-event'])
|
||||
|
||||
|
||||
class TestServeView(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def setUp(self):
|
||||
# Explicitly clear the cache of site root paths. Normally this would be kept
|
||||
# in sync by the Site.save logic, but this is bypassed when the database is
|
||||
# rolled back between tests using transactions.
|
||||
from django.core.cache import cache
|
||||
cache.delete('wagtail_site_root_paths')
|
||||
|
||||
def test_serve(self):
|
||||
response = self.client.get('/events/christmas/')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.templates[0].name, 'tests/event_page.html')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
self.assertEqual(response.context['self'], christmas_page)
|
||||
|
||||
self.assertContains(response, '<h1>Christmas</h1>')
|
||||
self.assertContains(response, '<h2>Event</h2>')
|
||||
|
||||
def test_serve_unknown_page_returns_404(self):
|
||||
response = self.client.get('/events/quinquagesima/')
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_serve_unpublished_page_returns_404(self):
|
||||
response = self.client.get('/events/tentative-unpublished-event/')
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_serve_with_multiple_sites(self):
|
||||
events_page = Page.objects.get(url_path='/home/events/')
|
||||
Site.objects.create(hostname='events.example.com', root_page=events_page)
|
||||
|
||||
response = self.client.get('/christmas/', HTTP_HOST='events.example.com')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.templates[0].name, 'tests/event_page.html')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
self.assertEqual(response.context['self'], christmas_page)
|
||||
|
||||
self.assertContains(response, '<h1>Christmas</h1>')
|
||||
self.assertContains(response, '<h2>Event</h2>')
|
||||
|
||||
# same request to the default host should return a 404
|
||||
c = Client()
|
||||
response = c.get('/christmas/', HTTP_HOST='localhost')
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_serve_with_custom_context(self):
|
||||
response = self.client.get('/events/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# should render the whole page
|
||||
self.assertContains(response, '<h1>Events</h1>')
|
||||
|
||||
# response should contain data from the custom 'events' context variable
|
||||
self.assertContains(response, '<a href="/events/christmas/">Christmas</a>')
|
||||
|
||||
def test_ajax_response(self):
|
||||
response = self.client.get('/events/', HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# should only render the content of includes/event_listing.html, not the whole page
|
||||
self.assertNotContains(response, '<h1>Events</h1>')
|
||||
self.assertContains(response, '<a href="/events/christmas/">Christmas</a>')
|
||||
|
||||
|
||||
class TestStaticSitePaths(TestCase):
|
||||
def setUp(self):
|
||||
self.root_page = Page.objects.get(id=1)
|
||||
|
||||
# For simple tests
|
||||
self.home_page = self.root_page.add_child(instance=SimplePage(title="Homepage", slug="home"))
|
||||
self.about_page = self.home_page.add_child(instance=SimplePage(title="About us", slug="about"))
|
||||
self.contact_page = self.home_page.add_child(instance=SimplePage(title="Contact", slug="contact"))
|
||||
|
||||
# For custom tests
|
||||
self.event_index = self.root_page.add_child(instance=EventIndex(title="Events", slug="events"))
|
||||
for i in range(20):
|
||||
self.event_index.add_child(instance=EventPage(title="Event " + str(i), slug="event" + str(i)))
|
||||
|
||||
def test_local_static_site_paths(self):
|
||||
paths = list(self.about_page.get_static_site_paths())
|
||||
|
||||
self.assertEqual(paths, ['/'])
|
||||
|
||||
def test_child_static_site_paths(self):
|
||||
paths = list(self.home_page.get_static_site_paths())
|
||||
|
||||
self.assertEqual(paths, ['/', '/about/', '/contact/'])
|
||||
|
||||
def test_custom_static_site_paths(self):
|
||||
paths = list(self.event_index.get_static_site_paths())
|
||||
|
||||
# Event index path
|
||||
expected_paths = ['/']
|
||||
|
||||
# One path for each page of results
|
||||
expected_paths.extend(['/' + str(i + 1) + '/' for i in range(5)])
|
||||
|
||||
# One path for each event page
|
||||
expected_paths.extend(['/event' + str(i) + '/' for i in range(20)])
|
||||
|
||||
paths.sort()
|
||||
expected_paths.sort()
|
||||
self.assertEqual(paths, expected_paths)
|
||||
|
||||
|
||||
class TestPageUrlTags(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_pageurl_tag(self):
|
||||
response = self.client.get('/events/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, '<a href="/events/christmas/">Christmas</a>')
|
||||
|
||||
def test_slugurl_tag(self):
|
||||
response = self.client.get('/events/christmas/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, '<a href="/events/">Back to events index</a>')
|
||||
|
||||
|
||||
class TestPagePermission(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_nonpublisher_page_permissions(self):
|
||||
event_editor = User.objects.get(username='eventeditor')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
|
||||
homepage_perms = homepage.permissions_for_user(event_editor)
|
||||
christmas_page_perms = christmas_page.permissions_for_user(event_editor)
|
||||
unpub_perms = unpublished_event_page.permissions_for_user(event_editor)
|
||||
someone_elses_event_perms = someone_elses_event_page.permissions_for_user(event_editor)
|
||||
|
||||
self.assertFalse(homepage_perms.can_add_subpage())
|
||||
self.assertTrue(christmas_page_perms.can_add_subpage())
|
||||
self.assertTrue(unpub_perms.can_add_subpage())
|
||||
self.assertTrue(someone_elses_event_perms.can_add_subpage())
|
||||
|
||||
self.assertFalse(homepage_perms.can_edit())
|
||||
self.assertTrue(christmas_page_perms.can_edit())
|
||||
self.assertTrue(unpub_perms.can_edit())
|
||||
self.assertFalse(someone_elses_event_perms.can_edit()) # basic 'add' permission doesn't allow editing pages owned by someone else
|
||||
|
||||
self.assertFalse(homepage_perms.can_delete())
|
||||
self.assertFalse(christmas_page_perms.can_delete()) # cannot delete because it is published
|
||||
self.assertTrue(unpub_perms.can_delete())
|
||||
self.assertFalse(someone_elses_event_perms.can_delete())
|
||||
|
||||
self.assertFalse(homepage_perms.can_publish())
|
||||
self.assertFalse(christmas_page_perms.can_publish())
|
||||
self.assertFalse(unpub_perms.can_publish())
|
||||
|
||||
self.assertFalse(homepage_perms.can_unpublish())
|
||||
self.assertFalse(christmas_page_perms.can_unpublish())
|
||||
self.assertFalse(unpub_perms.can_unpublish())
|
||||
|
||||
self.assertFalse(homepage_perms.can_publish_subpage())
|
||||
self.assertFalse(christmas_page_perms.can_publish_subpage())
|
||||
self.assertFalse(unpub_perms.can_publish_subpage())
|
||||
|
||||
self.assertFalse(homepage_perms.can_reorder_children())
|
||||
self.assertFalse(christmas_page_perms.can_reorder_children())
|
||||
self.assertFalse(unpub_perms.can_reorder_children())
|
||||
|
||||
self.assertFalse(homepage_perms.can_move())
|
||||
self.assertFalse(christmas_page_perms.can_move()) # cannot move because this would involve unpublishing from its current location
|
||||
self.assertTrue(unpub_perms.can_move())
|
||||
self.assertFalse(someone_elses_event_perms.can_move())
|
||||
|
||||
self.assertFalse(christmas_page_perms.can_move_to(unpublished_event_page)) # cannot move because this would involve unpublishing from its current location
|
||||
self.assertTrue(unpub_perms.can_move_to(christmas_page))
|
||||
self.assertFalse(unpub_perms.can_move_to(homepage)) # no permission to create pages at destination
|
||||
self.assertFalse(unpub_perms.can_move_to(unpublished_event_page)) # cannot make page a child of itself
|
||||
|
||||
|
||||
def test_publisher_page_permissions(self):
|
||||
event_moderator = User.objects.get(username='eventmoderator')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
|
||||
homepage_perms = homepage.permissions_for_user(event_moderator)
|
||||
christmas_page_perms = christmas_page.permissions_for_user(event_moderator)
|
||||
unpub_perms = unpublished_event_page.permissions_for_user(event_moderator)
|
||||
|
||||
self.assertFalse(homepage_perms.can_add_subpage())
|
||||
self.assertTrue(christmas_page_perms.can_add_subpage())
|
||||
self.assertTrue(unpub_perms.can_add_subpage())
|
||||
|
||||
self.assertFalse(homepage_perms.can_edit())
|
||||
self.assertTrue(christmas_page_perms.can_edit())
|
||||
self.assertTrue(unpub_perms.can_edit())
|
||||
|
||||
self.assertFalse(homepage_perms.can_delete())
|
||||
self.assertTrue(christmas_page_perms.can_delete()) # cannot delete because it is published
|
||||
self.assertTrue(unpub_perms.can_delete())
|
||||
|
||||
self.assertFalse(homepage_perms.can_publish())
|
||||
self.assertTrue(christmas_page_perms.can_publish())
|
||||
self.assertTrue(unpub_perms.can_publish())
|
||||
|
||||
self.assertFalse(homepage_perms.can_unpublish())
|
||||
self.assertTrue(christmas_page_perms.can_unpublish())
|
||||
self.assertFalse(unpub_perms.can_unpublish()) # cannot unpublish a page that isn't published
|
||||
|
||||
self.assertFalse(homepage_perms.can_publish_subpage())
|
||||
self.assertTrue(christmas_page_perms.can_publish_subpage())
|
||||
self.assertTrue(unpub_perms.can_publish_subpage())
|
||||
|
||||
self.assertFalse(homepage_perms.can_reorder_children())
|
||||
self.assertTrue(christmas_page_perms.can_reorder_children())
|
||||
self.assertTrue(unpub_perms.can_reorder_children())
|
||||
|
||||
self.assertFalse(homepage_perms.can_move())
|
||||
self.assertTrue(christmas_page_perms.can_move())
|
||||
self.assertTrue(unpub_perms.can_move())
|
||||
|
||||
self.assertTrue(christmas_page_perms.can_move_to(unpublished_event_page))
|
||||
self.assertTrue(unpub_perms.can_move_to(christmas_page))
|
||||
self.assertFalse(unpub_perms.can_move_to(homepage)) # no permission to create pages at destination
|
||||
self.assertFalse(unpub_perms.can_move_to(unpublished_event_page)) # cannot make page a child of itself
|
||||
|
||||
def test_inactive_user_has_no_permissions(self):
|
||||
user = User.objects.get(username='inactiveuser')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
|
||||
christmas_page_perms = christmas_page.permissions_for_user(user)
|
||||
unpub_perms = unpublished_event_page.permissions_for_user(user)
|
||||
|
||||
self.assertFalse(unpub_perms.can_add_subpage())
|
||||
self.assertFalse(unpub_perms.can_edit())
|
||||
self.assertFalse(unpub_perms.can_delete())
|
||||
self.assertFalse(unpub_perms.can_publish())
|
||||
self.assertFalse(christmas_page_perms.can_unpublish())
|
||||
self.assertFalse(unpub_perms.can_publish_subpage())
|
||||
self.assertFalse(unpub_perms.can_reorder_children())
|
||||
self.assertFalse(unpub_perms.can_move())
|
||||
self.assertFalse(unpub_perms.can_move_to(christmas_page))
|
||||
|
||||
def test_superuser_has_full_permissions(self):
|
||||
user = User.objects.get(username='superuser')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
root = Page.objects.get(url_path='/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
|
||||
homepage_perms = homepage.permissions_for_user(user)
|
||||
root_perms = root.permissions_for_user(user)
|
||||
unpub_perms = unpublished_event_page.permissions_for_user(user)
|
||||
|
||||
self.assertTrue(homepage_perms.can_add_subpage())
|
||||
self.assertTrue(root_perms.can_add_subpage())
|
||||
|
||||
self.assertTrue(homepage_perms.can_edit())
|
||||
self.assertFalse(root_perms.can_edit()) # root is not a real editable page, even to superusers
|
||||
|
||||
self.assertTrue(homepage_perms.can_delete())
|
||||
self.assertFalse(root_perms.can_delete())
|
||||
|
||||
self.assertTrue(homepage_perms.can_publish())
|
||||
self.assertFalse(root_perms.can_publish())
|
||||
|
||||
self.assertTrue(homepage_perms.can_unpublish())
|
||||
self.assertFalse(root_perms.can_unpublish())
|
||||
self.assertFalse(unpub_perms.can_unpublish())
|
||||
|
||||
self.assertTrue(homepage_perms.can_publish_subpage())
|
||||
self.assertTrue(root_perms.can_publish_subpage())
|
||||
|
||||
self.assertTrue(homepage_perms.can_reorder_children())
|
||||
self.assertTrue(root_perms.can_reorder_children())
|
||||
|
||||
self.assertTrue(homepage_perms.can_move())
|
||||
self.assertFalse(root_perms.can_move())
|
||||
|
||||
self.assertTrue(homepage_perms.can_move_to(root))
|
||||
self.assertFalse(homepage_perms.can_move_to(unpublished_event_page))
|
||||
|
||||
def test_editable_pages_for_user_with_add_permission(self):
|
||||
event_editor = User.objects.get(username='eventeditor')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
|
||||
editable_pages = UserPagePermissionsProxy(event_editor).editable_pages()
|
||||
|
||||
self.assertFalse(editable_pages.filter(id=homepage.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=christmas_page.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=unpublished_event_page.id).exists())
|
||||
self.assertFalse(editable_pages.filter(id=someone_elses_event_page.id).exists())
|
||||
|
||||
def test_editable_pages_for_user_with_edit_permission(self):
|
||||
event_moderator = User.objects.get(username='eventmoderator')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
|
||||
editable_pages = UserPagePermissionsProxy(event_moderator).editable_pages()
|
||||
|
||||
self.assertFalse(editable_pages.filter(id=homepage.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=christmas_page.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=unpublished_event_page.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=someone_elses_event_page.id).exists())
|
||||
|
||||
def test_editable_pages_for_inactive_user(self):
|
||||
user = User.objects.get(username='inactiveuser')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
|
||||
editable_pages = UserPagePermissionsProxy(user).editable_pages()
|
||||
|
||||
self.assertFalse(editable_pages.filter(id=homepage.id).exists())
|
||||
self.assertFalse(editable_pages.filter(id=christmas_page.id).exists())
|
||||
self.assertFalse(editable_pages.filter(id=unpublished_event_page.id).exists())
|
||||
self.assertFalse(editable_pages.filter(id=someone_elses_event_page.id).exists())
|
||||
|
||||
def test_editable_pages_for_superuser(self):
|
||||
user = User.objects.get(username='superuser')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
|
||||
editable_pages = UserPagePermissionsProxy(user).editable_pages()
|
||||
|
||||
self.assertTrue(editable_pages.filter(id=homepage.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=christmas_page.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=unpublished_event_page.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=someone_elses_event_page.id).exists())
|
||||
|
||||
|
||||
class TestPageQuerySet(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_live(self):
|
||||
pages = Page.objects.live()
|
||||
|
||||
# All pages must be live
|
||||
for page in pages:
|
||||
self.assertTrue(page.live)
|
||||
|
||||
# Check that the homepage is in the results
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
self.assertTrue(pages.filter(id=homepage.id).exists())
|
||||
|
||||
def test_not_live(self):
|
||||
pages = Page.objects.not_live()
|
||||
|
||||
# All pages must not be live
|
||||
for page in pages:
|
||||
self.assertFalse(page.live)
|
||||
|
||||
# Check that "someone elses event" is in the results
|
||||
event = Page.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
self.assertTrue(pages.filter(id=event.id).exists())
|
||||
|
||||
def test_page(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
pages = Page.objects.page(homepage)
|
||||
|
||||
# Should only select the homepage
|
||||
self.assertEqual(pages.count(), 1)
|
||||
self.assertEqual(pages.first(), homepage)
|
||||
|
||||
def test_not_page(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
pages = Page.objects.not_page(homepage)
|
||||
|
||||
# Should select everything except for the homepage
|
||||
self.assertEqual(pages.count(), Page.objects.all().count() - 1)
|
||||
for page in pages:
|
||||
self.assertNotEqual(page, homepage)
|
||||
|
||||
def test_descendant_of(self):
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.descendant_of(events_index)
|
||||
|
||||
# Check that all pages descend from events index
|
||||
for page in pages:
|
||||
self.assertTrue(page.get_ancestors().filter(id=events_index.id).exists())
|
||||
|
||||
def test_descendant_of_inclusive(self):
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.descendant_of(events_index, inclusive=True)
|
||||
|
||||
# Check that all pages descend from events index, includes event index
|
||||
for page in pages:
|
||||
self.assertTrue(page == events_index or page.get_ancestors().filter(id=events_index.id).exists())
|
||||
|
||||
# Check that event index was included
|
||||
self.assertTrue(pages.filter(id=events_index.id).exists())
|
||||
|
||||
def test_not_descendant_of(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.not_descendant_of(events_index)
|
||||
|
||||
# Check that no pages descend from events_index
|
||||
for page in pages:
|
||||
self.assertFalse(page.get_ancestors().filter(id=events_index.id).exists())
|
||||
|
||||
# As this is not inclusive, events index should be in the results
|
||||
self.assertTrue(pages.filter(id=events_index.id).exists())
|
||||
|
||||
def test_not_descendant_of_inclusive(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.not_descendant_of(events_index, inclusive=True)
|
||||
|
||||
# Check that all pages descend from homepage but not events index
|
||||
for page in pages:
|
||||
self.assertFalse(page.get_ancestors().filter(id=events_index.id).exists())
|
||||
|
||||
# As this is inclusive, events index should not be in the results
|
||||
self.assertFalse(pages.filter(id=events_index.id).exists())
|
||||
|
||||
def test_child_of(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
pages = Page.objects.child_of(homepage)
|
||||
|
||||
# Check that all pages are children of homepage
|
||||
for page in pages:
|
||||
self.assertEqual(page.get_parent(), homepage)
|
||||
|
||||
def test_not_child_of(self):
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.not_child_of(events_index)
|
||||
|
||||
# Check that all pages are not children of events_index
|
||||
for page in pages:
|
||||
self.assertNotEqual(page.get_parent(), events_index)
|
||||
|
||||
def test_ancestor_of(self):
|
||||
root_page = Page.objects.get(id=1)
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.ancestor_of(events_index)
|
||||
|
||||
self.assertEqual(pages.count(), 2)
|
||||
self.assertEqual(pages[0], root_page)
|
||||
self.assertEqual(pages[1], homepage)
|
||||
|
||||
def test_ancestor_of_inclusive(self):
|
||||
root_page = Page.objects.get(id=1)
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.ancestor_of(events_index, inclusive=True)
|
||||
|
||||
self.assertEqual(pages.count(), 3)
|
||||
self.assertEqual(pages[0], root_page)
|
||||
self.assertEqual(pages[1], homepage)
|
||||
self.assertEqual(pages[2], events_index)
|
||||
|
||||
def test_not_ancestor_of(self):
|
||||
root_page = Page.objects.get(id=1)
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.not_ancestor_of(events_index)
|
||||
|
||||
# Test that none of the ancestors are in pages
|
||||
for page in pages:
|
||||
self.assertNotEqual(page, root_page)
|
||||
self.assertNotEqual(page, homepage)
|
||||
|
||||
# Test that events index is in pages
|
||||
self.assertTrue(pages.filter(id=events_index.id).exists())
|
||||
|
||||
def test_not_ancestor_of_inclusive(self):
|
||||
root_page = Page.objects.get(id=1)
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.not_ancestor_of(events_index, inclusive=True)
|
||||
|
||||
# Test that none of the ancestors or the events_index are in pages
|
||||
for page in pages:
|
||||
self.assertNotEqual(page, root_page)
|
||||
self.assertNotEqual(page, homepage)
|
||||
self.assertNotEqual(page, events_index)
|
||||
|
||||
def test_parent_of(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.parent_of(events_index)
|
||||
|
||||
# Pages must only contain homepage
|
||||
self.assertEqual(pages.count(), 1)
|
||||
self.assertEqual(pages[0], homepage)
|
||||
|
||||
def test_not_parent_of(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.not_parent_of(events_index)
|
||||
|
||||
# Pages must not contain homepage
|
||||
for page in pages:
|
||||
self.assertNotEqual(page, homepage)
|
||||
|
||||
# Test that events index is in pages
|
||||
self.assertTrue(pages.filter(id=events_index.id).exists())
|
||||
|
||||
def test_sibling_of(self):
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
event = Page.objects.get(url_path='/home/events/christmas/')
|
||||
pages = Page.objects.sibling_of(event)
|
||||
|
||||
# Check that all pages are children of events_index
|
||||
for page in pages:
|
||||
self.assertEqual(page.get_parent(), events_index)
|
||||
|
||||
# Check that the event is not included
|
||||
self.assertFalse(pages.filter(id=event.id).exists())
|
||||
|
||||
def test_sibling_of_inclusive(self):
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
event = Page.objects.get(url_path='/home/events/christmas/')
|
||||
pages = Page.objects.sibling_of(event, inclusive=True)
|
||||
|
||||
# Check that all pages are children of events_index
|
||||
for page in pages:
|
||||
self.assertEqual(page.get_parent(), events_index)
|
||||
|
||||
# Check that the event is included
|
||||
self.assertTrue(pages.filter(id=event.id).exists())
|
||||
|
||||
def test_not_sibling_of(self):
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
event = Page.objects.get(url_path='/home/events/christmas/')
|
||||
pages = Page.objects.not_sibling_of(event)
|
||||
|
||||
# Check that all pages are not children of events_index
|
||||
for page in pages:
|
||||
if page != event:
|
||||
self.assertNotEqual(page.get_parent(), events_index)
|
||||
|
||||
# Check that the event is included
|
||||
self.assertTrue(pages.filter(id=event.id).exists())
|
||||
|
||||
# Test that events index is in pages
|
||||
self.assertTrue(pages.filter(id=events_index.id).exists())
|
||||
|
||||
def test_not_sibling_of_inclusive(self):
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
event = Page.objects.get(url_path='/home/events/christmas/')
|
||||
pages = Page.objects.not_sibling_of(event, inclusive=True)
|
||||
|
||||
# Check that all pages are not children of events_index
|
||||
for page in pages:
|
||||
self.assertNotEqual(page.get_parent(), events_index)
|
||||
|
||||
# Check that the event is not included
|
||||
self.assertFalse(pages.filter(id=event.id).exists())
|
||||
|
||||
# Test that events index is in pages
|
||||
self.assertTrue(pages.filter(id=events_index.id).exists())
|
||||
|
||||
def test_type(self):
|
||||
pages = Page.objects.type(EventPage)
|
||||
|
||||
# Check that all objects are EventPages
|
||||
for page in pages:
|
||||
self.assertIsInstance(page.specific, EventPage)
|
||||
|
||||
# Check that "someone elses event" is in the results
|
||||
event = Page.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
self.assertTrue(pages.filter(id=event.id).exists())
|
||||
|
||||
def test_not_type(self):
|
||||
pages = Page.objects.not_type(EventPage)
|
||||
|
||||
# Check that no objects are EventPages
|
||||
for page in pages:
|
||||
self.assertNotIsInstance(page.specific, EventPage)
|
||||
|
||||
# Check that the homepage is in the results
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
self.assertTrue(pages.filter(id=homepage.id).exists())
|
||||
|
||||
|
||||
class TestMovePage(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_move_page(self):
|
||||
about_us_page = SimplePage.objects.get(url_path='/home/about-us/')
|
||||
events_index = EventIndex.objects.get(url_path='/home/events/')
|
||||
|
||||
events_index.move(about_us_page, pos='last-child')
|
||||
|
||||
# re-fetch events index to confirm that db fields have been updated
|
||||
events_index = EventIndex.objects.get(id=events_index.id)
|
||||
self.assertEqual(events_index.url_path, '/home/about-us/events/')
|
||||
self.assertEqual(events_index.depth, 4)
|
||||
self.assertEqual(events_index.get_parent().id, about_us_page.id)
|
||||
|
||||
# children of events_index should also have been updated
|
||||
christmas = events_index.get_children().get(slug='christmas')
|
||||
self.assertEqual(christmas.depth, 5)
|
||||
self.assertEqual(christmas.url_path, '/home/about-us/events/christmas/')
|
||||
|
||||
|
||||
class TestIssue7(TestCase):
|
||||
"""
|
||||
This tests for an issue where if a site root page was moved, all the page
|
||||
urls in that site would change to None.
|
||||
|
||||
The issue was caused by the 'wagtail_site_root_paths' cache variable not being
|
||||
cleared when a site root page was moved. Which left all the child pages
|
||||
thinking that they are no longer in the site and return None as their url.
|
||||
|
||||
Fix: d6cce69a397d08d5ee81a8cbc1977ab2c9db2682
|
||||
Discussion: https://github.com/torchbox/wagtail/issues/7
|
||||
"""
|
||||
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_issue7(self):
|
||||
# Get homepage, root page and site
|
||||
root_page = Page.objects.get(id=1)
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
default_site = Site.objects.get(is_default_site=True)
|
||||
|
||||
# Create a new homepage under current homepage
|
||||
new_homepage = SimplePage(title="New Homepage", slug="new-homepage")
|
||||
homepage.add_child(instance=new_homepage)
|
||||
|
||||
# Set new homepage as the site root page
|
||||
default_site.root_page = new_homepage
|
||||
default_site.save()
|
||||
|
||||
# Warm up the cache by getting the url
|
||||
_ = homepage.url
|
||||
|
||||
# Move new homepage to root
|
||||
new_homepage.move(root_page, pos='last-child')
|
||||
|
||||
# Get fresh instance of new_homepage
|
||||
new_homepage = Page.objects.get(id=new_homepage.id)
|
||||
|
||||
# Check url
|
||||
self.assertEqual(new_homepage.url, '/')
|
||||
|
||||
|
||||
class TestIssue157(TestCase):
|
||||
"""
|
||||
This tests for an issue where if a site root pages slug was changed, all the page
|
||||
urls in that site would change to None.
|
||||
|
||||
The issue was caused by the 'wagtail_site_root_paths' cache variable not being
|
||||
cleared when a site root page was changed. Which left all the child pages
|
||||
thinking that they are no longer in the site and return None as their url.
|
||||
|
||||
Fix: d6cce69a397d08d5ee81a8cbc1977ab2c9db2682
|
||||
Discussion: https://github.com/torchbox/wagtail/issues/157
|
||||
"""
|
||||
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_issue157(self):
|
||||
# Get homepage
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
|
||||
# Warm up the cache by getting the url
|
||||
_ = homepage.url
|
||||
|
||||
# Change homepage title and slug
|
||||
homepage.title = "New home"
|
||||
homepage.slug = "new-home"
|
||||
homepage.save()
|
||||
|
||||
# Get fresh instance of homepage
|
||||
homepage = Page.objects.get(id=homepage.id)
|
||||
|
||||
# Check url
|
||||
self.assertEqual(homepage.url, '/')
|
||||
|
||||
|
||||
class TestFixTreeCommand(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def run_command(self):
|
||||
management.call_command('fixtree', interactive=False, stdout=StringIO())
|
||||
|
||||
def test_fixes_numchild(self):
|
||||
# Get homepage and save old value
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
old_numchild = homepage.numchild
|
||||
|
||||
# Break it
|
||||
homepage.numchild = 12345
|
||||
homepage.save()
|
||||
|
||||
# Check that its broken
|
||||
self.assertEqual(Page.objects.get(url_path='/home/').numchild, 12345)
|
||||
|
||||
# Call command
|
||||
self.run_command()
|
||||
|
||||
# Check if its fixed
|
||||
self.assertEqual(Page.objects.get(url_path='/home/').numchild, old_numchild)
|
||||
|
||||
def test_fixes_depth(self):
|
||||
# Get homepage and save old value
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
old_depth = homepage.depth
|
||||
|
||||
# Break it
|
||||
homepage.depth = 12345
|
||||
homepage.save()
|
||||
|
||||
# Check that its broken
|
||||
self.assertEqual(Page.objects.get(url_path='/home/').depth, 12345)
|
||||
|
||||
# Call command
|
||||
self.run_command()
|
||||
|
||||
# Check if its fixed
|
||||
self.assertEqual(Page.objects.get(url_path='/home/').depth, old_depth)
|
||||
|
||||
|
||||
class TestMovePagesCommand(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def run_command(self, from_, to):
|
||||
management.call_command('move_pages', str(from_), str(to), interactive=False, stdout=StringIO())
|
||||
|
||||
def test_move_pages(self):
|
||||
# Get pages
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
about_us = Page.objects.get(url_path='/home/about-us/')
|
||||
page_ids = events_index.get_children().values_list('id', flat=True)
|
||||
|
||||
# Move all events into "about us"
|
||||
self.run_command(events_index.id, about_us.id)
|
||||
|
||||
# Check that all pages moved
|
||||
for page_id in page_ids:
|
||||
self.assertEqual(Page.objects.get(id=page_id).get_parent(), about_us)
|
||||
|
||||
|
||||
class TestReplaceTextCommand(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def run_command(self, from_text, to_text):
|
||||
management.call_command('replace_text', from_text, to_text, interactive=False, stdout=StringIO())
|
||||
|
||||
def test_replace_text(self):
|
||||
# Check that the christmas page is definitely about christmas
|
||||
self.assertEqual(Page.objects.get(url_path='/home/events/christmas/').title, "Christmas")
|
||||
|
||||
# Make it about easter
|
||||
self.run_command("Christmas", "Easter")
|
||||
|
||||
# Check that its now about easter
|
||||
self.assertEqual(Page.objects.get(url_path='/home/events/christmas/').title, "Easter")
|
||||
0
wagtail/wagtailcore/tests/__init__.py
Normal file
0
wagtail/wagtailcore/tests/__init__.py
Normal file
89
wagtail/wagtailcore/tests/test_management_commands.py
Normal file
89
wagtail/wagtailcore/tests/test_management_commands.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
from StringIO import StringIO
|
||||
|
||||
from django.test import TestCase, Client
|
||||
from django.http import HttpRequest, Http404
|
||||
from django.core import management
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy
|
||||
from wagtail.tests.models import EventPage, EventIndex, SimplePage
|
||||
|
||||
|
||||
class TestFixTreeCommand(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def run_command(self):
|
||||
management.call_command('fixtree', interactive=False, stdout=StringIO())
|
||||
|
||||
def test_fixes_numchild(self):
|
||||
# Get homepage and save old value
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
old_numchild = homepage.numchild
|
||||
|
||||
# Break it
|
||||
homepage.numchild = 12345
|
||||
homepage.save()
|
||||
|
||||
# Check that its broken
|
||||
self.assertEqual(Page.objects.get(url_path='/home/').numchild, 12345)
|
||||
|
||||
# Call command
|
||||
self.run_command()
|
||||
|
||||
# Check if its fixed
|
||||
self.assertEqual(Page.objects.get(url_path='/home/').numchild, old_numchild)
|
||||
|
||||
def test_fixes_depth(self):
|
||||
# Get homepage and save old value
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
old_depth = homepage.depth
|
||||
|
||||
# Break it
|
||||
homepage.depth = 12345
|
||||
homepage.save()
|
||||
|
||||
# Check that its broken
|
||||
self.assertEqual(Page.objects.get(url_path='/home/').depth, 12345)
|
||||
|
||||
# Call command
|
||||
self.run_command()
|
||||
|
||||
# Check if its fixed
|
||||
self.assertEqual(Page.objects.get(url_path='/home/').depth, old_depth)
|
||||
|
||||
|
||||
class TestMovePagesCommand(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def run_command(self, from_, to):
|
||||
management.call_command('move_pages', str(from_), str(to), interactive=False, stdout=StringIO())
|
||||
|
||||
def test_move_pages(self):
|
||||
# Get pages
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
about_us = Page.objects.get(url_path='/home/about-us/')
|
||||
page_ids = events_index.get_children().values_list('id', flat=True)
|
||||
|
||||
# Move all events into "about us"
|
||||
self.run_command(events_index.id, about_us.id)
|
||||
|
||||
# Check that all pages moved
|
||||
for page_id in page_ids:
|
||||
self.assertEqual(Page.objects.get(id=page_id).get_parent(), about_us)
|
||||
|
||||
|
||||
class TestReplaceTextCommand(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def run_command(self, from_text, to_text):
|
||||
management.call_command('replace_text', from_text, to_text, interactive=False, stdout=StringIO())
|
||||
|
||||
def test_replace_text(self):
|
||||
# Check that the christmas page is definitely about christmas
|
||||
self.assertEqual(Page.objects.get(url_path='/home/events/christmas/').title, "Christmas")
|
||||
|
||||
# Make it about easter
|
||||
self.run_command("Christmas", "Easter")
|
||||
|
||||
# Check that its now about easter
|
||||
self.assertEqual(Page.objects.get(url_path='/home/events/christmas/').title, "Easter")
|
||||
226
wagtail/wagtailcore/tests/test_page_model.py
Normal file
226
wagtail/wagtailcore/tests/test_page_model.py
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
from StringIO import StringIO
|
||||
|
||||
from django.test import TestCase, Client
|
||||
from django.http import HttpRequest, Http404
|
||||
from django.core import management
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy
|
||||
from wagtail.tests.models import EventPage, EventIndex, SimplePage
|
||||
|
||||
|
||||
class TestRouting(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_find_site_for_request(self):
|
||||
default_site = Site.objects.get(is_default_site=True)
|
||||
events_page = Page.objects.get(url_path='/home/events/')
|
||||
events_site = Site.objects.create(hostname='events.example.com', root_page=events_page)
|
||||
|
||||
# requests without a Host: header should be directed to the default site
|
||||
request = HttpRequest()
|
||||
request.path = '/'
|
||||
self.assertEqual(Site.find_for_request(request), default_site)
|
||||
|
||||
# requests with a known Host: header should be directed to the specific site
|
||||
request = HttpRequest()
|
||||
request.path = '/'
|
||||
request.META['HTTP_HOST'] = 'events.example.com'
|
||||
self.assertEqual(Site.find_for_request(request), events_site)
|
||||
|
||||
# requests with an unrecognised Host: header should be directed to the default site
|
||||
request = HttpRequest()
|
||||
request.path = '/'
|
||||
request.META['HTTP_HOST'] = 'unknown.example.com'
|
||||
self.assertEqual(Site.find_for_request(request), default_site)
|
||||
|
||||
def test_urls(self):
|
||||
default_site = Site.objects.get(is_default_site=True)
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = Page.objects.get(url_path='/home/events/christmas/')
|
||||
|
||||
# Basic installation only has one site configured, so page.url will return local URLs
|
||||
self.assertEqual(homepage.full_url, 'http://localhost/')
|
||||
self.assertEqual(homepage.url, '/')
|
||||
self.assertEqual(homepage.relative_url(default_site), '/')
|
||||
|
||||
self.assertEqual(christmas_page.full_url, 'http://localhost/events/christmas/')
|
||||
self.assertEqual(christmas_page.url, '/events/christmas/')
|
||||
self.assertEqual(christmas_page.relative_url(default_site), '/events/christmas/')
|
||||
|
||||
def test_urls_with_multiple_sites(self):
|
||||
events_page = Page.objects.get(url_path='/home/events/')
|
||||
events_site = Site.objects.create(hostname='events.example.com', root_page=events_page)
|
||||
|
||||
default_site = Site.objects.get(is_default_site=True)
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = Page.objects.get(url_path='/home/events/christmas/')
|
||||
|
||||
# with multiple sites, page.url will return full URLs to ensure that
|
||||
# they work across sites
|
||||
self.assertEqual(homepage.full_url, 'http://localhost/')
|
||||
self.assertEqual(homepage.url, 'http://localhost/')
|
||||
self.assertEqual(homepage.relative_url(default_site), '/')
|
||||
self.assertEqual(homepage.relative_url(events_site), 'http://localhost/')
|
||||
|
||||
self.assertEqual(christmas_page.full_url, 'http://events.example.com/christmas/')
|
||||
self.assertEqual(christmas_page.url, 'http://events.example.com/christmas/')
|
||||
self.assertEqual(christmas_page.relative_url(default_site), 'http://events.example.com/christmas/')
|
||||
self.assertEqual(christmas_page.relative_url(events_site), '/christmas/')
|
||||
|
||||
def test_request_routing(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
|
||||
request = HttpRequest()
|
||||
request.path = '/events/christmas/'
|
||||
response = homepage.route(request, ['events', 'christmas'])
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.context_data['self'], christmas_page)
|
||||
used_template = response.resolve_template(response.template_name)
|
||||
self.assertEqual(used_template.name, 'tests/event_page.html')
|
||||
|
||||
def test_route_to_unknown_page_returns_404(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
|
||||
request = HttpRequest()
|
||||
request.path = '/events/quinquagesima/'
|
||||
with self.assertRaises(Http404):
|
||||
homepage.route(request, ['events', 'quinquagesima'])
|
||||
|
||||
def test_route_to_unpublished_page_returns_404(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
|
||||
request = HttpRequest()
|
||||
request.path = '/events/tentative-unpublished-event/'
|
||||
with self.assertRaises(Http404):
|
||||
homepage.route(request, ['events', 'tentative-unpublished-event'])
|
||||
|
||||
|
||||
class TestServeView(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def setUp(self):
|
||||
# Explicitly clear the cache of site root paths. Normally this would be kept
|
||||
# in sync by the Site.save logic, but this is bypassed when the database is
|
||||
# rolled back between tests using transactions.
|
||||
from django.core.cache import cache
|
||||
cache.delete('wagtail_site_root_paths')
|
||||
|
||||
def test_serve(self):
|
||||
response = self.client.get('/events/christmas/')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.templates[0].name, 'tests/event_page.html')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
self.assertEqual(response.context['self'], christmas_page)
|
||||
|
||||
self.assertContains(response, '<h1>Christmas</h1>')
|
||||
self.assertContains(response, '<h2>Event</h2>')
|
||||
|
||||
def test_serve_unknown_page_returns_404(self):
|
||||
response = self.client.get('/events/quinquagesima/')
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_serve_unpublished_page_returns_404(self):
|
||||
response = self.client.get('/events/tentative-unpublished-event/')
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_serve_with_multiple_sites(self):
|
||||
events_page = Page.objects.get(url_path='/home/events/')
|
||||
Site.objects.create(hostname='events.example.com', root_page=events_page)
|
||||
|
||||
response = self.client.get('/christmas/', HTTP_HOST='events.example.com')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.templates[0].name, 'tests/event_page.html')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
self.assertEqual(response.context['self'], christmas_page)
|
||||
|
||||
self.assertContains(response, '<h1>Christmas</h1>')
|
||||
self.assertContains(response, '<h2>Event</h2>')
|
||||
|
||||
# same request to the default host should return a 404
|
||||
c = Client()
|
||||
response = c.get('/christmas/', HTTP_HOST='localhost')
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_serve_with_custom_context(self):
|
||||
response = self.client.get('/events/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# should render the whole page
|
||||
self.assertContains(response, '<h1>Events</h1>')
|
||||
|
||||
# response should contain data from the custom 'events' context variable
|
||||
self.assertContains(response, '<a href="/events/christmas/">Christmas</a>')
|
||||
|
||||
def test_ajax_response(self):
|
||||
response = self.client.get('/events/', HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# should only render the content of includes/event_listing.html, not the whole page
|
||||
self.assertNotContains(response, '<h1>Events</h1>')
|
||||
self.assertContains(response, '<a href="/events/christmas/">Christmas</a>')
|
||||
|
||||
|
||||
class TestStaticSitePaths(TestCase):
|
||||
def setUp(self):
|
||||
self.root_page = Page.objects.get(id=1)
|
||||
|
||||
# For simple tests
|
||||
self.home_page = self.root_page.add_child(instance=SimplePage(title="Homepage", slug="home"))
|
||||
self.about_page = self.home_page.add_child(instance=SimplePage(title="About us", slug="about"))
|
||||
self.contact_page = self.home_page.add_child(instance=SimplePage(title="Contact", slug="contact"))
|
||||
|
||||
# For custom tests
|
||||
self.event_index = self.root_page.add_child(instance=EventIndex(title="Events", slug="events"))
|
||||
for i in range(20):
|
||||
self.event_index.add_child(instance=EventPage(title="Event " + str(i), slug="event" + str(i)))
|
||||
|
||||
def test_local_static_site_paths(self):
|
||||
paths = list(self.about_page.get_static_site_paths())
|
||||
|
||||
self.assertEqual(paths, ['/'])
|
||||
|
||||
def test_child_static_site_paths(self):
|
||||
paths = list(self.home_page.get_static_site_paths())
|
||||
|
||||
self.assertEqual(paths, ['/', '/about/', '/contact/'])
|
||||
|
||||
def test_custom_static_site_paths(self):
|
||||
paths = list(self.event_index.get_static_site_paths())
|
||||
|
||||
# Event index path
|
||||
expected_paths = ['/']
|
||||
|
||||
# One path for each page of results
|
||||
expected_paths.extend(['/' + str(i + 1) + '/' for i in range(5)])
|
||||
|
||||
# One path for each event page
|
||||
expected_paths.extend(['/event' + str(i) + '/' for i in range(20)])
|
||||
|
||||
paths.sort()
|
||||
expected_paths.sort()
|
||||
self.assertEqual(paths, expected_paths)
|
||||
|
||||
|
||||
class TestMovePage(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_move_page(self):
|
||||
about_us_page = SimplePage.objects.get(url_path='/home/about-us/')
|
||||
events_index = EventIndex.objects.get(url_path='/home/events/')
|
||||
|
||||
events_index.move(about_us_page, pos='last-child')
|
||||
|
||||
# re-fetch events index to confirm that db fields have been updated
|
||||
events_index = EventIndex.objects.get(id=events_index.id)
|
||||
self.assertEqual(events_index.url_path, '/home/about-us/events/')
|
||||
self.assertEqual(events_index.depth, 4)
|
||||
self.assertEqual(events_index.get_parent().id, about_us_page.id)
|
||||
|
||||
# children of events_index should also have been updated
|
||||
christmas = events_index.get_children().get(slug='christmas')
|
||||
self.assertEqual(christmas.depth, 5)
|
||||
self.assertEqual(christmas.url_path, '/home/about-us/events/christmas/')
|
||||
226
wagtail/wagtailcore/tests/test_page_permissions.py
Normal file
226
wagtail/wagtailcore/tests/test_page_permissions.py
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
from StringIO import StringIO
|
||||
|
||||
from django.test import TestCase, Client
|
||||
from django.http import HttpRequest, Http404
|
||||
from django.core import management
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy
|
||||
from wagtail.tests.models import EventPage, EventIndex, SimplePage
|
||||
|
||||
|
||||
class TestPagePermission(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_nonpublisher_page_permissions(self):
|
||||
event_editor = User.objects.get(username='eventeditor')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
|
||||
homepage_perms = homepage.permissions_for_user(event_editor)
|
||||
christmas_page_perms = christmas_page.permissions_for_user(event_editor)
|
||||
unpub_perms = unpublished_event_page.permissions_for_user(event_editor)
|
||||
someone_elses_event_perms = someone_elses_event_page.permissions_for_user(event_editor)
|
||||
|
||||
self.assertFalse(homepage_perms.can_add_subpage())
|
||||
self.assertTrue(christmas_page_perms.can_add_subpage())
|
||||
self.assertTrue(unpub_perms.can_add_subpage())
|
||||
self.assertTrue(someone_elses_event_perms.can_add_subpage())
|
||||
|
||||
self.assertFalse(homepage_perms.can_edit())
|
||||
self.assertTrue(christmas_page_perms.can_edit())
|
||||
self.assertTrue(unpub_perms.can_edit())
|
||||
self.assertFalse(someone_elses_event_perms.can_edit()) # basic 'add' permission doesn't allow editing pages owned by someone else
|
||||
|
||||
self.assertFalse(homepage_perms.can_delete())
|
||||
self.assertFalse(christmas_page_perms.can_delete()) # cannot delete because it is published
|
||||
self.assertTrue(unpub_perms.can_delete())
|
||||
self.assertFalse(someone_elses_event_perms.can_delete())
|
||||
|
||||
self.assertFalse(homepage_perms.can_publish())
|
||||
self.assertFalse(christmas_page_perms.can_publish())
|
||||
self.assertFalse(unpub_perms.can_publish())
|
||||
|
||||
self.assertFalse(homepage_perms.can_unpublish())
|
||||
self.assertFalse(christmas_page_perms.can_unpublish())
|
||||
self.assertFalse(unpub_perms.can_unpublish())
|
||||
|
||||
self.assertFalse(homepage_perms.can_publish_subpage())
|
||||
self.assertFalse(christmas_page_perms.can_publish_subpage())
|
||||
self.assertFalse(unpub_perms.can_publish_subpage())
|
||||
|
||||
self.assertFalse(homepage_perms.can_reorder_children())
|
||||
self.assertFalse(christmas_page_perms.can_reorder_children())
|
||||
self.assertFalse(unpub_perms.can_reorder_children())
|
||||
|
||||
self.assertFalse(homepage_perms.can_move())
|
||||
self.assertFalse(christmas_page_perms.can_move()) # cannot move because this would involve unpublishing from its current location
|
||||
self.assertTrue(unpub_perms.can_move())
|
||||
self.assertFalse(someone_elses_event_perms.can_move())
|
||||
|
||||
self.assertFalse(christmas_page_perms.can_move_to(unpublished_event_page)) # cannot move because this would involve unpublishing from its current location
|
||||
self.assertTrue(unpub_perms.can_move_to(christmas_page))
|
||||
self.assertFalse(unpub_perms.can_move_to(homepage)) # no permission to create pages at destination
|
||||
self.assertFalse(unpub_perms.can_move_to(unpublished_event_page)) # cannot make page a child of itself
|
||||
|
||||
|
||||
def test_publisher_page_permissions(self):
|
||||
event_moderator = User.objects.get(username='eventmoderator')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
|
||||
homepage_perms = homepage.permissions_for_user(event_moderator)
|
||||
christmas_page_perms = christmas_page.permissions_for_user(event_moderator)
|
||||
unpub_perms = unpublished_event_page.permissions_for_user(event_moderator)
|
||||
|
||||
self.assertFalse(homepage_perms.can_add_subpage())
|
||||
self.assertTrue(christmas_page_perms.can_add_subpage())
|
||||
self.assertTrue(unpub_perms.can_add_subpage())
|
||||
|
||||
self.assertFalse(homepage_perms.can_edit())
|
||||
self.assertTrue(christmas_page_perms.can_edit())
|
||||
self.assertTrue(unpub_perms.can_edit())
|
||||
|
||||
self.assertFalse(homepage_perms.can_delete())
|
||||
self.assertTrue(christmas_page_perms.can_delete()) # cannot delete because it is published
|
||||
self.assertTrue(unpub_perms.can_delete())
|
||||
|
||||
self.assertFalse(homepage_perms.can_publish())
|
||||
self.assertTrue(christmas_page_perms.can_publish())
|
||||
self.assertTrue(unpub_perms.can_publish())
|
||||
|
||||
self.assertFalse(homepage_perms.can_unpublish())
|
||||
self.assertTrue(christmas_page_perms.can_unpublish())
|
||||
self.assertFalse(unpub_perms.can_unpublish()) # cannot unpublish a page that isn't published
|
||||
|
||||
self.assertFalse(homepage_perms.can_publish_subpage())
|
||||
self.assertTrue(christmas_page_perms.can_publish_subpage())
|
||||
self.assertTrue(unpub_perms.can_publish_subpage())
|
||||
|
||||
self.assertFalse(homepage_perms.can_reorder_children())
|
||||
self.assertTrue(christmas_page_perms.can_reorder_children())
|
||||
self.assertTrue(unpub_perms.can_reorder_children())
|
||||
|
||||
self.assertFalse(homepage_perms.can_move())
|
||||
self.assertTrue(christmas_page_perms.can_move())
|
||||
self.assertTrue(unpub_perms.can_move())
|
||||
|
||||
self.assertTrue(christmas_page_perms.can_move_to(unpublished_event_page))
|
||||
self.assertTrue(unpub_perms.can_move_to(christmas_page))
|
||||
self.assertFalse(unpub_perms.can_move_to(homepage)) # no permission to create pages at destination
|
||||
self.assertFalse(unpub_perms.can_move_to(unpublished_event_page)) # cannot make page a child of itself
|
||||
|
||||
def test_inactive_user_has_no_permissions(self):
|
||||
user = User.objects.get(username='inactiveuser')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
|
||||
christmas_page_perms = christmas_page.permissions_for_user(user)
|
||||
unpub_perms = unpublished_event_page.permissions_for_user(user)
|
||||
|
||||
self.assertFalse(unpub_perms.can_add_subpage())
|
||||
self.assertFalse(unpub_perms.can_edit())
|
||||
self.assertFalse(unpub_perms.can_delete())
|
||||
self.assertFalse(unpub_perms.can_publish())
|
||||
self.assertFalse(christmas_page_perms.can_unpublish())
|
||||
self.assertFalse(unpub_perms.can_publish_subpage())
|
||||
self.assertFalse(unpub_perms.can_reorder_children())
|
||||
self.assertFalse(unpub_perms.can_move())
|
||||
self.assertFalse(unpub_perms.can_move_to(christmas_page))
|
||||
|
||||
def test_superuser_has_full_permissions(self):
|
||||
user = User.objects.get(username='superuser')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
root = Page.objects.get(url_path='/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
|
||||
homepage_perms = homepage.permissions_for_user(user)
|
||||
root_perms = root.permissions_for_user(user)
|
||||
unpub_perms = unpublished_event_page.permissions_for_user(user)
|
||||
|
||||
self.assertTrue(homepage_perms.can_add_subpage())
|
||||
self.assertTrue(root_perms.can_add_subpage())
|
||||
|
||||
self.assertTrue(homepage_perms.can_edit())
|
||||
self.assertFalse(root_perms.can_edit()) # root is not a real editable page, even to superusers
|
||||
|
||||
self.assertTrue(homepage_perms.can_delete())
|
||||
self.assertFalse(root_perms.can_delete())
|
||||
|
||||
self.assertTrue(homepage_perms.can_publish())
|
||||
self.assertFalse(root_perms.can_publish())
|
||||
|
||||
self.assertTrue(homepage_perms.can_unpublish())
|
||||
self.assertFalse(root_perms.can_unpublish())
|
||||
self.assertFalse(unpub_perms.can_unpublish())
|
||||
|
||||
self.assertTrue(homepage_perms.can_publish_subpage())
|
||||
self.assertTrue(root_perms.can_publish_subpage())
|
||||
|
||||
self.assertTrue(homepage_perms.can_reorder_children())
|
||||
self.assertTrue(root_perms.can_reorder_children())
|
||||
|
||||
self.assertTrue(homepage_perms.can_move())
|
||||
self.assertFalse(root_perms.can_move())
|
||||
|
||||
self.assertTrue(homepage_perms.can_move_to(root))
|
||||
self.assertFalse(homepage_perms.can_move_to(unpublished_event_page))
|
||||
|
||||
def test_editable_pages_for_user_with_add_permission(self):
|
||||
event_editor = User.objects.get(username='eventeditor')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
|
||||
editable_pages = UserPagePermissionsProxy(event_editor).editable_pages()
|
||||
|
||||
self.assertFalse(editable_pages.filter(id=homepage.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=christmas_page.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=unpublished_event_page.id).exists())
|
||||
self.assertFalse(editable_pages.filter(id=someone_elses_event_page.id).exists())
|
||||
|
||||
def test_editable_pages_for_user_with_edit_permission(self):
|
||||
event_moderator = User.objects.get(username='eventmoderator')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
|
||||
editable_pages = UserPagePermissionsProxy(event_moderator).editable_pages()
|
||||
|
||||
self.assertFalse(editable_pages.filter(id=homepage.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=christmas_page.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=unpublished_event_page.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=someone_elses_event_page.id).exists())
|
||||
|
||||
def test_editable_pages_for_inactive_user(self):
|
||||
user = User.objects.get(username='inactiveuser')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
|
||||
editable_pages = UserPagePermissionsProxy(user).editable_pages()
|
||||
|
||||
self.assertFalse(editable_pages.filter(id=homepage.id).exists())
|
||||
self.assertFalse(editable_pages.filter(id=christmas_page.id).exists())
|
||||
self.assertFalse(editable_pages.filter(id=unpublished_event_page.id).exists())
|
||||
self.assertFalse(editable_pages.filter(id=someone_elses_event_page.id).exists())
|
||||
|
||||
def test_editable_pages_for_superuser(self):
|
||||
user = User.objects.get(username='superuser')
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
|
||||
unpublished_event_page = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/')
|
||||
someone_elses_event_page = EventPage.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
|
||||
editable_pages = UserPagePermissionsProxy(user).editable_pages()
|
||||
|
||||
self.assertTrue(editable_pages.filter(id=homepage.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=christmas_page.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=unpublished_event_page.id).exists())
|
||||
self.assertTrue(editable_pages.filter(id=someone_elses_event_page.id).exists())
|
||||
256
wagtail/wagtailcore/tests/test_page_queryset.py
Normal file
256
wagtail/wagtailcore/tests/test_page_queryset.py
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
from StringIO import StringIO
|
||||
|
||||
from django.test import TestCase, Client
|
||||
from django.http import HttpRequest, Http404
|
||||
from django.core import management
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy
|
||||
from wagtail.tests.models import EventPage, EventIndex, SimplePage
|
||||
|
||||
|
||||
class TestPageQuerySet(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_live(self):
|
||||
pages = Page.objects.live()
|
||||
|
||||
# All pages must be live
|
||||
for page in pages:
|
||||
self.assertTrue(page.live)
|
||||
|
||||
# Check that the homepage is in the results
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
self.assertTrue(pages.filter(id=homepage.id).exists())
|
||||
|
||||
def test_not_live(self):
|
||||
pages = Page.objects.not_live()
|
||||
|
||||
# All pages must not be live
|
||||
for page in pages:
|
||||
self.assertFalse(page.live)
|
||||
|
||||
# Check that "someone elses event" is in the results
|
||||
event = Page.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
self.assertTrue(pages.filter(id=event.id).exists())
|
||||
|
||||
def test_page(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
pages = Page.objects.page(homepage)
|
||||
|
||||
# Should only select the homepage
|
||||
self.assertEqual(pages.count(), 1)
|
||||
self.assertEqual(pages.first(), homepage)
|
||||
|
||||
def test_not_page(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
pages = Page.objects.not_page(homepage)
|
||||
|
||||
# Should select everything except for the homepage
|
||||
self.assertEqual(pages.count(), Page.objects.all().count() - 1)
|
||||
for page in pages:
|
||||
self.assertNotEqual(page, homepage)
|
||||
|
||||
def test_descendant_of(self):
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.descendant_of(events_index)
|
||||
|
||||
# Check that all pages descend from events index
|
||||
for page in pages:
|
||||
self.assertTrue(page.get_ancestors().filter(id=events_index.id).exists())
|
||||
|
||||
def test_descendant_of_inclusive(self):
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.descendant_of(events_index, inclusive=True)
|
||||
|
||||
# Check that all pages descend from events index, includes event index
|
||||
for page in pages:
|
||||
self.assertTrue(page == events_index or page.get_ancestors().filter(id=events_index.id).exists())
|
||||
|
||||
# Check that event index was included
|
||||
self.assertTrue(pages.filter(id=events_index.id).exists())
|
||||
|
||||
def test_not_descendant_of(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.not_descendant_of(events_index)
|
||||
|
||||
# Check that no pages descend from events_index
|
||||
for page in pages:
|
||||
self.assertFalse(page.get_ancestors().filter(id=events_index.id).exists())
|
||||
|
||||
# As this is not inclusive, events index should be in the results
|
||||
self.assertTrue(pages.filter(id=events_index.id).exists())
|
||||
|
||||
def test_not_descendant_of_inclusive(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.not_descendant_of(events_index, inclusive=True)
|
||||
|
||||
# Check that all pages descend from homepage but not events index
|
||||
for page in pages:
|
||||
self.assertFalse(page.get_ancestors().filter(id=events_index.id).exists())
|
||||
|
||||
# As this is inclusive, events index should not be in the results
|
||||
self.assertFalse(pages.filter(id=events_index.id).exists())
|
||||
|
||||
def test_child_of(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
pages = Page.objects.child_of(homepage)
|
||||
|
||||
# Check that all pages are children of homepage
|
||||
for page in pages:
|
||||
self.assertEqual(page.get_parent(), homepage)
|
||||
|
||||
def test_not_child_of(self):
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.not_child_of(events_index)
|
||||
|
||||
# Check that all pages are not children of events_index
|
||||
for page in pages:
|
||||
self.assertNotEqual(page.get_parent(), events_index)
|
||||
|
||||
def test_ancestor_of(self):
|
||||
root_page = Page.objects.get(id=1)
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.ancestor_of(events_index)
|
||||
|
||||
self.assertEqual(pages.count(), 2)
|
||||
self.assertEqual(pages[0], root_page)
|
||||
self.assertEqual(pages[1], homepage)
|
||||
|
||||
def test_ancestor_of_inclusive(self):
|
||||
root_page = Page.objects.get(id=1)
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.ancestor_of(events_index, inclusive=True)
|
||||
|
||||
self.assertEqual(pages.count(), 3)
|
||||
self.assertEqual(pages[0], root_page)
|
||||
self.assertEqual(pages[1], homepage)
|
||||
self.assertEqual(pages[2], events_index)
|
||||
|
||||
def test_not_ancestor_of(self):
|
||||
root_page = Page.objects.get(id=1)
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.not_ancestor_of(events_index)
|
||||
|
||||
# Test that none of the ancestors are in pages
|
||||
for page in pages:
|
||||
self.assertNotEqual(page, root_page)
|
||||
self.assertNotEqual(page, homepage)
|
||||
|
||||
# Test that events index is in pages
|
||||
self.assertTrue(pages.filter(id=events_index.id).exists())
|
||||
|
||||
def test_not_ancestor_of_inclusive(self):
|
||||
root_page = Page.objects.get(id=1)
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.not_ancestor_of(events_index, inclusive=True)
|
||||
|
||||
# Test that none of the ancestors or the events_index are in pages
|
||||
for page in pages:
|
||||
self.assertNotEqual(page, root_page)
|
||||
self.assertNotEqual(page, homepage)
|
||||
self.assertNotEqual(page, events_index)
|
||||
|
||||
def test_parent_of(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.parent_of(events_index)
|
||||
|
||||
# Pages must only contain homepage
|
||||
self.assertEqual(pages.count(), 1)
|
||||
self.assertEqual(pages[0], homepage)
|
||||
|
||||
def test_not_parent_of(self):
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
pages = Page.objects.not_parent_of(events_index)
|
||||
|
||||
# Pages must not contain homepage
|
||||
for page in pages:
|
||||
self.assertNotEqual(page, homepage)
|
||||
|
||||
# Test that events index is in pages
|
||||
self.assertTrue(pages.filter(id=events_index.id).exists())
|
||||
|
||||
def test_sibling_of(self):
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
event = Page.objects.get(url_path='/home/events/christmas/')
|
||||
pages = Page.objects.sibling_of(event)
|
||||
|
||||
# Check that all pages are children of events_index
|
||||
for page in pages:
|
||||
self.assertEqual(page.get_parent(), events_index)
|
||||
|
||||
# Check that the event is not included
|
||||
self.assertFalse(pages.filter(id=event.id).exists())
|
||||
|
||||
def test_sibling_of_inclusive(self):
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
event = Page.objects.get(url_path='/home/events/christmas/')
|
||||
pages = Page.objects.sibling_of(event, inclusive=True)
|
||||
|
||||
# Check that all pages are children of events_index
|
||||
for page in pages:
|
||||
self.assertEqual(page.get_parent(), events_index)
|
||||
|
||||
# Check that the event is included
|
||||
self.assertTrue(pages.filter(id=event.id).exists())
|
||||
|
||||
def test_not_sibling_of(self):
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
event = Page.objects.get(url_path='/home/events/christmas/')
|
||||
pages = Page.objects.not_sibling_of(event)
|
||||
|
||||
# Check that all pages are not children of events_index
|
||||
for page in pages:
|
||||
if page != event:
|
||||
self.assertNotEqual(page.get_parent(), events_index)
|
||||
|
||||
# Check that the event is included
|
||||
self.assertTrue(pages.filter(id=event.id).exists())
|
||||
|
||||
# Test that events index is in pages
|
||||
self.assertTrue(pages.filter(id=events_index.id).exists())
|
||||
|
||||
def test_not_sibling_of_inclusive(self):
|
||||
events_index = Page.objects.get(url_path='/home/events/')
|
||||
event = Page.objects.get(url_path='/home/events/christmas/')
|
||||
pages = Page.objects.not_sibling_of(event, inclusive=True)
|
||||
|
||||
# Check that all pages are not children of events_index
|
||||
for page in pages:
|
||||
self.assertNotEqual(page.get_parent(), events_index)
|
||||
|
||||
# Check that the event is not included
|
||||
self.assertFalse(pages.filter(id=event.id).exists())
|
||||
|
||||
# Test that events index is in pages
|
||||
self.assertTrue(pages.filter(id=events_index.id).exists())
|
||||
|
||||
def test_type(self):
|
||||
pages = Page.objects.type(EventPage)
|
||||
|
||||
# Check that all objects are EventPages
|
||||
for page in pages:
|
||||
self.assertIsInstance(page.specific, EventPage)
|
||||
|
||||
# Check that "someone elses event" is in the results
|
||||
event = Page.objects.get(url_path='/home/events/someone-elses-event/')
|
||||
self.assertTrue(pages.filter(id=event.id).exists())
|
||||
|
||||
def test_not_type(self):
|
||||
pages = Page.objects.not_type(EventPage)
|
||||
|
||||
# Check that no objects are EventPages
|
||||
for page in pages:
|
||||
self.assertNotIsInstance(page.specific, EventPage)
|
||||
|
||||
# Check that the homepage is in the results
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
self.assertTrue(pages.filter(id=homepage.id).exists())
|
||||
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>')
|
||||
99
wagtail/wagtailcore/tests/tests.py
Normal file
99
wagtail/wagtailcore/tests/tests.py
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
from StringIO import StringIO
|
||||
|
||||
from django.test import TestCase, Client
|
||||
from django.http import HttpRequest, Http404
|
||||
from django.core import management
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy
|
||||
from wagtail.tests.models import EventPage, EventIndex, SimplePage
|
||||
|
||||
|
||||
class TestPageUrlTags(TestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_pageurl_tag(self):
|
||||
response = self.client.get('/events/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, '<a href="/events/christmas/">Christmas</a>')
|
||||
|
||||
def test_slugurl_tag(self):
|
||||
response = self.client.get('/events/christmas/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, '<a href="/events/">Back to events index</a>')
|
||||
|
||||
|
||||
class TestIssue7(TestCase):
|
||||
"""
|
||||
This tests for an issue where if a site root page was moved, all the page
|
||||
urls in that site would change to None.
|
||||
|
||||
The issue was caused by the 'wagtail_site_root_paths' cache variable not being
|
||||
cleared when a site root page was moved. Which left all the child pages
|
||||
thinking that they are no longer in the site and return None as their url.
|
||||
|
||||
Fix: d6cce69a397d08d5ee81a8cbc1977ab2c9db2682
|
||||
Discussion: https://github.com/torchbox/wagtail/issues/7
|
||||
"""
|
||||
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_issue7(self):
|
||||
# Get homepage, root page and site
|
||||
root_page = Page.objects.get(id=1)
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
default_site = Site.objects.get(is_default_site=True)
|
||||
|
||||
# Create a new homepage under current homepage
|
||||
new_homepage = SimplePage(title="New Homepage", slug="new-homepage")
|
||||
homepage.add_child(instance=new_homepage)
|
||||
|
||||
# Set new homepage as the site root page
|
||||
default_site.root_page = new_homepage
|
||||
default_site.save()
|
||||
|
||||
# Warm up the cache by getting the url
|
||||
_ = homepage.url
|
||||
|
||||
# Move new homepage to root
|
||||
new_homepage.move(root_page, pos='last-child')
|
||||
|
||||
# Get fresh instance of new_homepage
|
||||
new_homepage = Page.objects.get(id=new_homepage.id)
|
||||
|
||||
# Check url
|
||||
self.assertEqual(new_homepage.url, '/')
|
||||
|
||||
|
||||
class TestIssue157(TestCase):
|
||||
"""
|
||||
This tests for an issue where if a site root pages slug was changed, all the page
|
||||
urls in that site would change to None.
|
||||
|
||||
The issue was caused by the 'wagtail_site_root_paths' cache variable not being
|
||||
cleared when a site root page was changed. Which left all the child pages
|
||||
thinking that they are no longer in the site and return None as their url.
|
||||
|
||||
Fix: d6cce69a397d08d5ee81a8cbc1977ab2c9db2682
|
||||
Discussion: https://github.com/torchbox/wagtail/issues/157
|
||||
"""
|
||||
|
||||
fixtures = ['test.json']
|
||||
|
||||
def test_issue157(self):
|
||||
# Get homepage
|
||||
homepage = Page.objects.get(url_path='/home/')
|
||||
|
||||
# Warm up the cache by getting the url
|
||||
_ = homepage.url
|
||||
|
||||
# Change homepage title and slug
|
||||
homepage.title = "New home"
|
||||
homepage.slug = "new-home"
|
||||
homepage.save()
|
||||
|
||||
# Get fresh instance of homepage
|
||||
homepage = Page.objects.get(id=homepage.id)
|
||||
|
||||
# Check url
|
||||
self.assertEqual(homepage.url, '/')
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from django.test import TestCase
|
||||
from wagtail.wagtaildocs import models
|
||||
from wagtail.tests.utils import login
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
from django.contrib.auth.models import User, Group, Permission
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.files.base import ContentFile
|
||||
|
|
@ -39,15 +39,17 @@ class TestDocumentPermissions(TestCase):
|
|||
## ===== ADMIN VIEWS =====
|
||||
|
||||
|
||||
class TestDocumentIndexView(TestCase):
|
||||
class TestDocumentIndexView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtaildocs_index'), params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtaildocs/documents/index.html')
|
||||
|
||||
def test_search(self):
|
||||
response = self.get({'q': "Hello"})
|
||||
|
|
@ -67,20 +69,24 @@ class TestDocumentIndexView(TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TestDocumentAddView(TestCase):
|
||||
class TestDocumentAddView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtaildocs_add_document'), params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtaildocs/documents/add.html')
|
||||
|
||||
# TODO: Test posting
|
||||
|
||||
|
||||
class TestDocumentEditView(TestCase):
|
||||
class TestDocumentEditView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
# Create a document to edit
|
||||
self.document = models.Document.objects.create(title="Test document")
|
||||
|
|
@ -88,13 +94,17 @@ class TestDocumentEditView(TestCase):
|
|||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtaildocs_edit_document', args=(self.document.id,)), params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtaildocs/documents/edit.html')
|
||||
|
||||
# TODO: Test posting
|
||||
|
||||
|
||||
class TestDocumentDeleteView(TestCase):
|
||||
class TestDocumentDeleteView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
# Create a document to delete
|
||||
self.document = models.Document.objects.create(title="Test document")
|
||||
|
|
@ -102,19 +112,26 @@ class TestDocumentDeleteView(TestCase):
|
|||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtaildocs_delete_document', args=(self.document.id,)), params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtaildocs/documents/confirm_delete.html')
|
||||
|
||||
# TODO: Test posting
|
||||
|
||||
|
||||
class TestDocumentChooserView(TestCase):
|
||||
class TestDocumentChooserView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtaildocs_chooser'), params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
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"})
|
||||
|
|
@ -128,9 +145,9 @@ class TestDocumentChooserView(TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TestDocumentChooserChosenView(TestCase):
|
||||
class TestDocumentChooserChosenView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
# Create a document to choose
|
||||
self.document = models.Document.objects.create(title="Test document")
|
||||
|
|
@ -138,19 +155,28 @@ class TestDocumentChooserChosenView(TestCase):
|
|||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtaildocs_document_chosen', args=(self.document.id,)), params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtaildocs/chooser/document_chosen.js')
|
||||
|
||||
# TODO: Test posting
|
||||
|
||||
|
||||
class TestDocumentChooserUploadView(TestCase):
|
||||
class TestDocumentChooserUploadView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtaildocs_chooser_upload'), params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
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
|
||||
|
||||
|
||||
class TestDocumentFilenameProperties(TestCase):
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import json
|
|||
|
||||
|
||||
class EmbedNotFoundException(Exception): pass
|
||||
|
||||
class EmbedlyException(Exception): pass
|
||||
class AccessDeniedEmbedlyException(EmbedlyException): pass
|
||||
|
||||
|
|
@ -52,7 +51,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:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,27 @@
|
|||
from mock import patch
|
||||
import urllib2
|
||||
|
||||
try:
|
||||
import embedly
|
||||
no_embedly = False
|
||||
except ImportError:
|
||||
no_embedly = True
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.client import Client
|
||||
from wagtail.tests.utils import login
|
||||
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
from wagtail.tests.utils import unittest
|
||||
|
||||
from wagtail.wagtailembeds import get_embed
|
||||
from wagtail.wagtailembeds.embeds import (
|
||||
EmbedNotFoundException,
|
||||
EmbedlyException,
|
||||
AccessDeniedEmbedlyException,
|
||||
)
|
||||
from wagtail.wagtailembeds.embeds import embedly as wagtail_embedly
|
||||
from wagtail.wagtailembeds.embeds import oembed as wagtail_oembed
|
||||
|
||||
|
||||
|
||||
class TestEmbeds(TestCase):
|
||||
|
|
@ -63,13 +83,168 @@ class TestEmbeds(TestCase):
|
|||
self.assertEqual(embed.width, None)
|
||||
|
||||
|
||||
class TestChooser(TestCase):
|
||||
class TestChooser(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
# login
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def test_chooser(self):
|
||||
r = self.client.get('/admin/embeds/chooser/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# TODO: Test submitting
|
||||
|
||||
class TestEmbedly(TestCase):
|
||||
@unittest.skipIf(no_embedly, "Embedly is not installed")
|
||||
def test_embedly_oembed_called_with_correct_arguments(self):
|
||||
with patch('embedly.Embedly.oembed') as oembed:
|
||||
oembed.return_value = {'type': 'photo',
|
||||
'url': 'http://www.example.com'}
|
||||
|
||||
wagtail_embedly('http://www.example.com', key='foo')
|
||||
oembed.assert_called_with('http://www.example.com', better=False)
|
||||
|
||||
wagtail_embedly('http://www.example.com', max_width=100, key='foo')
|
||||
oembed.assert_called_with('http://www.example.com', maxwidth=100, better=False)
|
||||
|
||||
@unittest.skipIf(no_embedly, "Embedly is not installed")
|
||||
def test_embedly_401(self):
|
||||
with patch('embedly.Embedly.oembed') as oembed:
|
||||
oembed.return_value = {'type': 'photo',
|
||||
'url': 'http://www.example.com',
|
||||
'error': True,
|
||||
'error_code': 401}
|
||||
self.assertRaises(AccessDeniedEmbedlyException,
|
||||
wagtail_embedly, 'http://www.example.com', key='foo')
|
||||
|
||||
@unittest.skipIf(no_embedly, "Embedly is not installed")
|
||||
def test_embedly_403(self):
|
||||
with patch('embedly.Embedly.oembed') as oembed:
|
||||
oembed.return_value = {'type': 'photo',
|
||||
'url': 'http://www.example.com',
|
||||
'error': True,
|
||||
'error_code': 403}
|
||||
self.assertRaises(AccessDeniedEmbedlyException,
|
||||
wagtail_embedly, 'http://www.example.com', key='foo')
|
||||
|
||||
@unittest.skipIf(no_embedly, "Embedly is not installed")
|
||||
def test_embedly_404(self):
|
||||
with patch('embedly.Embedly.oembed') as oembed:
|
||||
oembed.return_value = {'type': 'photo',
|
||||
'url': 'http://www.example.com',
|
||||
'error': True,
|
||||
'error_code': 404}
|
||||
self.assertRaises(EmbedNotFoundException,
|
||||
wagtail_embedly, 'http://www.example.com', key='foo')
|
||||
|
||||
@unittest.skipIf(no_embedly, "Embedly is not installed")
|
||||
def test_embedly_other_error(self):
|
||||
with patch('embedly.Embedly.oembed') as oembed:
|
||||
oembed.return_value = {'type': 'photo',
|
||||
'url': 'http://www.example.com',
|
||||
'error': True,
|
||||
'error_code': 999}
|
||||
self.assertRaises(EmbedlyException, wagtail_embedly,
|
||||
'http://www.example.com', key='foo')
|
||||
|
||||
@unittest.skipIf(no_embedly, "Embedly is not installed")
|
||||
def test_embedly_html_conversion(self):
|
||||
with patch('embedly.Embedly.oembed') as oembed:
|
||||
oembed.return_value = {'type': 'photo',
|
||||
'url': 'http://www.example.com'}
|
||||
result = wagtail_embedly('http://www.example.com', key='foo')
|
||||
self.assertEqual(result['html'], '<img src="http://www.example.com" />')
|
||||
|
||||
oembed.return_value = {'type': 'something else',
|
||||
'html': '<foo>bar</foo>'}
|
||||
result = wagtail_embedly('http://www.example.com', key='foo')
|
||||
self.assertEqual(result['html'], '<foo>bar</foo>')
|
||||
|
||||
@unittest.skipIf(no_embedly, "Embedly is not installed")
|
||||
def test_embedly_return_value(self):
|
||||
with patch('embedly.Embedly.oembed') as oembed:
|
||||
oembed.return_value = {'type': 'something else',
|
||||
'html': '<foo>bar</foo>'}
|
||||
result = wagtail_embedly('http://www.example.com', key='foo')
|
||||
self.assertEqual(result, {
|
||||
'title': '',
|
||||
'author_name': '',
|
||||
'provider_name': '',
|
||||
'type': 'something else',
|
||||
'thumbnail_url': None,
|
||||
'width': None,
|
||||
'height': None,
|
||||
'html': '<foo>bar</foo>'})
|
||||
|
||||
oembed.return_value = {'type': 'something else',
|
||||
'author_name': 'Alice',
|
||||
'provider_name': 'Bob',
|
||||
'title': 'foo',
|
||||
'thumbnail_url': 'http://www.example.com',
|
||||
'width': 100,
|
||||
'height': 100,
|
||||
'html': '<foo>bar</foo>'}
|
||||
result = wagtail_embedly('http://www.example.com', key='foo')
|
||||
self.assertEqual(result, {'type': 'something else',
|
||||
'author_name': 'Alice',
|
||||
'provider_name': 'Bob',
|
||||
'title': 'foo',
|
||||
'thumbnail_url': 'http://www.example.com',
|
||||
'width': 100,
|
||||
'height': 100,
|
||||
'html': '<foo>bar</foo>'})
|
||||
|
||||
|
||||
class TestOembed(TestCase):
|
||||
def setUp(self):
|
||||
class DummyResponse(object):
|
||||
def read(self):
|
||||
return "foo"
|
||||
self.dummy_response = DummyResponse()
|
||||
|
||||
def test_oembed_invalid_provider(self):
|
||||
self.assertRaises(EmbedNotFoundException, wagtail_oembed, "foo")
|
||||
|
||||
def test_oembed_invalid_request(self):
|
||||
config = {'side_effect': urllib2.URLError('foo')}
|
||||
with patch.object(urllib2, 'urlopen', **config) as urlopen:
|
||||
self.assertRaises(EmbedNotFoundException, wagtail_oembed,
|
||||
"http://www.youtube.com/watch/")
|
||||
|
||||
@patch('urllib2.urlopen')
|
||||
@patch('json.loads')
|
||||
def test_oembed_photo_request(self, loads, urlopen) :
|
||||
urlopen.return_value = self.dummy_response
|
||||
loads.return_value = {'type': 'photo',
|
||||
'url': 'http://www.example.com'}
|
||||
result = wagtail_oembed("http://www.youtube.com/watch/")
|
||||
self.assertEqual(result['type'], 'photo')
|
||||
self.assertEqual(result['html'], '<img src="http://www.example.com" />')
|
||||
loads.assert_called_with("foo")
|
||||
|
||||
@patch('urllib2.urlopen')
|
||||
@patch('json.loads')
|
||||
def test_oembed_return_values(self, loads, urlopen):
|
||||
urlopen.return_value = self.dummy_response
|
||||
loads.return_value = {
|
||||
'type': 'something',
|
||||
'url': 'http://www.example.com',
|
||||
'title': 'test_title',
|
||||
'author_name': 'test_author',
|
||||
'provider_name': 'test_provider_name',
|
||||
'thumbnail_url': 'test_thumbail_url',
|
||||
'width': 'test_width',
|
||||
'height': 'test_height',
|
||||
'html': 'test_html'
|
||||
}
|
||||
result = wagtail_oembed("http://www.youtube.com/watch/")
|
||||
self.assertEqual(result, {
|
||||
'type': 'something',
|
||||
'title': 'test_title',
|
||||
'author_name': 'test_author',
|
||||
'provider_name': 'test_provider_name',
|
||||
'thumbnail_url': 'test_thumbail_url',
|
||||
'width': 'test_width',
|
||||
'height': 'test_height',
|
||||
'html': 'test_html'
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
|
||||
class InvalidImageBackendError(ImproperlyConfigured):
|
||||
pass
|
||||
|
||||
|
|
@ -8,23 +9,22 @@ class InvalidImageBackendError(ImproperlyConfigured):
|
|||
class BaseImageBackend(object):
|
||||
def __init__(self, params):
|
||||
self.quality = getattr(settings, 'IMAGE_COMPRESSION_QUALITY', 85)
|
||||
|
||||
|
||||
def open_image(self, input_file):
|
||||
"""
|
||||
Open an image and return the backend specific image object to pass
|
||||
to other methods. The object return has to have a size attribute
|
||||
Open an image and return the backend specific image object to pass
|
||||
to other methods. The object return has to have a size attribute
|
||||
which is a tuple with the width and height of the image and a format
|
||||
attribute with the format of the image.
|
||||
"""
|
||||
raise NotImplementedError('subclasses of BaseImageBackend must provide an open_image() method')
|
||||
|
||||
|
||||
|
||||
def save_image(self, image, output):
|
||||
"""
|
||||
Save the image to the output
|
||||
"""
|
||||
raise NotImplementedError('subclasses of BaseImageBackend must provide a save_image() method')
|
||||
|
||||
|
||||
def resize(self, image, size):
|
||||
"""
|
||||
resize image to the requested size, using highest quality settings
|
||||
|
|
@ -32,11 +32,9 @@ class BaseImageBackend(object):
|
|||
"""
|
||||
raise NotImplementedError('subclasses of BaseImageBackend must provide an resize() method')
|
||||
|
||||
|
||||
def crop_to_centre(self, image, size):
|
||||
raise NotImplementedError('subclasses of BaseImageBackend must provide a crop_to_centre() method')
|
||||
|
||||
|
||||
def resize_to_max(self, image, size):
|
||||
"""
|
||||
Resize image down to fit within the given dimensions, preserving aspect ratio.
|
||||
|
|
@ -58,10 +56,8 @@ class BaseImageBackend(object):
|
|||
final_size = (target_width, int(original_height * horz_scale))
|
||||
else:
|
||||
final_size = (int(original_width * vert_scale), target_height)
|
||||
|
||||
return self.resize(image, final_size)
|
||||
|
||||
|
||||
return self.resize(image, final_size)
|
||||
|
||||
def resize_to_min(self, image, size):
|
||||
"""
|
||||
|
|
@ -87,7 +83,6 @@ class BaseImageBackend(object):
|
|||
|
||||
return self.resize(image, final_size)
|
||||
|
||||
|
||||
def resize_to_width(self, image, target_width):
|
||||
"""
|
||||
Resize image down to the given width, preserving aspect ratio.
|
||||
|
|
@ -104,7 +99,6 @@ class BaseImageBackend(object):
|
|||
|
||||
return self.resize(image, final_size)
|
||||
|
||||
|
||||
def resize_to_height(self, image, target_height):
|
||||
"""
|
||||
Resize image down to the given height, preserving aspect ratio.
|
||||
|
|
@ -121,7 +115,6 @@ class BaseImageBackend(object):
|
|||
|
||||
return self.resize(image, final_size)
|
||||
|
||||
|
||||
def resize_to_fill(self, image, size):
|
||||
"""
|
||||
Resize down and crop image to fill the given dimensions. Most suitable for thumbnails.
|
||||
|
|
@ -130,3 +123,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,6 +1,9 @@
|
|||
from base import BaseImageBackend
|
||||
from __future__ import absolute_import
|
||||
|
||||
from .base import BaseImageBackend
|
||||
import PIL.Image
|
||||
|
||||
|
||||
class PillowBackend(BaseImageBackend):
|
||||
def __init__(self, params):
|
||||
super(PillowBackend, self).__init__(params)
|
||||
|
|
@ -32,4 +35,4 @@ class PillowBackend(BaseImageBackend):
|
|||
top = (original_height - final_height) / 2
|
||||
return image.crop(
|
||||
(left, top, left + final_width, top + final_height)
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from .base import BaseImageBackend
|
||||
|
||||
from wand.image import Image
|
||||
from wand.api import library
|
||||
|
||||
|
||||
class WandBackend(BaseImageBackend):
|
||||
def __init__(self, params):
|
||||
|
|
@ -10,6 +11,7 @@ class WandBackend(BaseImageBackend):
|
|||
|
||||
def open_image(self, input_file):
|
||||
image = Image(file=input_file)
|
||||
image.wand = library.MagickCoalesceImages(image.wand)
|
||||
return image
|
||||
|
||||
def save_image(self, image, output, format):
|
||||
|
|
@ -18,8 +20,9 @@ class WandBackend(BaseImageBackend):
|
|||
image.save(file=output)
|
||||
|
||||
def resize(self, image, size):
|
||||
image.resize(size[0], size[1])
|
||||
return image
|
||||
new_image = image.clone()
|
||||
new_image.resize(size[0], size[1])
|
||||
return new_image
|
||||
|
||||
def crop_to_centre(self, image, size):
|
||||
(original_width, original_height) = image.size
|
||||
|
|
@ -34,7 +37,9 @@ class WandBackend(BaseImageBackend):
|
|||
|
||||
left = (original_width - final_width) / 2
|
||||
top = (original_height - final_height) / 2
|
||||
image.crop(
|
||||
|
||||
new_image = image.clone()
|
||||
new_image.crop(
|
||||
left=left, top=top, right=left + final_width, bottom=top + final_height
|
||||
)
|
||||
return image
|
||||
return new_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'):
|
||||
"""
|
||||
|
|
@ -180,27 +194,25 @@ class Filter(models.Model):
|
|||
generate an output image with this filter applied, returning it
|
||||
as another django.core.files.File object
|
||||
"""
|
||||
|
||||
backend = get_image_backend(backend_name)
|
||||
|
||||
|
||||
if not self.method:
|
||||
self._parse_spec_string()
|
||||
|
||||
|
||||
# If file is closed, open it
|
||||
input_file.open('rb')
|
||||
image = backend.open_image(input_file)
|
||||
file_format = image.format
|
||||
|
||||
|
||||
method = getattr(backend, self.method_name)
|
||||
|
||||
image = method(image, self.method_arg)
|
||||
|
||||
output = StringIO.StringIO()
|
||||
backend.save_image(image, output, file_format)
|
||||
|
||||
|
||||
# and then close the input file
|
||||
input_file.close()
|
||||
|
||||
|
||||
# generate new filename derived from old one, inserting the filter spec string before the extension
|
||||
input_filename_parts = os.path.basename(input_file.name).split('.')
|
||||
|
|
@ -210,7 +222,6 @@ class Filter(models.Model):
|
|||
output_filename = '.'.join(output_filename_parts)
|
||||
|
||||
output_file = File(output, name=output_filename)
|
||||
|
||||
|
||||
return output_file
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
function(modal) {
|
||||
var searchUrl = $('form.image-search', modal.body).attr('action');
|
||||
|
||||
function ajaxifyLinks (context) {
|
||||
$('.listing a', context).click(function() {
|
||||
modal.loadUrl(this.href);
|
||||
|
|
@ -12,7 +14,6 @@ function(modal) {
|
|||
});
|
||||
}
|
||||
|
||||
var searchUrl = $('form.image-search', modal.body).attr('action');
|
||||
function search() {
|
||||
$.ajax({
|
||||
url: searchUrl,
|
||||
|
|
@ -24,8 +25,8 @@ function(modal) {
|
|||
});
|
||||
return false;
|
||||
}
|
||||
function setPage(page) {
|
||||
|
||||
function setPage(page) {
|
||||
if($('#id_q').val().length){
|
||||
dataObj = {q: $('#id_q').val(), p: page};
|
||||
}else{
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from django.contrib.auth.models import User, Group, Permission
|
|||
from django.core.urlresolvers import reverse
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
|
||||
from wagtail.tests.utils import login, unittest
|
||||
from wagtail.tests.utils import unittest, WagtailTestUtils
|
||||
from wagtail.wagtailimages.models import get_image_model
|
||||
from wagtail.wagtailimages.templatetags import image_tags
|
||||
|
||||
|
|
@ -84,10 +84,10 @@ class TestRenditions(TestCase):
|
|||
# default backend should be pillow
|
||||
backend = get_image_backend()
|
||||
self.assertTrue(isinstance(backend, PillowBackend))
|
||||
|
||||
|
||||
def test_minification(self):
|
||||
rendition = self.image.get_rendition('width-400')
|
||||
|
||||
|
||||
# Check size
|
||||
self.assertEqual(rendition.width, 400)
|
||||
self.assertEqual(rendition.height, 300)
|
||||
|
|
@ -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')
|
||||
|
|
@ -114,7 +121,7 @@ class TestRenditions(TestCase):
|
|||
|
||||
# Check that they are the same object
|
||||
self.assertEqual(first_rendition, second_rendition)
|
||||
|
||||
|
||||
|
||||
class TestRenditionsWand(TestCase):
|
||||
def setUp(self):
|
||||
|
|
@ -134,18 +141,18 @@ class TestRenditionsWand(TestCase):
|
|||
|
||||
def test_minification(self):
|
||||
rendition = self.image.get_rendition('width-400')
|
||||
|
||||
|
||||
# Check size
|
||||
self.assertEqual(rendition.width, 400)
|
||||
self.assertEqual(rendition.height, 300)
|
||||
|
||||
|
||||
def test_resize_to_max(self):
|
||||
rendition = self.image.get_rendition('max-100x100')
|
||||
|
||||
|
||||
# Check size
|
||||
self.assertEqual(rendition.width, 100)
|
||||
self.assertEqual(rendition.height, 75)
|
||||
|
||||
|
||||
def test_resize_to_min(self):
|
||||
rendition = self.image.get_rendition('min-120x120')
|
||||
|
||||
|
|
@ -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')
|
||||
|
|
@ -160,7 +174,7 @@ class TestRenditionsWand(TestCase):
|
|||
|
||||
# Check that they are the same object
|
||||
self.assertEqual(first_rendition, second_rendition)
|
||||
|
||||
|
||||
|
||||
class TestImageTag(TestCase):
|
||||
def setUp(self):
|
||||
|
|
@ -187,15 +201,17 @@ class TestImageTag(TestCase):
|
|||
## ===== ADMIN VIEWS =====
|
||||
|
||||
|
||||
class TestImageIndexView(TestCase):
|
||||
class TestImageIndexView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailimages_index'), params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailimages/images/index.html')
|
||||
|
||||
def test_search(self):
|
||||
response = self.get({'q': "Hello"})
|
||||
|
|
@ -215,9 +231,9 @@ class TestImageIndexView(TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TestImageAddView(TestCase):
|
||||
class TestImageAddView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailimages_add_image'), params)
|
||||
|
|
@ -225,8 +241,10 @@ class TestImageAddView(TestCase):
|
|||
def post(self, post_data={}):
|
||||
return self.client.post(reverse('wagtailimages_add_image'), post_data)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailimages/images/add.html')
|
||||
|
||||
def test_add(self):
|
||||
response = self.post({
|
||||
|
|
@ -236,6 +254,7 @@ class TestImageAddView(TestCase):
|
|||
|
||||
# Should redirect back to index
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, reverse('wagtailimages_index'))
|
||||
|
||||
# Check that the image was created
|
||||
images = Image.objects.filter(title="Test image")
|
||||
|
|
@ -247,9 +266,9 @@ class TestImageAddView(TestCase):
|
|||
self.assertEqual(image.height, 480)
|
||||
|
||||
|
||||
class TestImageEditView(TestCase):
|
||||
class TestImageEditView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
# Create an image to edit
|
||||
self.image = Image.objects.create(
|
||||
|
|
@ -263,8 +282,10 @@ class TestImageEditView(TestCase):
|
|||
def post(self, post_data={}):
|
||||
return self.client.post(reverse('wagtailimages_edit_image', args=(self.image.id,)), post_data)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailimages/images/edit.html')
|
||||
|
||||
def test_edit(self):
|
||||
response = self.post({
|
||||
|
|
@ -273,15 +294,16 @@ class TestImageEditView(TestCase):
|
|||
|
||||
# Should redirect back to index
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, reverse('wagtailimages_index'))
|
||||
|
||||
# Check that the image was edited
|
||||
image = Image.objects.get(id=self.image.id)
|
||||
self.assertEqual(image.title, "Edited")
|
||||
|
||||
|
||||
class TestImageDeleteView(TestCase):
|
||||
class TestImageDeleteView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
# Create an image to edit
|
||||
self.image = Image.objects.create(
|
||||
|
|
@ -295,8 +317,10 @@ class TestImageDeleteView(TestCase):
|
|||
def post(self, post_data={}):
|
||||
return self.client.post(reverse('wagtailimages_delete_image', args=(self.image.id,)), post_data)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailimages/images/confirm_delete.html')
|
||||
|
||||
def test_delete(self):
|
||||
response = self.post({
|
||||
|
|
@ -305,21 +329,25 @@ class TestImageDeleteView(TestCase):
|
|||
|
||||
# Should redirect back to index
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, reverse('wagtailimages_index'))
|
||||
|
||||
# Check that the image was deleted
|
||||
images = Image.objects.filter(title="Test image")
|
||||
self.assertEqual(images.count(), 0)
|
||||
|
||||
|
||||
class TestImageChooserView(TestCase):
|
||||
class TestImageChooserView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailimages_chooser'), params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailimages/chooser/chooser.html')
|
||||
self.assertTemplateUsed(response, 'wagtailimages/chooser/chooser.js')
|
||||
|
||||
def test_search(self):
|
||||
response = self.get({'q': "Hello"})
|
||||
|
|
@ -333,9 +361,9 @@ class TestImageChooserView(TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TestImageChooserChosenView(TestCase):
|
||||
class TestImageChooserChosenView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
# Create an image to edit
|
||||
self.image = Image.objects.create(
|
||||
|
|
@ -346,16 +374,25 @@ class TestImageChooserChosenView(TestCase):
|
|||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailimages_image_chosen', args=(self.image.id,)), params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailimages/chooser/image_chosen.js')
|
||||
|
||||
# TODO: Test posting
|
||||
|
||||
|
||||
class TestImageChooserUploadView(TestCase):
|
||||
class TestImageChooserUploadView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailimages_chooser_upload'), params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailimages/chooser/chooser.html')
|
||||
self.assertTemplateUsed(response, 'wagtailimages/chooser/chooser.js')
|
||||
|
||||
# TODO: Test uploading through chooser
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from django.test import TestCase
|
||||
from django.test.client import Client
|
||||
from wagtail.wagtailredirects import models
|
||||
from wagtail.tests.utils import login
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
|
||||
|
|
@ -66,15 +66,17 @@ class TestRedirects(TestCase):
|
|||
self.assertTrue(r.has_header('Location'))
|
||||
|
||||
|
||||
class TestRedirectsIndexView(TestCase):
|
||||
class TestRedirectsIndexView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailredirects_index'), params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailredirects/index.html')
|
||||
|
||||
def test_search(self):
|
||||
response = self.get({'q': "Hello"})
|
||||
|
|
@ -88,9 +90,9 @@ class TestRedirectsIndexView(TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TestRedirectsAddView(TestCase):
|
||||
class TestRedirectsAddView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailredirects_add_redirect'), params)
|
||||
|
|
@ -98,8 +100,10 @@ class TestRedirectsAddView(TestCase):
|
|||
def post(self, post_data={}):
|
||||
return self.client.post(reverse('wagtailredirects_add_redirect'), post_data)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailredirects/add.html')
|
||||
|
||||
def test_add(self):
|
||||
response = self.post({
|
||||
|
|
@ -110,6 +114,7 @@ class TestRedirectsAddView(TestCase):
|
|||
|
||||
# Should redirect back to index
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, reverse('wagtailredirects_index'))
|
||||
|
||||
# Check that the redirect was created
|
||||
redirects = models.Redirect.objects.filter(old_path='/test')
|
||||
|
|
@ -127,14 +132,14 @@ class TestRedirectsAddView(TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TestRedirectsEditView(TestCase):
|
||||
class TestRedirectsEditView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
# Create a redirect to edit
|
||||
self.redirect = models.Redirect(old_path='/test', redirect_link='http://www.test.com/')
|
||||
self.redirect.save()
|
||||
|
||||
# Login
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}, redirect_id=None):
|
||||
return self.client.get(reverse('wagtailredirects_edit_redirect', args=(redirect_id or self.redirect.id, )), params)
|
||||
|
|
@ -142,8 +147,10 @@ class TestRedirectsEditView(TestCase):
|
|||
def post(self, post_data={}, redirect_id=None):
|
||||
return self.client.post(reverse('wagtailredirects_edit_redirect', args=(redirect_id or self.redirect.id, )), post_data)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailredirects/edit.html')
|
||||
|
||||
def test_nonexistant_redirect(self):
|
||||
self.assertEqual(self.get(redirect_id=100000).status_code, 404)
|
||||
|
|
@ -157,6 +164,7 @@ class TestRedirectsEditView(TestCase):
|
|||
|
||||
# Should redirect back to index
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, reverse('wagtailredirects_index'))
|
||||
|
||||
# Check that the redirect was edited
|
||||
redirects = models.Redirect.objects.filter(old_path='/test')
|
||||
|
|
@ -173,14 +181,14 @@ class TestRedirectsEditView(TestCase):
|
|||
# Should not redirect to index
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
class TestRedirectsDeleteView(TestCase):
|
||||
class TestRedirectsDeleteView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
# Create a redirect to edit
|
||||
self.redirect = models.Redirect(old_path='/test', redirect_link='http://www.test.com/')
|
||||
self.redirect.save()
|
||||
|
||||
# Login
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}, redirect_id=None):
|
||||
return self.client.get(reverse('wagtailredirects_delete_redirect', args=(redirect_id or self.redirect.id, )), params)
|
||||
|
|
@ -188,8 +196,10 @@ class TestRedirectsDeleteView(TestCase):
|
|||
def post(self, post_data={}, redirect_id=None):
|
||||
return self.client.post(reverse('wagtailredirects_delete_redirect', args=(redirect_id or self.redirect.id, )), post_data)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailredirects/confirm_delete.html')
|
||||
|
||||
def test_nonexistant_redirect(self):
|
||||
self.assertEqual(self.get(redirect_id=100000).status_code, 404)
|
||||
|
|
@ -201,6 +211,7 @@ class TestRedirectsDeleteView(TestCase):
|
|||
|
||||
# Should redirect back to index
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, reverse('wagtailredirects_index'))
|
||||
|
||||
# Check that the redirect was deleted
|
||||
redirects = models.Redirect.objects.filter(old_path='/test')
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ EditorsPickFormSetBase = inlineformset_factory(models.Query, models.EditorsPick,
|
|||
|
||||
|
||||
class EditorsPickFormSet(EditorsPickFormSetBase):
|
||||
minimum_forms = 1
|
||||
minimum_forms_message = _("Please specify at least one recommendation for this search term.")
|
||||
def add_fields(self, form, *args, **kwargs):
|
||||
super(EditorsPickFormSet, self).add_fields(form, *args, **kwargs)
|
||||
|
||||
|
|
@ -40,3 +42,20 @@ class EditorsPickFormSet(EditorsPickFormSetBase):
|
|||
|
||||
# Remove query field
|
||||
del form.fields['query']
|
||||
|
||||
def clean(self):
|
||||
# Editors pick must have at least one recommended page to be valid
|
||||
# Check there is at least one non-deleted form.
|
||||
non_deleted_forms = self.total_form_count()
|
||||
non_empty_forms = 0
|
||||
for i in xrange(0, self.total_form_count()):
|
||||
form = self.forms[i]
|
||||
if self.can_delete and self._should_delete_form(form):
|
||||
non_deleted_forms -= 1
|
||||
if not (form.instance.id is None and not form.has_changed()):
|
||||
non_empty_forms += 1
|
||||
if (
|
||||
non_deleted_forms < self.minimum_forms
|
||||
or non_empty_forms < self.minimum_forms
|
||||
):
|
||||
raise forms.ValidationError(self.minimum_forms_message)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
{% include "wagtailadmin/shared/header.html" with title=add_str icon="pick" %}
|
||||
|
||||
<div class="nice-padding">
|
||||
{% blocktrans %}s
|
||||
{% blocktrans %}
|
||||
<p>Editors picks are a means of recommending specific pages that might not organically come high up in search results. E.g recommending your primary donation page to a user searching with a less common term like "<em>giving</em>".</p>
|
||||
{% endblocktrans %}
|
||||
{% blocktrans %}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from django.test import TestCase
|
||||
from wagtail.tests.utils import login
|
||||
from wagtail.tests.utils import unittest, WagtailTestUtils
|
||||
from wagtail.wagtailsearch import models
|
||||
|
||||
|
||||
|
|
@ -45,15 +45,17 @@ class TestEditorsPicks(TestCase):
|
|||
self.assertEqual(models.Query.get("root page").editors_picks.last().description, "Last editors pick")
|
||||
|
||||
|
||||
class TestEditorsPicksIndexView(TestCase):
|
||||
class TestEditorsPicksIndexView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get('/admin/search/editorspicks/', params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/index.html')
|
||||
|
||||
def test_search(self):
|
||||
response = self.get({'q': "Hello"})
|
||||
|
|
@ -67,20 +69,24 @@ class TestEditorsPicksIndexView(TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TestEditorsPicksAddView(TestCase):
|
||||
class TestEditorsPicksAddView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get('/admin/search/editorspicks/add/', params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/add.html')
|
||||
|
||||
# TODO: Test posting
|
||||
|
||||
|
||||
class TestEditorsPicksEditView(TestCase):
|
||||
class TestEditorsPicksEditView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
# Create an editors pick to edit
|
||||
self.query = models.Query.get("Hello")
|
||||
|
|
@ -89,13 +95,17 @@ class TestEditorsPicksEditView(TestCase):
|
|||
def get(self, params={}):
|
||||
return self.client.get('/admin/search/editorspicks/' + str(self.query.id) + '/', params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/edit.html')
|
||||
|
||||
# TODO: Test posting
|
||||
|
||||
|
||||
class TestEditorsPicksDeleteView(TestCase):
|
||||
class TestEditorsPicksDeleteView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
# Create an editors pick to delete
|
||||
self.query = models.Query.get("Hello")
|
||||
|
|
@ -104,5 +114,9 @@ class TestEditorsPicksDeleteView(TestCase):
|
|||
def get(self, params={}):
|
||||
return self.client.get('/admin/search/editorspicks/' + str(self.query.id) + '/delete/', params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/confirm_delete.html')
|
||||
|
||||
# TODO: Test posting
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ class TestSearchView(TestCase):
|
|||
def get(self, params={}):
|
||||
return self.client.get('/search/', params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearch/search_results.html')
|
||||
|
||||
def test_search(self):
|
||||
response = self.get({'q': "Hello"})
|
||||
|
|
@ -24,8 +26,10 @@ class TestSuggestionsView(TestCase):
|
|||
def get(self, params={}):
|
||||
return self.client.get('/search/suggest/', params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# TODO: Check that a valid JSON document was returned
|
||||
|
||||
def test_search(self):
|
||||
response = self.get({'q': "Hello"})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from django.test import TestCase
|
||||
from django.core import management
|
||||
from wagtail.wagtailsearch import models
|
||||
from wagtail.tests.utils import login, unittest
|
||||
from wagtail.tests.utils import unittest, WagtailTestUtils
|
||||
from StringIO import StringIO
|
||||
|
||||
|
||||
|
|
@ -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):
|
||||
|
|
@ -139,15 +149,18 @@ class TestGarbageCollectCommand(TestCase):
|
|||
# TODO: Test that this command is acctually doing its job
|
||||
|
||||
|
||||
class TestQueryChooserView(TestCase):
|
||||
class TestQueryChooserView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get('/admin/search/queries/chooser/', params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearch/queries/chooser/chooser.html')
|
||||
self.assertTemplateUsed(response, 'wagtailsearch/queries/chooser/chooser.js')
|
||||
|
||||
def test_search(self):
|
||||
response = self.get({'q': "Hello"})
|
||||
|
|
|
|||
|
|
@ -45,12 +45,12 @@ def index(request):
|
|||
|
||||
|
||||
def save_editorspicks(query, new_query, editors_pick_formset):
|
||||
# Set sort_order
|
||||
for i, form in enumerate(editors_pick_formset.ordered_forms):
|
||||
form.instance.sort_order = i
|
||||
|
||||
# Save
|
||||
if editors_pick_formset.is_valid():
|
||||
# Set sort_order
|
||||
for i, form in enumerate(editors_pick_formset.ordered_forms):
|
||||
form.instance.sort_order = i
|
||||
|
||||
editors_pick_formset.save()
|
||||
|
||||
# If query was changed, move all editors picks to the new query
|
||||
|
|
@ -72,10 +72,14 @@ def add(request):
|
|||
|
||||
# Save editors picks
|
||||
editors_pick_formset = forms.EditorsPickFormSet(request.POST, instance=query)
|
||||
|
||||
if save_editorspicks(query, query, editors_pick_formset):
|
||||
messages.success(request, _("Editor's picks for '{0}' created.").format(query))
|
||||
return redirect('wagtailsearch_editorspicks_index')
|
||||
else:
|
||||
if len(editors_pick_formset.non_form_errors()):
|
||||
messages.error(request, " ".join(error for error in editors_pick_formset.non_form_errors())) # formset level error (e.g. no forms submitted)
|
||||
else:
|
||||
messages.error(request, _("Recommendations have not been created due to errors")) # specific errors will be displayed within form fields
|
||||
else:
|
||||
editors_pick_formset = forms.EditorsPickFormSet()
|
||||
else:
|
||||
|
|
@ -95,15 +99,22 @@ def edit(request, query_id):
|
|||
if request.POST:
|
||||
# Get query
|
||||
query_form = forms.QueryForm(request.POST)
|
||||
# and the recommendations
|
||||
editors_pick_formset = forms.EditorsPickFormSet(request.POST, instance=query)
|
||||
|
||||
if query_form.is_valid():
|
||||
new_query = models.Query.get(query_form['query_string'].value())
|
||||
|
||||
# Save editors picks
|
||||
editors_pick_formset = forms.EditorsPickFormSet(request.POST, instance=query)
|
||||
|
||||
if save_editorspicks(query, new_query, editors_pick_formset):
|
||||
messages.success(request, _("Editor's picks for '{0}' updated.").format(new_query))
|
||||
return redirect('wagtailsearch_editorspicks_index')
|
||||
else:
|
||||
if len(editors_pick_formset.non_form_errors()):
|
||||
messages.error(request, " ".join(error for error in editors_pick_formset.non_form_errors())) # formset level error (e.g. no forms submitted)
|
||||
else:
|
||||
messages.error(request, _("Recommendations have not been saved due to errors")) # specific errors will be displayed within form fields
|
||||
|
||||
else:
|
||||
query_form = forms.QueryForm(initial=dict(query_string=query.query_string))
|
||||
editors_pick_formset = forms.EditorsPickFormSet(instance=query)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -2,45 +2,50 @@ from django.test import TestCase
|
|||
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.utils import unittest, WagtailTestUtils
|
||||
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
|
||||
|
||||
class TestSnippetIndexView(TestCase):
|
||||
class TestSnippetIndexView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailsnippets_index'), params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsnippets/snippets/index.html')
|
||||
|
||||
def test_displays_snippet(self):
|
||||
self.assertContains(self.get(), "Adverts")
|
||||
|
||||
|
||||
class TestSnippetListView(TestCase):
|
||||
class TestSnippetListView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailsnippets_list',
|
||||
args=('tests', 'advert')),
|
||||
params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsnippets/snippets/type_index.html')
|
||||
|
||||
def test_displays_add_button(self):
|
||||
self.assertContains(self.get(), "Add advert")
|
||||
|
||||
|
||||
class TestSnippetCreateView(TestCase):
|
||||
class TestSnippetCreateView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailsnippets_create',
|
||||
|
|
@ -52,8 +57,10 @@ class TestSnippetCreateView(TestCase):
|
|||
args=('tests', 'advert')),
|
||||
post_data)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsnippets/snippets/create.html')
|
||||
|
||||
def test_create_invalid(self):
|
||||
response = self.post(post_data={'foo': 'bar'})
|
||||
|
|
@ -64,20 +71,21 @@ class TestSnippetCreateView(TestCase):
|
|||
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')))
|
||||
|
||||
snippets = Advert.objects.filter(text='test_advert')
|
||||
self.assertEqual(snippets.count(), 1)
|
||||
self.assertEqual(snippets.first().url, 'http://www.example.com/')
|
||||
|
||||
|
||||
class TestSnippetEditView(TestCase):
|
||||
class TestSnippetEditView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.test_snippet = Advert()
|
||||
self.test_snippet.text = 'test_advert'
|
||||
self.test_snippet.url = 'http://www.example.com/'
|
||||
self.test_snippet.save()
|
||||
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailsnippets_edit',
|
||||
|
|
@ -89,8 +97,10 @@ class TestSnippetEditView(TestCase):
|
|||
args=('tests', 'advert', self.test_snippet.id)),
|
||||
post_data)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsnippets/snippets/edit.html')
|
||||
|
||||
def test_non_existant_model(self):
|
||||
response = self.client.get(reverse('wagtailsnippets_edit',
|
||||
|
|
@ -111,20 +121,21 @@ class TestSnippetEditView(TestCase):
|
|||
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')))
|
||||
|
||||
snippets = Advert.objects.filter(text='edited_test_advert')
|
||||
self.assertEqual(snippets.count(), 1)
|
||||
self.assertEqual(snippets.first().url, 'http://www.example.com/edited')
|
||||
|
||||
|
||||
class TestSnippetDelete(TestCase):
|
||||
class TestSnippetDelete(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.test_snippet = Advert()
|
||||
self.test_snippet.text = 'test_advert'
|
||||
self.test_snippet.url = 'http://www.example.com/'
|
||||
self.test_snippet.save()
|
||||
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def test_delete_get(self):
|
||||
response = self.client.get(reverse('wagtailsnippets_delete', args=('tests', 'advert', self.test_snippet.id, )))
|
||||
|
|
@ -136,6 +147,7 @@ class TestSnippetDelete(TestCase):
|
|||
|
||||
# Should be redirected to explorer page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, reverse('wagtailsnippets_list', args=('tests', 'advert')))
|
||||
|
||||
# Check that the page is gone
|
||||
self.assertEqual(Advert.objects.filter(text='test_advert').count(), 0)
|
||||
|
|
@ -168,3 +180,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))
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
from django.test import TestCase
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.models import User
|
||||
from wagtail.tests.utils import login
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
|
||||
|
||||
class TestUserIndexView(TestCase):
|
||||
class TestUserIndexView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailusers_index'), params)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailusers/index.html')
|
||||
|
||||
def test_search(self):
|
||||
response = self.get({'q': "Hello"})
|
||||
|
|
@ -26,9 +28,9 @@ class TestUserIndexView(TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TestUserCreateView(TestCase):
|
||||
class TestUserCreateView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse('wagtailusers_create'), params)
|
||||
|
|
@ -36,8 +38,10 @@ class TestUserCreateView(TestCase):
|
|||
def post(self, post_data={}):
|
||||
return self.client.post(reverse('wagtailusers_create'), post_data)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailusers/create.html')
|
||||
|
||||
def test_create(self):
|
||||
response = self.post({
|
||||
|
|
@ -51,6 +55,7 @@ class TestUserCreateView(TestCase):
|
|||
|
||||
# Should redirect back to index
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, reverse('wagtailusers_index'))
|
||||
|
||||
# Check that the user was created
|
||||
users = User.objects.filter(username='testuser')
|
||||
|
|
@ -58,13 +63,13 @@ class TestUserCreateView(TestCase):
|
|||
self.assertEqual(users.first().email, 'test@user.com')
|
||||
|
||||
|
||||
class TestUserEditView(TestCase):
|
||||
class TestUserEditView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
# Create a user to edit
|
||||
self.test_user = User.objects.create_user(username='testuser', email='testuser@email.com', password='password')
|
||||
|
||||
# Login
|
||||
login(self.client)
|
||||
self.login()
|
||||
|
||||
def get(self, params={}, user_id=None):
|
||||
return self.client.get(reverse('wagtailusers_edit', args=(user_id or self.test_user.id, )), params)
|
||||
|
|
@ -72,8 +77,10 @@ class TestUserEditView(TestCase):
|
|||
def post(self, post_data={}, user_id=None):
|
||||
return self.client.post(reverse('wagtailusers_edit', args=(user_id or self.test_user.id, )), post_data)
|
||||
|
||||
def test_status_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
def test_simple(self):
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailusers/edit.html')
|
||||
|
||||
def test_nonexistant_redirect(self):
|
||||
self.assertEqual(self.get(user_id=100000).status_code, 404)
|
||||
|
|
@ -90,6 +97,7 @@ class TestUserEditView(TestCase):
|
|||
|
||||
# Should redirect back to index
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertURLEqual(response.url, reverse('wagtailusers_index'))
|
||||
|
||||
# Check that the user was edited
|
||||
user = User.objects.get(id=self.test_user.id)
|
||||
|
|
|
|||
Loading…
Reference in a new issue