@@ -87,6 +87,11 @@ class SubmitterFilter(Filter):
self.person = None
self.person_match = None
submitter_id = None
+
+ str = str.strip()
+ if str == '':
+ return
+
try:
submitter_id = int(str)
except ValueError:
@@ -128,15 +133,8 @@ class SubmitterFilter(Filter):
return ''
def _form(self):
- name = ''
- if self.person:
- name = self.person.name
- return mark_safe(('<input onKeyUp="submitter_field_change(this)" ' +
- 'name="submitter" id="submitter_input" ' +
- 'class="form-control"' +
- 'value="%s">' % escape(name)) +
- '<select id="submitter_select" ' +
- 'disabled="true"></select>')
+ return mark_safe(('<input type="text" name="submitter" ' + \
+ 'id="submitter_input" class="form-control">'))
def key(self):
if self.person:
@@ -19,127 +19,40 @@ function filter_click()
}
-function enable_selected_submitter(select, input)
-{
- select.name = 'submitter';
- input.name = '';
-}
-function filter_form_submit(form)
-{
- var i;
-
- var submitter_select = document.getElementById("submitter_select");
- var submitter_input = document.getElementById("submitter_input");
- if (!submitter_select || !submitter_input) {
- req = null;
- return;
- }
-
- /* submitter handling. if possible, use the select box, otherwise leave
- * as-is (and so the text box is used). */
-
- if (submitter_select.options.length == 0) {
- /* if there's no match, just use the input */
-
- } else if (submitter_select.options.length == 1) {
- /* if there's only one match, request by id */
- submitter_select.selectedIndex = 0;
- enable_selected_submitter(submitter_select, submitter_input);
-
- } else if (submitter_select.selectedIndex != -1) {
- /* if the user has explicitly selected, request by id */
- enable_selected_submitter(submitter_select, submitter_input);
-
- }
- for (i = 0; i < form.elements.length; i++) {
- var e = form.elements[i];
- if (e.type == 'submit') {
- continue;
- }
-
- /* handle submitter data */
- if (e.type == 'select-one') {
- if (e.name == '') {
- e.disabled = true;
- }
- if (e.selectedIndex != -1
- && e.options[e.selectedIndex].value == '') {
- e.disabled = true;
+$(document).ready(function() {
+ $('#submitter_input').selectize({
+ valueField: 'pk',
+ labelField: 'name',
+ searchField: ['name', 'email'],
+ maxItems: 1,
+ persist: false,
+ render: {
+ option: function(item, escape) {
+ return '<div>' + escape(item.name) + ' <' +
+ escape(item.email) + '>' + '</div>';
+ },
+ item: function(item, escape) {
+ return '<div>' + escape(item.name) + '</div>';
}
-
- continue;
- }
-
- if (e.value == '') {
- e.disabled = true;
- }
- }
-}
-
-var req = null;
-
-function submitter_complete_response()
-{
- if (req.readyState != 4) {
- return
- }
-
- var completions;
- eval("completions = " + req.responseText);
-
- if (completions.length == 0) {
- req = null;
- return;
- }
-
- var submitter_select = document.getElementById("submitter_select");
- var submitter_input = document.getElementById("submitter_input");
- if (!submitter_select || !submitter_input) {
- req = null;
- return;
- }
-
- for (i = 0; i < completions.length; i++) {
- name = completions[i]['fields']['name'];
- if (name) {
- name = completions[i]['fields']['name'] +
- ' <' + completions[i]['fields']['email'] + '>';
- } else {
- name = completions[i]['fields']['email'];
+ },
+ load: function(query, callback) {
+ if (query.length < 4)
+ return callback();
+
+ req = $.ajax({
+ url: '{% url 'patchwork.views.submitter_complete' %}?q=' +
+ encodeURIComponent(query) + '&l=10',
+ error: function() {
+ callback();
+ },
+ success: function(res) {
+ callback(res);
+ }
+ });
}
- o = new Option(name, completions[i]['pk']);
- submitter_select.options[i] = o;
- }
-
- /* remove remaining options */
- for (; i < submitter_select.length; i++) {
- submitter_select.options[i] = null;
- }
-
- submitter_select.disabled = false;
- req = null;
-}
-
-function submitter_field_change(field)
-{
- var limit = 20;
- var value = field.value;
- if (value.length < 4) {
- return;
- }
-
- if (req) {
- return;
- }
-
- var url = '{% url 'patchwork.views.submitter_complete' %}?q=' + value +
- '&l=' + limit;
- req = new XMLHttpRequest();
- req.onreadystatechange = submitter_complete_response;
- req.open("GET", url, true);
- req.send('');
-}
+ });
+});
</script>
<div class="filters">
@@ -162,8 +75,7 @@ function submitter_field_change(field)
{% endif %}
</div>
<div id="filterform" style="padding-top: 1em; display: none">
- <form class="form-horizontal" role="form" method="get"
- onSubmit="return filter_form_submit(this)">
+ <form class="form-horizontal" role="form" method="get">
{% for filter in filters.available_filters %}
<div class="form-group">
<label class="col-sm-2 control-label">{{ filter.name }}</label>
@@ -36,14 +36,14 @@ class SubmitterCompletionTest(TestCase):
self.assertEquals(response.status_code, 200)
data = json.loads(response.content)
self.assertEquals(len(data), 1)
- self.assertEquals(data[0]['fields']['name'], 'Test Name')
+ self.assertEquals(data[0]['name'], 'Test Name')
def testEmailComplete(self):
response = self.client.get('/submitter/', {'q': 'test2'})
self.assertEquals(response.status_code, 200)
data = json.loads(response.content)
self.assertEquals(len(data), 1)
- self.assertEquals(data[0]['fields']['email'], 'test2@example.com')
+ self.assertEquals(data[0]['email'], 'test2@example.com')
def testCompleteLimit(self):
for i in range(3,10):
@@ -17,12 +17,13 @@
# along with Patchwork; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+import json
from patchwork.models import Patch, Project, Person, EmailConfirmation
from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponse, HttpResponseRedirect, Http404
from patchwork.requestcontext import PatchworkRequestContext
-from django.core import serializers, urlresolvers
+from django.core import urlresolvers
from django.template.loader import render_to_string
from django.conf import settings
from django.db.models import Q
@@ -87,10 +88,9 @@ def confirm(request, key):
def submitter_complete(request):
search = request.GET.get('q', '')
limit = request.GET.get('l', None)
- response = HttpResponse(content_type = "text/plain")
if len(search) <= 3:
- return response
+ return HttpResponse(content_type="application/json")
queryset = Person.objects.filter(Q(name__icontains = search) |
Q(email__icontains = search))
@@ -103,9 +103,15 @@ def submitter_complete(request):
if limit is not None and limit > 0:
queryset = queryset[:limit]
- json_serializer = serializers.get_serializer("json")()
- json_serializer.serialize(queryset, ensure_ascii=False, stream=response)
- return response
+ data = []
+ for submitter in queryset:
+ item = {}
+ item['pk'] = submitter.id
+ item['name'] = submitter.name
+ item['email'] = submitter.email
+ data.append(item)
+
+ return HttpResponse(json.dumps(data), content_type="application/json")
help_pages = {'': 'index.html',
'about/': 'about.html',
We now have a nice(r) autocompletion widget that will popup the list of options directly underneath the input fild. A few changes are done in this commit that couldn't be split any further: - Use jQuery's $.ajax() for the completion query - Use the application/json content type on the completion answer to allow jQuery to directly create an object from it instead of giving back a string (because of the text/plain content type) - Crafted more logical objects in the json answer ie all properties are at the same level instead of the default queryset serializer that put the object fields in a 'field' sub-object. - Use selectize.js for the autocompletion widget logic A slight change in behaviour is that we now don't allow "free form" submitter search, ie we need a valid completion to search with that specific person (through its primary key). I didn't remove the backend logic that allows the "free form" mechanism, but maybe we should. v2: Squash the unit tests fixes into this patch (Jeremy Kerr) Signed-off-by: Damien Lespiau <damien.lespiau@intel.com> --- patchwork/filters.py | 16 ++- patchwork/templates/patchwork/filters.html | 152 ++++++----------------------- patchwork/tests/test_person.py | 4 +- patchwork/views/base.py | 18 ++-- 4 files changed, 53 insertions(+), 137 deletions(-)