From bba9d4faf29c7a8b84e557223174f6930f0cc378 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 20 Jun 2014 12:41:51 +0100 Subject: [PATCH 01/11] Added new search configuration format --- wagtail/wagtailadmin/taggable.py | 21 ++++------- wagtail/wagtailcore/models.py | 25 +++++-------- wagtail/wagtaildocs/models.py | 12 +++---- wagtail/wagtailimages/models.py | 12 +++---- wagtail/wagtailsearch/indexed.py | 60 ++++++++++++++++++++++++++++++++ wagtail/wagtailsearch/models.py | 15 +++++--- 6 files changed, 94 insertions(+), 51 deletions(-) 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/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 7f3070615..4964966a3 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) diff --git a/wagtail/wagtaildocs/models.py b/wagtail/wagtaildocs/models.py index a87b9c02b..513c0ede0 100644 --- a/wagtail/wagtaildocs/models.py +++ b/wagtail/wagtaildocs/models.py @@ -12,6 +12,7 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import python_2_unicode_compatible from wagtail.wagtailadmin.taggable import TagSearchable +from wagtail.wagtailsearch import indexed @python_2_unicode_compatible @@ -23,14 +24,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/wagtailimages/models.py b/wagtail/wagtailimages/models.py index db73b048a..c561cdcc5 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 @@ -48,14 +49,9 @@ class AbstractImage(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/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..509368463 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 @@ -82,12 +82,17 @@ class EditorsPick(models.Model): # 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 +101,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'), + ) From dd52ccab2aa41f8410da18ee0b26d99e3adb87a3 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Thu, 12 Jun 2014 13:03:46 +0100 Subject: [PATCH 02/11] Remove X-Requested-With header hacks on the incoming requests to preview_on_edit and preview_on_create - no longer necessary, because we're not passing that request to the preview logic Conflicts: wagtail/wagtailadmin/views/pages.py --- wagtail/wagtailadmin/views/pages.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/wagtail/wagtailadmin/views/pages.py b/wagtail/wagtailadmin/views/pages.py index fd0c97b02..7a2ec4f06 100644 --- a/wagtail/wagtailadmin/views/pages.py +++ b/wagtail/wagtailadmin/views/pages.py @@ -409,13 +409,6 @@ 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: @@ -465,13 +458,6 @@ 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: From 78481dc84665a9b27645c1139cb90a02ff6128e1 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Thu, 12 Jun 2014 16:48:44 +0100 Subject: [PATCH 03/11] syntactic sugar for get_page_modes: change it to a property called preview_modes and add a default_preview_mode helper Conflicts: wagtail/wagtailadmin/views/pages.py --- docs/building_your_site/djangodevelopers.rst | 2 +- docs/model_recipes.rst | 6 ++--- wagtail/wagtailadmin/views/pages.py | 23 +++++++------------- wagtail/wagtailcore/models.py | 15 ++++++++++--- wagtail/wagtailforms/models.py | 9 ++++---- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/docs/building_your_site/djangodevelopers.rst b/docs/building_your_site/djangodevelopers.rst index aeabb264f..e72573089 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,7 +214,6 @@ Methods: * get_descendants * get_siblings * search -* get_page_modes * show_as_mode diff --git a/docs/model_recipes.rst b/docs/model_recipes.rst index 6a4abed47..ae011126a 100644 --- a/docs/model_recipes.rst +++ b/docs/model_recipes.rst @@ -190,10 +190,10 @@ Load Alternate Templates by Overriding get_template() -Page Modes ----------- +Preview Modes +------------- -get_page_modes +preview_modes show_as_mode diff --git a/wagtail/wagtailadmin/views/pages.py b/wagtail/wagtailadmin/views/pages.py index 7a2ec4f06..e5a0c885c 100644 --- a/wagtail/wagtailadmin/views/pages.py +++ b/wagtail/wagtailadmin/views/pages.py @@ -230,7 +230,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(), + 'display_modes': page.preview_modes, 'form': form, # Used in unit tests }) @@ -361,7 +361,7 @@ def edit(request, page_id): 'page': page, 'edit_handler': edit_handler, 'errors_debug': errors_debug, - 'display_modes': page.get_page_modes(), + 'display_modes': page.preview_modes, 'form': form, # Used in unit tests }) @@ -409,12 +409,8 @@ def preview_on_edit(request, page_id): if form.is_valid(): form.save(commit=False) - 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 = page.show_as_mode(preview_mode) response['X-Wagtail-Preview'] = 'ok' return response @@ -425,7 +421,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(), + 'display_modes': page.preview_modes, }) response['X-Wagtail-Preview'] = 'error' return response @@ -458,11 +454,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] - 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 = page.show_as_mode(preview_mode) response['X-Wagtail-Preview'] = 'ok' return response @@ -476,7 +469,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(), + 'display_modes': page.preview_modes, }) response['X-Wagtail-Preview'] = 'error' return response diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index b980d7f54..42e132daa 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -706,19 +706,28 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, Indexed)): "request middleware returned a response") return request - def get_page_modes(self): + @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 self.get_page_modes() + + def get_page_modes(self): + # Deprecated accessor for the preview_modes property return [('', 'Default')] + @property + def default_preview_mode(self): + return self.preview_modes[0][0] + def show_as_mode(self, mode_name): """ - Given an internal name from the get_page_modes() list, return an HTTP response + Given an internal name from the preview_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 diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py index 41414d391..6bde684ac 100644 --- a/wagtail/wagtailforms/models.py +++ b/wagtail/wagtailforms/models.py @@ -170,11 +170,10 @@ 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): if mode == 'landing': From 0a38e0b8d3122db090475ac887ff6d00f9d1b2ff Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Thu, 12 Jun 2014 16:57:09 +0100 Subject: [PATCH 04/11] rename display_modes to preview_modes for consistency Conflicts: wagtail/wagtailadmin/views/pages.py --- .../wagtailadmin/templates/wagtailadmin/pages/create.html | 4 ++-- .../wagtailadmin/templates/wagtailadmin/pages/edit.html | 4 ++-- wagtail/wagtailadmin/views/pages.py | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) 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 %}