kopia lustrzana https://github.com/wagtail/wagtail
Merge branch 'master' of github.com:torchbox/wagtail
commit
d7b2b631a7
|
@ -0,0 +1,8 @@
|
|||
image: kaedroho/django-base
|
||||
script:
|
||||
- pip3.4 install mock python-dateutil pytz elasticsearch
|
||||
- python3.4 setup.py install
|
||||
- python3.4 runtests.py
|
||||
services:
|
||||
- postgres
|
||||
- dockerfile/elasticsearch
|
|
@ -22,7 +22,8 @@ Changelog
|
|||
* Added validation to prevent pages being crated with only whitespace characters in their title fields (Frank Wiles)
|
||||
* Page model fields without a FieldPanel are no longer displayed in the form
|
||||
* No longer need to specify the base model on InlinePanel definitions
|
||||
|
||||
* The project template Vagrantfile now listens on port 8000
|
||||
* The external link chooser in rich text areas now accepts URLs of the form '/some/local/path', to allow linking to non-Wagtail-controlled URLs within the local site (Eric Drechsel)
|
||||
|
||||
0.8.5 (xx.xx.20xx)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
@ -34,6 +35,10 @@ Changelog
|
|||
* Fix: Storage backends that return raw ContentFile objects are now handled correctly when resizing images (@georgewhewell)
|
||||
* Fix: Punctuation characters are no longer stripped when performing search queries
|
||||
* Fix: When adding tags where there were none before, it is now possible to save a single tag with multiple words in it
|
||||
* Fix: richtext template tag no longer raises TypeError if None is passed into it (Alejandro Varas)
|
||||
* Fix: Serving documents now uses a streaming HTTP response and will no longer break Djangos cache middleware
|
||||
* Fix: User admin area no longer fails in the presence of negative user IDs (as used by django-guardian's default settings)
|
||||
* Fix: Password reset emails now use the ``BASE_URL`` setting for the reset URL
|
||||
|
||||
0.8.4 (04.12.2014)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -42,6 +42,7 @@ Contributors
|
|||
* georgewhewell
|
||||
* Frank Wiles
|
||||
* Sebastian Spiegel
|
||||
* Alejandro Varas
|
||||
|
||||
Translators
|
||||
===========
|
||||
|
|
|
@ -23,7 +23,11 @@ Within the models.py of one of your apps, create a model that extends wagtailfor
|
|||
|
||||
|
||||
.. code:: python
|
||||
|
||||
|
||||
from modelcluster.fields import ParentalKey
|
||||
from wagtail.wagtailadmin.edit_handlers import (FieldPanel, InlinePanel,
|
||||
MultiFieldPanel)
|
||||
from wagtail.wagtailcore.fields import RichTextField
|
||||
from wagtail.wagtailforms.models import AbstractEmailForm, AbstractFormField
|
||||
|
||||
class FormField(AbstractFormField):
|
||||
|
|
|
@ -20,7 +20,6 @@ This example represents a typical blog post:
|
|||
from wagtail.wagtailcore.fields import RichTextField
|
||||
from wagtail.wagtailadmin.edit_handlers import FieldPanel
|
||||
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
|
||||
from wagtail.wagtailimages.models import Image
|
||||
|
||||
class BlogPage(Page):
|
||||
body = RichTextField()
|
||||
|
|
|
@ -1,148 +1,81 @@
|
|||
=====================
|
||||
Creating your project
|
||||
=====================
|
||||
===========================
|
||||
Starting your first project
|
||||
===========================
|
||||
|
||||
.. contents:: Contents
|
||||
:local:
|
||||
Once you've installed Wagtail, you are ready start your first project. Wagtail projects are ordinary Django projects with a few extra apps installed.
|
||||
|
||||
|
||||
The ``wagtail start`` command
|
||||
=============================
|
||||
|
||||
The easiest way to start a new project with wagtail is to use the ``wagtail start`` command. This command is installed into your environment when you install Wagtail (see: :doc:`installation`).
|
||||
|
||||
The command works the same way as ``django-admin.py startproject`` except that the produced project is pre-configured for Wagtail. It also contains some useful extras which we will look at in the next section.
|
||||
|
||||
To create a project, cd into a directory where you would like to create your project and run the following command:
|
||||
Wagtail provides a command to get you started called ``wagtail start``. Open up a command line shell in your project folder and type:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
wagtail start mysite
|
||||
|
||||
|
||||
The project
|
||||
===========
|
||||
|
||||
Lets look at what ``wagtail start`` created::
|
||||
|
||||
mysite/
|
||||
core/
|
||||
static/
|
||||
templates/
|
||||
base.html
|
||||
404.html
|
||||
500.html
|
||||
mysite/
|
||||
settings/
|
||||
base.py
|
||||
dev.py
|
||||
production.py
|
||||
manage.py
|
||||
vagrant/
|
||||
provision.sh
|
||||
Vagrantfile
|
||||
readme.rst
|
||||
requirements.txt
|
||||
|
||||
|
||||
The "core" app
|
||||
----------------
|
||||
|
||||
Location: ``/mysite/core/``
|
||||
|
||||
This app is here to help get you started quicker by providing a ``HomePage`` model with migrations to create one when you first setup your app.
|
||||
This should create a new folder called ``mysite``. Its contents are similar to what ``django-admin.py startproject`` creates but ``wagtail start`` comes with some useful extras that are documented :doc:`here <../reference/project_template>`.
|
||||
|
||||
|
||||
Default templates and static files
|
||||
----------------------------------
|
||||
Running it
|
||||
==========
|
||||
|
||||
Location: ``/mysite/core/templates/`` and ``/mysite/core/static/``
|
||||
|
||||
The templates directory contains ``base.html``, ``404.html`` and ``500.html``. These files are very commonly needed on Wagtail sites to they have been added into the template.
|
||||
|
||||
The static directory contains an empty javascript and sass file. Wagtail uses ``django-compressor`` for compiling and compressing static files. For more information, see: `Django Compressor Documentation <http://django-compressor.readthedocs.org/en/latest/>`_
|
||||
Firstly, open up a command line shell in your new projects directory.
|
||||
|
||||
|
||||
Vagrant configuration
|
||||
---------------------
|
||||
* **1. Create a virtual environment**
|
||||
|
||||
Location: ``/Vagrantfile`` and ``/vagrant/``
|
||||
This is only required when you first run your project. This creates a folder to install extra Python modules into.
|
||||
|
||||
If you have Vagrant installed, these files let you easily setup a development environment with PostgreSQL and Elasticsearch inside a virtual machine.
|
||||
**Linux/Mac OSX:** :code:`pyvenv venv`
|
||||
|
||||
See below section `With Vagrant`_ for info on how to use Vagrant in development
|
||||
|
||||
If you do not want to use Vagrant, you can just delete these files.
|
||||
**Windows:** :code:`c:\Python34\python -m venv myenv`
|
||||
|
||||
|
||||
Django settings
|
||||
---------------
|
||||
|
||||
Location: ``/mysite/mysite/settings/``
|
||||
|
||||
The Django settings files are split up into ``base.py``, ``dev.py``, ``production.py`` and ``local.py``.
|
||||
|
||||
.. glossary::
|
||||
|
||||
``base.py``
|
||||
|
||||
This file is for global settings that will be used in both development and production. Aim to keep most of your configuration in this file.
|
||||
|
||||
``dev.py``
|
||||
|
||||
This file is for settings that will only be used by developers. For example: ``DEBUG = True``
|
||||
|
||||
``production.py``
|
||||
|
||||
This file is for settings that will only run on a production server. For example: ``DEBUG = False``
|
||||
|
||||
``local.py``
|
||||
|
||||
This file is used for settings local to a particular machine. This file should never be tracked by a version control system.
|
||||
|
||||
.. tip::
|
||||
|
||||
On production servers, we recommend that you only store secrets in local.py (such as API keys and passwords). This can save you headaches in the future if you are ever trying to debug why a server is behaving badly. If you are using multiple servers which need different settings then we recommend that you create a different ``production.py`` file for each one.
|
||||
https://docs.python.org/3/library/venv.html
|
||||
|
||||
|
||||
Getting it running
|
||||
==================
|
||||
**Python 2.7**
|
||||
|
||||
``pyvenv`` is only included with Python 3.3 onwards. To get virtual environments on Python 2, use the ``virtualenv`` package:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install virtualenv
|
||||
virtualenv venv
|
||||
|
||||
|
||||
With Vagrant
|
||||
------------
|
||||
* **2. Activate the virtual environment**
|
||||
|
||||
This is the easiest way to get the project running. Vagrant runs your project locally in a virtual machine so you can use PostgreSQL and Elasticsearch in development without having to install them on your host machine. If you haven't yet installed Vagrant, see: `Installing Vagrant <https://docs.vagrantup.com/v2/installation/>`_.
|
||||
**Linux/Mac OSX:** :code:`source venv/bin/activate`
|
||||
|
||||
**Windows:** :code:`venv/Scripts/activate.bat`
|
||||
|
||||
https://docs.python.org/3/library/venv.html
|
||||
|
||||
|
||||
To setup the Vagrant box, run the following commands
|
||||
* **3. Install PIP requirements**
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
vagrant up # This may take some time on first run
|
||||
vagrant ssh
|
||||
# within the ssh session
|
||||
dj createsuperuser
|
||||
djrun
|
||||
:code:`pip install -r requirements.txt`
|
||||
|
||||
|
||||
If you now visit http://localhost:8111 you should see a very basic "Welcome to your new Wagtail site!" page.
|
||||
* **4. Create the database**
|
||||
|
||||
You can browse the Wagtail admin interface at: http://localhost:8111/admin
|
||||
By default, this would create an SQLite database file within the project directory.
|
||||
|
||||
You can read more about how Vagrant works at: https://docs.vagrantup.com/v2/
|
||||
:code:`python manage.py migrate`
|
||||
|
||||
|
||||
.. topic:: The ``dj`` and ``djrun`` aliases
|
||||
* **5. Create an admin user**
|
||||
|
||||
When using Vagrant, the Wagtail template provides two aliases: ``dj`` and ``djrun`` which can be used in the ``vagrant ssh`` session.
|
||||
:code:`python manage.py createsuperuser`
|
||||
|
||||
.. glossary::
|
||||
|
||||
``dj``
|
||||
|
||||
This is short for ``python manage.py`` so you can use it to reduce typing. For example: ``python manage.py syncdb`` becomes ``dj syncdb``.
|
||||
* **6. Run the development server**
|
||||
|
||||
``djrun``
|
||||
|
||||
This is short for ``python manage.py runserver 0.0.0.0:8000``. This is used to run the testing server which is accessible from ``http://localhost:8111`` (note that the port number gets changed by Vagrant)
|
||||
:code:`python manage.py runserver`
|
||||
|
||||
Your site is now accessible at ``http://localhost:8000``, with the admin backend available at ``http://localhost:8000/admin/``.
|
||||
|
||||
|
||||
Using Vagrant
|
||||
-------------
|
||||
|
||||
:doc:`using_vagrant`
|
||||
|
|
|
@ -5,5 +5,7 @@ Getting started
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
trying_wagtail
|
||||
installation
|
||||
creating_your_project
|
||||
using_vagrant
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
Installation
|
||||
============
|
||||
|
||||
|
||||
Before you start
|
||||
================
|
||||
|
||||
|
@ -10,75 +11,46 @@ A basic Wagtail setup can be installed on your machine with only a few prerequis
|
|||
Whether you just want to try out the demo site, or you're ready to dive in and create a Wagtail site with all bells and whistles enabled, we strongly recommend the Vagrant approach. Nevertheless, if you're the sort of person who balks at the idea of downloading a whole operating system just to run a web app, we've got you covered too. Start from `A basic Wagtail installation`_ below.
|
||||
|
||||
|
||||
The demo site (a.k.a. the no-installation route)
|
||||
================================================
|
||||
Install Python
|
||||
==============
|
||||
|
||||
We provide a demo site containing a set of standard templates and page types - if you're new to Wagtail, this is the best way to try it out and familiarise yourself with how Wagtail works from the point of view of an editor.
|
||||
|
||||
If you're happy to use Vagrant, and you just want to set up the Wagtail demo site, or any other pre-existing Wagtail site that ships with Vagrant support, you don't need to install Wagtail at all. Install `Vagrant <http://www.vagrantup.com/>`__ and `VirtualBox <https://www.virtualbox.org/>`__, and run::
|
||||
|
||||
git clone https://github.com/torchbox/wagtaildemo.git
|
||||
cd wagtaildemo
|
||||
vagrant up
|
||||
vagrant ssh
|
||||
If you haven't got Python installed yet, we recommend installing Python 3.4. You can find the download for it here: https://www.python.org/downloads/
|
||||
|
||||
|
||||
Then, within the SSH session::
|
||||
pip
|
||||
---
|
||||
|
||||
./manage.py createsuperuser
|
||||
./manage.py runserver 0.0.0.0:8000
|
||||
Python 3.4 has this built in. If you are using Python 2.7 or 3.3, you will have to install PIP separately
|
||||
|
||||
See: https://pip.pypa.io/en/latest/installing.html
|
||||
|
||||
|
||||
This will make the demo site available on your host machine at the URL http://localhost:8111/ - you can access the Wagtail admin interface at http://localhost:8111/admin/ . Further instructions can be found at :ref:`editor_manual`.
|
||||
Virtual environments
|
||||
--------------------
|
||||
|
||||
Once you’ve experimented with the demo site and are ready to build your own site, it's time to install Wagtail on your host machine. Even if you intend to do all further Wagtail work within Vagrant, installing the Wagtail package on your host machine will provide the ``wagtail start`` command that sets up the initial file structure for your project.
|
||||
Python 3.3 and 3.4 has this built in. If you are using Python 2.7 you will have to install the ``virtualenv`` package from pip:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install virtualenv
|
||||
|
||||
|
||||
A basic Wagtail installation
|
||||
============================
|
||||
Install Wagtail
|
||||
===============
|
||||
|
||||
This provides everything you need to create a new Wagtail project from scratch, containing no page definitions or templates other than a basic homepage as a starting point for building your site. (For a gentler introduction to Wagtail, you may wish to try out the demo site first!)
|
||||
Wagtail is available as a pip-installable package. To get the latest stable version:
|
||||
|
||||
You will need Python's `pip <http://pip.readthedocs.org/en/latest/installing.html>`__ package manager. We also recommend `virtualenvwrapper <http://virtualenvwrapper.readthedocs.org/en/latest/>`_ so that you can manage multiple independent Python environments for different projects - although this is not strictly necessary if you intend to do all your development under Vagrant.
|
||||
|
||||
Wagtail is based on the Django web framework and various other Python libraries. Most of these are pure Python and will install automatically using ``pip``, but there are a few native-code components that require further attention:
|
||||
|
||||
* libsass-python (for compiling SASS stylesheets) - requires a C++ compiler and the Python development headers.
|
||||
* Pillow (for image processing) - additionally requires libjpeg and zlib.
|
||||
|
||||
On Debian or Ubuntu, these can be installed with the command::
|
||||
|
||||
sudo apt-get install python-dev python-pip g++ libjpeg62-dev zlib1g-dev
|
||||
|
||||
With these dependencies installed, Wagtail can then be installed with the command::
|
||||
.. code-block:: bash
|
||||
|
||||
pip install wagtail
|
||||
|
||||
(or if you're not using virtualenvwrapper: ``sudo pip install wagtail``.)
|
||||
|
||||
You will now be able to run the following command to set up an initial file structure for your Wagtail project (replace ``myprojectname`` with a name of your choice)::
|
||||
To check that Wagtail can be seen by Python. Type ``python`` in your shell then try to import ``wagtail`` from the prompt:
|
||||
|
||||
wagtail start myprojectname
|
||||
.. code-block:: python
|
||||
|
||||
**Without Vagrant:** Run the following steps to complete setup of your project (the ``createsuperuser`` step will prompt you to set up a superuser account)::
|
||||
>>> import wagtail
|
||||
|
||||
cd myprojectname
|
||||
pip install -r requirements.txt
|
||||
python manage.py migrate
|
||||
python manage.py createsuperuser
|
||||
python manage.py runserver
|
||||
|
||||
Your site is now accessible at http://localhost:8000, with the admin backend available at http://localhost:8000/admin/ .
|
||||
|
||||
**With Vagrant:** Run the following steps to bring up the virtual machine and complete setup of your project (the ``createsuperuser`` step will prompt you to set up a superuser account)::
|
||||
|
||||
cd myprojectname
|
||||
vagrant up
|
||||
vagrant ssh
|
||||
./manage.py createsuperuser
|
||||
./manage.py runserver 0.0.0.0:8000
|
||||
|
||||
Your site is now accessible at http://localhost:8111, with the admin backend available at http://localhost:8111/admin/ .
|
||||
|
||||
Optional extras
|
||||
===============
|
||||
|
@ -104,6 +76,7 @@ This assumes that your PostgreSQL instance is configured to allow you to connect
|
|||
|
||||
ElasticSearch
|
||||
-------------
|
||||
|
||||
Wagtail integrates with ElasticSearch to provide full-text searching of your content, both within the Wagtail interface and on your site's front-end. If ElasticSearch is not available, Wagtail will fall back to much more basic search functionality using database queries. ElasticSearch is pre-installed as part of the Vagrant virtual machine image; non-Vagrant users can use the `debian.sh <https://github.com/torchbox/wagtail/blob/master/scripts/install/debian.sh>`__ or `ubuntu.sh <https://github.com/torchbox/wagtail/blob/master/scripts/install/ubuntu.sh>`__ installation scripts as a guide.
|
||||
|
||||
To enable ElasticSearch for your project, uncomment the ``elasticsearch`` line from your project's requirements.txt, and in ``myprojectname/settings/base.py``, uncomment the WAGTAILSEARCH_BACKENDS section. Then run::
|
||||
|
@ -115,52 +88,3 @@ To enable ElasticSearch for your project, uncomment the ``elasticsearch`` line f
|
|||
Image feature detection
|
||||
-----------------------
|
||||
Wagtail can use the OpenCV computer vision library to detect faces and other features in images, and use this information to select the most appropriate centre point when cropping the image. OpenCV is pre-installed as part of the Vagrant virtual machine image, and Vagrant users can enable this by setting ``WAGTAILIMAGES_FEATURE_DETECTION_ENABLED`` to True in ``myprojectname/settings/base.py``. For installation outside of Vagrant, see :ref:`image_feature_detection`.
|
||||
|
||||
|
||||
Alternative installation methods
|
||||
================================
|
||||
|
||||
Ubuntu
|
||||
------
|
||||
|
||||
If you have a fresh instance of Ubuntu 13.04 or later, you can install Wagtail,
|
||||
along with a demonstration site containing a set of standard templates and page
|
||||
types, in one step. As the root user::
|
||||
|
||||
curl -O https://raw.githubusercontent.com/torchbox/wagtail/master/scripts/install/ubuntu.sh; bash ubuntu.sh
|
||||
|
||||
This script installs all the dependencies for a production-ready Wagtail site,
|
||||
including PostgreSQL, Redis, Elasticsearch, Nginx and uwsgi. We
|
||||
recommend you check through the script before running it, and adapt it according
|
||||
to your deployment preferences. The canonical version is at
|
||||
`github.com/torchbox/wagtail/blob/master/scripts/install/ubuntu.sh
|
||||
<https://github.com/torchbox/wagtail/blob/master/scripts/install/ubuntu.sh>`_.
|
||||
|
||||
|
||||
Debian
|
||||
------
|
||||
|
||||
If you have a fresh instance of Debian 7, you can install Wagtail, along with a
|
||||
demonstration site containing a set of standard templates and page types, in one
|
||||
step. As the root user::
|
||||
|
||||
curl -O https://raw.githubusercontent.com/torchbox/wagtail/master/scripts/install/debian.sh; bash debian.sh
|
||||
|
||||
This script installs all the dependencies for a production-ready Wagtail site,
|
||||
including PostgreSQL, Redis, Elasticsearch, Nginx and uwsgi. We
|
||||
recommend you check through the script before running it, and adapt it according
|
||||
to your deployment preferences. The canonical version is at
|
||||
`github.com/torchbox/wagtail/blob/master/scripts/install/debian.sh
|
||||
<https://github.com/torchbox/wagtail/blob/master/scripts/install/debian.sh>`_.
|
||||
|
||||
Docker
|
||||
------
|
||||
|
||||
`@oyvindsk <https://github.com/oyvindsk>`_ has built a Dockerfile for the Wagtail demo. Simply run::
|
||||
|
||||
docker run -p 8000:8000 -d oyvindsk/wagtail-demo
|
||||
|
||||
then access the site at http://your-ip:8000 and the admin
|
||||
interface at http://your-ip:8000/admin using admin / test.
|
||||
|
||||
See https://index.docker.io/u/oyvindsk/wagtail-demo/ for more details.
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
==============
|
||||
Trying Wagtail
|
||||
==============
|
||||
|
||||
|
||||
Wagtail demo
|
||||
============
|
||||
|
||||
We provide a demo site containing a set of standard templates and page types - if you're new to Wagtail, this is the best way to try it out and familiarise yourself with how Wagtail works from the point of view of an editor.
|
||||
|
||||
If you're happy to use Vagrant, and you just want to set up the Wagtail demo site, or any other pre-existing Wagtail site that ships with Vagrant support, you don't need to install Wagtail at all. Install `Vagrant <http://www.vagrantup.com/>`__ and `VirtualBox <https://www.virtualbox.org/>`__, and run::
|
||||
|
||||
git clone https://github.com/torchbox/wagtaildemo.git
|
||||
cd wagtaildemo
|
||||
vagrant up
|
||||
vagrant ssh
|
||||
|
||||
|
||||
Then, within the SSH session::
|
||||
|
||||
./manage.py createsuperuser
|
||||
./manage.py runserver 0.0.0.0:8000
|
||||
|
||||
|
||||
This will make the demo site available on your host machine at the URL http://localhost:8000/ - you can access the Wagtail admin interface at http://localhost:8000/admin/ . Further instructions can be found at :ref:`editor_manual`.
|
||||
|
||||
Once you’ve experimented with the demo site and are ready to build your own site, it's time to install Wagtail on your host machine. Even if you intend to do all further Wagtail work within Vagrant, installing the Wagtail package on your host machine will provide the ``wagtail start`` command that sets up the initial file structure for your project.
|
||||
|
||||
|
||||
One line install
|
||||
================
|
||||
|
||||
|
||||
Ubuntu
|
||||
------
|
||||
|
||||
If you have a fresh instance of Ubuntu 13.04 or later, you can install Wagtail,
|
||||
along with a demonstration site containing a set of standard templates and page
|
||||
types, in one step. As the root user::
|
||||
|
||||
curl -O https://raw.githubusercontent.com/torchbox/wagtail/master/scripts/install/ubuntu.sh; bash ubuntu.sh
|
||||
|
||||
This script installs all the dependencies for a production-ready Wagtail site,
|
||||
including PostgreSQL, Redis, Elasticsearch, Nginx and uwsgi. We
|
||||
recommend you check through the script before running it, and adapt it according
|
||||
to your deployment preferences. The canonical version is at
|
||||
`github.com/torchbox/wagtail/blob/master/scripts/install/ubuntu.sh
|
||||
<https://github.com/torchbox/wagtail/blob/master/scripts/install/ubuntu.sh>`_.
|
||||
|
||||
|
||||
Debian
|
||||
------
|
||||
|
||||
If you have a fresh instance of Debian 7, you can install Wagtail, along with a
|
||||
demonstration site containing a set of standard templates and page types, in one
|
||||
step. As the root user::
|
||||
|
||||
curl -O https://raw.githubusercontent.com/torchbox/wagtail/master/scripts/install/debian.sh; bash debian.sh
|
||||
|
||||
This script installs all the dependencies for a production-ready Wagtail site,
|
||||
including PostgreSQL, Redis, Elasticsearch, Nginx and uwsgi. We
|
||||
recommend you check through the script before running it, and adapt it according
|
||||
to your deployment preferences. The canonical version is at
|
||||
`github.com/torchbox/wagtail/blob/master/scripts/install/debian.sh
|
||||
<https://github.com/torchbox/wagtail/blob/master/scripts/install/debian.sh>`_.
|
||||
|
||||
|
||||
Docker
|
||||
======
|
||||
|
||||
`@oyvindsk <https://github.com/oyvindsk>`_ has built a Dockerfile for the Wagtail demo. Simply run::
|
||||
|
||||
docker run -p 8000:8000 -d oyvindsk/wagtail-demo
|
||||
|
||||
then access the site at http://your-ip:8000 and the admin
|
||||
interface at http://your-ip:8000/admin using admin / test.
|
||||
|
||||
See https://index.docker.io/u/oyvindsk/wagtail-demo/ for more details.
|
|
@ -0,0 +1,37 @@
|
|||
Using Vagrant
|
||||
=============
|
||||
|
||||
This is the easiest way to get the project running. Vagrant runs your project locally in a virtual machine so you can use PostgreSQL and Elasticsearch in development without having to install them on your host machine. If you haven't yet installed Vagrant, see: `Installing Vagrant <https://docs.vagrantup.com/v2/installation/>`_.
|
||||
|
||||
|
||||
To setup the Vagrant box, run the following commands
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
vagrant up # This may take some time on first run
|
||||
vagrant ssh
|
||||
# within the ssh session
|
||||
dj createsuperuser
|
||||
djrun
|
||||
|
||||
|
||||
If you now visit http://localhost:8000 you should see a very basic "Welcome to your new Wagtail site!" page.
|
||||
|
||||
You can browse the Wagtail admin interface at: http://localhost:8000/admin
|
||||
|
||||
You can read more about how Vagrant works at: https://docs.vagrantup.com/v2/
|
||||
|
||||
|
||||
.. topic:: The ``dj`` and ``djrun`` aliases
|
||||
|
||||
When using Vagrant, the Wagtail template provides two aliases: ``dj`` and ``djrun`` which can be used in the ``vagrant ssh`` session.
|
||||
|
||||
.. glossary::
|
||||
|
||||
``dj``
|
||||
|
||||
This is short for ``python manage.py`` so you can use it to reduce typing. For example: ``python manage.py syncdb`` becomes ``dj syncdb``.
|
||||
|
||||
``djrun``
|
||||
|
||||
This is short for ``python manage.py runserver 0.0.0.0:8000``. This is used to run the testing server which is accessible from ``http://localhost:8000`` (note that the port number gets changed by Vagrant)
|
|
@ -1,12 +1,44 @@
|
|||
Welcome to Wagtail's documentation
|
||||
==================================
|
||||
|
||||
Wagtail is a modern, flexible CMS, built on Django.
|
||||
Wagtail is an open source CMS written in `Python <https://www.python.org/>`_ and built on the `Django web framework <https://www.djangoproject.com/>`_.
|
||||
|
||||
Below are some useful links to help you get started with Wagtail.
|
||||
|
||||
|
||||
* **First steps**
|
||||
|
||||
:doc:`getting_started/trying_wagtail`
|
||||
|
||||
:doc:`getting_started/installation`
|
||||
|
||||
:doc:`getting_started/creating_your_project`
|
||||
|
||||
|
||||
* **Creating your Wagtail site**
|
||||
|
||||
:doc:`core_components/pages/creating_pages`
|
||||
|
||||
:doc:`Writing templates <core_components/pages/writing_templates>`
|
||||
|
||||
:doc:`core_components/images/index`
|
||||
|
||||
:doc:`core_components/search/index`
|
||||
|
||||
:doc:`howto/third_party_tutorials`
|
||||
|
||||
|
||||
* **Using Wagtail**
|
||||
|
||||
:doc:`Editors guide <editor_manual/index>`
|
||||
|
||||
|
||||
Index
|
||||
-----
|
||||
|
||||
It supports Django 1.7.0+ on Python 2.7, 3.3 and 3.4.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
:maxdepth: 2
|
||||
:titlesonly:
|
||||
|
||||
getting_started/index
|
||||
|
|
|
@ -6,3 +6,4 @@ Reference
|
|||
:maxdepth: 2
|
||||
|
||||
management_commands
|
||||
project_template
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
The project template
|
||||
====================
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
mysite/
|
||||
core/
|
||||
static/
|
||||
templates/
|
||||
base.html
|
||||
404.html
|
||||
500.html
|
||||
mysite/
|
||||
settings/
|
||||
base.py
|
||||
dev.py
|
||||
production.py
|
||||
manage.py
|
||||
vagrant/
|
||||
provision.sh
|
||||
Vagrantfile
|
||||
readme.rst
|
||||
requirements.txt
|
||||
|
||||
|
||||
The "core" app
|
||||
----------------
|
||||
|
||||
Location: ``/mysite/core/``
|
||||
|
||||
This app is here to help get you started quicker by providing a ``HomePage`` model with migrations to create one when you first setup your app.
|
||||
|
||||
|
||||
Default templates and static files
|
||||
----------------------------------
|
||||
|
||||
Location: ``/mysite/core/templates/`` and ``/mysite/core/static/``
|
||||
|
||||
The templates directory contains ``base.html``, ``404.html`` and ``500.html``. These files are very commonly needed on Wagtail sites to they have been added into the template.
|
||||
|
||||
The static directory contains an empty javascript and sass file. Wagtail uses ``django-compressor`` for compiling and compressing static files. For more information, see: `Django Compressor Documentation <http://django-compressor.readthedocs.org/en/latest/>`_
|
||||
|
||||
|
||||
Vagrant configuration
|
||||
---------------------
|
||||
|
||||
Location: ``/Vagrantfile`` and ``/vagrant/``
|
||||
|
||||
If you have Vagrant installed, these files let you easily setup a development environment with PostgreSQL and Elasticsearch inside a virtual machine.
|
||||
|
||||
See :doc:`../getting_started/using_vagrant` for info on how to use Vagrant in development
|
||||
|
||||
If you do not want to use Vagrant, you can just delete these files.
|
||||
|
||||
|
||||
Django settings
|
||||
---------------
|
||||
|
||||
Location: ``/mysite/mysite/settings/``
|
||||
|
||||
The Django settings files are split up into ``base.py``, ``dev.py``, ``production.py`` and ``local.py``.
|
||||
|
||||
.. glossary::
|
||||
|
||||
``base.py``
|
||||
|
||||
This file is for global settings that will be used in both development and production. Aim to keep most of your configuration in this file.
|
||||
|
||||
``dev.py``
|
||||
|
||||
This file is for settings that will only be used by developers. For example: ``DEBUG = True``
|
||||
|
||||
``production.py``
|
||||
|
||||
This file is for settings that will only run on a production server. For example: ``DEBUG = False``
|
||||
|
||||
``local.py``
|
||||
|
||||
This file is used for settings local to a particular machine. This file should never be tracked by a version control system.
|
||||
|
||||
.. tip::
|
||||
|
||||
On production servers, we recommend that you only store secrets in local.py (such as API keys and passwords). This can save you headaches in the future if you are ever trying to debug why a server is behaving badly. If you are using multiple servers which need different settings then we recommend that you create a different ``production.py`` file for each one.
|
|
@ -20,3 +20,6 @@ Bug fixes
|
|||
* Storage backends that return raw ContentFile objects are now handled correctly when resizing images
|
||||
* Punctuation characters are no longer stripped when performing search queries
|
||||
* When adding tags where there were none before, it is now possible to save a single tag with multiple words in it
|
||||
* ``richtext`` template tag no longer raises ``TypeError`` if ``None`` is passed into it
|
||||
* Serving documents now uses a streaming HTTP response and will no longer break Djangos cache middleware
|
||||
* Fix: User admin area no longer fails in the presence of negative user IDs (as used by django-guardian's default settings)
|
|
@ -23,14 +23,17 @@ Minor features
|
|||
* Dropped Python 2.6 and 3.2 support
|
||||
* Dropped Elasticsearch 0.90.x support
|
||||
* Search view accepts "page" GET parameter in line with pagination
|
||||
* Removed the dependency on `LOGIN_URL` and `LOGIN_REDIRECT_URL` settings
|
||||
* Removed the dependency on ``LOGIN_URL`` and ``LOGIN_REDIRECT_URL`` settings
|
||||
* Password reset view names namespaced to wagtailadmin
|
||||
* Removed the need to add permission check on admin views (now automated)
|
||||
* Reversing `django.contrib.auth.admin.login` will no longer lead to Wagtails login view (making it easier to have front end views)
|
||||
* Reversing ``django.contrib.auth.admin.login`` will no longer lead to Wagtails login view (making it easier to have front end views)
|
||||
* Added cache-control headers to all admin views. This allows Varnish/Squid/CDN to run on vanilla settings in front of a Wagtail site
|
||||
* Added validation to prevent pages being crated with only whitespace characters in their title fields
|
||||
* Added validation to prevent pages being created with only whitespace characters in their title fields
|
||||
* Page model fields without a FieldPanel are no longer displayed in the form
|
||||
* No longer need to specify the base model on InlinePanel definitions
|
||||
* The project template Vagrantfile now listens on port 8000
|
||||
* The external link chooser in rich text areas now accepts URLs of the form '/some/local/path', to allow linking to non-Wagtail-controlled URLs within the local site
|
||||
* Password reset emails now use the ``BASE_URL`` setting for the reset URL
|
||||
|
||||
|
||||
Bug fixes
|
||||
|
@ -47,18 +50,51 @@ This release drops support for Django 1.6, Python 2.6/3.2 and Elasticsearch 0.90
|
|||
|
||||
If you are upgrading from Elasticsearch 0.90.x, you may also need to update the ``elasticsearch`` pip package to a version greater than ``1.0`` as well.
|
||||
|
||||
InlinePanel definitions no longer need to specify the base model
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In previous versions of Wagtail, inline child blocks on a page or snippet were defined using a declaration like::
|
||||
|
||||
InlinePanel(HomePage, 'carousel_items', label="Carousel items")
|
||||
|
||||
It is no longer necessary to pass the base model as a parameter, so this declaration should be changed to::
|
||||
|
||||
InlinePanel('carousel_items', label="Carousel items")
|
||||
|
||||
The old format is now deprecated; all existing InlinePanel declarations should be updated to the new format.
|
||||
|
||||
Login/Password reset views renamed
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It was previously possible to reverse the Wagtail login using django.contrib.auth.views.login.
|
||||
This is no longer possible. Update any references to `wagtailadmin_login`.
|
||||
This is no longer possible. Update any references to ``wagtailadmin_login``.
|
||||
|
||||
Password reset view name has changed from `password_reset` to `wagtailadmin_password_reset`.
|
||||
Password reset view name has changed from ``password_reset`` to ``wagtailadmin_password_reset``.
|
||||
|
||||
You no longer need `LOGIN_URL` and `LOGIN_REDIRECT_URL` to point to Wagtail admin.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
You no longer need ``LOGIN_URL`` and ``LOGIN_REDIRECT_URL`` to point to Wagtail admin.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Javascript includes in admin backend have been moved
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To improve compatibility with third-party form widgets, pages within the Wagtail admin backend now output their Javascript includes in the HTML header, rather than at the end of the page. If your project extends the admin backend (through the ``register_admin_menu_item`` hook, for example) you will need to ensure that all associated Javascript code runs correctly from the new location. In particular, any code that accesses HTML elements will need to be contained in an 'onload' handler (e.g. jQuery's ``$(document).ready()``).
|
||||
|
||||
EditHandler internal API has changed
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
While it is not an official Wagtail API, it has been possible for Wagtail site implementers to define their own ``EditHandler`` subclasses for use in panel definitions, to customise the behaviour of the page / snippet editing forms. If you have made use of this facility, you will need to update your custom EditHandlers, as this mechanism has been refactored (to allow EditHandler classes to keep a persistent reference to their corresponding model). If you have only used Wagtail's built-in panel types (``FieldPanel``, ``InlinePanel``, ``PageChooserPanel`` and so on), you are unaffected by this change.
|
||||
|
||||
Previously, functions like ``FieldPanel`` acted as 'factory' functions, where a call such as ``FieldPanel('title')`` constructed and returned an ``EditHandler`` subclass tailored to work on a 'title' field. These functions now return an object with a ``bind_to_model`` method instead; the EditHandler subclass can be obtained by calling this with the model class as a parameter. As a guide to updating your custom EditHandler code, you may wish to refer to `the relevant change to the Wagtail codebase <https://github.com/torchbox/wagtail/commit/121c01c7f7db6087a985fa8dc9957bc78b9f6a6a>`_.
|
||||
|
||||
chooser_panel templates are obsolete
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you have added your own custom admin views to the Wagtail admin (e.g. through the ``register_admin_urls`` hook), you may have used one of the following template includes to incorporate a chooser element for pages, documents, images or snippets into your forms:
|
||||
|
||||
- ``wagtailadmin/edit_handlers/chooser_panel.html``
|
||||
- ``wagtailadmin/edit_handlers/page_chooser_panel.html``
|
||||
- ``wagtaildocs/edit_handlers/document_chooser_panel.html``
|
||||
- ``wagtailimages/edit_handlers/image_chooser_panel.html``
|
||||
- ``wagtailsnippets/edit_handlers/snippet_chooser_panel.html``
|
||||
|
||||
All of these templates are now deprecated. Wagtail now provides a set of Django form widgets for this purpose - ``AdminPageChooser``, ``AdminDocumentChooser``, ``AdminImageChooser`` and ``AdminSnippetChooser`` - which can be used in place of the ``HiddenInput`` widget that these form fields were previously using. The field can then be rendered using the regular ``wagtailadmin/shared/field.html`` or ``wagtailadmin/shared/field_as_li.html`` template.
|
||||
|
|
|
@ -347,12 +347,6 @@
|
|||
{% for field in example_form %}
|
||||
{% if field.name == 'file' %}
|
||||
{% include "wagtailimages/images/_file_field.html" %}
|
||||
{% elif field.name == 'page_chooser' %}
|
||||
<li>{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=field choose_one_text_str="Choose a page" choose_another_text_str="Choose another page" only %}</li>
|
||||
{% elif field.name == 'image_chooser' %}
|
||||
<li>{% include "wagtailimages/edit_handlers/image_chooser_panel.html" with field=field choose_one_text_str="Choose an image" choose_another_text_str="Choose another image" only %}</li>
|
||||
{% elif field.name == 'document_chooser' %}
|
||||
<li>{% include "wagtaildocs/edit_handlers/document_chooser_panel.html" with field=field choose_one_text_str="Choose a document" choose_another_text_str="Choose another document" only %}</li>
|
||||
{% else %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" %}
|
||||
{% endif %}
|
||||
|
|
|
@ -8,7 +8,7 @@ Vagrant::Config.run do |config|
|
|||
|
||||
# Forward a port from the guest to the host, which allows for outside
|
||||
# computers to access the VM, whereas host only networking does not.
|
||||
config.vm.forward_port 8000, 8111
|
||||
config.vm.forward_port 8000, 8000
|
||||
|
||||
# Share an additional folder to the guest VM. The first argument is
|
||||
# an identifier, the second is the path on the guest to mount the
|
||||
|
|
|
@ -16,6 +16,8 @@ DATABASES = {
|
|||
'TEST_NAME': os.environ.get('DATABASE_NAME', 'test_wagtaildemo'),
|
||||
'USER': os.environ.get('DATABASE_USER', 'postgres'),
|
||||
'PASSWORD': os.environ.get('DATABASE_PASS', None),
|
||||
'HOST': os.environ.get('POSTGRES_PORT_5432_TCP_ADDR', None),
|
||||
'PORT': os.environ.get('POSTGRES_PORT_5432_TCP_PORT', None),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,6 +123,13 @@ try:
|
|||
'TIMEOUT': 10,
|
||||
'max_retries': 1,
|
||||
}
|
||||
|
||||
# Check if we're running in Drone
|
||||
if 'ELASTICSEARCH_PORT_9200_TCP_PORT' in os.environ:
|
||||
ip = os.environ.get('ELASTICSEARCH_PORT_9200_TCP_ADDR')
|
||||
port = os.environ.get('ELASTICSEARCH_PORT_9200_TCP_PORT')
|
||||
|
||||
WAGTAILSEARCH_BACKENDS['elasticsearch']['URLS'] = ['http://%s:%s/' % (ip, port)]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
|
|
@ -25,9 +25,9 @@ class WagtailTestUtils(object):
|
|||
with warnings.catch_warnings(record=True) as warning_list: # catch all warnings
|
||||
yield
|
||||
|
||||
# rethrow all warnings that were not DeprecationWarnings
|
||||
# rethrow all warnings that were not DeprecationWarnings or PendingDeprecationWarnings
|
||||
for w in warning_list:
|
||||
if not issubclass(w.category, DeprecationWarning):
|
||||
if not issubclass(w.category, (DeprecationWarning, PendingDeprecationWarning)):
|
||||
warnings.showwarning(message=w.message, category=w.category, filename=w.filename, lineno=w.lineno, file=w.file, line=w.line)
|
||||
|
||||
# borrowed from https://github.com/django/django/commit/9f427617e4559012e1c2fd8fce46cbe225d8515d
|
||||
|
|
|
@ -5,16 +5,22 @@ from django.utils.safestring import mark_safe
|
|||
|
||||
|
||||
class WidgetWithScript(Widget):
|
||||
def render(self, name, value, attrs=None):
|
||||
widget = super(WidgetWithScript, self).render(name, value, attrs)
|
||||
def render_html(self, name, value, attrs):
|
||||
"""Render the HTML (non-JS) portion of the field markup"""
|
||||
return super(WidgetWithScript, self).render(name, value, attrs)
|
||||
|
||||
final_attrs = self.build_attrs(attrs, name=name)
|
||||
id_ = final_attrs.get('id', None)
|
||||
if id_ is None:
|
||||
return widget
|
||||
def render(self, name, value, attrs=None):
|
||||
# no point trying to come up with sensible semantics for when 'id' is missing from attrs,
|
||||
# so let's make sure it fails early in the process
|
||||
try:
|
||||
id_ = attrs['id']
|
||||
except (KeyError, TypeError):
|
||||
raise TypeError("WidgetWithScript cannot be rendered without an 'id' attribute")
|
||||
|
||||
widget_html = self.render_html(name, value, attrs)
|
||||
|
||||
js = self.render_js_init(id_, name, value)
|
||||
out = '{0}<script>{1}</script>'.format(widget, js)
|
||||
out = '{0}<script>{1}</script>'.format(widget_html, js)
|
||||
return mark_safe(out)
|
||||
|
||||
def render_js_init(self, id_, name, value):
|
||||
|
|
|
@ -422,18 +422,16 @@ class BaseFieldPanel(EditHandler):
|
|||
def render_as_object(self):
|
||||
return mark_safe(render_to_string(self.object_template, {
|
||||
'self': self,
|
||||
'field_content': self.render_as_field(show_help_text=False),
|
||||
'field_content': self.render_as_field(),
|
||||
}))
|
||||
|
||||
field_template = "wagtailadmin/edit_handlers/field_panel_field.html"
|
||||
|
||||
def render_as_field(self, show_help_text=True, extra_context={}):
|
||||
def render_as_field(self):
|
||||
context = {
|
||||
'field': self.bound_field,
|
||||
'field_type': self.field_type(),
|
||||
'show_help_text': show_help_text,
|
||||
}
|
||||
context.update(extra_context)
|
||||
return mark_safe(render_to_string(self.field_template, context))
|
||||
|
||||
@classmethod
|
||||
|
@ -482,7 +480,7 @@ class BaseChooserPanel(BaseFieldPanel):
|
|||
a hidden foreign key input.
|
||||
|
||||
Subclasses provide:
|
||||
* field_template
|
||||
* field_template (only required if the default template of field_panel_field.html is not usable)
|
||||
* object_type_name - something like 'image' which will be used as the var name
|
||||
for the object instance in the field_template
|
||||
"""
|
||||
|
@ -496,20 +494,17 @@ class BaseChooserPanel(BaseFieldPanel):
|
|||
# like every other unpopulated field type. Yay consistency!
|
||||
return None
|
||||
|
||||
def render_as_field(self, show_help_text=True, extra_context={}):
|
||||
def render_as_field(self):
|
||||
instance_obj = self.get_chosen_item()
|
||||
context = {
|
||||
'field': self.bound_field,
|
||||
self.object_type_name: instance_obj,
|
||||
'is_chosen': bool(instance_obj),
|
||||
'show_help_text': show_help_text,
|
||||
'is_chosen': bool(instance_obj), # DEPRECATED - passed to templates for backwards compatibility only
|
||||
}
|
||||
context.update(extra_context)
|
||||
return mark_safe(render_to_string(self.field_template, context))
|
||||
|
||||
|
||||
class BasePageChooserPanel(BaseChooserPanel):
|
||||
field_template = "wagtailadmin/edit_handlers/page_chooser_panel.html"
|
||||
object_type_name = "page"
|
||||
|
||||
_target_content_type = None
|
||||
|
@ -539,14 +534,6 @@ class BasePageChooserPanel(BaseChooserPanel):
|
|||
|
||||
return cls._target_content_type
|
||||
|
||||
def render_as_field(self, show_help_text=True, extra_context={}):
|
||||
context = {
|
||||
'choose_another_text_str': ugettext_lazy("Choose another page"),
|
||||
'choose_one_text_str': ugettext_lazy("Choose a page"),
|
||||
}
|
||||
context.update(extra_context)
|
||||
return super(BasePageChooserPanel, self).render_as_field(show_help_text, context)
|
||||
|
||||
|
||||
class PageChooserPanel(object):
|
||||
def __init__(self, field_name, page_type=None):
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
from django import forms
|
||||
from django.core import validators
|
||||
from django.forms.widgets import TextInput
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm
|
||||
from django.utils.translation import ugettext as _
|
||||
|
@ -6,6 +8,25 @@ from django.utils.translation import ungettext, ugettext_lazy
|
|||
from wagtail.wagtailadmin.widgets import AdminPageChooser
|
||||
from wagtail.wagtailcore.models import Page
|
||||
|
||||
class URLOrAbsolutePathValidator(validators.URLValidator):
|
||||
@staticmethod
|
||||
def is_absolute_path(value):
|
||||
return value.startswith('/')
|
||||
|
||||
def __call__(self, value):
|
||||
if URLOrAbsolutePathValidator.is_absolute_path(value):
|
||||
return None
|
||||
else:
|
||||
return super(URLOrAbsolutePathValidator, self).__call__(value)
|
||||
|
||||
class URLOrAbsolutePathField(forms.URLField):
|
||||
widget = TextInput
|
||||
default_validators = [URLOrAbsolutePathValidator()]
|
||||
|
||||
def to_python(self, value):
|
||||
if not URLOrAbsolutePathValidator.is_absolute_path(value):
|
||||
value = super(URLOrAbsolutePathField, self).to_python(value)
|
||||
return value
|
||||
|
||||
class SearchForm(forms.Form):
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -22,11 +43,11 @@ class SearchForm(forms.Form):
|
|||
|
||||
|
||||
class ExternalLinkChooserForm(forms.Form):
|
||||
url = forms.URLField(required=True)
|
||||
url = URLOrAbsolutePathField(required=True)
|
||||
|
||||
|
||||
class ExternalLinkChooserWithLinkTextForm(forms.Form):
|
||||
url = forms.URLField(required=True)
|
||||
url = URLOrAbsolutePathField(required=True)
|
||||
link_text = forms.CharField(required=True)
|
||||
|
||||
|
||||
|
|
|
@ -2,13 +2,8 @@ from __future__ import unicode_literals
|
|||
|
||||
from six import text_type, with_metaclass
|
||||
|
||||
try:
|
||||
# renamed util -> utils in Django 1.7; try the new name first
|
||||
from django.forms.utils import flatatt
|
||||
except ImportError:
|
||||
from django.forms.util import flatatt
|
||||
|
||||
from django.forms import MediaDefiningClass, Media
|
||||
from django.forms.utils import flatatt
|
||||
from django.utils.text import slugify
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
{% load i18n %}
|
||||
{% load i18n wagtailadmin_tags %}{% base_url_setting as base_url %}
|
||||
{% trans "Please follow the link below to reset your password" %}
|
||||
{{ protocol }}://{{ domain }}{% url 'wagtailadmin_password_reset_confirm' uidb64=uid token=token %}
|
||||
{% if base_url %}{{ base_url }}{% else %}{{ protocol }}://{{ domain }}{% endif %}{% url 'wagtailadmin_password_reset_confirm' uidb64=uid token=token %}
|
|
@ -1,6 +1,12 @@
|
|||
{% extends "wagtailadmin/shared/field.html" %}
|
||||
{% load i18n %}
|
||||
{% comment %}
|
||||
------
|
||||
DEPRECATED - provided for backwards compatibility with custom (third-party) chooser panels
|
||||
created prior to Wagtail 0.9. New choosers should subclass wagtail.wagtailadmin.widgets.AdminChooser,
|
||||
with a template that extends wagtailadmin/widgets/chooser.html.
|
||||
------
|
||||
|
||||
Either the chosen or unchosen div will be shown, depending on the presence
|
||||
of the 'blank' class on the container.
|
||||
|
||||
|
@ -10,21 +16,21 @@
|
|||
|
||||
{% block form_field %}
|
||||
|
||||
<div id="{{ field.id_for_label }}-chooser" class="chooser {% block chooser_class %}page-chooser{% endblock %} {% if not is_chosen %}blank{% endif %}">
|
||||
<div id="{{ field.id_for_label }}-chooser" class="chooser {% block chooser_class %}page-chooser{% endblock %} {% if not field.value %}blank{% endif %}">
|
||||
|
||||
<div class="chosen">
|
||||
{% block chosen_state_view %}{% endblock %}
|
||||
|
||||
<div class="actions">
|
||||
{% if not field.field.required %}
|
||||
<input type="button" class="action-clear button-small button-secondary" value="{% block clear_button_label %}{% trans "Clear choice" %}{% endblock %}">
|
||||
<input type="button" class="action-clear button-small button-secondary" value="{{ field.field.widget.clear_choice_text }}">
|
||||
{% endif %}
|
||||
<input type="button" class="action-choose button-small button-secondary" value="{% block choose_another_button_label %}{% trans "Choose another item" %}{% endblock %}">
|
||||
<input type="button" class="action-choose button-small button-secondary" value="{{ field.field.widget.choose_another_text }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="unchosen">
|
||||
<input type="button" class="action-choose button-small button-secondary" value="{% block choose_button_label %}{% trans "Choose an item" %}{% endblock %}">
|
||||
<input type="button" class="action-choose button-small button-secondary" value="{{ field.field.widget.choose_one_text }}">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,2 @@
|
|||
{% extends "wagtailadmin/edit_handlers/chooser_panel.html" %}
|
||||
|
||||
{% block chosen_state_view %}
|
||||
<span class="title">{{ page.title }}</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block choose_another_button_label %}{{ choose_another_text_str }}{% endblock %}
|
||||
{% block choose_button_label %}{{ choose_one_text_str }}{% endblock %}
|
||||
{# Page chooser is now implemented as an entirely standard form widget - page_chooser_panel.html is redundant #}
|
||||
{% include "wagtailadmin/shared/field.html" %}
|
||||
|
|
|
@ -13,12 +13,7 @@
|
|||
<ul class="fields">
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.new_title %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.new_slug %}
|
||||
|
||||
<li class="{% if form.new_parent_page.field.required %}required{% endif %}">
|
||||
{% trans "Change page" as choose_another_text_str %}
|
||||
{% trans "Choose page" as choose_one_text_str %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.new_parent_page page=parent_page is_chosen=True choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
</li>
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.new_parent_page %}
|
||||
|
||||
{% if form.copy_subpages %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.copy_subpages %}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
{% load i18n %}
|
||||
{% comment %}
|
||||
Either the chosen or unchosen div will be shown, depending on the presence
|
||||
of the 'blank' class on the container.
|
||||
|
||||
Any element with the 'action-choose' class will open the page chooser modal
|
||||
when clicked.
|
||||
{% endcomment %}
|
||||
|
||||
<div id="{{ attrs.id }}-chooser" class="chooser {% block chooser_class %}page-chooser{% endblock %} {% if not value %}blank{% endif %}">
|
||||
|
||||
<div class="chosen">
|
||||
{% block chosen_state_view %}{% endblock %}
|
||||
|
||||
<div class="actions">
|
||||
{% if not widget.is_required %}
|
||||
<input type="button" class="action-clear button-small button-secondary" value="{{ widget.clear_choice_text }}">
|
||||
{% endif %}
|
||||
<input type="button" class="action-choose button-small button-secondary" value="{{ widget.choose_another_text }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="unchosen">
|
||||
<input type="button" class="action-choose button-small button-secondary" value="{{ widget.choose_one_text }}">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{{ original_field_html }}
|
|
@ -0,0 +1,5 @@
|
|||
{% extends "wagtailadmin/widgets/chooser.html" %}
|
||||
|
||||
{% block chosen_state_view %}
|
||||
<span class="title">{{ page.title }}</span>
|
||||
{% endblock %}
|
|
@ -134,6 +134,11 @@ def usage_count_enabled():
|
|||
return getattr(settings, 'WAGTAIL_USAGE_COUNT_ENABLED', False)
|
||||
|
||||
|
||||
@register.assignment_tag
|
||||
def base_url_setting():
|
||||
return getattr(settings, 'BASE_URL', None)
|
||||
|
||||
|
||||
class EscapeScriptNode(template.Node):
|
||||
TAG_NAME = 'escapescript'
|
||||
SCRIPT_RE = re.compile(r'<(-*)/script>')
|
||||
|
|
|
@ -101,9 +101,24 @@ class TestChooserExternalLink(TestCase, WagtailTestUtils):
|
|||
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/'")
|
||||
response = self.post({'url': 'http://www.example.com'})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "'onload'") # indicates success / post back to calling page
|
||||
self.assertContains(response, "'url': 'http://www.example.com/',")
|
||||
self.assertContains(response, "'title': 'http://www.example.com/'")
|
||||
|
||||
def test_invalid_url(self):
|
||||
response = self.post({'url': 'ntp://www.example.com'})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "'html'") # indicates failure / show error message
|
||||
self.assertContains(response, "Enter a valid URL.")
|
||||
|
||||
def test_allow_local_url(self):
|
||||
response = self.post({'url': '/admin/'})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "'onload'") # indicates success / post back to calling page
|
||||
self.assertContains(response, "'url': '/admin/',")
|
||||
self.assertContains(response, "'title': '/admin/'")
|
||||
|
||||
|
||||
class TestChooserEmailLink(TestCase, WagtailTestUtils):
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
from django.test import TestCase, override_settings
|
||||
from django.core import mail
|
||||
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
from wagtail.wagtailcore.models import Site
|
||||
|
||||
|
||||
class TestUserPasswordReset(TestCase, WagtailTestUtils):
|
||||
fixtures = ['test.json']
|
||||
|
||||
# need to clear urlresolver caches before/after tests, because we override ROOT_URLCONF
|
||||
# in some tests here
|
||||
def setUp(self):
|
||||
from django.core.urlresolvers import clear_url_caches
|
||||
clear_url_caches()
|
||||
|
||||
def tearDown(self):
|
||||
from django.core.urlresolvers import clear_url_caches
|
||||
clear_url_caches()
|
||||
|
||||
@override_settings(ROOT_URLCONF="wagtail.wagtailadmin.urls")
|
||||
def test_email_found_default_url(self):
|
||||
response = self.client.post('/password_reset/', {'email': 'siteeditor@example.com'})
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertIn("testserver", mail.outbox[0].body)
|
||||
|
||||
@override_settings(ROOT_URLCONF="wagtail.wagtailadmin.urls", BASE_URL='http://mysite.com')
|
||||
def test_email_found_base_url(self):
|
||||
response = self.client.post('/password_reset/', {'email': 'siteeditor@example.com'})
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertIn("mysite.com", mail.outbox[0].body)
|
|
@ -698,7 +698,6 @@ def copy(request, page_id):
|
|||
|
||||
return render(request, 'wagtailadmin/pages/copy.html', {
|
||||
'page': page,
|
||||
'parent_page': parent_page,
|
||||
'form': form,
|
||||
})
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ import json
|
|||
from django.core.urlresolvers import reverse
|
||||
from django.forms import widgets
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from wagtail.utils.widgets import WidgetWithScript
|
||||
from wagtail.wagtailcore.models import Page
|
||||
|
@ -34,14 +36,64 @@ class AdminTagWidget(WidgetWithScript, TagWidget):
|
|||
json.dumps(reverse('wagtailadmin_tag_autocomplete')))
|
||||
|
||||
|
||||
class AdminPageChooser(WidgetWithScript, widgets.Input):
|
||||
class AdminChooser(WidgetWithScript, widgets.Input):
|
||||
input_type = 'hidden'
|
||||
choose_one_text = _("Choose an item")
|
||||
choose_another_text = _("Choose another item")
|
||||
clear_choice_text = _("Clear choice")
|
||||
|
||||
def get_instance(self, model_class, value):
|
||||
# helper method for cleanly turning 'value' into an instance object
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
return model_class.objects.get(pk=value)
|
||||
except model_class.DoesNotExist:
|
||||
return None
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
# treat the empty string as None
|
||||
result = super(AdminChooser, self).value_from_datadict(data, files, name)
|
||||
if result == '':
|
||||
return None
|
||||
else:
|
||||
return result
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
# allow choose_one_text / choose_another_text to be overridden per-instance
|
||||
if 'choose_one_text' in kwargs:
|
||||
self.choose_one_text = kwargs.pop('choose_one_text')
|
||||
if 'choose_another_text' in kwargs:
|
||||
self.choose_another_text = kwargs.pop('choose_another_text')
|
||||
if 'clear_choice_text' in kwargs:
|
||||
self.clear_choice_text = kwargs.pop('clear_choice_text')
|
||||
super(AdminChooser, self).__init__(**kwargs)
|
||||
|
||||
|
||||
class AdminPageChooser(AdminChooser):
|
||||
target_content_type = None
|
||||
choose_one_text = _('Choose a page')
|
||||
choose_another_text = _('Choose another page')
|
||||
|
||||
def __init__(self, content_type=None, **kwargs):
|
||||
super(AdminPageChooser, self).__init__(**kwargs)
|
||||
self.target_content_type = content_type or ContentType.objects.get_for_model(Page)
|
||||
|
||||
def render_html(self, name, value, attrs):
|
||||
original_field_html = super(AdminPageChooser, self).render_html(name, value, attrs)
|
||||
|
||||
model_class = self.target_content_type.model_class()
|
||||
instance = self.get_instance(model_class, value)
|
||||
|
||||
return render_to_string("wagtailadmin/widgets/page_chooser.html", {
|
||||
'widget': self,
|
||||
'original_field_html': original_field_html,
|
||||
'attrs': attrs,
|
||||
'value': value,
|
||||
'page': instance,
|
||||
})
|
||||
|
||||
def render_js_init(self, id_, name, value):
|
||||
page = Page.objects.get(pk=value) if value else None
|
||||
parent = page.get_parent() if page else None
|
||||
|
|
|
@ -26,11 +26,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.core.exceptions import ValidationError, ImproperlyConfigured, ObjectDoesNotExist
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
|
||||
try:
|
||||
from django.core import checks
|
||||
except ImportError:
|
||||
pass
|
||||
from django.core import checks
|
||||
|
||||
from treebeard.mp_tree import MP_Node
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from django.db.models import Q, get_models
|
||||
from django.db.models import Q
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.apps import apps
|
||||
|
||||
from treebeard.mp_tree import MP_NodeQuerySet
|
||||
|
||||
from wagtail.wagtailsearch.backends import get_search_backend
|
||||
|
@ -154,7 +156,7 @@ class PageQuerySet(MP_NodeQuerySet):
|
|||
|
||||
def type_q(self, klass):
|
||||
content_types = ContentType.objects.get_for_models(*[
|
||||
model for model in get_models()
|
||||
model for model in apps.get_models()
|
||||
if issubclass(model, klass)
|
||||
]).values()
|
||||
|
||||
|
|
|
@ -35,4 +35,9 @@ def wagtail_version():
|
|||
|
||||
@register.filter
|
||||
def richtext(value):
|
||||
return mark_safe('<div class="rich-text">' + expand_db_html(value) + '</div>')
|
||||
if value is not None:
|
||||
html = expand_db_html(value)
|
||||
else:
|
||||
html = ''
|
||||
|
||||
return mark_safe('<div class="rich-text">' + html + '</div>')
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import unittest
|
||||
|
||||
from django.test import TestCase
|
||||
from django.core.cache import cache
|
||||
from django.utils.safestring import SafeString
|
||||
|
||||
from wagtail.wagtailcore.models import Page, Site
|
||||
from wagtail.wagtailcore.templatetags.wagtailcore_tags import richtext
|
||||
from wagtail.wagtailcore.utils import resolve_model_string
|
||||
from wagtail.tests.models import SimplePage
|
||||
|
||||
|
||||
|
@ -142,3 +147,53 @@ class TestSiteRootPathsCache(TestCase):
|
|||
|
||||
# Check url
|
||||
self.assertEqual(homepage.url, '/')
|
||||
|
||||
|
||||
class TestResolveModelString(TestCase):
|
||||
def test_resolve_from_string(self):
|
||||
model = resolve_model_string('wagtailcore.Page')
|
||||
|
||||
self.assertEqual(model, Page)
|
||||
|
||||
def test_resolve_from_string_with_default_app(self):
|
||||
model = resolve_model_string('Page', default_app='wagtailcore')
|
||||
|
||||
self.assertEqual(model, Page)
|
||||
|
||||
def test_resolve_from_string_with_different_default_app(self):
|
||||
model = resolve_model_string('wagtailcore.Page', default_app='wagtailadmin')
|
||||
|
||||
self.assertEqual(model, Page)
|
||||
|
||||
def test_resolve_from_class(self):
|
||||
model = resolve_model_string(Page)
|
||||
|
||||
self.assertEqual(model, Page)
|
||||
|
||||
def test_resolve_from_string_invalid(self):
|
||||
self.assertRaises(ValueError, resolve_model_string, 'wagtail.wagtailcore.Page')
|
||||
|
||||
def test_resolve_from_string_with_incorrect_default_app(self):
|
||||
self.assertRaises(LookupError, resolve_model_string, 'Page', default_app='wagtailadmin')
|
||||
|
||||
def test_resolve_from_string_with_no_default_app(self):
|
||||
self.assertRaises(ValueError, resolve_model_string, 'Page')
|
||||
|
||||
@unittest.expectedFailure # Raising LookupError instead
|
||||
def test_resolve_from_class_that_isnt_a_model(self):
|
||||
self.assertRaises(ValueError, resolve_model_string, object)
|
||||
|
||||
@unittest.expectedFailure # Raising LookupError instead
|
||||
def test_resolve_from_bad_type(self):
|
||||
self.assertRaises(ValueError, resolve_model_string, resolve_model_string)
|
||||
|
||||
|
||||
class TestRichtextTag(TestCase):
|
||||
def test_call_with_text(self):
|
||||
result = richtext("Hello world!")
|
||||
self.assertEqual(result, '<div class="rich-text">Hello world!</div>')
|
||||
self.assertIsInstance(result, SafeString)
|
||||
|
||||
def test_call_with_none(self):
|
||||
result = richtext(None)
|
||||
self.assertEqual(result, '<div class="rich-text"></div>')
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import re
|
||||
from django.db.models import Model, get_model
|
||||
from six import string_types
|
||||
|
||||
from django.db.models import Model
|
||||
from django.apps import apps
|
||||
|
||||
|
||||
def camelcase_to_underscore(str):
|
||||
# http://djangosnippets.org/snippets/585/
|
||||
|
@ -26,10 +28,7 @@ def resolve_model_string(model_string, default_app=None):
|
|||
"should be in the form app_label.model_name".format(
|
||||
model_string), model_string)
|
||||
|
||||
model = get_model(app_label, model_name)
|
||||
if not model:
|
||||
raise LookupError("Can not resolve {0!r} into a model".format(model_string), model_string)
|
||||
return model
|
||||
return apps.get_model(app_label, model_name)
|
||||
|
||||
elif isinstance(model_string, type) and issubclass(model_string, Model):
|
||||
return model_string
|
||||
|
|
|
@ -5,7 +5,6 @@ from .widgets import AdminDocumentChooser
|
|||
|
||||
|
||||
class BaseDocumentChooserPanel(BaseChooserPanel):
|
||||
field_template = "wagtaildocs/edit_handlers/document_chooser_panel.html"
|
||||
object_type_name = "document"
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -1,11 +1,2 @@
|
|||
{% extends "wagtailadmin/edit_handlers/chooser_panel.html" %}
|
||||
{% load i18n %}
|
||||
{% block chooser_class %}document-chooser{% endblock %}
|
||||
|
||||
{% block chosen_state_view %}
|
||||
<span class="title">{{ document.title }}</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block clear_button_label %}{% trans "Clear choice" %}{% endblock %}
|
||||
{% block choose_another_button_label %}{% trans "Choose another document" %}{% endblock %}
|
||||
{% block choose_button_label %}{% trans "Choose a document" %}{% endblock %}
|
||||
{# Document chooser is now implemented as an entirely standard form widget - document_chooser_panel.html is redundant #}
|
||||
{% include "wagtailadmin/shared/field.html" %}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{% extends "wagtailadmin/widgets/chooser.html" %}
|
||||
{% block chooser_class %}document-chooser{% endblock %}
|
||||
|
||||
{% block chosen_state_view %}
|
||||
<span class="title">{{ document.title }}</span>
|
||||
{% endblock %}
|
|
@ -1,5 +1,6 @@
|
|||
from six import b
|
||||
import unittest
|
||||
import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth import get_user_model
|
||||
|
@ -17,9 +18,6 @@ from wagtail.wagtaildocs.models import Document
|
|||
from wagtail.wagtaildocs import models
|
||||
|
||||
|
||||
# TODO: Test serve view
|
||||
|
||||
|
||||
class TestDocumentPermissions(TestCase):
|
||||
def setUp(self):
|
||||
# Create some user accounts for testing permissions
|
||||
|
@ -519,3 +517,46 @@ class TestIssue613(TestCase, WagtailTestUtils):
|
|||
# Check
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertEqual(results[0].id, document.id)
|
||||
|
||||
|
||||
class TestServeView(TestCase):
|
||||
def setUp(self):
|
||||
self.document = models.Document(title="Test document")
|
||||
self.document.file.save('example.doc', ContentFile("A boring example document"))
|
||||
|
||||
def get(self):
|
||||
return self.client.get(reverse('wagtaildocs_serve', args=(self.document.id, 'example.doc')))
|
||||
|
||||
def test_response_code(self):
|
||||
self.assertEqual(self.get().status_code, 200)
|
||||
|
||||
@unittest.expectedFailure # Filename has a random string appended to it
|
||||
def test_content_disposition_header(self):
|
||||
self.assertEqual(self.get()['Content-Disposition'], 'attachment; filename=example.doc')
|
||||
|
||||
def test_content_length_header(self):
|
||||
self.assertEqual(self.get()['Content-Length'], '25')
|
||||
|
||||
def test_is_streaming_response(self):
|
||||
self.assertTrue(self.get().streaming)
|
||||
|
||||
def test_content(self):
|
||||
self.assertEqual(b"".join(self.get().streaming_content), b"A boring example document")
|
||||
|
||||
def test_document_served_fired(self):
|
||||
mock_handler = mock.MagicMock()
|
||||
models.document_served.connect(mock_handler)
|
||||
|
||||
self.get()
|
||||
|
||||
self.assertEqual(mock_handler.call_count, 1)
|
||||
self.assertEqual(mock_handler.mock_calls[0][2]['sender'], self.document)
|
||||
|
||||
def test_with_nonexistent_document(self):
|
||||
response = self.client.get(reverse('wagtaildocs_serve', args=(1000, 'blahblahblah', )))
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
@unittest.expectedFailure
|
||||
def test_with_incorrect_filename(self):
|
||||
response = self.client.get(reverse('wagtaildocs_serve', args=(self.document.id, 'incorrectfilename')))
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.shortcuts import get_object_or_404
|
||||
from django.core.servers.basehttp import FileWrapper
|
||||
from django.http import HttpResponse
|
||||
from wsgiref.util import FileWrapper
|
||||
from django.http import StreamingHttpResponse
|
||||
|
||||
from wagtail.wagtaildocs.models import Document, document_served
|
||||
|
||||
|
@ -8,7 +8,7 @@ from wagtail.wagtaildocs.models import Document, document_served
|
|||
def serve(request, document_id, document_filename):
|
||||
doc = get_object_or_404(Document, id=document_id)
|
||||
wrapper = FileWrapper(doc.file)
|
||||
response = HttpResponse(wrapper, content_type='application/octet-stream')
|
||||
response = StreamingHttpResponse(wrapper, content_type='application/octet-stream')
|
||||
|
||||
# TODO: strip out weird characters like semicolons from the filename
|
||||
# (there doesn't seem to be an official way of escaping them)
|
||||
|
|
|
@ -2,13 +2,29 @@ from __future__ import absolute_import, unicode_literals
|
|||
|
||||
import json
|
||||
|
||||
from django.forms import widgets
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.utils.widgets import WidgetWithScript
|
||||
from wagtail.wagtailadmin.widgets import AdminChooser
|
||||
from wagtail.wagtaildocs.models import Document
|
||||
|
||||
|
||||
class AdminDocumentChooser(WidgetWithScript, widgets.Input):
|
||||
input_type = 'hidden'
|
||||
class AdminDocumentChooser(AdminChooser):
|
||||
choose_one_text = _('Choose a document')
|
||||
choose_another_text = _('Choose another document')
|
||||
|
||||
def render_html(self, name, value, attrs):
|
||||
original_field_html = super(AdminDocumentChooser, self).render_html(name, value, attrs)
|
||||
|
||||
instance = self.get_instance(Document, value)
|
||||
|
||||
return render_to_string("wagtaildocs/widgets/document_chooser.html", {
|
||||
'widget': self,
|
||||
'original_field_html': original_field_html,
|
||||
'attrs': attrs,
|
||||
'value': value,
|
||||
'document': instance,
|
||||
})
|
||||
|
||||
def render_js_init(self, id_, name, value):
|
||||
return "createDocumentChooser({0});".format(json.dumps(id_))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from django.forms.util import ErrorList
|
||||
from django.forms.utils import ErrorList
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from wagtail.wagtailadmin.modal_workflow import render_modal_workflow
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from collections import OrderedDict
|
||||
|
||||
import django.forms
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
||||
|
||||
class BaseForm(django.forms.Form):
|
||||
|
@ -75,7 +76,7 @@ class FormBuilder(object):
|
|||
|
||||
@property
|
||||
def formfields(self):
|
||||
formfields = SortedDict()
|
||||
formfields = OrderedDict()
|
||||
|
||||
for field in self.fields:
|
||||
options = self.get_field_options(field)
|
||||
|
|
|
@ -5,7 +5,6 @@ from .widgets import AdminImageChooser
|
|||
|
||||
|
||||
class BaseImageChooserPanel(BaseChooserPanel):
|
||||
field_template = "wagtailimages/edit_handlers/image_chooser_panel.html"
|
||||
object_type_name = "image"
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -38,6 +38,8 @@ class DoNothingOperation(Operation):
|
|||
|
||||
|
||||
class FillOperation(Operation):
|
||||
vary_fields = ('focal_point_width', 'focal_point_height', 'focal_point_x', 'focal_point_y')
|
||||
|
||||
def construct(self, size, *extra):
|
||||
# Get width and height
|
||||
width_str, height_str = size.split('x')
|
||||
|
@ -158,37 +160,16 @@ class FillOperation(Operation):
|
|||
# Crop!
|
||||
willow.crop(int(left), int(top), int(right), int(bottom))
|
||||
|
||||
# Resize the final image
|
||||
# Get scale for resizing
|
||||
# The scale should be the same for both the horizontal and
|
||||
# vertical axes
|
||||
aftercrop_width, aftercrop_height = willow.get_size()
|
||||
horz_scale = self.width / aftercrop_width
|
||||
vert_scale = self.height / aftercrop_height
|
||||
scale = self.width / aftercrop_width
|
||||
|
||||
if aftercrop_width <= self.width or aftercrop_height <= self.height:
|
||||
return
|
||||
|
||||
if horz_scale > vert_scale:
|
||||
width = self.width
|
||||
height = int(aftercrop_height * horz_scale)
|
||||
else:
|
||||
width = int(aftercrop_width * vert_scale)
|
||||
height = self.height
|
||||
|
||||
willow.resize(width, height)
|
||||
|
||||
def get_vary(self, image):
|
||||
focal_point = image.get_focal_point()
|
||||
|
||||
if focal_point is not None:
|
||||
focal_point_key = "%(x)d-%(y)d-%(width)dx%(height)d" % {
|
||||
'x': int(focal_point.centroid_x),
|
||||
'y': int(focal_point.centroid_y),
|
||||
'width': int(focal_point.width),
|
||||
'height': int(focal_point.height),
|
||||
}
|
||||
else:
|
||||
focal_point_key = ''
|
||||
|
||||
return [focal_point_key]
|
||||
# Only resize if the image is too big
|
||||
if scale < 1.0:
|
||||
# Resize!
|
||||
willow.resize(self.width, self.height)
|
||||
|
||||
|
||||
class MinMaxOperation(Operation):
|
||||
|
|
|
@ -302,17 +302,20 @@ class Filter(models.Model):
|
|||
vary = []
|
||||
|
||||
for operation in self.operations:
|
||||
if hasattr(operation, 'get_vary'):
|
||||
vary.extend(operation.get_vary(image))
|
||||
for field in getattr(operation, 'vary_fields', []):
|
||||
value = getattr(image, field, '')
|
||||
vary.append(str(value))
|
||||
|
||||
return vary
|
||||
|
||||
def get_vary_key(self, image):
|
||||
vary_string = '-'.join(self.get_vary(image))
|
||||
vary_key = hashlib.sha1(vary_string.encode('utf-8')).hexdigest()
|
||||
|
||||
return vary_key[:8]
|
||||
# Return blank string if there are no vary fields
|
||||
if not vary_string:
|
||||
return ''
|
||||
|
||||
return hashlib.sha1(vary_string.encode('utf-8')).hexdigest()[:8]
|
||||
|
||||
_registered_operations = None
|
||||
|
||||
|
|
|
@ -57,13 +57,3 @@ class Rect(object):
|
|||
x + width / 2,
|
||||
y + height / 2,
|
||||
)
|
||||
|
||||
|
||||
# DELETEME
|
||||
def get_key(self):
|
||||
return "%(x)d-%(y)d-%(width)dx%(height)d" % {
|
||||
'x': int(self.centroid_x),
|
||||
'y': int(self.centroid_y),
|
||||
'width': int(self.width),
|
||||
'height': int(self.height),
|
||||
}
|
||||
|
|
|
@ -1,19 +1,2 @@
|
|||
{% extends "wagtailadmin/edit_handlers/chooser_panel.html" %}
|
||||
{% load wagtailimages_tags %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block chooser_class %}image-chooser{% endblock %}
|
||||
|
||||
{% block chosen_state_view %}
|
||||
<div class="preview-image">
|
||||
{% if image %}
|
||||
{% image image max-130x130 %}
|
||||
{% else %}
|
||||
<img>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block clear_button_label %}{% trans "Clear image" %}{% endblock %}
|
||||
{% block choose_another_button_label %}{% trans "Choose another image" %}{% endblock %}
|
||||
{% block choose_button_label %}{% trans "Choose an image" %}{% endblock %}
|
||||
{# Image chooser is now implemented as an entirely standard form widget - image_chooser_panel.html is redundant #}
|
||||
{% include "wagtailadmin/shared/field.html" %}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
{% extends "wagtailadmin/widgets/chooser.html" %}
|
||||
{% load wagtailimages_tags %}
|
||||
|
||||
{% block chooser_class %}image-chooser{% endblock %}
|
||||
|
||||
{% block chosen_state_view %}
|
||||
<div class="preview-image">
|
||||
{% if image %}
|
||||
{% image image max-130x130 %}
|
||||
{% else %}
|
||||
<img>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -2,7 +2,7 @@ import unittest
|
|||
|
||||
from wagtail.wagtailimages import image_operations
|
||||
from wagtail.wagtailimages.exceptions import InvalidFilterSpecError
|
||||
from wagtail.wagtailimages.models import Image
|
||||
from wagtail.wagtailimages.models import Image, Filter
|
||||
|
||||
|
||||
class WillowOperationRecorder(object):
|
||||
|
@ -151,6 +151,13 @@ class TestFillOperation(ImageOperationTestCase):
|
|||
('resize', (800, 600), {}),
|
||||
]),
|
||||
|
||||
# Basic usage with an oddly-sized original image
|
||||
# This checks for a rounding precision issue (#968)
|
||||
('fill-200x200', Image(width=539, height=720), [
|
||||
('crop', (0, 90, 539, 629), {}),
|
||||
('resize', (200, 200), {}),
|
||||
]),
|
||||
|
||||
# Closeness shouldn't have any effect when used without a focal point
|
||||
('fill-800x600-c100', Image(width=1000, height=1000), [
|
||||
('crop', (0, 125, 1000, 875), {}),
|
||||
|
@ -318,3 +325,33 @@ class TestWidthHeightOperation(ImageOperationTestCase):
|
|||
]
|
||||
|
||||
TestWidthHeightOperation.setup_test_methods()
|
||||
|
||||
|
||||
class TestVaryKey(unittest.TestCase):
|
||||
def test_vary_key(self):
|
||||
image = Image(width=1000, height=1000)
|
||||
fil = Filter(spec='max-100x100')
|
||||
vary_key = fil.get_vary_key(image)
|
||||
|
||||
self.assertEqual(vary_key, '')
|
||||
|
||||
def test_vary_key_fill_filter(self):
|
||||
image = Image(width=1000, height=1000)
|
||||
fil = Filter(spec='fill-100x100')
|
||||
vary_key = fil.get_vary_key(image)
|
||||
|
||||
self.assertEqual(vary_key, '2e16d0ba')
|
||||
|
||||
def test_vary_key_fill_filter_with_focal_point(self):
|
||||
image = Image(
|
||||
width=1000,
|
||||
height=1000,
|
||||
focal_point_width=100,
|
||||
focal_point_height=100,
|
||||
focal_point_x=500,
|
||||
focal_point_y=500,
|
||||
)
|
||||
fil = Filter(spec='fill-100x100')
|
||||
vary_key = fil.get_vary_key(image)
|
||||
|
||||
self.assertEqual(vary_key, '0bbe3b2f')
|
||||
|
|
|
@ -237,7 +237,3 @@ class TestRect(TestCase):
|
|||
def test_from_point(self):
|
||||
rect = Rect.from_point(100, 200, 50, 20)
|
||||
self.assertEqual(rect, Rect(75, 190, 125, 210))
|
||||
|
||||
def test_get_key(self):
|
||||
rect = Rect(100, 150, 200, 250)
|
||||
self.assertEqual(rect.get_key(), '150-200-100x100')
|
||||
|
|
|
@ -2,13 +2,34 @@ from __future__ import absolute_import, unicode_literals
|
|||
|
||||
import json
|
||||
|
||||
from django.forms import widgets
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.utils.widgets import WidgetWithScript
|
||||
from wagtail.wagtailadmin.widgets import AdminChooser
|
||||
from wagtail.wagtailimages.models import get_image_model
|
||||
|
||||
|
||||
class AdminImageChooser(WidgetWithScript, widgets.Input):
|
||||
input_type = 'hidden'
|
||||
class AdminImageChooser(AdminChooser):
|
||||
choose_one_text = _('Choose an image')
|
||||
choose_another_text = _('Choose another image')
|
||||
clear_choice_text = _('Clear image')
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(AdminImageChooser, self).__init__(**kwargs)
|
||||
self.image_model = get_image_model()
|
||||
|
||||
def render_html(self, name, value, attrs):
|
||||
original_field_html = super(AdminImageChooser, self).render_html(name, value, attrs)
|
||||
|
||||
instance = self.get_instance(self.image_model, value)
|
||||
|
||||
return render_to_string("wagtailimages/widgets/image_chooser.html", {
|
||||
'widget': self,
|
||||
'original_field_html': original_field_html,
|
||||
'attrs': attrs,
|
||||
'value': value,
|
||||
'image': instance,
|
||||
})
|
||||
|
||||
def render_js_init(self, id_, name, value):
|
||||
return "createImageChooser({0});".format(json.dumps(id_))
|
||||
|
|
|
@ -10,13 +10,7 @@
|
|||
<legend>{% trans "Promoted search result" %}</legend>
|
||||
<ul class="fields">
|
||||
<li class="model_choice_field">
|
||||
{% trans "Choose another page" as choose_another_text_str %}
|
||||
{% trans "Choose a page" as choose_one_text_str %}
|
||||
{% if form.instance.page %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.page page=form.instance.page is_chosen=True choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% else %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.page is_chosen=False choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% endif %}
|
||||
{% include "wagtailadmin/shared/field.html" with field=form.page only %}
|
||||
</li>
|
||||
<li class="char_field">
|
||||
{% include "wagtailadmin/shared/field.html" with field=form.description only %}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.wagtailcore.models import Site
|
||||
from wagtail.wagtailadmin.widgets import AdminPageChooser
|
||||
|
@ -7,7 +8,9 @@ from wagtail.wagtailadmin.widgets import AdminPageChooser
|
|||
class SiteForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SiteForm, self).__init__(*args, **kwargs)
|
||||
self.fields['root_page'].widget = AdminPageChooser()
|
||||
self.fields['root_page'].widget = AdminPageChooser(
|
||||
choose_one_text=_('Choose a root page'), choose_another_text=_('Choose a different root page')
|
||||
)
|
||||
|
||||
required_css_class = "required"
|
||||
|
||||
|
|
|
@ -15,17 +15,7 @@
|
|||
<ul class="fields">
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.hostname %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.port %}
|
||||
|
||||
<li>
|
||||
{% trans "Choose a different root page" as choose_another_text_str %}
|
||||
{% trans "Choose a root page" as choose_one_text_str %}
|
||||
{% if form.instance.root_page %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.root_page page=form.instance.root_page is_chosen=True choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% else %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.root_page is_chosen=False choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.root_page %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.is_default_site %}
|
||||
|
||||
<li><input type="submit" value="{% trans 'Save' %}" /></li>
|
||||
|
|
|
@ -16,18 +16,7 @@
|
|||
<ul class="fields">
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.hostname %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.port %}
|
||||
|
||||
<li>
|
||||
{% trans "Change page" as choose_another_text_str %}
|
||||
{% trans "Choose page" as choose_one_text_str %}
|
||||
|
||||
{% if form.instance.root_page %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.root_page page=form.instance.root_page is_chosen=True choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% else %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.root_page is_chosen=False choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.root_page %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" with field=form.is_default_site %}
|
||||
|
||||
<li>
|
||||
|
|
|
@ -10,7 +10,6 @@ from .widgets import AdminSnippetChooser
|
|||
|
||||
|
||||
class BaseSnippetChooserPanel(BaseChooserPanel):
|
||||
field_template = "wagtailsnippets/edit_handlers/snippet_chooser_panel.html"
|
||||
object_type_name = 'item'
|
||||
|
||||
_content_type = None
|
||||
|
@ -18,7 +17,7 @@ class BaseSnippetChooserPanel(BaseChooserPanel):
|
|||
@classmethod
|
||||
def widget_overrides(cls):
|
||||
return {cls.field_name: AdminSnippetChooser(
|
||||
content_type=cls.content_type())}
|
||||
content_type=cls.content_type(), snippet_type_name=cls.snippet_type_name)}
|
||||
|
||||
@classmethod
|
||||
def content_type(cls):
|
||||
|
@ -28,14 +27,12 @@ class BaseSnippetChooserPanel(BaseChooserPanel):
|
|||
|
||||
return cls._content_type
|
||||
|
||||
def render_as_field(self, show_help_text=True):
|
||||
def render_as_field(self):
|
||||
instance_obj = self.get_chosen_item()
|
||||
return mark_safe(render_to_string(self.field_template, {
|
||||
'field': self.bound_field,
|
||||
self.object_type_name: instance_obj,
|
||||
'snippet_type_name': self.snippet_type_name,
|
||||
'is_chosen': bool(instance_obj),
|
||||
'show_help_text': show_help_text,
|
||||
}))
|
||||
|
||||
|
||||
|
|
|
@ -1,11 +1,2 @@
|
|||
{% extends "wagtailadmin/edit_handlers/chooser_panel.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block chooser_class %}snippet-chooser{% endblock %}
|
||||
|
||||
{% block chosen_state_view %}
|
||||
<span class="title">{% if is_chosen %}{{ item }}{% endif %}</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block choose_another_button_label %}{% blocktrans %}Choose another {{ snippet_type_name }}{% endblocktrans %}{% endblock %}
|
||||
{% block choose_button_label %}{% blocktrans %}Choose {{ snippet_type_name }}{% endblocktrans %}{% endblock %}
|
||||
{# Snippet chooser is now implemented as an entirely standard form widget - snippet_chooser_panel.html is redundant #}
|
||||
{% include "wagtailadmin/shared/field.html" %}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{% extends "wagtailadmin/widgets/chooser.html" %}
|
||||
|
||||
{% block chooser_class %}snippet-chooser{% endblock %}
|
||||
|
||||
{% block chosen_state_view %}
|
||||
<span class="title">{{ item }}</span>
|
||||
{% endblock %}
|
|
@ -2,20 +2,39 @@ from __future__ import absolute_import, unicode_literals
|
|||
|
||||
import json
|
||||
|
||||
from django.forms import widgets
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.utils.widgets import WidgetWithScript
|
||||
from wagtail.wagtailadmin.widgets import AdminChooser
|
||||
|
||||
|
||||
class AdminSnippetChooser(WidgetWithScript, widgets.Input):
|
||||
input_type = 'hidden'
|
||||
class AdminSnippetChooser(AdminChooser):
|
||||
target_content_type = None
|
||||
|
||||
def __init__(self, content_type=None, **kwargs):
|
||||
if 'snippet_type_name' in kwargs:
|
||||
snippet_type_name = kwargs.pop('snippet_type_name')
|
||||
self.choose_one_text = _('Choose %s') % snippet_type_name
|
||||
self.choose_another_text = _('Choose another %s') % snippet_type_name
|
||||
|
||||
super(AdminSnippetChooser, self).__init__(**kwargs)
|
||||
if content_type is not None:
|
||||
self.target_content_type = content_type
|
||||
|
||||
def render_html(self, name, value, attrs):
|
||||
original_field_html = super(AdminSnippetChooser, self).render_html(name, value, attrs)
|
||||
|
||||
model_class = self.target_content_type.model_class()
|
||||
instance = self.get_instance(model_class, value)
|
||||
|
||||
return render_to_string("wagtailsnippets/widgets/snippet_chooser.html", {
|
||||
'widget': self,
|
||||
'original_field_html': original_field_html,
|
||||
'attrs': attrs,
|
||||
'value': value,
|
||||
'item': instance,
|
||||
})
|
||||
|
||||
def render_js_init(self, id_, name, value):
|
||||
content_type = self.target_content_type
|
||||
|
||||
|
|
|
@ -1,14 +1,7 @@
|
|||
{% load i18n %}
|
||||
|
||||
<td>
|
||||
{% trans "Edit page" as choose_another_text_str %}
|
||||
{% trans "Choose page" as choose_one_text_str %}
|
||||
|
||||
{% if form.instance.page %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.page page=form.instance.page is_chosen=True choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% else %}
|
||||
{% include "wagtailadmin/edit_handlers/page_chooser_panel.html" with field=form.page is_chosen=False choose_one_text_str=choose_one_text_str choose_another_text_str=choose_another_text_str only %}
|
||||
{% endif %}
|
||||
{% include "wagtailadmin/edit_handlers/field_panel_field.html" with field=form.page only %}
|
||||
</td>
|
||||
<td>
|
||||
{% include "wagtailadmin/edit_handlers/field_panel_field.html" with field=form.permission_type only %}
|
||||
|
|
|
@ -16,6 +16,8 @@ from wagtail.wagtailcore.models import Page, GroupPagePermission
|
|||
|
||||
class TestUserIndexView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
# create a user that should be visible in the listing
|
||||
self.test_user = get_user_model().objects.create_user(username='testuser', email='testuser@email.com', password='password')
|
||||
self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
|
@ -25,6 +27,15 @@ class TestUserIndexView(TestCase, WagtailTestUtils):
|
|||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailusers/users/index.html')
|
||||
self.assertContains(response, 'testuser')
|
||||
|
||||
def test_allows_negative_ids(self):
|
||||
# see https://github.com/torchbox/wagtail/issues/565
|
||||
get_user_model().objects.create_user('guardian', 'guardian@example.com', 'gu@rd14n', id=-1)
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'testuser')
|
||||
self.assertContains(response, 'guardian')
|
||||
|
||||
def test_search(self):
|
||||
response = self.get({'q': "Hello"})
|
||||
|
@ -215,7 +226,7 @@ class TestGroupCreateView(TestCase, WagtailTestUtils):
|
|||
new_group = Group.objects.get(name='test group')
|
||||
self.assertEqual(new_group.page_permissions.all().count(), 2)
|
||||
|
||||
@unittest.skip("currently failing on Django 1.7")
|
||||
@unittest.expectedFailure
|
||||
def test_duplicate_page_permissions_error(self):
|
||||
# Try to submit duplicate page permission entries
|
||||
response = self.post({
|
||||
|
|
|
@ -4,5 +4,5 @@ from wagtail.wagtailusers.views import users
|
|||
urlpatterns = [
|
||||
url(r'^$', users.index, name='wagtailusers_users_index'),
|
||||
url(r'^new/$', users.create, name='wagtailusers_users_create'),
|
||||
url(r'^(\d+)/$', users.edit, name='wagtailusers_users_edit'),
|
||||
url(r'^([^\/]+)/$', users.edit, name='wagtailusers_users_edit'),
|
||||
]
|
||||
|
|
Ładowanie…
Reference in New Issue