mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-05-11 08:43:10 +00:00
Merge branch 'master' of https://github.com/torchbox/wagtail into published_pagination
This commit is contained in:
commit
6ce38148b0
57 changed files with 3451 additions and 2220 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,16 @@ 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
|
||||
* Support for setting a subpage_types property on page models, to define which page types are allowed as subpages
|
||||
* 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)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ Contributors
|
|||
* Ben Margolis
|
||||
* Tom Talbot
|
||||
* Jeffrey Hearn
|
||||
* Robert Clark
|
||||
|
||||
Translators
|
||||
===========
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
|||
|
|
@ -371,8 +371,8 @@ Edit Handler API
|
|||
~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
Hooks
|
||||
-----
|
||||
Admin Hooks
|
||||
-----------
|
||||
|
||||
On loading, Wagtail will search for any app with the file ``wagtail_hooks.py`` and execute the contents. This provides a way to register your own functions to execute at certain points in Wagtail's execution, such as when a ``Page`` object is saved or when the main menu is constructed.
|
||||
|
||||
|
|
@ -547,6 +547,38 @@ Where ``'hook'`` is one of the following hook strings and ``function`` is a func
|
|||
hooks.register('insert_editor_css', editor_css)
|
||||
|
||||
|
||||
Image Formats in the Rich Text Editor
|
||||
-------------------------------------
|
||||
|
||||
On loading, Wagtail will search for any app with the file ``image_formats.py`` and execute the contents. This provides a way to customize the formatting options shown to the editor when inserting images in the ``RichTextField`` editor.
|
||||
|
||||
As an example, add a "thumbnail" format:
|
||||
|
||||
.. code-block:: python
|
||||
# image_formats.py
|
||||
from wagtail.wagtailimages.formats import Format, register_image_format
|
||||
|
||||
register_image_format(Format('thumbnail', 'Thumbnail', 'richtext-image thumbnail', 'max-120x120'))
|
||||
|
||||
|
||||
To begin, import the the ``Format`` class, ``register_image_format`` function, and optionally ``unregister_image_format`` function. To register a new ``Format``, call the ``register_image_format`` with the ``Format`` object as the argument. The ``Format`` takes the following init arguments:
|
||||
|
||||
``name``
|
||||
The unique key used to identify the format. To unregister this format, call ``unregister_image_format`` with this string as the only argument.
|
||||
|
||||
``label``
|
||||
The label used in the chooser form when inserting the image into the ``RichTextField``.
|
||||
|
||||
``classnames``
|
||||
The string to assign to the ``class`` attribute of the generated ``<img>`` tag.
|
||||
|
||||
``filter_spec``
|
||||
The string specification to create the image rendition. For more, see the :ref:`image_tag`.
|
||||
|
||||
|
||||
To unregister, call ``unregister_image_format`` with the string of the ``name`` of the ``Format`` as the only argument.
|
||||
|
||||
|
||||
Content Index Pages (CRUD)
|
||||
--------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -101,7 +101,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',
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -294,3 +294,16 @@ class ZuluSnippet(models.Model):
|
|||
|
||||
def __unicode__(self):
|
||||
return self.text
|
||||
|
||||
|
||||
class StandardIndex(Page):
|
||||
pass
|
||||
|
||||
class StandardChild(Page):
|
||||
pass
|
||||
|
||||
class BusinessIndex(Page):
|
||||
subpage_types = ['tests.BusinessChild']
|
||||
|
||||
class BusinessChild(Page):
|
||||
subpage_types = []
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -648,6 +648,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{
|
||||
|
|
|
|||
|
|
@ -430,13 +430,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;
|
||||
|
|
@ -468,7 +468,7 @@ footer{
|
|||
content:"n";
|
||||
padding-left:20px;
|
||||
font-size:2em;
|
||||
color:$color-teal;
|
||||
color:$color-teal-darker;
|
||||
line-height:0.9em;
|
||||
}
|
||||
}
|
||||
|
|
@ -739,6 +739,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>
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@
|
|||
<div class="nice-padding">
|
||||
<p>{% trans "Choose which type of page you'd like to create." %}</p>
|
||||
|
||||
{% if all_page_types %}
|
||||
{% if page_types %}
|
||||
<ul class="listing">
|
||||
{% for content_type in all_page_types %}
|
||||
{% for content_type in page_types %}
|
||||
<li>
|
||||
<div class="row row-flush">
|
||||
<div class="col6">
|
||||
|
|
|
|||
|
|
@ -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.models import SimplePage, EventPage, StandardIndex, StandardChild, BusinessIndex, BusinessChild
|
||||
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
|
||||
|
|
@ -185,8 +188,13 @@ class TestPageCreation(TestCase):
|
|||
self.assertTemplateUsed(response, 'tests/simple_page.html')
|
||||
self.assertContains(response, "New page!")
|
||||
|
||||
# Check that the treebeard attributes were set correctly on the page object
|
||||
self.assertEqual(response.context['self'].depth, self.root_page.depth + 1)
|
||||
self.assertTrue(response.context['self'].path.startswith(self.root_page.path))
|
||||
self.assertEqual(response.context['self'].get_parent(), self.root_page)
|
||||
|
||||
class TestPageEdit(TestCase):
|
||||
|
||||
class TestPageEdit(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
# Find root page
|
||||
self.root_page = Page.objects.get(id=2)
|
||||
|
|
@ -206,7 +214,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 +246,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 +264,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 +288,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 +317,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 +329,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 +355,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 +402,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 +425,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 +454,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 +514,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 +530,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 +553,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 +605,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 +660,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
|
||||
|
|
@ -659,3 +674,48 @@ class TestContentTypeUse(TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailadmin/pages/content_type_use.html')
|
||||
self.assertContains(response, "Christmas")
|
||||
|
||||
|
||||
class TestSubpageBusinessRules(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
# Find root page
|
||||
self.root_page = Page.objects.get(id=2)
|
||||
|
||||
# Add standard page
|
||||
self.standard_index = StandardIndex()
|
||||
self.standard_index.title = "Standard Index"
|
||||
self.standard_index.slug = "standard-index"
|
||||
self.root_page.add_child(instance=self.standard_index)
|
||||
|
||||
# Add business page
|
||||
self.business_index = BusinessIndex()
|
||||
self.business_index.title = "Business Index"
|
||||
self.business_index.slug = "business-index"
|
||||
self.root_page.add_child(instance=self.business_index)
|
||||
|
||||
# Add business child
|
||||
self.business_child = BusinessChild()
|
||||
self.business_child.title = "Business Child"
|
||||
self.business_child.slug = "business-child"
|
||||
self.business_index.add_child(instance=self.business_child)
|
||||
|
||||
# Login
|
||||
self.login()
|
||||
|
||||
def test_standard_subpage(self):
|
||||
response = self.client.get(reverse('wagtailadmin_pages_add_subpage', args=(self.standard_index.id, )))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'Standard Child')
|
||||
self.assertContains(response, 'Business Child')
|
||||
|
||||
def test_business_subpage(self):
|
||||
response = self.client.get(reverse('wagtailadmin_pages_add_subpage', args=(self.business_index.id, )))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotContains(response, 'Standard Child')
|
||||
self.assertContains(response, 'Business Child')
|
||||
|
||||
def test_business_child_subpage(self):
|
||||
response = self.client.get(reverse('wagtailadmin_pages_add_subpage', args=(self.business_child.id, )))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotContains(response, 'Standard Child')
|
||||
self.assertEqual(0, len(response.context['page_types']))
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
|
|
@ -57,13 +57,11 @@ def add_subpage(request, parent_page_id):
|
|||
if not parent_page.permissions_for_user(request.user).can_add_subpage():
|
||||
raise PermissionDenied
|
||||
|
||||
page_types = sorted([ContentType.objects.get_for_model(model_class) for model_class in parent_page.clean_subpage_types()], key=lambda pagetype: pagetype.name.lower())
|
||||
all_page_types = sorted(get_page_types(), key=lambda pagetype: pagetype.name.lower())
|
||||
page_types = sorted(parent_page.clean_subpage_types(), key=lambda pagetype: pagetype.name.lower())
|
||||
|
||||
return render(request, 'wagtailadmin/pages/add_subpage.html', {
|
||||
'parent_page': parent_page,
|
||||
'page_types': page_types,
|
||||
'all_page_types': all_page_types,
|
||||
})
|
||||
|
||||
|
||||
|
|
@ -364,6 +362,10 @@ def preview_on_create(request, content_type_app_name, content_type_model_name, p
|
|||
parent_page = get_object_or_404(Page, id=parent_page_id).specific
|
||||
page.set_url_path(parent_page)
|
||||
|
||||
# Set treebeard attributes
|
||||
page.depth = parent_page.depth + 1
|
||||
page.path = Page._get_children_path_interval(parent_page.path)[1]
|
||||
|
||||
# This view will generally be invoked as an AJAX request; as such, in the case of
|
||||
# an error Django will return a plaintext response. This isn't what we want, since
|
||||
# we will be writing the response back to an HTML page regardless of success or
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
from django.shortcuts import render
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
|
||||
from wagtail.wagtailadmin.userbar import EditPageItem, AddPageItem, ApproveModerationEditPageItem, RejectModerationEditPageItem
|
||||
from wagtail.wagtailadmin import hooks
|
||||
from wagtail.wagtailcore.models import Page, PageRevision
|
||||
|
||||
|
||||
|
||||
@permission_required('wagtailadmin.access_admin')
|
||||
def for_frontend(request, page_id):
|
||||
items = [
|
||||
EditPageItem(Page.objects.get(id=page_id)),
|
||||
|
|
@ -24,6 +28,8 @@ def for_frontend(request, page_id):
|
|||
'items': rendered_items,
|
||||
})
|
||||
|
||||
|
||||
@permission_required('wagtailadmin.access_admin')
|
||||
def for_moderation(request, revision_id):
|
||||
items = [
|
||||
EditPageItem(PageRevision.objects.get(id=revision_id).page),
|
||||
|
|
@ -44,4 +50,4 @@ def for_moderation(request, revision_id):
|
|||
# Render the edit bird
|
||||
return render(request, 'wagtailadmin/userbar/base.html', {
|
||||
'items': rendered_items,
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import sys
|
||||
import os
|
||||
from StringIO import StringIO
|
||||
from urlparse import urlparse
|
||||
import warnings
|
||||
|
||||
from modelcluster.models import ClusterableModel
|
||||
|
||||
|
|
@ -101,30 +100,29 @@ def get_page_types():
|
|||
return _PAGE_CONTENT_TYPES
|
||||
|
||||
|
||||
LEAF_PAGE_MODEL_CLASSES = []
|
||||
_LEAF_PAGE_CONTENT_TYPE_IDS = []
|
||||
|
||||
|
||||
def get_leaf_page_content_type_ids():
|
||||
global _LEAF_PAGE_CONTENT_TYPE_IDS
|
||||
if len(_LEAF_PAGE_CONTENT_TYPE_IDS) != len(LEAF_PAGE_MODEL_CLASSES):
|
||||
_LEAF_PAGE_CONTENT_TYPE_IDS = [
|
||||
ContentType.objects.get_for_model(cls).id for cls in LEAF_PAGE_MODEL_CLASSES
|
||||
]
|
||||
return _LEAF_PAGE_CONTENT_TYPE_IDS
|
||||
|
||||
|
||||
NAVIGABLE_PAGE_MODEL_CLASSES = []
|
||||
_NAVIGABLE_PAGE_CONTENT_TYPE_IDS = []
|
||||
|
||||
warnings.warn("""
|
||||
get_leaf_page_content_type_ids is deprecated, as it treats pages without an explicit subpage_types
|
||||
setting as 'leaf' pages. Code that calls get_leaf_page_content_type_ids must be rewritten to avoid
|
||||
this incorrect assumption.
|
||||
""", DeprecationWarning)
|
||||
return [
|
||||
content_type.id
|
||||
for content_type in get_page_types()
|
||||
if not getattr(content_type.model_class(), 'subpage_types', None)
|
||||
]
|
||||
|
||||
def get_navigable_page_content_type_ids():
|
||||
global _NAVIGABLE_PAGE_CONTENT_TYPE_IDS
|
||||
if len(_NAVIGABLE_PAGE_CONTENT_TYPE_IDS) != len(NAVIGABLE_PAGE_MODEL_CLASSES):
|
||||
_NAVIGABLE_PAGE_CONTENT_TYPE_IDS = [
|
||||
ContentType.objects.get_for_model(cls).id for cls in NAVIGABLE_PAGE_MODEL_CLASSES
|
||||
]
|
||||
return _NAVIGABLE_PAGE_CONTENT_TYPE_IDS
|
||||
warnings.warn("""
|
||||
get_navigable_page_content_type_ids is deprecated, as it treats pages without an explicit subpage_types
|
||||
setting as 'leaf' pages. Code that calls get_navigable_page_content_type_ids must be rewritten to avoid
|
||||
this incorrect assumption.
|
||||
""", DeprecationWarning)
|
||||
return [
|
||||
content_type.id
|
||||
for content_type in get_page_types()
|
||||
if getattr(content_type.model_class(), 'subpage_types', None)
|
||||
]
|
||||
|
||||
|
||||
class PageManager(models.Manager):
|
||||
|
|
@ -209,10 +207,6 @@ class PageBase(models.base.ModelBase):
|
|||
if not cls.is_abstract:
|
||||
# register this type in the list of page content types
|
||||
PAGE_MODEL_CLASSES.append(cls)
|
||||
if cls.subpage_types:
|
||||
NAVIGABLE_PAGE_MODEL_CLASSES.append(cls)
|
||||
else:
|
||||
LEAF_PAGE_MODEL_CLASSES.append(cls)
|
||||
|
||||
|
||||
class Page(MP_Node, ClusterableModel, Indexed):
|
||||
|
|
@ -259,9 +253,6 @@ class Page(MP_Node, ClusterableModel, Indexed):
|
|||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
# by default pages do not allow any kind of subpages
|
||||
subpage_types = []
|
||||
|
||||
is_abstract = True # don't offer Page in the list of page types a superuser can create
|
||||
|
||||
def set_url_path(self, parent):
|
||||
|
|
@ -394,32 +385,32 @@ 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):
|
||||
"""
|
||||
Return true if it's meaningful to browse subpages of this page -
|
||||
i.e. it currently has subpages, or its page type indicates that sub-pages are supported,
|
||||
i.e. it currently has subpages,
|
||||
or it's at the top level (this rule necessary for empty out-of-the-box sites to have working navigation)
|
||||
"""
|
||||
return (not self.is_leaf()) or (self.content_type_id not in get_leaf_page_content_type_ids()) or self.depth == 2
|
||||
return (not self.is_leaf()) or self.depth == 2
|
||||
|
||||
def get_other_siblings(self):
|
||||
# get sibling pages excluding self
|
||||
|
|
@ -484,25 +475,30 @@ class Page(MP_Node, ClusterableModel, Indexed):
|
|||
where required
|
||||
"""
|
||||
if cls._clean_subpage_types is None:
|
||||
res = []
|
||||
for page_type in cls.subpage_types:
|
||||
if isinstance(page_type, basestring):
|
||||
try:
|
||||
app_label, model_name = page_type.split(".")
|
||||
except ValueError:
|
||||
# If we can't split, assume a model in current app
|
||||
app_label = cls._meta.app_label
|
||||
model_name = page_type
|
||||
subpage_types = getattr(cls, 'subpage_types', None)
|
||||
if subpage_types is None:
|
||||
# if subpage_types is not specified on the Page class, allow all page types as subpages
|
||||
res = get_page_types()
|
||||
else:
|
||||
res = []
|
||||
for page_type in cls.subpage_types:
|
||||
if isinstance(page_type, basestring):
|
||||
try:
|
||||
app_label, model_name = page_type.split(".")
|
||||
except ValueError:
|
||||
# If we can't split, assume a model in current app
|
||||
app_label = cls._meta.app_label
|
||||
model_name = page_type
|
||||
|
||||
model = get_model(app_label, model_name)
|
||||
if model:
|
||||
res.append(ContentType.objects.get_for_model(model))
|
||||
else:
|
||||
raise NameError(_("name '{0}' (used in subpage_types list) is not defined.").format(page_type))
|
||||
|
||||
model = get_model(app_label, model_name)
|
||||
if model:
|
||||
res.append(model)
|
||||
else:
|
||||
raise NameError(_("name '{0}' (used in subpage_types list) is not defined.").format(page_type))
|
||||
|
||||
else:
|
||||
# assume it's already a model class
|
||||
res.append(page_type)
|
||||
# assume it's already a model class
|
||||
res.append(ContentType.objects.get_for_model(page_type))
|
||||
|
||||
cls._clean_subpage_types = res
|
||||
|
||||
|
|
@ -657,13 +653,8 @@ class Page(MP_Node, ClusterableModel, Indexed):
|
|||
|
||||
def get_navigation_menu_items():
|
||||
# Get all pages that appear in the navigation menu: ones which have children,
|
||||
# or are a non-leaf type (indicating that they *could* have children),
|
||||
# or are at the top-level (this rule required so that an empty site out-of-the-box has a working menu)
|
||||
navigable_content_type_ids = get_navigable_page_content_type_ids()
|
||||
if navigable_content_type_ids:
|
||||
pages = Page.objects.filter(Q(content_type__in=navigable_content_type_ids)|Q(depth=2)|Q(numchild__gt=0)).order_by('path')
|
||||
else:
|
||||
pages = Page.objects.filter(Q(depth=2)|Q(numchild__gt=0)).order_by('path')
|
||||
pages = Page.objects.filter(Q(depth=2)|Q(numchild__gt=0)).order_by('path')
|
||||
|
||||
# Turn this into a tree structure:
|
||||
# tree_node = (page, children)
|
||||
|
|
|
|||
136
wagtail/wagtailcore/tests/test_whitelist.py
Normal file
136
wagtail/wagtailcore/tests/test_whitelist.py
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
from bs4 import BeautifulSoup, NavigableString
|
||||
|
||||
from django.test import TestCase
|
||||
from wagtail.wagtailcore.whitelist import (
|
||||
check_url,
|
||||
attribute_rule,
|
||||
allow_without_attributes,
|
||||
Whitelister
|
||||
)
|
||||
|
||||
class TestCheckUrl(TestCase):
|
||||
def test_allowed_url_schemes(self):
|
||||
for url_scheme in ['', 'http', 'https', 'ftp', 'mailto', 'tel']:
|
||||
url = url_scheme + "://www.example.com"
|
||||
self.assertTrue(bool(check_url(url)))
|
||||
|
||||
def test_disallowed_url_scheme(self):
|
||||
self.assertFalse(bool(check_url("invalid://url")))
|
||||
|
||||
|
||||
class TestAttributeRule(TestCase):
|
||||
def setUp(self):
|
||||
self.soup = BeautifulSoup('<b foo="bar">baz</b>')
|
||||
|
||||
def test_no_rule_for_attr(self):
|
||||
"""
|
||||
Test that attribute_rule() drops attributes for
|
||||
which no rule has been defined.
|
||||
"""
|
||||
tag = self.soup.b
|
||||
fn = attribute_rule({'snowman': 'barbecue'})
|
||||
fn(tag)
|
||||
self.assertEqual(str(tag), '<b>baz</b>')
|
||||
|
||||
def test_rule_true_for_attr(self):
|
||||
"""
|
||||
Test that attribute_rule() does not change atrributes
|
||||
when the corresponding rule returns True
|
||||
"""
|
||||
tag = self.soup.b
|
||||
fn = attribute_rule({'foo': True})
|
||||
fn(tag)
|
||||
self.assertEqual(str(tag), '<b foo="bar">baz</b>')
|
||||
|
||||
def test_rule_false_for_attr(self):
|
||||
"""
|
||||
Test that attribute_rule() drops atrributes
|
||||
when the corresponding rule returns False
|
||||
"""
|
||||
tag = self.soup.b
|
||||
fn = attribute_rule({'foo': False})
|
||||
fn(tag)
|
||||
self.assertEqual(str(tag), '<b>baz</b>')
|
||||
|
||||
def test_callable_called_on_attr(self):
|
||||
"""
|
||||
Test that when the rule returns a callable,
|
||||
attribute_rule() replaces the attribute with
|
||||
the result of calling the callable on the attribute.
|
||||
"""
|
||||
tag = self.soup.b
|
||||
fn = attribute_rule({'foo': len})
|
||||
fn(tag)
|
||||
self.assertEqual(str(tag), '<b foo="3">baz</b>')
|
||||
|
||||
def test_callable_returns_None(self):
|
||||
"""
|
||||
Test that when the rule returns a callable,
|
||||
attribute_rule() replaces the attribute with
|
||||
the result of calling the callable on the attribute.
|
||||
"""
|
||||
tag = self.soup.b
|
||||
fn = attribute_rule({'foo': lambda x: None})
|
||||
fn(tag)
|
||||
self.assertEqual(str(tag), '<b>baz</b>')
|
||||
|
||||
def test_allow_without_attributes(self):
|
||||
"""
|
||||
Test that attribute_rule() with will drop all
|
||||
attributes.
|
||||
"""
|
||||
soup = BeautifulSoup('<b foo="bar" baz="quux" snowman="barbecue"></b>')
|
||||
tag = soup.b
|
||||
allow_without_attributes(tag)
|
||||
self.assertEqual(str(tag), '<b></b>')
|
||||
|
||||
|
||||
class TestWhitelister(TestCase):
|
||||
def test_clean_unknown_node(self):
|
||||
"""
|
||||
Unknown node should remove a node from the parent document
|
||||
"""
|
||||
soup = BeautifulSoup('<foo><bar>baz</bar>quux</foo>')
|
||||
tag = soup.foo
|
||||
Whitelister.clean_unknown_node('', soup.bar)
|
||||
self.assertEqual(str(tag), '<foo>quux</foo>')
|
||||
|
||||
def test_clean_tag_node_cleans_nested_recognised_node(self):
|
||||
"""
|
||||
<b> tags are allowed without attributes. This remains true
|
||||
when tags are nested.
|
||||
"""
|
||||
soup = BeautifulSoup('<b><b class="delete me">foo</b></b>')
|
||||
tag = soup.b
|
||||
Whitelister.clean_tag_node(tag, tag)
|
||||
self.assertEqual(str(tag), '<b><b>foo</b></b>')
|
||||
|
||||
def test_clean_tag_node_disallows_nested_unrecognised_node(self):
|
||||
"""
|
||||
<foo> tags should be removed, even when nested.
|
||||
"""
|
||||
soup = BeautifulSoup('<b><foo>bar</foo></b>')
|
||||
tag = soup.b
|
||||
Whitelister.clean_tag_node(tag, tag)
|
||||
self.assertEqual(str(tag), '<b>bar</b>')
|
||||
|
||||
def test_clean_string_node_does_nothing(self):
|
||||
soup = BeautifulSoup('<b>bar</b>')
|
||||
string = soup.b.string
|
||||
Whitelister.clean_string_node(string, string)
|
||||
self.assertEqual(str(string), 'bar')
|
||||
|
||||
def test_clean_node_does_not_change_navigable_strings(self):
|
||||
soup = BeautifulSoup('<b>bar</b>')
|
||||
string = soup.b.string
|
||||
Whitelister.clean_node(string, string)
|
||||
self.assertEqual(str(string), 'bar')
|
||||
|
||||
def test_clean(self):
|
||||
"""
|
||||
Whitelister.clean should remove disallowed tags and attributes from
|
||||
a string
|
||||
"""
|
||||
string = '<b foo="bar">snowman <barbecue>Yorkshire</barbecue></b>'
|
||||
cleaned_string = Whitelister.clean(string)
|
||||
self.assertEqual(cleaned_string, '<b>snowman Yorkshire</b>')
|
||||
|
|
@ -89,7 +89,10 @@ class Whitelister(object):
|
|||
cls.clean_string_node(doc, node)
|
||||
elif isinstance(node, Tag):
|
||||
cls.clean_tag_node(doc, node)
|
||||
else:
|
||||
# This branch is here in case node is a BeautifulSoup object that does
|
||||
# not inherit from NavigableString or Tag. I can't find any examples
|
||||
# of such a thing at the moment, so this branch is untested.
|
||||
else: # pragma: no cover
|
||||
cls.clean_unknown_node(doc, node)
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -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,24 @@
|
|||
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, unittest
|
||||
|
||||
from wagtail.wagtailembeds import get_embed
|
||||
from wagtail.wagtailembeds.embeds import (
|
||||
EmbedNotFoundException,
|
||||
EmbedlyException,
|
||||
AccessDeniedEmbedlyException,
|
||||
)
|
||||
from wagtail.wagtailembeds.embeds import embedly as wagtail_embedly, oembed as wagtail_oembed
|
||||
|
||||
|
||||
|
||||
class TestEmbeds(TestCase):
|
||||
|
|
@ -63,13 +80,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)
|
||||
|
|
|
|||
|
|
@ -2,46 +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.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',
|
||||
|
|
@ -53,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'})
|
||||
|
|
@ -65,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',
|
||||
|
|
@ -90,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',
|
||||
|
|
@ -112,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, )))
|
||||
|
|
@ -137,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)
|
||||
|
|
|
|||
|
|
@ -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