diff --git a/docs/building_your_site/djangodevelopers.rst b/docs/building_your_site/djangodevelopers.rst
index aeabb264f..c24040364 100644
--- a/docs/building_your_site/djangodevelopers.rst
+++ b/docs/building_your_site/djangodevelopers.rst
@@ -201,6 +201,7 @@ Properties:
* status_string
* subpage_types
* indexed_fields
+* preview_modes
Methods:
@@ -213,8 +214,7 @@ Methods:
* get_descendants
* get_siblings
* search
-* get_page_modes
-* show_as_mode
+* serve_preview
Page Queryset Methods
diff --git a/docs/building_your_site/frontenddevelopers.rst b/docs/building_your_site/frontenddevelopers.rst
index 35aa1c845..a3ba8eaa2 100644
--- a/docs/building_your_site/frontenddevelopers.rst
+++ b/docs/building_your_site/frontenddevelopers.rst
@@ -192,9 +192,10 @@ More control over the ``img`` tag
Wagtail provides two shorcuts to give greater control over the ``img`` element:
-.. versionadded:: 0.4
**Adding attributes to the {% image %} tag**
+.. versionadded:: 0.4
+
Extra attributes can be specified with the syntax ``attribute="value"``:
.. code-block:: django
@@ -215,10 +216,11 @@ Wagtail can assign the image data to another object using Django's ``as`` syntax
-.. versionadded:: 0.4
The ``attrs`` shortcut
-----------------------
+.. versionadded:: 0.4
+
You can also use the ``attrs`` property as a shorthand to output the ``src``, ``width``, ``height`` and ``alt`` attributes in one go:
.. code-block:: django
diff --git a/docs/editing_api.rst b/docs/editing_api.rst
index 5113eb3b8..a12d883d3 100644
--- a/docs/editing_api.rst
+++ b/docs/editing_api.rst
@@ -1,6 +1,6 @@
Defining models with the Editing API
-===========
+====================================
.. note::
This documentation is currently being written.
@@ -251,7 +251,7 @@ Field Customization
By adding CSS classnames to your panel definitions or adding extra parameters to your field definitions, you can control much of how your fields will display in the Wagtail page editing interface. Wagtail's page editing interface takes much of its behavior from Django's admin, so you may find many options for customization covered there. (See `Django model field reference`_ ).
-.. _Django model field reference:https://docs.djangoproject.com/en/dev/ref/models/fields/
+.. _Django model field reference: https://docs.djangoproject.com/en/dev/ref/models/fields/
Full-Width Input
@@ -374,7 +374,7 @@ For more on ``django-modelcluster``, visit `the django-modelcluster github proje
Extending the WYSIWYG Editor (hallo.js)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-To inject javascript into the Wagtail page editor, see the :ref:`insert_editor_js` hook. Once you have the hook in place and your hallo.js plugin loads into the Wagtail page editor, use the following Javascript to register the plugin with hallo.js.
+To inject javascript into the Wagtail page editor, see the :ref:`insert_editor_js ` hook. Once you have the hook in place and your hallo.js plugin loads into the Wagtail page editor, use the following Javascript to register the plugin with hallo.js.
.. code-block:: javascript
@@ -565,7 +565,8 @@ Where ``'hook'`` is one of the following hook strings and ``function`` is a func
.. _construct_whitelister_element_rules:
``construct_whitelister_element_rules``
- .. versionadded:: 0.4
+.. versionadded:: 0.4
+
Customise the rules that define which HTML elements are allowed in rich text areas. By default only a limited set of HTML elements and attributes are whitelisted - all others are stripped out. The callables passed into this hook must return a dict, which maps element names to handler functions that will perform some kind of manipulation of the element. These handler functions receive the element as a `BeautifulSoup `_ Tag object.
The ``wagtail.wagtailcore.whitelist`` module provides a few helper functions to assist in defining these handlers: ``allow_without_attributes``, a handler which preserves the element but strips out all of its attributes, and ``attribute_rule`` which accepts a dict specifying how to handle each attribute, and returns a handler function. This dict will map attribute names to either True (indicating that the attribute should be kept), False (indicating that it should be dropped), or a callable (which takes the initial attribute value and returns either a final value for the attribute, or None to drop the attribute).
@@ -593,6 +594,7 @@ On loading, Wagtail will search for any app with the file ``image_formats.py`` a
As an example, add a "thumbnail" format:
.. code-block:: python
+
# image_formats.py
from wagtail.wagtailimages.formats import Format, register_image_format
diff --git a/docs/editor_manual/documents_images_snippets/index.rst b/docs/editor_manual/documents_images_snippets/index.rst
index 13ceb039e..35817aa79 100644
--- a/docs/editor_manual/documents_images_snippets/index.rst
+++ b/docs/editor_manual/documents_images_snippets/index.rst
@@ -10,3 +10,4 @@ Wagtail allows you to manage all of your documents and images through their own
documents
images
+ snippets
diff --git a/docs/editor_manual/documents_images_snippets/snippets.rst b/docs/editor_manual/documents_images_snippets/snippets.rst
index dd9fbccc0..0dfa1fdad 100644
--- a/docs/editor_manual/documents_images_snippets/snippets.rst
+++ b/docs/editor_manual/documents_images_snippets/snippets.rst
@@ -1,6 +1,9 @@
Snippets
~~~~~~~~
+.. note::
+ Documentation currently incomplete and in draft status
+
.. UNSURE HOW TO WRITE THIS AS THE ADVERT EXAMPLE IN WAGTAIL DEMO IS NOT A PARTICULARLY HELPFUL USE CASE.
When creating a page on a website, it is a common occurance to want to add in a piece of content that already exists on another page. An example of this would be a person's contact details, or an advert that you want to simply show on every page of your site, without having to manually apply it.
diff --git a/docs/frontend_cache_purging.rst b/docs/frontend_cache_purging.rst
index 34a6fda50..c1b03624f 100644
--- a/docs/frontend_cache_purging.rst
+++ b/docs/frontend_cache_purging.rst
@@ -1,3 +1,5 @@
+.. _frontend_cache_purging:
+
Frontend cache purging
======================
diff --git a/docs/gettingstarted.rst b/docs/gettingstarted.rst
index de635256c..04905700e 100644
--- a/docs/gettingstarted.rst
+++ b/docs/gettingstarted.rst
@@ -40,7 +40,7 @@ Once you've experimented with the demo app and are ready to build your pages via
On OS X
~~~~~~~
-Install `pip `_ and `virtualenvwrapper `_ if you don't have them already. Then, in your terminal::
+Install `pip `__ and `virtualenvwrapper `_ if you don't have them already. Then, in your terminal::
mkvirtualenv wagtaildemo
git clone https://github.com/torchbox/wagtaildemo.git
@@ -75,10 +75,10 @@ Wagtail instance available as the basis for your new site:
./manage.py runserver 0.0.0.0:8000
- This will make the app accessible on the host machine as
-`localhost:8111 `_ - you can access the Wagtail admin
-interface at `localhost:8111/admin `_. The codebase
-is located on the host machine, exported to the VM as a shared folder; code
-editing and Git operations will generally be done on the host.
+ `localhost:8111 `_ - you can access the Wagtail admin
+ interface at `localhost:8111/admin `_. The codebase
+ is located on the host machine, exported to the VM as a shared folder; code
+ editing and Git operations will generally be done on the host.
Using Docker
~~~~~~~~~~~~
@@ -101,7 +101,7 @@ use the following steps:
Required dependencies
=====================
-- `pip `_
+- `pip `__
- `libjpeg `_
- `libxml2 `_
- `libxslt `_
diff --git a/docs/index.rst b/docs/index.rst
index ae2300556..d200f2bc1 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -20,6 +20,8 @@ It supports Django 1.6.2+ on Python 2.6, 2.7, 3.2, 3.3 and 3.4. Django 1.7 suppo
deploying
performance
static_site_generation
+ frontend_cache_purging
+ sitemap_generation
management_commands
contributing
support
diff --git a/docs/management_commands.rst b/docs/management_commands.rst
index 3f77fc3ef..96cfe95c3 100644
--- a/docs/management_commands.rst
+++ b/docs/management_commands.rst
@@ -25,10 +25,10 @@ This command moves a selection of pages from one section of the tree to another.
Options:
- **from**
- This is the **id** of the page to move pages from. All descendants of this page will be moved to the destination. After the operation is complete, this page will have no children.
+ This is the **id** of the page to move pages from. All descendants of this page will be moved to the destination. After the operation is complete, this page will have no children.
- **to**
- This is the **id** of the page to move pages to.
+ This is the **id** of the page to move pages to.
update_index
------------
diff --git a/docs/model_recipes.rst b/docs/model_recipes.rst
index 6a4abed47..e799ef70a 100644
--- a/docs/model_recipes.rst
+++ b/docs/model_recipes.rst
@@ -190,11 +190,11 @@ Load Alternate Templates by Overriding get_template()
-Page Modes
-----------
+Preview Modes
+-------------
-get_page_modes
-show_as_mode
+preview_modes
+serve_preview
diff --git a/docs/settings.rst b/docs/settings.rst
index 018af5d01..66fc2de98 100644
--- a/docs/settings.rst
+++ b/docs/settings.rst
@@ -3,7 +3,7 @@
Configuring Django for Wagtail
==============================
-To install Wagtail completely from scratch, create a new Django project and an app within that project. For instructions on these tasks, see `Writing your first Django app`_. Your project directory will look like the following::
+To install Wagtail completely from scratch, create a new Django project and an app within that project. For instructions on these tasks, see `Writing your first Django app `_. Your project directory will look like the following::
myproject/
myproject/
@@ -19,13 +19,7 @@ To install Wagtail completely from scratch, create a new Django project and an a
views.py
manage.py
-From your app directory, you can safely remove ``admin.py`` and ``views.py``, since Wagtail will provide this functionality for your models. Configuring Django to load Wagtail involves adding modules and variables to ``settings.py`` and urlconfs to ``urls.py``. For a more complete view of what's defined in these files, see `Django Settings`_ and `Django URL Dispatcher`_.
-
-.. _Writing your first Django app: https://docs.djangoproject.com/en/dev/intro/tutorial01/
-
-.. _Django Settings: https://docs.djangoproject.com/en/dev/topics/settings/
-
-.. _Django URL Dispatcher: https://docs.djangoproject.com/en/dev/topics/http/urls/
+From your app directory, you can safely remove ``admin.py`` and ``views.py``, since Wagtail will provide this functionality for your models. Configuring Django to load Wagtail involves adding modules and variables to ``settings.py`` and urlconfs to ``urls.py``. For a more complete view of what's defined in these files, see `Django Settings `__ and `Django URL Dispatcher `_.
What follows is a settings reference which skips many boilerplate Django settings. If you just want to get your Wagtail install up quickly without fussing with settings at the moment, see :ref:`complete_example_config`.
@@ -266,9 +260,7 @@ Other Django Settings Used by Wagtail
TEMPLATE_CONTEXT_PROCESSORS
USE_I18N
-For information on what these settings do, see `Django Settings`_.
-
-.. _Django Settings: https://docs.djangoproject.com/en/dev/ref/settings/
+For information on what these settings do, see `Django Settings `__.
Search Signal Handlers
diff --git a/docs/sitemap_generation.rst b/docs/sitemap_generation.rst
index 9c6d50e48..203a423fb 100644
--- a/docs/sitemap_generation.rst
+++ b/docs/sitemap_generation.rst
@@ -1,3 +1,5 @@
+.. _sitemap_generation:
+
Sitemap generation
==================
diff --git a/docs/wagtail_search.rst b/docs/wagtail_search.rst
index 6a77b06f3..4c0a1ac74 100644
--- a/docs/wagtail_search.rst
+++ b/docs/wagtail_search.rst
@@ -1,5 +1,5 @@
-.. _search:
+.. _wagtail_search:
Search
======
diff --git a/wagtail/wagtailadmin/taggable.py b/wagtail/wagtailadmin/taggable.py
index 39213e956..91c36ca7f 100644
--- a/wagtail/wagtailadmin/taggable.py
+++ b/wagtail/wagtailadmin/taggable.py
@@ -4,27 +4,20 @@ from django.contrib.contenttypes.models import ContentType
from django.db.models import Count
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
-from wagtail.wagtailsearch import Indexed, get_search_backend
+from wagtail.wagtailsearch import indexed
+from wagtail.wagtailsearch.backends import get_search_backend
-class TagSearchable(Indexed):
+class TagSearchable(indexed.Indexed):
"""
Mixin to provide a 'search' method, searching on the 'title' field and tags,
for models that provide those things.
"""
- indexed_fields = {
- 'title': {
- 'type': 'string',
- 'analyzer': 'edgengram_analyzer',
- 'boost': 10,
- },
- 'get_tags': {
- 'type': 'string',
- 'analyzer': 'edgengram_analyzer',
- 'boost': 10,
- },
- }
+ search_fields = (
+ indexed.SearchField('title', partial_match=True, boost=10),
+ indexed.SearchField('get_tags', partial_match=True, boost=10)
+ )
@property
def get_tags(self):
diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/pages/create.html b/wagtail/wagtailadmin/templates/wagtailadmin/pages/create.html
index b87af8fae..f21b93a27 100644
--- a/wagtail/wagtailadmin/templates/wagtailadmin/pages/create.html
+++ b/wagtail/wagtailadmin/templates/wagtailadmin/pages/create.html
@@ -39,12 +39,12 @@
{% trans 'Preview' as preview_label %}
- {% if display_modes|length > 1 %}
+ {% if preview_modes|length > 1 %}
{% include "wagtailadmin/pages/_preview_button_on_create.html" with label=preview_label icon=1 %}
- {% for mode_name, mode_display_name in display_modes %}
+ {% for mode_name, mode_display_name in preview_modes %}
{% include "wagtailadmin/pages/_preview_button_on_create.html" with mode=mode_name label=mode_display_name %}
{% trans 'Preview' as preview_label %}
- {% if display_modes|length > 1 %}
+ {% if preview_modes|length > 1 %}
{% include "wagtailadmin/pages/_preview_button_on_edit.html" with label=preview_label icon=1 %}
- {% for mode_name, mode_display_name in display_modes %}
+ {% for mode_name, mode_display_name in preview_modes %}
{% include "wagtailadmin/pages/_preview_button_on_edit.html" with mode=mode_name label=mode_display_name %}
diff --git a/wagtail/wagtailadmin/views/pages.py b/wagtail/wagtailadmin/views/pages.py
index fd0c97b02..013ce40c0 100644
--- a/wagtail/wagtailadmin/views/pages.py
+++ b/wagtail/wagtailadmin/views/pages.py
@@ -1,3 +1,5 @@
+import warnings
+
from django.http import Http404, HttpResponse
from django.shortcuts import render, redirect, get_object_or_404
from django.core.exceptions import ValidationError, PermissionDenied
@@ -7,6 +9,7 @@ from django.contrib.auth.decorators import permission_required
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.utils import timezone
from django.utils.translation import ugettext as _
+from django.views.decorators.http import require_GET
from django.views.decorators.vary import vary_on_headers
from wagtail.wagtailadmin.edit_handlers import TabbedInterface, ObjectList
@@ -230,7 +233,7 @@ def create(request, content_type_app_name, content_type_model_name, parent_page_
'page_class': page_class,
'parent_page': parent_page,
'edit_handler': edit_handler,
- 'display_modes': page.get_page_modes(),
+ 'preview_modes': page.preview_modes,
'form': form, # Used in unit tests
})
@@ -361,7 +364,7 @@ def edit(request, page_id):
'page': page,
'edit_handler': edit_handler,
'errors_debug': errors_debug,
- 'display_modes': page.get_page_modes(),
+ 'preview_modes': page.preview_modes,
'form': form, # Used in unit tests
})
@@ -393,7 +396,28 @@ def delete(request, page_id):
@permission_required('wagtailadmin.access_admin')
def view_draft(request, page_id):
page = get_object_or_404(Page, id=page_id).get_latest_revision_as_page()
- return page.serve(request)
+ return page.serve_preview(page.dummy_request(), page.default_preview_mode)
+
+
+def get_preview_response(page, preview_mode):
+ """
+ Helper function for preview_on_edit and preview_on_create -
+ return a page's preview response via either serve_preview or the deprecated
+ show_as_mode method
+ """
+ # Check the deprecated Page.show_as_mode method, as subclasses of Page
+ # might be overriding that to return a response
+ response = page.show_as_mode(preview_mode)
+ if response:
+ warnings.warn(
+ "Defining 'show_as_mode' on a page model is deprecated. Use 'serve_preview' instead",
+ DeprecationWarning
+ )
+ return response
+ else:
+ # show_as_mode did not return a response, so go ahead and use the 'proper'
+ # serve_preview method
+ return page.serve_preview(page.dummy_request(), preview_mode)
@permission_required('wagtailadmin.access_admin')
@@ -409,19 +433,8 @@ def preview_on_edit(request, page_id):
if form.is_valid():
form.save(commit=False)
- # 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
- # failure - as such, we strip out the X-Requested-With header to get Django to return
- # an HTML error response
- request.META.pop('HTTP_X_REQUESTED_WITH', None)
-
- try:
- display_mode = request.GET['mode']
- except KeyError:
- display_mode = page.get_page_modes()[0][0]
-
- response = page.show_as_mode(display_mode)
+ preview_mode = request.GET.get('mode', page.default_preview_mode)
+ response = get_preview_response(page, preview_mode)
response['X-Wagtail-Preview'] = 'ok'
return response
@@ -432,7 +445,7 @@ def preview_on_edit(request, page_id):
response = render(request, 'wagtailadmin/pages/edit.html', {
'page': page,
'edit_handler': edit_handler,
- 'display_modes': page.get_page_modes(),
+ 'preview_modes': page.preview_modes,
})
response['X-Wagtail-Preview'] = 'error'
return response
@@ -465,18 +478,8 @@ def preview_on_create(request, content_type_app_name, content_type_model_name, p
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
- # failure - as such, we strip out the X-Requested-With header to get Django to return
- # an HTML error response
- request.META.pop('HTTP_X_REQUESTED_WITH', None)
-
- try:
- display_mode = request.GET['mode']
- except KeyError:
- display_mode = page.get_page_modes()[0][0]
- response = page.show_as_mode(display_mode)
+ preview_mode = request.GET.get('mode', page.default_preview_mode)
+ response = get_preview_response(page, preview_mode)
response['X-Wagtail-Preview'] = 'ok'
return response
@@ -490,7 +493,7 @@ def preview_on_create(request, content_type_app_name, content_type_model_name, p
'page_class': page_class,
'parent_page': parent_page,
'edit_handler': edit_handler,
- 'display_modes': page.get_page_modes(),
+ 'preview_modes': page.preview_modes,
})
response['X-Wagtail-Preview'] = 'error'
return response
@@ -727,6 +730,7 @@ def reject_moderation(request, revision_id):
@permission_required('wagtailadmin.access_admin')
+@require_GET
def preview_for_moderation(request, revision_id):
revision = get_object_or_404(PageRevision, id=revision_id)
if not revision.page.permissions_for_user(request.user).can_publish():
@@ -740,4 +744,6 @@ def preview_for_moderation(request, revision_id):
request.revision_id = revision_id
- return page.serve(request)
+ # pass in the real user request rather than page.dummy_request(), so that request.user
+ # and request.revision_id will be picked up by the wagtail user bar
+ return page.serve_preview(request, page.default_preview_mode)
diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py
index 928dc5f64..b742bfd59 100644
--- a/wagtail/wagtailcore/models.py
+++ b/wagtail/wagtailcore/models.py
@@ -28,7 +28,8 @@ from treebeard.mp_tree import MP_Node
from wagtail.wagtailcore.utils import camelcase_to_underscore
from wagtail.wagtailcore.query import PageQuerySet
-from wagtail.wagtailsearch import Indexed, get_search_backend
+from wagtail.wagtailsearch import indexed
+from wagtail.wagtailsearch.backends import get_search_backend
class SiteManager(models.Manager):
@@ -260,7 +261,7 @@ class PageBase(models.base.ModelBase):
@python_2_unicode_compatible
-class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, Indexed)):
+class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, indexed.Indexed)):
title = models.CharField(max_length=255, help_text=_("The page title as you'd like it to be seen by the public"))
slug = models.SlugField(help_text=_("The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/"))
# TODO: enforce uniqueness on slug field per parent (will have to be done at the Django
@@ -279,21 +280,11 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, Indexed)):
expire_at = models.DateTimeField(verbose_name=_("Expiry date/time"), help_text=_("Please add a date-time in the form YYYY-MM-DD hh:mm."), blank=True, null=True)
expired = models.BooleanField(default=False, editable=False)
- indexed_fields = {
- 'title': {
- 'type': 'string',
- 'analyzer': 'edgengram_analyzer',
- 'boost': 100,
- },
- 'live': {
- 'type': 'boolean',
- 'index': 'not_analyzed',
- },
- 'path': {
- 'type': 'string',
- 'index': 'not_analyzed',
- },
- }
+ search_fields = (
+ indexed.SearchField('title', partial_match=True, boost=100),
+ indexed.FilterField('live'),
+ indexed.FilterField('path'),
+ )
def __init__(self, *args, **kwargs):
super(Page, self).__init__(*args, **kwargs)
@@ -706,25 +697,64 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, Indexed)):
"request middleware returned a response")
return request
- def get_page_modes(self):
+ DEFAULT_PREVIEW_MODES = [('', 'Default')]
+
+ @property
+ def preview_modes(self):
"""
- Return a list of (internal_name, display_name) tuples for the modes in which
+ A list of (internal_name, display_name) tuples for the modes in which
this page can be displayed for preview/moderation purposes. Ordinarily a page
will only have one display mode, but subclasses of Page can override this -
for example, a page containing a form might have a default view of the form,
and a post-submission 'thankyou' page
"""
- return [('', 'Default')]
+ modes = self.get_page_modes()
+ if modes is not Page.DEFAULT_PREVIEW_MODES:
+ # User has overriden get_page_modes instead of using preview_modes
+ warnings.warn("Overriding get_page_modes is deprecated. Define a preview_modes property instead", DeprecationWarning)
+
+ return modes
+
+ def get_page_modes(self):
+ # Deprecated accessor for the preview_modes property
+ return Page.DEFAULT_PREVIEW_MODES
+
+ @property
+ def default_preview_mode(self):
+ return self.preview_modes[0][0]
+
+ def serve_preview(self, request, mode_name):
+ """
+ Return an HTTP response for use in page previews. Normally this would be equivalent
+ to self.serve(request), since we obviously want the preview to be indicative of how
+ it looks on the live site. However, there are a couple of cases where this is not
+ appropriate, and custom behaviour is required:
+
+ 1) The page has custom routing logic that derives some additional required
+ args/kwargs to be passed to serve(). The routing mechanism is bypassed when
+ previewing, so there's no way to know what args we should pass. In such a case,
+ the page model needs to implement its own version of serve_preview.
+
+ 2) The page has several different renderings that we would like to be able to see
+ when previewing - for example, a form page might have one rendering that displays
+ the form, and another rendering to display a landing page when the form is posted.
+ This can be done by setting a custom preview_modes list on the page model -
+ Wagtail will allow the user to specify one of those modes when previewing, and
+ pass the chosen mode_name to serve_preview so that the page model can decide how
+ to render it appropriately. (Page models that do not specify their own preview_modes
+ list will always receive an empty string as mode_name.)
+
+ Any templates rendered during this process should use the 'request' object passed
+ here - this ensures that request.user and other properties are set appropriately for
+ the wagtail user bar to be displayed. This request will always be a GET.
+ """
+ return self.serve(request)
def show_as_mode(self, mode_name):
- """
- Given an internal name from the get_page_modes() list, return an HTTP response
- indicative of the page being viewed in that mode. By default this passes a
- dummy request into the serve() mechanism, ensuring that it matches the behaviour
- on the front-end; subclasses that define additional page modes will need to
- implement alternative logic to serve up the appropriate view here.
- """
- return self.serve(self.dummy_request())
+ # Deprecated API for rendering previews. If this returns something other than None,
+ # we know that a subclass of Page has overridden this, and we should try to work with
+ # that response if possible.
+ return None
def get_cached_paths(self):
"""
diff --git a/wagtail/wagtaildocs/models.py b/wagtail/wagtaildocs/models.py
index f75d0ae16..588e29961 100644
--- a/wagtail/wagtaildocs/models.py
+++ b/wagtail/wagtaildocs/models.py
@@ -13,6 +13,7 @@ from django.utils.encoding import python_2_unicode_compatible
from wagtail.wagtailadmin.taggable import TagSearchable
from wagtail.wagtailadmin.utils import usage_count, used_by
+from wagtail.wagtailsearch import indexed
@python_2_unicode_compatible
@@ -24,14 +25,9 @@ class Document(models.Model, TagSearchable):
tags = TaggableManager(help_text=None, blank=True, verbose_name=_('Tags'))
- indexed_fields = {
- 'uploaded_by_user_id': {
- 'type': 'integer',
- 'store': 'yes',
- 'indexed': 'no',
- 'boost': 0,
- },
- }
+ search_fields = TagSearchable.search_fields + (
+ indexed.FilterField('uploaded_by_user'),
+ )
def __str__(self):
return self.title
diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py
index 41414d391..79dcaa0e2 100644
--- a/wagtail/wagtailforms/models.py
+++ b/wagtail/wagtailforms/models.py
@@ -170,19 +170,18 @@ class AbstractForm(Page):
'form': form,
})
- def get_page_modes(self):
- return [
- ('form', 'Form'),
- ('landing', 'Landing page'),
- ]
+ preview_modes = [
+ ('form', 'Form'),
+ ('landing', 'Landing page'),
+ ]
- def show_as_mode(self, mode):
+ def serve_preview(self, request, mode):
if mode == 'landing':
- return render(self.dummy_request(), self.landing_page_template, {
+ return render(request, self.landing_page_template, {
'self': self,
})
else:
- return super(AbstractForm, self).show_as_mode(mode)
+ return super(AbstractForm, self).serve_preview(request, mode)
class AbstractEmailForm(AbstractForm):
diff --git a/wagtail/wagtailforms/tests.py b/wagtail/wagtailforms/tests.py
index 38903d2ff..28df108ab 100644
--- a/wagtail/wagtailforms/tests.py
+++ b/wagtail/wagtailforms/tests.py
@@ -61,7 +61,7 @@ class TestPageModes(TestCase):
self.form_page = Page.objects.get(url_path='/home/contact-us/').specific
def test_form(self):
- response = self.form_page.show_as_mode('form')
+ response = self.form_page.serve_preview(self.form_page.dummy_request(), 'form')
# Check response
self.assertContains(response, """""")
@@ -69,7 +69,7 @@ class TestPageModes(TestCase):
self.assertTemplateNotUsed(response, 'tests/form_page_landing.html')
def test_landing(self):
- response = self.form_page.show_as_mode('landing')
+ response = self.form_page.serve_preview(self.form_page.dummy_request(), 'landing')
# Check response
self.assertContains(response, "Thank you for your feedback.")
diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py
index cec65173c..d4be8d499 100644
--- a/wagtail/wagtailimages/models.py
+++ b/wagtail/wagtailimages/models.py
@@ -20,6 +20,7 @@ from unidecode import unidecode
from wagtail.wagtailadmin.taggable import TagSearchable
from wagtail.wagtailimages.backends import get_image_backend
+from wagtail.wagtailsearch import indexed
from .utils import validate_image_format
from wagtail.wagtailadmin.utils import usage_count, used_by
@@ -57,14 +58,9 @@ class AbstractImage(models.Model, TagSearchable):
def used_by(self):
return used_by(self)
- indexed_fields = {
- 'uploaded_by_user_id': {
- 'type': 'integer',
- 'store': 'yes',
- 'indexed': 'no',
- 'boost': 0,
- },
- }
+ search_fields = TagSearchable.search_fields + (
+ indexed.FilterField('uploaded_by_user'),
+ )
def __str__(self):
return self.title
diff --git a/wagtail/wagtailsearch/indexed.py b/wagtail/wagtailsearch/indexed.py
index b53481727..e2817905d 100644
--- a/wagtail/wagtailsearch/indexed.py
+++ b/wagtail/wagtailsearch/indexed.py
@@ -35,6 +35,11 @@ class Indexed(object):
@classmethod
def indexed_get_indexed_fields(cls):
+ # New way
+ if hasattr(cls, 'search_fields'):
+ return dict((field.get_attname(cls), field.to_dict(cls)) for field in cls.search_fields)
+
+ # Old way
# Get indexed fields for this class as dictionary
indexed_fields = cls.indexed_fields
if isinstance(indexed_fields, dict):
@@ -83,3 +88,58 @@ class Indexed(object):
return doc
indexed_fields = ()
+
+
+class BaseField(object):
+ def __init__(self, field_name, **kwargs):
+ self.field_name = field_name
+ self.kwargs = kwargs
+
+ def get_field(self, cls):
+ return cls._meta.get_field_by_name(self.field_name)[0]
+
+ def get_attname(self, cls):
+ try:
+ field = self.get_field(cls)
+ return field.attname
+ except models.fields.FieldDoesNotExist:
+ return self.field_name
+
+ def to_dict(self, cls):
+ dic = {
+ 'type': 'string'
+ }
+
+ if 'es_extra' in self.kwargs:
+ for key, value in self.kwargs['es_extra'].items():
+ dic[key] = value
+
+ return dic
+
+
+class SearchField(BaseField):
+ def __init__(self, field_name, boost=None, partial_match=False, **kwargs):
+ super(SearchField, self).__init__(field_name, **kwargs)
+ self.boost = boost
+ self.partial_match = partial_match
+
+ def to_dict(self, cls):
+ dic = super(SearchField, self).to_dict(cls)
+
+ if self.boost and 'boost' not in dic:
+ dic['boost'] = self.boost
+
+ if self.partial_match and 'analyzer' not in dic:
+ dic['analyzer'] = 'edgengram_analyzer'
+
+ return dic
+
+
+class FilterField(BaseField):
+ def to_dict(self, cls):
+ dic = super(FilterField, self).to_dict(cls)
+
+ if 'index' not in dic:
+ dic['index'] = 'not_analyzed'
+
+ return dic
diff --git a/wagtail/wagtailsearch/models.py b/wagtail/wagtailsearch/models.py
index abb479295..f6cfdf1dd 100644
--- a/wagtail/wagtailsearch/models.py
+++ b/wagtail/wagtailsearch/models.py
@@ -4,7 +4,7 @@ from django.db import models
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
-from wagtail.wagtailsearch.indexed import Indexed
+from wagtail.wagtailsearch import indexed
from wagtail.wagtailsearch.utils import normalise_query_string, MAX_QUERY_STRING_LENGTH
@@ -76,18 +76,26 @@ class EditorsPick(models.Model):
sort_order = models.IntegerField(null=True, blank=True, editable=False)
description = models.TextField(blank=True)
+ def __repr__(self):
+ return 'EditorsPick(query="' + self.query.query_string + '", page="' + self.page.title + '")'
+
class Meta:
ordering = ('sort_order', )
# Used for tests
-class SearchTest(models.Model, Indexed):
+class SearchTest(models.Model, indexed.Indexed):
title = models.CharField(max_length=255)
content = models.TextField()
live = models.BooleanField(default=False)
- indexed_fields = ("title", "content", "callable_indexed_field", "live")
+ search_fields = (
+ indexed.SearchField('title'),
+ indexed.SearchField('content'),
+ indexed.SearchField('callable_indexed_field'),
+ indexed.SearchField('live'),
+ )
def callable_indexed_field(self):
return "Callable"
@@ -96,4 +104,6 @@ class SearchTest(models.Model, Indexed):
class SearchTestChild(SearchTest):
extra_content = models.TextField()
- indexed_fields = "extra_content"
+ search_fields = SearchTest.search_fields + (
+ indexed.SearchField('extra_content'),
+ )
diff --git a/wagtail/wagtailsearch/tests/test_editorspicks.py b/wagtail/wagtailsearch/tests/test_editorspicks.py
index b2ebece9f..0888d115c 100644
--- a/wagtail/wagtailsearch/tests/test_editorspicks.py
+++ b/wagtail/wagtailsearch/tests/test_editorspicks.py
@@ -218,6 +218,10 @@ class TestEditorsPicksEditView(TestCase, WagtailTestUtils):
# User should be redirected back to the index
self.assertRedirects(response, reverse('wagtailsearch_editorspicks_index'))
+ # Check that the ordering has been saved correctly
+ self.assertEqual(models.EditorsPick.objects.get(id=self.editors_pick.id).sort_order, 1)
+ self.assertEqual(models.EditorsPick.objects.get(id=self.editors_pick_2.id).sort_order, 0)
+
# Check that the recommendations were reordered
self.assertEqual(models.Query.get("Hello").editors_picks.all()[0], self.editors_pick_2)
self.assertEqual(models.Query.get("Hello").editors_picks.all()[1], self.editors_pick)
diff --git a/wagtail/wagtailsearch/views/editorspicks.py b/wagtail/wagtailsearch/views/editorspicks.py
index 8e1155922..fe45a7c56 100644
--- a/wagtail/wagtailsearch/views/editorspicks.py
+++ b/wagtail/wagtailsearch/views/editorspicks.py
@@ -55,6 +55,9 @@ def save_editorspicks(query, new_query, editors_pick_formset):
for i, form in enumerate(editors_pick_formset.ordered_forms):
form.instance.sort_order = i
+ # Make sure the form is marked as changed so it gets saved with the new order
+ form.has_changed = lambda: True
+
editors_pick_formset.save()
# If query was changed, move all editors picks to the new query