mirror of
https://github.com/wagtail/wagtail.git
synced 2024-12-01 11:41:20 +01:00
Merge pull request #1235 from kaedroho/searchpicks-module
Search promotions module
This commit is contained in:
commit
9dff7a21ef
2
.jscsrc
2
.jscsrc
@ -12,7 +12,7 @@
|
||||
"**/*.min.js",
|
||||
"**/vendor/**/*.js",
|
||||
"./wagtail/wagtailadmin/templates/wagtailadmin/edit_handlers/inline_panel.js",
|
||||
"./wagtail/wagtailsearch/templates/wagtailsearch/editorspicks/includes/editorspicks_formset.js",
|
||||
"./wagtail/contrib/wagtailsearchpromotions/templates/wagtailsearchpromotions/includes/searchpromotions_formset.js",
|
||||
"./wagtail/wagtailusers/templates/wagtailusers/groups/includes/page_permissions_formset.js",
|
||||
"./wagtail/wagtailsnippets/templates/wagtailsnippets/chooser/chosen.js",
|
||||
"./wagtail/wagtailimages/templates/wagtailimages/chooser/image_chosen.js",
|
||||
|
@ -13,6 +13,7 @@ Wagtail ships with a variety of extra optional modules.
|
||||
frontendcache
|
||||
routablepage
|
||||
api/index
|
||||
searchpromotions
|
||||
|
||||
|
||||
:doc:`forms`
|
||||
@ -49,3 +50,9 @@ Provides a way of embedding Django URLconfs into pages.
|
||||
----------------
|
||||
|
||||
A module for adding a read only, JSON based web API to your Wagtail site
|
||||
|
||||
|
||||
:doc:`searchpromotions`
|
||||
-----------------------
|
||||
|
||||
A module for managing "Promoted Search Results"
|
||||
|
63
docs/reference/contrib/searchpromotions.rst
Normal file
63
docs/reference/contrib/searchpromotions.rst
Normal file
@ -0,0 +1,63 @@
|
||||
.. _editors-picks:
|
||||
|
||||
=======================
|
||||
Promoted search results
|
||||
=======================
|
||||
|
||||
.. module:: wagtail.contrib.wagtailsearchpromotions
|
||||
|
||||
.. versionchanged:: 1.1
|
||||
|
||||
Before Wagtail 1.1, promoted search results were implemented in the :mod:`wagtail.wagtailsearch` core module and called "editors picks".
|
||||
|
||||
The ``searchpromotions`` module provides the models and user interface for managing "Promoted search results" and displaying them in a search results page.
|
||||
|
||||
"Promoted search results" allow editors to explicitly link relevant content to search terms, so results pages can contain curated content in addition to results from the search engine.
|
||||
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
The ``searchpromotions`` module is not enabled by default. To install it, add ``wagtail.contrib.wagtailsearchpromotions`` to ``INSTALLED_APPS`` in your project's Django settings file.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
INSTALLED_APPS = [
|
||||
...
|
||||
|
||||
'wagtail.contrib.wagtailsearchpromotions',
|
||||
]
|
||||
|
||||
This app contains migrations so make sure you run the ``migrate`` django-admin command after installing.
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Once installed, a new menu item called "Promoted search results" should appear in the "Settings" menu. This is where you can assign pages to popular search terms.
|
||||
|
||||
|
||||
Displaying on a search results page
|
||||
-----------------------------------
|
||||
|
||||
To retrieve a list of promoted search results for a particular search query, you can use the ``{% get_search_promotions %}`` template tag from the ``wagtailsearchpromotions_tags`` templatetag library:
|
||||
|
||||
.. code-block:: HTML+Django
|
||||
|
||||
{% load wagtailcore_tags wagtailsearchpromotions_tags %}
|
||||
|
||||
...
|
||||
|
||||
{% get_search_promotions search_query as search_promotions %}
|
||||
|
||||
<ul>
|
||||
{% for search_promotion in search_promotions %}
|
||||
<li>
|
||||
<a href="{% pageurl search_promotion.page %}">
|
||||
<h2>{{ search_promotion.page.title }}</h2>
|
||||
<p>{{ search_promotion.description }}</p>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
@ -100,28 +100,12 @@ And here's a template to go with it:
|
||||
{% endblock %}
|
||||
|
||||
|
||||
.. _editors-picks:
|
||||
Promoted search results
|
||||
=======================
|
||||
|
||||
"Promoted search results" allow editors to explicitly link relevant content to search terms, so results pages can contain curated content in addition to results from the search engine.
|
||||
|
||||
Editor's picks
|
||||
==============
|
||||
|
||||
Editor's picks are a way of explicitly linking relevant content to search terms, so results pages can contain curated content in addition to results from the search algorithm.
|
||||
|
||||
You can get a list of editors picks for a particular query using the ``Query`` class:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
editors_picks = Query.get(search_query).editors_picks.all()
|
||||
|
||||
|
||||
Each editors pick contains the following fields:
|
||||
|
||||
``page``
|
||||
The page object associated with the pick. Use ``{% pageurl editors_pick.page %}`` to generate a URL or provide other properties of the page object.
|
||||
|
||||
``description``
|
||||
The description entered when choosing the pick, perhaps explaining why the page is relevant to the search terms.
|
||||
This functionality is provided by the :mod:`~wagtail.contrib.wagtailsearchpromotions` contrib module.
|
||||
|
||||
|
||||
Searching Images, Documents and custom models
|
||||
|
1
wagtail/contrib/wagtailsearchpromotions/__init__.py
Normal file
1
wagtail/contrib/wagtailsearchpromotions/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
default_app_config = 'wagtail.contrib.wagtailsearchpromotions.apps.WagtailSearchPromotionsAppConfig'
|
10
wagtail/contrib/wagtailsearchpromotions/admin_urls.py
Normal file
10
wagtail/contrib/wagtailsearchpromotions/admin_urls.py
Normal file
@ -0,0 +1,10 @@
|
||||
from django.conf.urls import url
|
||||
from wagtail.contrib.wagtailsearchpromotions import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.index, name='index'),
|
||||
url(r'^add/$', views.add, name='add'),
|
||||
url(r'^(\d+)/$', views.edit, name='edit'),
|
||||
url(r'^(\d+)/delete/$', views.delete, name='delete'),
|
||||
]
|
7
wagtail/contrib/wagtailsearchpromotions/apps.py
Normal file
7
wagtail/contrib/wagtailsearchpromotions/apps.py
Normal file
@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class WagtailSearchPromotionsAppConfig(AppConfig):
|
||||
name = 'wagtail.contrib.wagtailsearchpromotions'
|
||||
label = 'wagtailsearchpromotions'
|
||||
verbose_name = "Wagtail search promotions"
|
58
wagtail/contrib/wagtailsearchpromotions/forms.py
Normal file
58
wagtail/contrib/wagtailsearchpromotions/forms.py
Normal file
@ -0,0 +1,58 @@
|
||||
from django import forms
|
||||
from django.forms.models import inlineformset_factory
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.wagtailadmin.widgets import AdminPageChooser
|
||||
from wagtail.wagtailsearch.models import Query
|
||||
from wagtail.contrib.wagtailsearchpromotions.models import SearchPromotion
|
||||
|
||||
|
||||
class SearchPromotionForm(forms.ModelForm):
|
||||
sort_order = forms.IntegerField(required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SearchPromotionForm, self).__init__(*args, **kwargs)
|
||||
self.fields['page'].widget = AdminPageChooser()
|
||||
|
||||
class Meta:
|
||||
model = SearchPromotion
|
||||
fields = ('query', 'page', 'description')
|
||||
|
||||
widgets = {
|
||||
'description': forms.Textarea(attrs=dict(rows=3)),
|
||||
}
|
||||
|
||||
|
||||
SearchPromotionsFormSetBase = inlineformset_factory(Query, SearchPromotion, form=SearchPromotionForm, can_order=True, can_delete=True, extra=0)
|
||||
|
||||
|
||||
class SearchPromotionsFormSet(SearchPromotionsFormSetBase):
|
||||
minimum_forms = 1
|
||||
minimum_forms_message = _("Please specify at least one recommendation for this search term.")
|
||||
|
||||
def add_fields(self, form, *args, **kwargs):
|
||||
super(SearchPromotionsFormSet, self).add_fields(form, *args, **kwargs)
|
||||
|
||||
# Hide delete and order fields
|
||||
form.fields['DELETE'].widget = forms.HiddenInput()
|
||||
form.fields['ORDER'].widget = forms.HiddenInput()
|
||||
|
||||
# Remove query field
|
||||
del form.fields['query']
|
||||
|
||||
def clean(self):
|
||||
# Search pick must have at least one recommended page to be valid
|
||||
# Check there is at least one non-deleted form.
|
||||
non_deleted_forms = self.total_form_count()
|
||||
non_empty_forms = 0
|
||||
for i in range(0, self.total_form_count()):
|
||||
form = self.forms[i]
|
||||
if self.can_delete and self._should_delete_form(form):
|
||||
non_deleted_forms -= 1
|
||||
if not (form.instance.id is None and not form.has_changed()):
|
||||
non_empty_forms += 1
|
||||
if (
|
||||
non_deleted_forms < self.minimum_forms
|
||||
or non_empty_forms < self.minimum_forms
|
||||
):
|
||||
raise forms.ValidationError(self.minimum_forms_message)
|
@ -0,0 +1,47 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wagtailcore', '0015_add_more_verbose_names'),
|
||||
('wagtailsearch', '0003_remove_editors_pick'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.SeparateDatabaseAndState(
|
||||
state_operations=[
|
||||
migrations.CreateModel(
|
||||
name='EditorsPick',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)),
|
||||
('sort_order', models.IntegerField(editable=False, null=True, blank=True)),
|
||||
('description', models.TextField(verbose_name='Description', blank=True)),
|
||||
('page', models.ForeignKey(verbose_name='Page', to='wagtailcore.Page')),
|
||||
('query', models.ForeignKey(to='wagtailsearch.Query', related_name='editors_picks')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'wagtailsearch_editorspick',
|
||||
'verbose_name': "Editor's Pick",
|
||||
'ordering': ('sort_order',),
|
||||
},
|
||||
),
|
||||
],
|
||||
database_operations=[]
|
||||
),
|
||||
migrations.AlterModelTable(
|
||||
name='editorspick',
|
||||
table=None,
|
||||
),
|
||||
migrations.RenameModel(
|
||||
old_name='EditorsPick',
|
||||
new_name='SearchPromotion'
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='searchpromotion',
|
||||
options={'ordering': ('sort_order',), 'verbose_name': 'Search promotion'},
|
||||
),
|
||||
]
|
18
wagtail/contrib/wagtailsearchpromotions/models.py
Normal file
18
wagtail/contrib/wagtailsearchpromotions/models.py
Normal file
@ -0,0 +1,18 @@
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.wagtailsearch.models import Query
|
||||
|
||||
|
||||
class SearchPromotion(models.Model):
|
||||
query = models.ForeignKey(Query, db_index=True, related_name='editors_picks')
|
||||
page = models.ForeignKey('wagtailcore.Page', verbose_name=_('Page'))
|
||||
sort_order = models.IntegerField(null=True, blank=True, editable=False)
|
||||
description = models.TextField(verbose_name=_('Description'), blank=True)
|
||||
|
||||
def __repr__(self):
|
||||
return 'SearchPromotion(query="' + self.query.query_string + '", page="' + self.page.title + '")'
|
||||
|
||||
class Meta:
|
||||
ordering = ('sort_order', )
|
||||
verbose_name = _("Search promotion")
|
@ -1,8 +1,8 @@
|
||||
{% extends "wagtailadmin/base.html" %}
|
||||
{% load i18n %}
|
||||
{% block titletag %}{% trans "Add editor's pick" %}{% endblock %}
|
||||
{% block titletag %}{% trans "Add search pick" %}{% endblock %}
|
||||
{% block content %}
|
||||
{% trans "Add editor's pick" as add_str %}
|
||||
{% trans "Add search pick" as add_str %}
|
||||
{% include "wagtailadmin/shared/header.html" with title=add_str icon="pick" %}
|
||||
|
||||
<div class="nice-padding">
|
||||
@ -15,7 +15,7 @@
|
||||
<p>The "Search term(s)/phrase" field below must contain the full and exact search for which you wish to provide recommended results, <em>including</em> any misspellings/user error. To help, you can choose from search terms that have been popular with users of your site.</p>
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
<form action="{% url 'wagtailsearch_editorspicks_add' %}" method="POST">
|
||||
<form action="{% url 'wagtailsearchpromotions:add' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
|
||||
<ul class="fields">
|
||||
@ -23,7 +23,7 @@
|
||||
{% include "wagtailsearch/queries/chooser_field.html" with field=query_form.query_string only %}
|
||||
</li>
|
||||
<li>
|
||||
{% include "wagtailsearch/editorspicks/includes/editorspicks_formset.html" with formset=editors_pick_formset only %}
|
||||
{% include "wagtailsearchpromotions/includes/searchpromotions_formset.html" with formset=searchpicks_formset only %}
|
||||
</li>
|
||||
<li><input type="submit" value="{% trans 'Save' %}" /></li>
|
||||
</ul>
|
||||
@ -38,7 +38,7 @@
|
||||
{% include "wagtailadmin/pages/_editor_js.html" %}
|
||||
|
||||
<script type="text/javascript">
|
||||
{% include "wagtailsearch/editorspicks/includes/editorspicks_formset.js" with formset=editors_pick_formset only %}
|
||||
{% include "wagtailsearchpromotions/includes/searchpromotions_formset.js" with formset=searchpicks_formset only %}
|
||||
{% include "wagtailsearch/queries/chooser_field.js" only %}
|
||||
|
||||
$(function() {
|
@ -7,7 +7,7 @@
|
||||
|
||||
<div class="nice-padding">
|
||||
<p>{% trans "Are you sure you want to delete all promoted results for this search term?" %}</p>
|
||||
<form action="{% url 'wagtailsearch_editorspicks_delete' query.id %}" method="POST">
|
||||
<form action="{% url 'wagtailsearchpromotions:delete' query.id %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="{% trans 'Yes, delete' %}" class="serious" />
|
||||
</form>
|
@ -5,7 +5,7 @@
|
||||
{% trans "Editing" as editing_str %}
|
||||
{% include "wagtailadmin/shared/header.html" with title=editing_str subtitle=query.query_string icon="pick" %}
|
||||
|
||||
<form action="{% url 'wagtailsearch_editorspicks_edit' query.id %}" method="POST" class="nice-padding">
|
||||
<form action="{% url 'wagtailsearchpromotions:edit' query.id %}" method="POST" class="nice-padding">
|
||||
{% csrf_token %}
|
||||
|
||||
<ul class="fields">
|
||||
@ -13,11 +13,11 @@
|
||||
{% include "wagtailsearch/queries/chooser_field.html" with field=query_form.query_string only %}
|
||||
</li>
|
||||
<li>
|
||||
{% include "wagtailsearch/editorspicks/includes/editorspicks_formset.html" with formset=editors_pick_formset only %}
|
||||
{% include "wagtailsearchpromotions/includes/searchpromotions_formset.html" with formset=searchpicks_formset only %}
|
||||
</li>
|
||||
<li>
|
||||
<input type="submit" value="{% trans 'Save' %}" />
|
||||
<a href="{% url 'wagtailsearch_editorspicks_delete' query.id %}" class="button button-secondary no">{% trans "Delete" %}</a>
|
||||
<a href="{% url 'wagtailsearchpromotions:delete' query.id %}" class="button button-secondary no">{% trans "Delete" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
@ -30,11 +30,11 @@
|
||||
{% include "wagtailadmin/pages/_editor_js.html" %}
|
||||
|
||||
<script type="text/javascript">
|
||||
{% include "wagtailsearch/editorspicks/includes/editorspicks_formset.js" with formset=editors_pick_formset only %}
|
||||
{% include "wagtailsearchpromotions/includes/searchpromotions_formset.js" with formset=searchpicks_formset only %}
|
||||
{% include "wagtailsearch/queries/chooser_field.js" only %}
|
||||
|
||||
$(function() {
|
||||
createQueryChooser('{{ query_form.query_string.auto_id }}');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
@ -2,13 +2,13 @@
|
||||
{{ formset.management_form }}
|
||||
<ul class="multiple" id="id_{{ formset.prefix }}-FORMS">
|
||||
{% for form in formset.forms %}
|
||||
{% include "wagtailsearch/editorspicks/includes/editorspicks_form.html" with form=form only %}
|
||||
{% include "wagtailsearchpromotions/includes/searchpromotion_form.html" with form=form only %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<script type="text/django-form-template" id="id_{{ formset.prefix }}-EMPTY_FORM_TEMPLATE">
|
||||
{% escapescript %}
|
||||
{% include "wagtailsearch/editorspicks/includes/editorspicks_form.html" with form=formset.empty_form only %}
|
||||
{% include "wagtailsearchpromotions/includes/searchpromotion_form.html" with form=formset.empty_form only %}
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
|
@ -6,7 +6,7 @@
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
window.headerSearch = {
|
||||
url: "{% url 'wagtailsearch_editorspicks_index' %}",
|
||||
url: "{% url 'wagtailsearchpromotions:index' %}",
|
||||
termInput: "#id_q",
|
||||
targetOutput: "#editorspicks-results"
|
||||
}
|
||||
@ -16,11 +16,11 @@
|
||||
{% block content %}
|
||||
{% trans "Promoted search results" as sp_title_str %}
|
||||
{% trans "Add new promoted result" as sp_text_str %}
|
||||
{% include "wagtailadmin/shared/header.html" with title=sp_title_str add_link="wagtailsearch_editorspicks_add" icon="pick" add_text=sp_text_str search_url="wagtailsearch_editorspicks_index" %}
|
||||
{% include "wagtailadmin/shared/header.html" with title=sp_title_str add_link="wagtailsearchpromotions:add" icon="pick" add_text=sp_text_str search_url="wagtailsearchpromotions:index" %}
|
||||
|
||||
<div class="nice-padding">
|
||||
<div id="editorspicks-results" class="redirects">
|
||||
{% include "wagtailsearch/editorspicks/results.html" %}
|
||||
{% include "wagtailsearchpromotions/results.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
@ -14,11 +14,11 @@
|
||||
{% for query in queries %}
|
||||
<tr>
|
||||
<td class="title">
|
||||
<h2><a href="{% url 'wagtailsearch_editorspicks_edit' query.id %}" title="{% trans 'Edit this pick' %}">{{ query.query_string }}</a></h2>
|
||||
<h2><a href="{% url 'wagtailsearchpromotions:edit' query.id %}" title="{% trans 'Edit this pick' %}">{{ query.query_string }}</a></h2>
|
||||
</td>
|
||||
<td>
|
||||
{% for editors_pick in query.editors_picks.all %}
|
||||
<a href="{% url 'wagtailadmin_pages:edit' editors_pick.page.id %}" class="nolink">{{ editors_pick.page.title }}</a>{% if not forloop.last %}, {% endif %}
|
||||
{% for searchpick in query.editors_picks.all %}
|
||||
<a href="{% url 'wagtailadmin_pages:edit' searchpick.page.id %}" class="nolink">{{ searchpick.page.title }}</a>{% if not forloop.last %}, {% endif %}
|
||||
{% empty %}
|
||||
{% trans "None" %}
|
||||
{% endfor %}
|
||||
@ -27,4 +27,4 @@
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</table>
|
@ -10,14 +10,14 @@
|
||||
</h2>
|
||||
{% endif %}
|
||||
|
||||
{% include "wagtailsearch/editorspicks/list.html" %}
|
||||
{% include "wagtailsearchpromotions/list.html" %}
|
||||
|
||||
{% include "wagtailadmin/shared/pagination_nav.html" with items=queries is_searching=is_searching linkurl="wagtailsearch_editorspicks_index" %}
|
||||
{% include "wagtailadmin/shared/pagination_nav.html" with items=queries is_searching=is_searching linkurl="wagtailsearchpromotions:index" %}
|
||||
{% else %}
|
||||
{% if is_searching %}
|
||||
<p>{% blocktrans %}Sorry, no promoted results match "<em>{{ query_string }}</em>"{% endblocktrans %}</p>
|
||||
{% else %}
|
||||
{% url 'wagtailsearch_editorspicks_add' as wagtailsearch_editorspicks_add_url %}
|
||||
<p>{% blocktrans %}No promoted results have been created. Why not <a href="{{ wagtailsearch_editorspicks_add_url }}">add one</a>?{% endblocktrans %}</p>
|
||||
{% url 'wagtailsearchpromotions:add' as wagtailsearchpromotions_add_url %}
|
||||
<p>{% blocktrans %}No promoted results have been created. Why not <a href="{{ wagtailsearchpromotions_add_url }}">add one</a>?{% endblocktrans %}</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
@ -0,0 +1,11 @@
|
||||
from django import template
|
||||
|
||||
from wagtail.wagtailsearch.models import Query
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.assignment_tag()
|
||||
def get_search_promotions(search_query):
|
||||
return Query.get(search_query).editors_picks.all()
|
336
wagtail/contrib/wagtailsearchpromotions/tests.py
Normal file
336
wagtail/contrib/wagtailsearchpromotions/tests.py
Normal file
@ -0,0 +1,336 @@
|
||||
from django.test import TestCase
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
from wagtail.wagtailsearch.models import Query
|
||||
from wagtail.contrib.wagtailsearchpromotions.models import SearchPromotion
|
||||
from wagtail.contrib.wagtailsearchpromotions.templatetags.wagtailsearchpromotions_tags import get_search_promotions
|
||||
|
||||
|
||||
class TestSearchPromotions(TestCase):
|
||||
def test_search_pick_create(self):
|
||||
# Create a search pick to the root page
|
||||
SearchPromotion.objects.create(
|
||||
query=Query.get("root page"),
|
||||
page_id=1,
|
||||
sort_order=0,
|
||||
description="First search pick",
|
||||
)
|
||||
|
||||
# Check
|
||||
self.assertEqual(Query.get("root page").editors_picks.count(), 1)
|
||||
self.assertEqual(Query.get("root page").editors_picks.first().page_id, 1)
|
||||
|
||||
def test_search_pick_ordering(self):
|
||||
# Add 3 search picks in a different order to their sort_order values
|
||||
# They should be ordered by their sort order values and not their insertion order
|
||||
SearchPromotion.objects.create(
|
||||
query=Query.get("root page"),
|
||||
page_id=1,
|
||||
sort_order=0,
|
||||
description="First search pick",
|
||||
)
|
||||
SearchPromotion.objects.create(
|
||||
query=Query.get("root page"),
|
||||
page_id=1,
|
||||
sort_order=2,
|
||||
description="Last search pick",
|
||||
)
|
||||
SearchPromotion.objects.create(
|
||||
query=Query.get("root page"),
|
||||
page_id=1,
|
||||
sort_order=1,
|
||||
description="Middle search pick",
|
||||
)
|
||||
|
||||
# Check
|
||||
self.assertEqual(Query.get("root page").editors_picks.count(), 3)
|
||||
self.assertEqual(Query.get("root page").editors_picks.first().description, "First search pick")
|
||||
self.assertEqual(Query.get("root page").editors_picks.last().description, "Last search pick")
|
||||
|
||||
|
||||
class TestGetSearchPromotionsTemplateTag(TestCase):
|
||||
def test_get_search_promotions_template_tag(self):
|
||||
# Create a search pick to the root page
|
||||
pick = SearchPromotion.objects.create(
|
||||
query=Query.get("root page"),
|
||||
page_id=1,
|
||||
sort_order=0,
|
||||
description="First search pick",
|
||||
)
|
||||
|
||||
# Create another search pick against a different query
|
||||
SearchPromotion.objects.create(
|
||||
query=Query.get("root page again"),
|
||||
page_id=1,
|
||||
sort_order=0,
|
||||
description="Second search pick",
|
||||
)
|
||||
|
||||
# Check
|
||||
search_picks = list(get_search_promotions("root page"))
|
||||
self.assertEqual(search_picks, [pick])
|
||||
|
||||
|
||||
class TestSearchPromotionsIndexView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
||||
def test_simple(self):
|
||||
response = self.client.get(reverse('wagtailsearchpromotions:index'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearchpromotions/index.html')
|
||||
|
||||
def test_search(self):
|
||||
response = self.client.get(reverse('wagtailsearchpromotions:index'), {'q': "Hello"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.context['query_string'], "Hello")
|
||||
|
||||
def make_search_picks(self):
|
||||
for i in range(50):
|
||||
SearchPromotion.objects.create(
|
||||
query=Query.get("query " + str(i)),
|
||||
page_id=1,
|
||||
sort_order=0,
|
||||
description="First search pick",
|
||||
)
|
||||
|
||||
def test_pagination(self):
|
||||
self.make_search_picks()
|
||||
|
||||
response = self.client.get(reverse('wagtailsearchpromotions:index'), {'p': 2})
|
||||
|
||||
# Check response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearchpromotions/index.html')
|
||||
|
||||
# Check that we got the correct page
|
||||
self.assertEqual(response.context['queries'].number, 2)
|
||||
|
||||
def test_pagination_invalid(self):
|
||||
self.make_search_picks()
|
||||
|
||||
response = self.client.get(reverse('wagtailsearchpromotions:index'), {'p': 'Hello World!'})
|
||||
|
||||
# Check response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearchpromotions/index.html')
|
||||
|
||||
# Check that we got page one
|
||||
self.assertEqual(response.context['queries'].number, 1)
|
||||
|
||||
def test_pagination_out_of_range(self):
|
||||
self.make_search_picks()
|
||||
|
||||
response = self.client.get(reverse('wagtailsearchpromotions:index'), {'p': 99999})
|
||||
|
||||
# Check response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearchpromotions/index.html')
|
||||
|
||||
# Check that we got the last page
|
||||
self.assertEqual(response.context['queries'].number, response.context['queries'].paginator.num_pages)
|
||||
|
||||
|
||||
class TestSearchPromotionsAddView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
||||
def test_simple(self):
|
||||
response = self.client.get(reverse('wagtailsearchpromotions:add'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearchpromotions/add.html')
|
||||
|
||||
def test_post(self):
|
||||
# Submit
|
||||
post_data = {
|
||||
'query_string': "test",
|
||||
'editors_picks-TOTAL_FORMS': 1,
|
||||
'editors_picks-INITIAL_FORMS': 0,
|
||||
'editors_picks-MAX_NUM_FORMS': 1000,
|
||||
'editors_picks-0-DELETE': '',
|
||||
'editors_picks-0-ORDER': 0,
|
||||
'editors_picks-0-page': 1,
|
||||
'editors_picks-0-description': "Hello",
|
||||
}
|
||||
response = self.client.post(reverse('wagtailsearchpromotions:add'), post_data)
|
||||
|
||||
# User should be redirected back to the index
|
||||
self.assertRedirects(response, reverse('wagtailsearchpromotions:index'))
|
||||
|
||||
# Check that the search pick was created
|
||||
self.assertTrue(Query.get('test').editors_picks.filter(page_id=1).exists())
|
||||
|
||||
def test_post_without_recommendations(self):
|
||||
# Submit
|
||||
post_data = {
|
||||
'query_string': "test",
|
||||
'editors_picks-TOTAL_FORMS': 0,
|
||||
'editors_picks-INITIAL_FORMS': 0,
|
||||
'editors_picks-MAX_NUM_FORMS': 1000,
|
||||
}
|
||||
response = self.client.post(reverse('wagtailsearchpromotions:add'), post_data)
|
||||
|
||||
# User should be given an error
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertFormsetError(response, 'searchpicks_formset', None, None, "Please specify at least one recommendation for this search term.")
|
||||
|
||||
|
||||
class TestSearchPromotionsEditView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
||||
# Create an search pick to edit
|
||||
self.query = Query.get("Hello")
|
||||
self.search_pick = self.query.editors_picks.create(page_id=1, description="Root page")
|
||||
self.search_pick_2 = self.query.editors_picks.create(page_id=2, description="Homepage")
|
||||
|
||||
def test_simple(self):
|
||||
response = self.client.get(reverse('wagtailsearchpromotions:edit', args=(self.query.id, )))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearchpromotions/edit.html')
|
||||
|
||||
def test_post(self):
|
||||
# Submit
|
||||
post_data = {
|
||||
'query_string': "Hello",
|
||||
'editors_picks-TOTAL_FORMS': 2,
|
||||
'editors_picks-INITIAL_FORMS': 2,
|
||||
'editors_picks-MAX_NUM_FORMS': 1000,
|
||||
'editors_picks-0-id': self.search_pick.id,
|
||||
'editors_picks-0-DELETE': '',
|
||||
'editors_picks-0-ORDER': 0,
|
||||
'editors_picks-0-page': 1,
|
||||
'editors_picks-0-description': "Description has changed", # Change
|
||||
'editors_picks-1-id': self.search_pick_2.id,
|
||||
'editors_picks-1-DELETE': '',
|
||||
'editors_picks-1-ORDER': 1,
|
||||
'editors_picks-1-page': 2,
|
||||
'editors_picks-1-description': "Homepage",
|
||||
}
|
||||
response = self.client.post(reverse('wagtailsearchpromotions:edit', args=(self.query.id, )), post_data)
|
||||
|
||||
# User should be redirected back to the index
|
||||
self.assertRedirects(response, reverse('wagtailsearchpromotions:index'))
|
||||
|
||||
# Check that the search pick description was edited
|
||||
self.assertEqual(SearchPromotion.objects.get(id=self.search_pick.id).description, "Description has changed")
|
||||
|
||||
def test_post_reorder(self):
|
||||
# Check order before reordering
|
||||
self.assertEqual(Query.get("Hello").editors_picks.all()[0], self.search_pick)
|
||||
self.assertEqual(Query.get("Hello").editors_picks.all()[1], self.search_pick_2)
|
||||
|
||||
# Submit
|
||||
post_data = {
|
||||
'query_string': "Hello",
|
||||
'editors_picks-TOTAL_FORMS': 2,
|
||||
'editors_picks-INITIAL_FORMS': 2,
|
||||
'editors_picks-MAX_NUM_FORMS': 1000,
|
||||
'editors_picks-0-id': self.search_pick.id,
|
||||
'editors_picks-0-DELETE': '',
|
||||
'editors_picks-0-ORDER': 1, # Change
|
||||
'editors_picks-0-page': 1,
|
||||
'editors_picks-0-description': "Root page",
|
||||
'editors_picks-1-id': self.search_pick_2.id,
|
||||
'editors_picks-1-DELETE': '',
|
||||
'editors_picks-1-ORDER': 0, # Change
|
||||
'editors_picks-1-page': 2,
|
||||
'editors_picks-1-description': "Homepage",
|
||||
}
|
||||
response = self.client.post(reverse('wagtailsearchpromotions:edit', args=(self.query.id, )), post_data)
|
||||
|
||||
# User should be redirected back to the index
|
||||
self.assertRedirects(response, reverse('wagtailsearchpromotions:index'))
|
||||
|
||||
# Check that the ordering has been saved correctly
|
||||
self.assertEqual(SearchPromotion.objects.get(id=self.search_pick.id).sort_order, 1)
|
||||
self.assertEqual(SearchPromotion.objects.get(id=self.search_pick_2.id).sort_order, 0)
|
||||
|
||||
# Check that the recommendations were reordered
|
||||
self.assertEqual(Query.get("Hello").editors_picks.all()[0], self.search_pick_2)
|
||||
self.assertEqual(Query.get("Hello").editors_picks.all()[1], self.search_pick)
|
||||
|
||||
def test_post_delete_recommendation(self):
|
||||
# Submit
|
||||
post_data = {
|
||||
'query_string': "Hello",
|
||||
'editors_picks-TOTAL_FORMS': 2,
|
||||
'editors_picks-INITIAL_FORMS': 2,
|
||||
'editors_picks-MAX_NUM_FORMS': 1000,
|
||||
'editors_picks-0-id': self.search_pick.id,
|
||||
'editors_picks-0-DELETE': '',
|
||||
'editors_picks-0-ORDER': 0,
|
||||
'editors_picks-0-page': 1,
|
||||
'editors_picks-0-description': "Root page",
|
||||
'editors_picks-1-id': self.search_pick_2.id,
|
||||
'editors_picks-1-DELETE': 1,
|
||||
'editors_picks-1-ORDER': 1,
|
||||
'editors_picks-1-page': 2,
|
||||
'editors_picks-1-description': "Homepage",
|
||||
}
|
||||
response = self.client.post(reverse('wagtailsearchpromotions:edit', args=(self.query.id, )), post_data)
|
||||
|
||||
# User should be redirected back to the index
|
||||
self.assertRedirects(response, reverse('wagtailsearchpromotions:index'))
|
||||
|
||||
# Check that the recommendation was deleted
|
||||
self.assertFalse(SearchPromotion.objects.filter(id=self.search_pick_2.id).exists())
|
||||
|
||||
# The other recommendation should still exist
|
||||
self.assertTrue(SearchPromotion.objects.filter(id=self.search_pick.id).exists())
|
||||
|
||||
def test_post_without_recommendations(self):
|
||||
# Submit
|
||||
post_data = {
|
||||
'query_string': "Hello",
|
||||
'editors_picks-TOTAL_FORMS': 2,
|
||||
'editors_picks-INITIAL_FORMS': 2,
|
||||
'editors_picks-MAX_NUM_FORMS': 1000,
|
||||
'editors_picks-0-id': self.search_pick.id,
|
||||
'editors_picks-0-DELETE': 1,
|
||||
'editors_picks-0-ORDER': 0,
|
||||
'editors_picks-0-page': 1,
|
||||
'editors_picks-0-description': "Description has changed", # Change
|
||||
'editors_picks-1-id': self.search_pick_2.id,
|
||||
'editors_picks-1-DELETE': 1,
|
||||
'editors_picks-1-ORDER': 1,
|
||||
'editors_picks-1-page': 2,
|
||||
'editors_picks-1-description': "Homepage",
|
||||
}
|
||||
response = self.client.post(reverse('wagtailsearchpromotions:edit', args=(self.query.id, )), post_data)
|
||||
|
||||
# User should be given an error
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertFormsetError(response, 'searchpicks_formset', None, None, "Please specify at least one recommendation for this search term.")
|
||||
|
||||
|
||||
class TestSearchPromotionsDeleteView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
||||
# Create an search pick to delete
|
||||
self.query = Query.get("Hello")
|
||||
self.search_pick = self.query.editors_picks.create(page_id=1, description="Root page")
|
||||
self.search_pick_2 = self.query.editors_picks.create(page_id=2, description="Homepage")
|
||||
|
||||
def test_simple(self):
|
||||
response = self.client.get(reverse('wagtailsearchpromotions:delete', args=(self.query.id, )))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearchpromotions/confirm_delete.html')
|
||||
|
||||
def test_post(self):
|
||||
# Submit
|
||||
post_data = {
|
||||
'foo': 'bar',
|
||||
}
|
||||
response = self.client.post(reverse('wagtailsearchpromotions:delete', args=(self.query.id, )), post_data)
|
||||
|
||||
# User should be redirected back to the index
|
||||
self.assertRedirects(response, reverse('wagtailsearchpromotions:index'))
|
||||
|
||||
# Check that both recommendations were deleted
|
||||
self.assertFalse(SearchPromotion.objects.filter(id=self.search_pick_2.id).exists())
|
||||
|
||||
# The other recommendation should still exist
|
||||
self.assertFalse(SearchPromotion.objects.filter(id=self.search_pick.id).exists())
|
@ -5,10 +5,13 @@ from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.vary import vary_on_headers
|
||||
|
||||
from wagtail.wagtailsearch import models, forms
|
||||
from wagtail.wagtailsearch import forms as search_forms
|
||||
from wagtail.wagtailsearch.models import Query
|
||||
from wagtail.wagtailadmin.forms import SearchForm
|
||||
from wagtail.wagtailadmin import messages
|
||||
|
||||
from wagtail.contrib.wagtailsearchpromotions import forms
|
||||
|
||||
|
||||
@vary_on_headers('X-Requested-With')
|
||||
def index(request):
|
||||
@ -16,7 +19,7 @@ def index(request):
|
||||
page = request.GET.get('p', 1)
|
||||
query_string = request.GET.get('q', "")
|
||||
|
||||
queries = models.Query.objects.filter(editors_picks__isnull=False).distinct()
|
||||
queries = Query.objects.filter(editors_picks__isnull=False).distinct()
|
||||
|
||||
# Search
|
||||
if query_string:
|
||||
@ -33,13 +36,13 @@ def index(request):
|
||||
queries = paginator.page(paginator.num_pages)
|
||||
|
||||
if request.is_ajax():
|
||||
return render(request, "wagtailsearch/editorspicks/results.html", {
|
||||
return render(request, "wagtailsearchpromotions/results.html", {
|
||||
'is_searching': is_searching,
|
||||
'queries': queries,
|
||||
'query_string': query_string,
|
||||
})
|
||||
else:
|
||||
return render(request, 'wagtailsearch/editorspicks/index.html', {
|
||||
return render(request, 'wagtailsearchpromotions/index.html', {
|
||||
'is_searching': is_searching,
|
||||
'queries': queries,
|
||||
'query_string': query_string,
|
||||
@ -47,21 +50,21 @@ def index(request):
|
||||
})
|
||||
|
||||
|
||||
def save_editorspicks(query, new_query, editors_pick_formset):
|
||||
def save_searchpicks(query, new_query, searchpicks_formset):
|
||||
# Save
|
||||
if editors_pick_formset.is_valid():
|
||||
if searchpicks_formset.is_valid():
|
||||
# Set sort_order
|
||||
for i, form in enumerate(editors_pick_formset.ordered_forms):
|
||||
for i, form in enumerate(searchpicks_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()
|
||||
searchpicks_formset.save()
|
||||
|
||||
# If query was changed, move all editors picks to the new query
|
||||
# If query was changed, move all search picks to the new query
|
||||
if query != new_query:
|
||||
editors_pick_formset.get_queryset().update(query=new_query)
|
||||
searchpicks_formset.get_queryset().update(query=new_query)
|
||||
|
||||
return True
|
||||
else:
|
||||
@ -71,77 +74,77 @@ def save_editorspicks(query, new_query, editors_pick_formset):
|
||||
def add(request):
|
||||
if request.POST:
|
||||
# Get query
|
||||
query_form = forms.QueryForm(request.POST)
|
||||
query_form = search_forms.QueryForm(request.POST)
|
||||
if query_form.is_valid():
|
||||
query = models.Query.get(query_form['query_string'].value())
|
||||
query = Query.get(query_form['query_string'].value())
|
||||
|
||||
# Save editors picks
|
||||
editors_pick_formset = forms.EditorsPickFormSet(request.POST, instance=query)
|
||||
if save_editorspicks(query, query, editors_pick_formset):
|
||||
# Save search picks
|
||||
searchpicks_formset = forms.SearchPromotionsFormSet(request.POST, instance=query)
|
||||
if save_searchpicks(query, query, searchpicks_formset):
|
||||
messages.success(request, _("Editor's picks for '{0}' created.").format(query), buttons=[
|
||||
messages.button(reverse('wagtailsearch_editorspicks_edit', args=(query.id,)), _('Edit'))
|
||||
messages.button(reverse('wagtailsearchpromotions:edit', args=(query.id,)), _('Edit'))
|
||||
])
|
||||
return redirect('wagtailsearch_editorspicks_index')
|
||||
return redirect('wagtailsearchpromotions:index')
|
||||
else:
|
||||
if len(editors_pick_formset.non_form_errors()):
|
||||
messages.error(request, " ".join(error for error in editors_pick_formset.non_form_errors())) # formset level error (e.g. no forms submitted)
|
||||
if len(searchpicks_formset.non_form_errors()):
|
||||
messages.error(request, " ".join(error for error in searchpicks_formset.non_form_errors())) # formset level error (e.g. no forms submitted)
|
||||
else:
|
||||
messages.error(request, _("Recommendations have not been created due to errors")) # specific errors will be displayed within form fields
|
||||
else:
|
||||
editors_pick_formset = forms.EditorsPickFormSet()
|
||||
searchpicks_formset = forms.SearchPromotionsFormSet()
|
||||
else:
|
||||
query_form = forms.QueryForm()
|
||||
editors_pick_formset = forms.EditorsPickFormSet()
|
||||
query_form = search_forms.QueryForm()
|
||||
searchpicks_formset = forms.SearchPromotionsFormSet()
|
||||
|
||||
return render(request, 'wagtailsearch/editorspicks/add.html', {
|
||||
return render(request, 'wagtailsearchpromotions/add.html', {
|
||||
'query_form': query_form,
|
||||
'editors_pick_formset': editors_pick_formset,
|
||||
'searchpicks_formset': searchpicks_formset,
|
||||
})
|
||||
|
||||
|
||||
def edit(request, query_id):
|
||||
query = get_object_or_404(models.Query, id=query_id)
|
||||
query = get_object_or_404(Query, id=query_id)
|
||||
|
||||
if request.POST:
|
||||
# Get query
|
||||
query_form = forms.QueryForm(request.POST)
|
||||
query_form = search_forms.QueryForm(request.POST)
|
||||
# and the recommendations
|
||||
editors_pick_formset = forms.EditorsPickFormSet(request.POST, instance=query)
|
||||
searchpicks_formset = forms.SearchPromotionsFormSet(request.POST, instance=query)
|
||||
|
||||
if query_form.is_valid():
|
||||
new_query = models.Query.get(query_form['query_string'].value())
|
||||
new_query = Query.get(query_form['query_string'].value())
|
||||
|
||||
# Save editors picks
|
||||
if save_editorspicks(query, new_query, editors_pick_formset):
|
||||
# Save search picks
|
||||
if save_searchpicks(query, new_query, searchpicks_formset):
|
||||
messages.success(request, _("Editor's picks for '{0}' updated.").format(new_query), buttons=[
|
||||
messages.button(reverse('wagtailsearch_editorspicks_edit', args=(query.id,)), _('Edit'))
|
||||
messages.button(reverse('wagtailsearchpromotions:edit', args=(query.id,)), _('Edit'))
|
||||
])
|
||||
return redirect('wagtailsearch_editorspicks_index')
|
||||
return redirect('wagtailsearchpromotions:index')
|
||||
else:
|
||||
if len(editors_pick_formset.non_form_errors()):
|
||||
messages.error(request, " ".join(error for error in editors_pick_formset.non_form_errors())) # formset level error (e.g. no forms submitted)
|
||||
if len(searchpicks_formset.non_form_errors()):
|
||||
messages.error(request, " ".join(error for error in searchpicks_formset.non_form_errors())) # formset level error (e.g. no forms submitted)
|
||||
else:
|
||||
messages.error(request, _("Recommendations have not been saved due to errors")) # specific errors will be displayed within form fields
|
||||
|
||||
else:
|
||||
query_form = forms.QueryForm(initial=dict(query_string=query.query_string))
|
||||
editors_pick_formset = forms.EditorsPickFormSet(instance=query)
|
||||
query_form = search_forms.QueryForm(initial=dict(query_string=query.query_string))
|
||||
searchpicks_formset = forms.SearchPromotionsFormSet(instance=query)
|
||||
|
||||
return render(request, 'wagtailsearch/editorspicks/edit.html', {
|
||||
return render(request, 'wagtailsearchpromotions/edit.html', {
|
||||
'query_form': query_form,
|
||||
'editors_pick_formset': editors_pick_formset,
|
||||
'searchpicks_formset': searchpicks_formset,
|
||||
'query': query,
|
||||
})
|
||||
|
||||
|
||||
def delete(request, query_id):
|
||||
query = get_object_or_404(models.Query, id=query_id)
|
||||
query = get_object_or_404(Query, id=query_id)
|
||||
|
||||
if request.POST:
|
||||
query.editors_picks.all().delete()
|
||||
messages.success(request, _("Editor's picks deleted."))
|
||||
return redirect('wagtailsearch_editorspicks_index')
|
||||
return redirect('wagtailsearchpromotions:index')
|
||||
|
||||
return render(request, 'wagtailsearch/editorspicks/confirm_delete.html', {
|
||||
return render(request, 'wagtailsearchpromotions/confirm_delete.html', {
|
||||
'query': query,
|
||||
})
|
26
wagtail/contrib/wagtailsearchpromotions/wagtail_hooks.py
Normal file
26
wagtail/contrib/wagtailsearchpromotions/wagtail_hooks.py
Normal file
@ -0,0 +1,26 @@
|
||||
from django.core import urlresolvers
|
||||
from django.conf.urls import include, url
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.wagtailcore import hooks
|
||||
from wagtail.contrib.wagtailsearchpromotions import admin_urls
|
||||
|
||||
from wagtail.wagtailadmin.menu import MenuItem
|
||||
|
||||
|
||||
@hooks.register('register_admin_urls')
|
||||
def register_admin_urls():
|
||||
return [
|
||||
url(r'^searchpicks/', include(admin_urls, namespace='wagtailsearchpromotions')),
|
||||
]
|
||||
|
||||
|
||||
class SearchPicksMenuItem(MenuItem):
|
||||
def is_shown(self, request):
|
||||
# TEMPORARY: Only show if the user is a superuser
|
||||
return request.user.is_superuser
|
||||
|
||||
|
||||
@hooks.register('register_settings_menu_item')
|
||||
def register_search_picks_menu_item():
|
||||
return SearchPicksMenuItem(_('Promoted search results'), urlresolvers.reverse('wagtailsearchpromotions:index'), classnames='icon icon-pick', order=900)
|
@ -94,6 +94,7 @@ INSTALLED_APPS = (
|
||||
'wagtail.contrib.wagtailroutablepage',
|
||||
'wagtail.contrib.wagtailfrontendcache',
|
||||
'wagtail.contrib.wagtailapi',
|
||||
'wagtail.contrib.wagtailsearchpromotions',
|
||||
'wagtail.wagtailforms',
|
||||
'wagtail.wagtailsearch',
|
||||
'wagtail.wagtailembeds',
|
||||
|
@ -1,10 +1,6 @@
|
||||
from django import forms
|
||||
from django.forms.models import inlineformset_factory
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.wagtailadmin.widgets import AdminPageChooser
|
||||
from wagtail.wagtailsearch import models
|
||||
|
||||
|
||||
class QueryForm(forms.Form):
|
||||
query_string = forms.CharField(label=_("Search term(s)/phrase"),
|
||||
@ -12,54 +8,3 @@ class QueryForm(forms.Form):
|
||||
"exact match is required for your Editors Picks to be "
|
||||
"displayed, wildcards are NOT allowed."),
|
||||
required=True)
|
||||
|
||||
|
||||
class EditorsPickForm(forms.ModelForm):
|
||||
sort_order = forms.IntegerField(required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditorsPickForm, self).__init__(*args, **kwargs)
|
||||
self.fields['page'].widget = AdminPageChooser()
|
||||
|
||||
class Meta:
|
||||
model = models.EditorsPick
|
||||
fields = ('query', 'page', 'description')
|
||||
|
||||
widgets = {
|
||||
'description': forms.Textarea(attrs=dict(rows=3)),
|
||||
}
|
||||
|
||||
|
||||
EditorsPickFormSetBase = inlineformset_factory(models.Query, models.EditorsPick, form=EditorsPickForm, can_order=True, can_delete=True, extra=0)
|
||||
|
||||
|
||||
class EditorsPickFormSet(EditorsPickFormSetBase):
|
||||
minimum_forms = 1
|
||||
minimum_forms_message = _("Please specify at least one recommendation for this search term.")
|
||||
|
||||
def add_fields(self, form, *args, **kwargs):
|
||||
super(EditorsPickFormSet, self).add_fields(form, *args, **kwargs)
|
||||
|
||||
# Hide delete and order fields
|
||||
form.fields['DELETE'].widget = forms.HiddenInput()
|
||||
form.fields['ORDER'].widget = forms.HiddenInput()
|
||||
|
||||
# Remove query field
|
||||
del form.fields['query']
|
||||
|
||||
def clean(self):
|
||||
# Editors pick must have at least one recommended page to be valid
|
||||
# Check there is at least one non-deleted form.
|
||||
non_deleted_forms = self.total_form_count()
|
||||
non_empty_forms = 0
|
||||
for i in range(0, self.total_form_count()):
|
||||
form = self.forms[i]
|
||||
if self.can_delete and self._should_delete_form(form):
|
||||
non_deleted_forms -= 1
|
||||
if not (form.instance.id is None and not form.has_changed()):
|
||||
non_empty_forms += 1
|
||||
if (
|
||||
non_deleted_forms < self.minimum_forms
|
||||
or non_empty_forms < self.minimum_forms
|
||||
):
|
||||
raise forms.ValidationError(self.minimum_forms_message)
|
||||
|
38
wagtail/wagtailsearch/migrations/0003_remove_editors_pick.py
Normal file
38
wagtail/wagtailsearch/migrations/0003_remove_editors_pick.py
Normal file
@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('wagtailsearch', '0002_add_verbose_names'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# EditorsPicks have been moved to the "wagtailsearchpromotions" module.
|
||||
|
||||
# Remove EditorsPick from wagtailsearch but don't drop the underlying table
|
||||
# so wagtailsearchpromotions can pick it up in its initial migration.
|
||||
|
||||
# If wagtailsearchpromotions isn't installed, this table will remain
|
||||
# in the database unmanaged until it is. This could potentially happen
|
||||
# at any point in the future so it's important to keep this behaviour
|
||||
# even if we decide to squash these migrations.
|
||||
migrations.SeparateDatabaseAndState(
|
||||
state_operations=[
|
||||
migrations.RemoveField(
|
||||
model_name='editorspick',
|
||||
name='page',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='editorspick',
|
||||
name='query',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='EditorsPick',
|
||||
),
|
||||
],
|
||||
database_operations=[],
|
||||
)
|
||||
]
|
@ -71,17 +71,3 @@ class QueryDailyHits(models.Model):
|
||||
('query', 'date'),
|
||||
)
|
||||
verbose_name = _('Query Daily Hits')
|
||||
|
||||
|
||||
class EditorsPick(models.Model):
|
||||
query = models.ForeignKey(Query, db_index=True, related_name='editors_picks')
|
||||
page = models.ForeignKey('wagtailcore.Page', verbose_name=_('Page'))
|
||||
sort_order = models.IntegerField(null=True, blank=True, editable=False)
|
||||
description = models.TextField(verbose_name=_('Description'), blank=True)
|
||||
|
||||
def __repr__(self):
|
||||
return 'EditorsPick(query="' + self.query.query_string + '", page="' + self.page.title + '")'
|
||||
|
||||
class Meta:
|
||||
ordering = ('sort_order', )
|
||||
verbose_name = _("Editor's Pick")
|
||||
|
@ -1,311 +0,0 @@
|
||||
from django.test import TestCase
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
from wagtail.wagtailsearch import models
|
||||
|
||||
|
||||
class TestEditorsPicks(TestCase):
|
||||
def test_editors_pick_create(self):
|
||||
# Create an editors pick to the root page
|
||||
models.EditorsPick.objects.create(
|
||||
query=models.Query.get("root page"),
|
||||
page_id=1,
|
||||
sort_order=0,
|
||||
description="First editors pick",
|
||||
)
|
||||
|
||||
# Check
|
||||
self.assertEqual(models.Query.get("root page").editors_picks.count(), 1)
|
||||
self.assertEqual(models.Query.get("root page").editors_picks.first().page_id, 1)
|
||||
|
||||
def test_editors_pick_ordering(self):
|
||||
# Add 3 editors picks in a different order to their sort_order values
|
||||
# They should be ordered by their sort order values and not their insertion order
|
||||
models.EditorsPick.objects.create(
|
||||
query=models.Query.get("root page"),
|
||||
page_id=1,
|
||||
sort_order=0,
|
||||
description="First editors pick",
|
||||
)
|
||||
models.EditorsPick.objects.create(
|
||||
query=models.Query.get("root page"),
|
||||
page_id=1,
|
||||
sort_order=2,
|
||||
description="Last editors pick",
|
||||
)
|
||||
models.EditorsPick.objects.create(
|
||||
query=models.Query.get("root page"),
|
||||
page_id=1,
|
||||
sort_order=1,
|
||||
description="Middle editors pick",
|
||||
)
|
||||
|
||||
# Check
|
||||
self.assertEqual(models.Query.get("root page").editors_picks.count(), 3)
|
||||
self.assertEqual(models.Query.get("root page").editors_picks.first().description, "First editors pick")
|
||||
self.assertEqual(models.Query.get("root page").editors_picks.last().description, "Last editors pick")
|
||||
|
||||
|
||||
class TestEditorsPicksIndexView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
||||
def test_simple(self):
|
||||
response = self.client.get(reverse('wagtailsearch_editorspicks_index'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/index.html')
|
||||
|
||||
def test_search(self):
|
||||
response = self.client.get(reverse('wagtailsearch_editorspicks_index'), {'q': "Hello"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.context['query_string'], "Hello")
|
||||
|
||||
def make_editors_picks(self):
|
||||
for i in range(50):
|
||||
models.EditorsPick.objects.create(
|
||||
query=models.Query.get("query " + str(i)),
|
||||
page_id=1,
|
||||
sort_order=0,
|
||||
description="First editors pick",
|
||||
)
|
||||
|
||||
def test_pagination(self):
|
||||
self.make_editors_picks()
|
||||
|
||||
response = self.client.get(reverse('wagtailsearch_editorspicks_index'), {'p': 2})
|
||||
|
||||
# Check response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/index.html')
|
||||
|
||||
# Check that we got the correct page
|
||||
self.assertEqual(response.context['queries'].number, 2)
|
||||
|
||||
def test_pagination_invalid(self):
|
||||
self.make_editors_picks()
|
||||
|
||||
response = self.client.get(reverse('wagtailsearch_editorspicks_index'), {'p': 'Hello World!'})
|
||||
|
||||
# Check response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/index.html')
|
||||
|
||||
# Check that we got page one
|
||||
self.assertEqual(response.context['queries'].number, 1)
|
||||
|
||||
def test_pagination_out_of_range(self):
|
||||
self.make_editors_picks()
|
||||
|
||||
response = self.client.get(reverse('wagtailsearch_editorspicks_index'), {'p': 99999})
|
||||
|
||||
# Check response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/index.html')
|
||||
|
||||
# Check that we got the last page
|
||||
self.assertEqual(response.context['queries'].number, response.context['queries'].paginator.num_pages)
|
||||
|
||||
|
||||
class TestEditorsPicksAddView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
||||
def test_simple(self):
|
||||
response = self.client.get(reverse('wagtailsearch_editorspicks_add'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/add.html')
|
||||
|
||||
def test_post(self):
|
||||
# Submit
|
||||
post_data = {
|
||||
'query_string': "test",
|
||||
'editors_picks-TOTAL_FORMS': 1,
|
||||
'editors_picks-INITIAL_FORMS': 0,
|
||||
'editors_picks-MAX_NUM_FORMS': 1000,
|
||||
'editors_picks-0-DELETE': '',
|
||||
'editors_picks-0-ORDER': 0,
|
||||
'editors_picks-0-page': 1,
|
||||
'editors_picks-0-description': "Hello",
|
||||
}
|
||||
response = self.client.post(reverse('wagtailsearch_editorspicks_add'), post_data)
|
||||
|
||||
# User should be redirected back to the index
|
||||
self.assertRedirects(response, reverse('wagtailsearch_editorspicks_index'))
|
||||
|
||||
# Check that the editors pick was created
|
||||
self.assertTrue(models.Query.get('test').editors_picks.filter(page_id=1).exists())
|
||||
|
||||
def test_post_without_recommendations(self):
|
||||
# Submit
|
||||
post_data = {
|
||||
'query_string': "test",
|
||||
'editors_picks-TOTAL_FORMS': 0,
|
||||
'editors_picks-INITIAL_FORMS': 0,
|
||||
'editors_picks-MAX_NUM_FORMS': 1000,
|
||||
}
|
||||
response = self.client.post(reverse('wagtailsearch_editorspicks_add'), post_data)
|
||||
|
||||
# User should be given an error
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertFormsetError(response, 'editors_pick_formset', None, None, "Please specify at least one recommendation for this search term.")
|
||||
|
||||
|
||||
class TestEditorsPicksEditView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
||||
# Create an editors pick to edit
|
||||
self.query = models.Query.get("Hello")
|
||||
self.editors_pick = self.query.editors_picks.create(page_id=1, description="Root page")
|
||||
self.editors_pick_2 = self.query.editors_picks.create(page_id=2, description="Homepage")
|
||||
|
||||
def test_simple(self):
|
||||
response = self.client.get(reverse('wagtailsearch_editorspicks_edit', args=(self.query.id, )))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/edit.html')
|
||||
|
||||
def test_post(self):
|
||||
# Submit
|
||||
post_data = {
|
||||
'query_string': "Hello",
|
||||
'editors_picks-TOTAL_FORMS': 2,
|
||||
'editors_picks-INITIAL_FORMS': 2,
|
||||
'editors_picks-MAX_NUM_FORMS': 1000,
|
||||
'editors_picks-0-id': self.editors_pick.id,
|
||||
'editors_picks-0-DELETE': '',
|
||||
'editors_picks-0-ORDER': 0,
|
||||
'editors_picks-0-page': 1,
|
||||
'editors_picks-0-description': "Description has changed", # Change
|
||||
'editors_picks-1-id': self.editors_pick_2.id,
|
||||
'editors_picks-1-DELETE': '',
|
||||
'editors_picks-1-ORDER': 1,
|
||||
'editors_picks-1-page': 2,
|
||||
'editors_picks-1-description': "Homepage",
|
||||
}
|
||||
response = self.client.post(reverse('wagtailsearch_editorspicks_edit', args=(self.query.id, )), post_data)
|
||||
|
||||
# User should be redirected back to the index
|
||||
self.assertRedirects(response, reverse('wagtailsearch_editorspicks_index'))
|
||||
|
||||
# Check that the editors pick description was edited
|
||||
self.assertEqual(models.EditorsPick.objects.get(id=self.editors_pick.id).description, "Description has changed")
|
||||
|
||||
def test_post_reorder(self):
|
||||
# Check order before reordering
|
||||
self.assertEqual(models.Query.get("Hello").editors_picks.all()[0], self.editors_pick)
|
||||
self.assertEqual(models.Query.get("Hello").editors_picks.all()[1], self.editors_pick_2)
|
||||
|
||||
# Submit
|
||||
post_data = {
|
||||
'query_string': "Hello",
|
||||
'editors_picks-TOTAL_FORMS': 2,
|
||||
'editors_picks-INITIAL_FORMS': 2,
|
||||
'editors_picks-MAX_NUM_FORMS': 1000,
|
||||
'editors_picks-0-id': self.editors_pick.id,
|
||||
'editors_picks-0-DELETE': '',
|
||||
'editors_picks-0-ORDER': 1, # Change
|
||||
'editors_picks-0-page': 1,
|
||||
'editors_picks-0-description': "Root page",
|
||||
'editors_picks-1-id': self.editors_pick_2.id,
|
||||
'editors_picks-1-DELETE': '',
|
||||
'editors_picks-1-ORDER': 0, # Change
|
||||
'editors_picks-1-page': 2,
|
||||
'editors_picks-1-description': "Homepage",
|
||||
}
|
||||
response = self.client.post(reverse('wagtailsearch_editorspicks_edit', args=(self.query.id, )), post_data)
|
||||
|
||||
# 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)
|
||||
|
||||
def test_post_delete_recommendation(self):
|
||||
# Submit
|
||||
post_data = {
|
||||
'query_string': "Hello",
|
||||
'editors_picks-TOTAL_FORMS': 2,
|
||||
'editors_picks-INITIAL_FORMS': 2,
|
||||
'editors_picks-MAX_NUM_FORMS': 1000,
|
||||
'editors_picks-0-id': self.editors_pick.id,
|
||||
'editors_picks-0-DELETE': '',
|
||||
'editors_picks-0-ORDER': 0,
|
||||
'editors_picks-0-page': 1,
|
||||
'editors_picks-0-description': "Root page",
|
||||
'editors_picks-1-id': self.editors_pick_2.id,
|
||||
'editors_picks-1-DELETE': 1,
|
||||
'editors_picks-1-ORDER': 1,
|
||||
'editors_picks-1-page': 2,
|
||||
'editors_picks-1-description': "Homepage",
|
||||
}
|
||||
response = self.client.post(reverse('wagtailsearch_editorspicks_edit', args=(self.query.id, )), post_data)
|
||||
|
||||
# User should be redirected back to the index
|
||||
self.assertRedirects(response, reverse('wagtailsearch_editorspicks_index'))
|
||||
|
||||
# Check that the recommendation was deleted
|
||||
self.assertFalse(models.EditorsPick.objects.filter(id=self.editors_pick_2.id).exists())
|
||||
|
||||
# The other recommendation should still exist
|
||||
self.assertTrue(models.EditorsPick.objects.filter(id=self.editors_pick.id).exists())
|
||||
|
||||
def test_post_without_recommendations(self):
|
||||
# Submit
|
||||
post_data = {
|
||||
'query_string': "Hello",
|
||||
'editors_picks-TOTAL_FORMS': 2,
|
||||
'editors_picks-INITIAL_FORMS': 2,
|
||||
'editors_picks-MAX_NUM_FORMS': 1000,
|
||||
'editors_picks-0-id': self.editors_pick.id,
|
||||
'editors_picks-0-DELETE': 1,
|
||||
'editors_picks-0-ORDER': 0,
|
||||
'editors_picks-0-page': 1,
|
||||
'editors_picks-0-description': "Description has changed", # Change
|
||||
'editors_picks-1-id': self.editors_pick_2.id,
|
||||
'editors_picks-1-DELETE': 1,
|
||||
'editors_picks-1-ORDER': 1,
|
||||
'editors_picks-1-page': 2,
|
||||
'editors_picks-1-description': "Homepage",
|
||||
}
|
||||
response = self.client.post(reverse('wagtailsearch_editorspicks_edit', args=(self.query.id, )), post_data)
|
||||
|
||||
# User should be given an error
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertFormsetError(response, 'editors_pick_formset', None, None, "Please specify at least one recommendation for this search term.")
|
||||
|
||||
|
||||
class TestEditorsPicksDeleteView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
||||
# Create an editors pick to delete
|
||||
self.query = models.Query.get("Hello")
|
||||
self.editors_pick = self.query.editors_picks.create(page_id=1, description="Root page")
|
||||
self.editors_pick_2 = self.query.editors_picks.create(page_id=2, description="Homepage")
|
||||
|
||||
def test_simple(self):
|
||||
response = self.client.get(reverse('wagtailsearch_editorspicks_delete', args=(self.query.id, )))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearch/editorspicks/confirm_delete.html')
|
||||
|
||||
def test_post(self):
|
||||
# Submit
|
||||
post_data = {
|
||||
'foo': 'bar',
|
||||
}
|
||||
response = self.client.post(reverse('wagtailsearch_editorspicks_delete', args=(self.query.id, )), post_data)
|
||||
|
||||
# User should be redirected back to the index
|
||||
self.assertRedirects(response, reverse('wagtailsearch_editorspicks_index'))
|
||||
|
||||
# Check that both recommendations were deleted
|
||||
self.assertFalse(models.EditorsPick.objects.filter(id=self.editors_pick_2.id).exists())
|
||||
|
||||
# The other recommendation should still exist
|
||||
self.assertFalse(models.EditorsPick.objects.filter(id=self.editors_pick.id).exists())
|
@ -1,13 +1,8 @@
|
||||
from django.conf.urls import url
|
||||
from wagtail.wagtailsearch.views import editorspicks, queries
|
||||
from wagtail.wagtailsearch.views import queries
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r"^editorspicks/$", editorspicks.index, name="wagtailsearch_editorspicks_index"),
|
||||
url(r"^editorspicks/add/$", editorspicks.add, name="wagtailsearch_editorspicks_add"),
|
||||
url(r"^editorspicks/(\d+)/$", editorspicks.edit, name="wagtailsearch_editorspicks_edit"),
|
||||
url(r"^editorspicks/(\d+)/delete/$", editorspicks.delete, name="wagtailsearch_editorspicks_delete"),
|
||||
|
||||
url(r"^queries/chooser/$", queries.chooser, name="wagtailsearch_queries_chooser"),
|
||||
url(r"^queries/chooser/results/$", queries.chooserresults, name="wagtailsearch_queries_chooserresults"),
|
||||
]
|
||||
|
@ -1,25 +1,11 @@
|
||||
from django.core import urlresolvers
|
||||
from django.conf.urls import include, url
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.wagtailcore import hooks
|
||||
from wagtail.wagtailsearch.urls import admin as admin_urls
|
||||
|
||||
from wagtail.wagtailadmin.menu import MenuItem
|
||||
|
||||
|
||||
@hooks.register('register_admin_urls')
|
||||
def register_admin_urls():
|
||||
return [
|
||||
url(r'^search/', include(admin_urls)),
|
||||
]
|
||||
|
||||
|
||||
class EditorsPicksMenuItem(MenuItem):
|
||||
def is_shown(self, request):
|
||||
# TEMPORARY: Only show if the user is a superuser
|
||||
return request.user.is_superuser
|
||||
|
||||
@hooks.register('register_settings_menu_item')
|
||||
def register_editors_picks_menu_item():
|
||||
return EditorsPicksMenuItem(_('Promoted search results'), urlresolvers.reverse('wagtailsearch_editorspicks_index'), classnames='icon icon-pick', order=900)
|
||||
|
Loading…
Reference in New Issue
Block a user