mirror of
https://github.com/wagtail/wagtail.git
synced 2024-12-01 11:41:20 +01:00
Added searchpicks admin views
This commit is contained in:
parent
df20340060
commit
0c7f00e078
10
wagtail/contrib/wagtailsearchpicks/admin_urls.py
Normal file
10
wagtail/contrib/wagtailsearchpicks/admin_urls.py
Normal file
@ -0,0 +1,10 @@
|
||||
from django.conf.urls import url
|
||||
from wagtail.contrib.wagtailsearchpicks 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'),
|
||||
]
|
57
wagtail/contrib/wagtailsearchpicks/forms.py
Normal file
57
wagtail/contrib/wagtailsearchpicks/forms.py
Normal file
@ -0,0 +1,57 @@
|
||||
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 SearchPickForm(forms.ModelForm):
|
||||
sort_order = forms.IntegerField(required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SearchPickForm, 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)),
|
||||
}
|
||||
|
||||
|
||||
SearchPicksFormSetBase = inlineformset_factory(models.Query, models.EditorsPick, form=SearchPickForm, can_order=True, can_delete=True, extra=0)
|
||||
|
||||
|
||||
class SearchPicksFormSet(SearchPicksFormSetBase):
|
||||
minimum_forms = 1
|
||||
minimum_forms_message = _("Please specify at least one recommendation for this search term.")
|
||||
|
||||
def add_fields(self, form, *args, **kwargs):
|
||||
super(SearchPicksFormSet, 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,48 @@
|
||||
{% extends "wagtailadmin/base.html" %}
|
||||
{% load i18n %}
|
||||
{% block titletag %}{% trans "Add search pick" %}{% endblock %}
|
||||
{% block content %}
|
||||
{% trans "Add search pick" as add_str %}
|
||||
{% include "wagtailadmin/shared/header.html" with title=add_str icon="pick" %}
|
||||
|
||||
<div class="nice-padding">
|
||||
<div class="help-block help-info">
|
||||
{% blocktrans %}
|
||||
<p>Promoted search results are a means of recommending specific pages that might not organically come high up in search results. E.g recommending your primary donation page to a user searching with the less common term "<em>giving</em>".</p>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% blocktrans %}
|
||||
<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 'wagtailsearchpicks:add' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
|
||||
<ul class="fields">
|
||||
<li>
|
||||
{% include "wagtailsearch/queries/chooser_field.html" with field=query_form.query_string only %}
|
||||
</li>
|
||||
<li>
|
||||
{% include "wagtailsearchpicks/includes/searchpicks_formset.html" with formset=searchpicks_formset only %}
|
||||
</li>
|
||||
<li><input type="submit" value="{% trans 'Save' %}" /></li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
{% include "wagtailadmin/pages/_editor_css.html" %}
|
||||
{% endblock %}
|
||||
{% block extra_js %}
|
||||
{% include "wagtailadmin/pages/_editor_js.html" %}
|
||||
|
||||
<script type="text/javascript">
|
||||
{% include "wagtailsearchpicks/includes/searchpicks_formset.js" with formset=searchpicks_formset only %}
|
||||
{% include "wagtailsearch/queries/chooser_field.js" only %}
|
||||
|
||||
$(function() {
|
||||
createQueryChooser('{{ query_form.query_string.auto_id }}');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -0,0 +1,15 @@
|
||||
{% extends "wagtailadmin/base.html" %}
|
||||
{% load i18n %}
|
||||
{% block titletag %}{% blocktrans with query=query.query_string %}Delete {{ query }}{% endblocktrans %}{% endblock %}
|
||||
{% block content %}
|
||||
{% trans "Delete" as delete_str %}
|
||||
{% include "wagtailadmin/shared/header.html" with title=delete_str subtitle=query.query_string %}
|
||||
|
||||
<div class="nice-padding">
|
||||
<p>{% trans "Are you sure you want to delete all promoted results for this search term?" %}</p>
|
||||
<form action="{% url 'wagtailsearchpicks:delete' query.id %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="{% trans 'Yes, delete' %}" class="serious" />
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,40 @@
|
||||
{% extends "wagtailadmin/base.html" %}
|
||||
{% load i18n %}
|
||||
{% block titletag %}{% blocktrans with query=query.query_string %}Editing {{ query }}{% endblocktrans %}{% endblock %}
|
||||
{% block content %}
|
||||
{% trans "Editing" as editing_str %}
|
||||
{% include "wagtailadmin/shared/header.html" with title=editing_str subtitle=query.query_string icon="pick" %}
|
||||
|
||||
<form action="{% url 'wagtailsearchpicks:edit' query.id %}" method="POST" class="nice-padding">
|
||||
{% csrf_token %}
|
||||
|
||||
<ul class="fields">
|
||||
<li>
|
||||
{% include "wagtailsearch/queries/chooser_field.html" with field=query_form.query_string only %}
|
||||
</li>
|
||||
<li>
|
||||
{% include "wagtailsearchpicks/includes/searchpicks_formset.html" with formset=searchpicks_formset only %}
|
||||
</li>
|
||||
<li>
|
||||
<input type="submit" value="{% trans 'Save' %}" />
|
||||
<a href="{% url 'wagtailsearchpicks:delete' query.id %}" class="button button-secondary no">{% trans "Delete" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
{% include "wagtailadmin/pages/_editor_css.html" %}
|
||||
{% endblock %}
|
||||
{% block extra_js %}
|
||||
{% include "wagtailadmin/pages/_editor_js.html" %}
|
||||
|
||||
<script type="text/javascript">
|
||||
{% include "wagtailsearchpicks/includes/searchpicks_formset.js" with formset=searchpicks_formset only %}
|
||||
{% include "wagtailsearch/queries/chooser_field.js" only %}
|
||||
|
||||
$(function() {
|
||||
createQueryChooser('{{ query_form.query_string.auto_id }}');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -0,0 +1,24 @@
|
||||
{% load i18n %}
|
||||
<li id="inline_child_{{ form.prefix }}"{% if form.DELETE.value %} style="display: none;"{% endif %}>
|
||||
<ul class="controls">
|
||||
<li><button class="icon text-replace icon-order-up inline-child-move-up" id="{{ form.prefix }}-move-up">{% trans "Move up" %}</button></li>
|
||||
<li><button class="icon text-replace icon-order-down inline-child-move-down" id="{{ form.prefix }}-move-down">{% trans "Move down" %}</button></li>
|
||||
<li><button class="icon text-replace icon-bin" id="{{ form.DELETE.id_for_label }}-button">{% trans "Delete" %}</button></li>
|
||||
</ul>
|
||||
|
||||
<fieldset>
|
||||
<legend>{% trans "Promoted search result" %}</legend>
|
||||
<ul class="fields">
|
||||
<li class="model_choice_field">
|
||||
{% include "wagtailadmin/shared/field.html" with field=form.page only %}
|
||||
</li>
|
||||
<li class="char_field">
|
||||
{% include "wagtailadmin/shared/field.html" with field=form.description only %}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{ form.id }}
|
||||
{{ form.ORDER }}
|
||||
{{ form.DELETE }}
|
||||
</fieldset>
|
||||
</li>
|
@ -0,0 +1,17 @@
|
||||
{% load i18n wagtailadmin_tags %}
|
||||
{{ formset.management_form }}
|
||||
<ul class="multiple" id="id_{{ formset.prefix }}-FORMS">
|
||||
{% for form in formset.forms %}
|
||||
{% include "wagtailsearchpicks/includes/searchpick_form.html" with form=form only %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<script type="text/django-form-template" id="id_{{ formset.prefix }}-EMPTY_FORM_TEMPLATE">
|
||||
{% escapescript %}
|
||||
{% include "wagtailsearchpicks/includes/searchpick_form.html" with form=formset.empty_form only %}
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
|
||||
<p class="add">
|
||||
<a class="button bicolor icon icon-plus" id="id_{{ formset.prefix }}-ADD" value="Add">{% trans "Add recommended page" %}</a>
|
||||
</p>
|
@ -0,0 +1,13 @@
|
||||
$(function() {
|
||||
var panel = InlinePanel({
|
||||
formsetPrefix: "id_{{ formset.prefix }}",
|
||||
emptyChildFormPrefix: "{{ formset.empty_form.prefix }}",
|
||||
canOrder: true
|
||||
});
|
||||
|
||||
{% for form in formset.forms %}
|
||||
panel.initChildControls('{{ formset.prefix }}-{{ forloop.counter0 }}');
|
||||
{% endfor %}
|
||||
|
||||
panel.updateMoveButtonDisabledStates();
|
||||
});
|
@ -0,0 +1,26 @@
|
||||
{% extends "wagtailadmin/base.html" %}
|
||||
{% load i18n %}
|
||||
{% block titletag %}{% trans "Search Terms" %}{% endblock %}
|
||||
{% block bodyclass %}menu-editorspicks{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
window.headerSearch = {
|
||||
url: "{% url 'wagtailsearchpicks:index' %}",
|
||||
termInput: "#id_q",
|
||||
targetOutput: "#editorspicks-results"
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% 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="wagtailsearchpicks:add" icon="pick" add_text=sp_text_str search_url="wagtailsearchpicks:index" %}
|
||||
|
||||
<div class="nice-padding">
|
||||
<div id="editorspicks-results" class="redirects">
|
||||
{% include "wagtailsearchpicks/results.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,30 @@
|
||||
{% load i18n %}
|
||||
<table class="listing">
|
||||
<col width="40%" />
|
||||
<col width="40%"/>
|
||||
<col />
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="title">{% trans "Search term(s)" %}</th>
|
||||
<th>{% trans "Promoted results" %}</th>
|
||||
<th>{% trans "Views (past week)" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for query in queries %}
|
||||
<tr>
|
||||
<td class="title">
|
||||
<h2><a href="{% url 'wagtailsearchpicks:edit' query.id %}" title="{% trans 'Edit this pick' %}">{{ query.query_string }}</a></h2>
|
||||
</td>
|
||||
<td>
|
||||
{% 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 %}
|
||||
</td>
|
||||
<td>{{ query.hits }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
@ -0,0 +1,23 @@
|
||||
{% load i18n %}
|
||||
{% if queries %}
|
||||
{% if is_searching %}
|
||||
<h2>
|
||||
{% blocktrans count counter=queries|length %}
|
||||
There is one match
|
||||
{% plural %}
|
||||
There are {{ counter }} matches
|
||||
{% endblocktrans %}
|
||||
</h2>
|
||||
{% endif %}
|
||||
|
||||
{% include "wagtailsearchpicks/list.html" %}
|
||||
|
||||
{% include "wagtailadmin/shared/pagination_nav.html" with items=queries is_searching=is_searching linkurl="wagtailsearchpicks_index" %}
|
||||
{% else %}
|
||||
{% if is_searching %}
|
||||
<p>{% blocktrans %}Sorry, no promoted results match "<em>{{ query_string }}</em>"{% endblocktrans %}</p>
|
||||
{% else %}
|
||||
{% url 'wagtailsearchpicks:add' as wagtailsearchpicks_add_url %}
|
||||
<p>{% blocktrans %}No promoted results have been created. Why not <a href="{{ wagtailsearchpicks_add_url }}">add one</a>?{% endblocktrans %}</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
311
wagtail/contrib/wagtailsearchpicks/tests.py
Normal file
311
wagtail/contrib/wagtailsearchpicks/tests.py
Normal file
@ -0,0 +1,311 @@
|
||||
from django.test import TestCase
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
from wagtail.wagtailsearch import models
|
||||
|
||||
|
||||
class TestSearchPicks(TestCase):
|
||||
def test_search_pick_create(self):
|
||||
# Create a search pick to the root page
|
||||
models.EditorsPick.objects.create(
|
||||
query=models.Query.get("root page"),
|
||||
page_id=1,
|
||||
sort_order=0,
|
||||
description="First search 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_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
|
||||
models.EditorsPick.objects.create(
|
||||
query=models.Query.get("root page"),
|
||||
page_id=1,
|
||||
sort_order=0,
|
||||
description="First search pick",
|
||||
)
|
||||
models.EditorsPick.objects.create(
|
||||
query=models.Query.get("root page"),
|
||||
page_id=1,
|
||||
sort_order=2,
|
||||
description="Last search pick",
|
||||
)
|
||||
models.EditorsPick.objects.create(
|
||||
query=models.Query.get("root page"),
|
||||
page_id=1,
|
||||
sort_order=1,
|
||||
description="Middle search 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 search pick")
|
||||
self.assertEqual(models.Query.get("root page").editors_picks.last().description, "Last search pick")
|
||||
|
||||
|
||||
class TestSearchPicksIndexView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
||||
def test_simple(self):
|
||||
response = self.client.get(reverse('wagtailsearchpicks:index'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearchpicks/index.html')
|
||||
|
||||
def test_search(self):
|
||||
response = self.client.get(reverse('wagtailsearchpicks: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):
|
||||
models.EditorsPick.objects.create(
|
||||
query=models.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('wagtailsearchpicks:index'), {'p': 2})
|
||||
|
||||
# Check response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearchpicks/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('wagtailsearchpicks:index'), {'p': 'Hello World!'})
|
||||
|
||||
# Check response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearchpicks/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('wagtailsearchpicks:index'), {'p': 99999})
|
||||
|
||||
# Check response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearchpicks/index.html')
|
||||
|
||||
# Check that we got the last page
|
||||
self.assertEqual(response.context['queries'].number, response.context['queries'].paginator.num_pages)
|
||||
|
||||
|
||||
class TestSearchPicksAddView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
||||
def test_simple(self):
|
||||
response = self.client.get(reverse('wagtailsearchpicks:add'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearchpicks/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('wagtailsearchpicks:add'), post_data)
|
||||
|
||||
# User should be redirected back to the index
|
||||
self.assertRedirects(response, reverse('wagtailsearchpicks:index'))
|
||||
|
||||
# Check that the search 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('wagtailsearchpicks: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 TestSearchPicksEditView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
||||
# Create an search pick to edit
|
||||
self.query = models.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('wagtailsearchpicks:edit', args=(self.query.id, )))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearchpicks/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('wagtailsearchpicks:edit', args=(self.query.id, )), post_data)
|
||||
|
||||
# User should be redirected back to the index
|
||||
self.assertRedirects(response, reverse('wagtailsearchpicks:index'))
|
||||
|
||||
# Check that the search pick description was edited
|
||||
self.assertEqual(models.EditorsPick.objects.get(id=self.search_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.search_pick)
|
||||
self.assertEqual(models.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('wagtailsearchpicks:edit', args=(self.query.id, )), post_data)
|
||||
|
||||
# User should be redirected back to the index
|
||||
self.assertRedirects(response, reverse('wagtailsearchpicks:index'))
|
||||
|
||||
# Check that the ordering has been saved correctly
|
||||
self.assertEqual(models.EditorsPick.objects.get(id=self.search_pick.id).sort_order, 1)
|
||||
self.assertEqual(models.EditorsPick.objects.get(id=self.search_pick_2.id).sort_order, 0)
|
||||
|
||||
# Check that the recommendations were reordered
|
||||
self.assertEqual(models.Query.get("Hello").editors_picks.all()[0], self.search_pick_2)
|
||||
self.assertEqual(models.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('wagtailsearchpicks:edit', args=(self.query.id, )), post_data)
|
||||
|
||||
# User should be redirected back to the index
|
||||
self.assertRedirects(response, reverse('wagtailsearchpicks:index'))
|
||||
|
||||
# Check that the recommendation was deleted
|
||||
self.assertFalse(models.EditorsPick.objects.filter(id=self.search_pick_2.id).exists())
|
||||
|
||||
# The other recommendation should still exist
|
||||
self.assertTrue(models.EditorsPick.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('wagtailsearchpicks: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 TestSearchPicksDeleteView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
||||
# Create an search pick to delete
|
||||
self.query = models.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('wagtailsearchpicks:delete', args=(self.query.id, )))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'wagtailsearchpicks/confirm_delete.html')
|
||||
|
||||
def test_post(self):
|
||||
# Submit
|
||||
post_data = {
|
||||
'foo': 'bar',
|
||||
}
|
||||
response = self.client.post(reverse('wagtailsearchpicks:delete', args=(self.query.id, )), post_data)
|
||||
|
||||
# User should be redirected back to the index
|
||||
self.assertRedirects(response, reverse('wagtailsearchpicks:index'))
|
||||
|
||||
# Check that both recommendations were deleted
|
||||
self.assertFalse(models.EditorsPick.objects.filter(id=self.search_pick_2.id).exists())
|
||||
|
||||
# The other recommendation should still exist
|
||||
self.assertFalse(models.EditorsPick.objects.filter(id=self.search_pick.id).exists())
|
149
wagtail/contrib/wagtailsearchpicks/views.py
Normal file
149
wagtail/contrib/wagtailsearchpicks/views.py
Normal file
@ -0,0 +1,149 @@
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
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 as search_forms
|
||||
from wagtail.wagtailadmin.forms import SearchForm
|
||||
from wagtail.wagtailadmin import messages
|
||||
|
||||
from wagtail.contrib.wagtailsearchpicks import forms
|
||||
|
||||
|
||||
@vary_on_headers('X-Requested-With')
|
||||
def index(request):
|
||||
is_searching = False
|
||||
page = request.GET.get('p', 1)
|
||||
query_string = request.GET.get('q', "")
|
||||
|
||||
queries = models.Query.objects.filter(editors_picks__isnull=False).distinct()
|
||||
|
||||
# Search
|
||||
if query_string:
|
||||
queries = queries.filter(query_string__icontains=query_string)
|
||||
is_searching = True
|
||||
|
||||
# Pagination
|
||||
paginator = Paginator(queries, 20)
|
||||
try:
|
||||
queries = paginator.page(page)
|
||||
except PageNotAnInteger:
|
||||
queries = paginator.page(1)
|
||||
except EmptyPage:
|
||||
queries = paginator.page(paginator.num_pages)
|
||||
|
||||
if request.is_ajax():
|
||||
return render(request, "wagtailsearchpicks/results.html", {
|
||||
'is_searching': is_searching,
|
||||
'queries': queries,
|
||||
'query_string': query_string,
|
||||
})
|
||||
else:
|
||||
return render(request, 'wagtailsearchpicks/index.html', {
|
||||
'is_searching': is_searching,
|
||||
'queries': queries,
|
||||
'query_string': query_string,
|
||||
'search_form': SearchForm(data=dict(q=query_string) if query_string else None, placeholder=_("Search editor's picks")),
|
||||
})
|
||||
|
||||
|
||||
def save_searchpicks(query, new_query, searchpicks_formset):
|
||||
# Save
|
||||
if searchpicks_formset.is_valid():
|
||||
# Set sort_order
|
||||
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
|
||||
|
||||
searchpicks_formset.save()
|
||||
|
||||
# If query was changed, move all search picks to the new query
|
||||
if query != new_query:
|
||||
searchpicks_formset.get_queryset().update(query=new_query)
|
||||
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def add(request):
|
||||
if request.POST:
|
||||
# Get query
|
||||
query_form = search_forms.QueryForm(request.POST)
|
||||
if query_form.is_valid():
|
||||
query = models.Query.get(query_form['query_string'].value())
|
||||
|
||||
# Save search picks
|
||||
searchpicks_formset = forms.SearchPicksFormSet(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('wagtailsearchpicks:edit', args=(query.id,)), _('Edit'))
|
||||
])
|
||||
return redirect('wagtailsearchpicks:index')
|
||||
else:
|
||||
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:
|
||||
searchpicks_formset = forms.SearchPicksFormSet()
|
||||
else:
|
||||
query_form = search_forms.QueryForm()
|
||||
searchpicks_formset = forms.SearchPicksFormSet()
|
||||
|
||||
return render(request, 'wagtailsearchpicks/add.html', {
|
||||
'query_form': query_form,
|
||||
'searchpicks_formset': searchpicks_formset,
|
||||
})
|
||||
|
||||
|
||||
def edit(request, query_id):
|
||||
query = get_object_or_404(models.Query, id=query_id)
|
||||
|
||||
if request.POST:
|
||||
# Get query
|
||||
query_form = search_forms.QueryForm(request.POST)
|
||||
# and the recommendations
|
||||
searchpicks_formset = forms.SearchPicksFormSet(request.POST, instance=query)
|
||||
|
||||
if query_form.is_valid():
|
||||
new_query = models.Query.get(query_form['query_string'].value())
|
||||
|
||||
# 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('wagtailsearchpicks:edit', args=(query.id,)), _('Edit'))
|
||||
])
|
||||
return redirect('wagtailsearchpicks:index')
|
||||
else:
|
||||
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 = search_forms.QueryForm(initial=dict(query_string=query.query_string))
|
||||
searchpicks_formset = forms.SearchPicksFormSet(instance=query)
|
||||
|
||||
return render(request, 'wagtailsearchpicks/edit.html', {
|
||||
'query_form': query_form,
|
||||
'searchpicks_formset': searchpicks_formset,
|
||||
'query': query,
|
||||
})
|
||||
|
||||
|
||||
def delete(request, query_id):
|
||||
query = get_object_or_404(models.Query, id=query_id)
|
||||
|
||||
if request.POST:
|
||||
query.editors_picks.all().delete()
|
||||
messages.success(request, _("Editor's picks deleted."))
|
||||
return redirect('wagtailsearchpicks:index')
|
||||
|
||||
return render(request, 'wagtailsearchpicks/confirm_delete.html', {
|
||||
'query': query,
|
||||
})
|
26
wagtail/contrib/wagtailsearchpicks/wagtail_hooks.py
Normal file
26
wagtail/contrib/wagtailsearchpicks/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.wagtailsearchpicks 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='wagtailsearchpicks')),
|
||||
]
|
||||
|
||||
|
||||
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('wagtailsearchpicks:index'), classnames='icon icon-pick', order=900)
|
Loading…
Reference in New Issue
Block a user