mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-05-11 00:33:17 +00:00
Merge branch 'master' into 146-display-calculated-usage
Conflicts: wagtail/wagtaildocs/models.py wagtail/wagtailimages/models.py
This commit is contained in:
commit
c661d6e8a1
26 changed files with 242 additions and 139 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|||
<img src="{{ tmp_photo.src }}" width="{{ tmp_photo.width }}"
|
||||
height="{{ tmp_photo.height }}" alt="{{ tmp_photo.alt }}" class="my-custom-class" />
|
||||
|
||||
.. 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
|
||||
|
|
|
|||
|
|
@ -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 <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 <http://www.crummy.com/software/BeautifulSoup/bs4/doc/>`_ 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
|
||||
|
||||
|
|
|
|||
|
|
@ -10,3 +10,4 @@ Wagtail allows you to manage all of your documents and images through their own
|
|||
|
||||
documents
|
||||
images
|
||||
snippets
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
.. _frontend_cache_purging:
|
||||
|
||||
Frontend cache purging
|
||||
======================
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <http://pip.readthedocs.org/en/latest/installing.html>`_ and `virtualenvwrapper <http://virtualenvwrapper.readthedocs.org/en/latest/>`_ if you don't have them already. Then, in your terminal::
|
||||
Install `pip <http://pip.readthedocs.org/en/latest/installing.html>`__ and `virtualenvwrapper <http://virtualenvwrapper.readthedocs.org/en/latest/>`_ 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 <http://localhost:8111>`_ - you can access the Wagtail admin
|
||||
interface at `localhost:8111/admin <http://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 <http://localhost:8111>`_ - you can access the Wagtail admin
|
||||
interface at `localhost:8111/admin <http://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 <https://github.com/pypa/pip>`_
|
||||
- `pip <https://github.com/pypa/pip>`__
|
||||
- `libjpeg <http://ijg.org/>`_
|
||||
- `libxml2 <http://xmlsoft.org/>`_
|
||||
- `libxslt <http://xmlsoft.org/XSLT/>`_
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
------------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <https://docs.djangoproject.com/en/dev/intro/tutorial01/>`_. 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 <https://docs.djangoproject.com/en/dev/topics/settings/>`__ and `Django URL Dispatcher <https://docs.djangoproject.com/en/dev/topics/http/urls/>`_.
|
||||
|
||||
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 <https://docs.djangoproject.com/en/dev/ref/settings/>`__.
|
||||
|
||||
|
||||
Search Signal Handlers
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
.. _sitemap_generation:
|
||||
|
||||
Sitemap generation
|
||||
==================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
.. _search:
|
||||
.. _wagtail_search:
|
||||
|
||||
Search
|
||||
======
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -39,12 +39,12 @@
|
|||
|
||||
<li class="actions preview">
|
||||
{% trans 'Preview' as preview_label %}
|
||||
{% if display_modes|length > 1 %}
|
||||
{% if preview_modes|length > 1 %}
|
||||
<div class="dropdown dropup dropdown-button match-width">
|
||||
{% include "wagtailadmin/pages/_preview_button_on_create.html" with label=preview_label icon=1 %}
|
||||
<div class="dropdown-toggle icon icon-arrow-up"></div>
|
||||
<ul role="menu">
|
||||
{% for mode_name, mode_display_name in display_modes %}
|
||||
{% for mode_name, mode_display_name in preview_modes %}
|
||||
<li>
|
||||
{% include "wagtailadmin/pages/_preview_button_on_create.html" with mode=mode_name label=mode_display_name %}
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -47,12 +47,12 @@
|
|||
|
||||
<li class="actions preview">
|
||||
{% trans 'Preview' as preview_label %}
|
||||
{% if display_modes|length > 1 %}
|
||||
{% if preview_modes|length > 1 %}
|
||||
<div class="dropdown dropup dropdown-button match-width">
|
||||
{% include "wagtailadmin/pages/_preview_button_on_edit.html" with label=preview_label icon=1 %}
|
||||
<div class="dropdown-toggle icon icon-arrow-up"></div>
|
||||
<ul role="menu">
|
||||
{% for mode_name, mode_display_name in display_modes %}
|
||||
{% for mode_name, mode_display_name in preview_modes %}
|
||||
<li>
|
||||
{% include "wagtailadmin/pages/_preview_button_on_edit.html" with mode=mode_name label=mode_display_name %}
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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, """<label for="id_your-email">Your email</label>""")
|
||||
|
|
@ -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.")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue