Merge branch 'master' into 146-display-calculated-usage

Conflicts:
	wagtail/wagtaildocs/models.py
	wagtail/wagtailimages/models.py
This commit is contained in:
Tom Talbot 2014-07-07 17:29:34 +01:00
commit c661d6e8a1
26 changed files with 242 additions and 139 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -10,3 +10,4 @@ Wagtail allows you to manage all of your documents and images through their own
documents
images
snippets

View file

@ -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.

View file

@ -1,3 +1,5 @@
.. _frontend_cache_purging:
Frontend cache purging
======================

View file

@ -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/>`_

View file

@ -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

View file

@ -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
------------

View file

@ -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

View file

@ -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

View file

@ -1,3 +1,5 @@
.. _sitemap_generation:
Sitemap generation
==================

View file

@ -1,5 +1,5 @@
.. _search:
.. _wagtail_search:
Search
======

View file

@ -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):

View file

@ -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>

View file

@ -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>

View file

@ -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)

View file

@ -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):
"""

View file

@ -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

View file

@ -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):

View file

@ -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.")

View file

@ -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

View file

@ -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

View file

@ -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'),
)

View file

@ -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)

View file

@ -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