diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 0b40776e3a..a3b91c626d 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1,3 +1,4 @@ +import operator from django import forms, template from django.forms.formsets import all_valid from django.forms.models import modelform_factory, modelformset_factory, inlineformset_factory @@ -9,6 +10,7 @@ from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_ob from django.core.exceptions import PermissionDenied from django.db import models, transaction from django.db.models.fields import BLANK_CHOICE_DASH +from django.db.models.query import QuerySet from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render_to_response from django.utils.datastructures import SortedDict @@ -19,7 +21,7 @@ from django.utils.functional import curry from django.utils.text import capfirst, get_text_list from django.utils.translation import ugettext as _ from django.utils.translation import ungettext, ugettext_lazy -from django.utils.encoding import force_unicode +from django.utils.encoding import force_unicode, smart_str try: set except NameError: @@ -242,6 +244,9 @@ class ModelAdmin(BaseModelAdmin): url(r'^add/$', wrap(self.add_view), name='%s_%s_add' % info), + url(r'^autocomplete/(?P[\w-]+)/$', + wrap(self.autocomplete_view), + name='%s_%s_autocomplete' % info), url(r'^(.+)/history/$', wrap(self.history_view), name='%s_%s_history' % info), @@ -708,6 +713,42 @@ class ModelAdmin(BaseModelAdmin): else: return HttpResponseRedirect(".") + def autocomplete_view(self, request, field, extra_content=None): + """ + Used by the JQuery Autocomplete plugin to do searches on fields of the related model + """ + query = request.GET.get('q', None) + + if field not in self.autocomplete_fields or query is None: + raise Http404 + + related = getattr(self.model, field) + rel_model = related.field.rel.to + queryset = rel_model._default_manager.all() + search_fields = self.autocomplete_fields[field] + + def construct_search(field_name): + # use different lookup methods depending on the notation + if field_name.startswith('^'): + return "%s__istartswith" % field_name[1:] + elif field_name.startswith('='): + return "%s__iexact" % field_name[1:] + elif field_name.startswith('@'): + return "%s__search" % field_name[1:] + else: + return "%s__icontains" % field_name + + for bit in query.split(): + or_queries = [models.Q(**{construct_search( + smart_str(field_name)): smart_str(bit)}) + for field_name in search_fields] + other_qs = QuerySet(rel_model) + other_qs.dup_select_related(queryset) + other_qs = other_qs.filter(reduce(operator.or_, or_queries)) + queryset = queryset & other_qs + + return HttpResponse(''.join([u'%s|%s\n' % (unicode(f), f.pk) for f in queryset])) + def add_view(self, request, form_url='', extra_context=None): "The 'add' admin view for this model." model = self.model diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index d95123e259..a220883cce 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -213,8 +213,6 @@ class AdminSite(object): url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'), - url(r'^foreignkey_autocomplete/$', - 'django.contrib.admin.views.autocomplete.foreignkey_autocomplete'), url(r'^r/(?P\d+)/(?P.+)/$', 'django.views.defaults.shortcut'), url(r'^(?P\w+)/$', diff --git a/django/contrib/admin/templates/widget/foreignkey_searchinput.html b/django/contrib/admin/templates/widget/foreignkey_searchinput.html index b0192c9291..0d970b763e 100644 --- a/django/contrib/admin/templates/widget/foreignkey_searchinput.html +++ b/django/contrib/admin/templates/widget/foreignkey_searchinput.html @@ -11,35 +11,11 @@ $(document).ready(function() { $('#id_{{ name }}').val(''); $('#lookup_{{ name }}').val(''); }; - function lookup(query) { - $.get('{{ search_path }}', { - 'search_fields': '{{ search_fields }}', - 'app_label': '{{ app_label }}', - 'model_name': '{{ model_name }}', - 'object_pk': query - }, function(data){ - $('#lookup_{{ name }}').val(data); - {{ name }}_value = query; - }); - }; - $('#id_{{ name }}').bind(($.browser.opera ? "keypress" : "keyup"), function(event) { - if ($(this).val()) { - if (event.keyCode == 27) { - reset(); - } else { - lookup($(this).val()); - }; - }; - }); - $('#lookup_{{ name }}').autocomplete('{{ search_path }}', { - extraParams: { - 'search_fields': '{{ search_fields }}', - 'app_label': '{{ app_label }}', - 'model_name': '{{ model_name }}' - } - }).result(function(event, data, formatted) { - if (data) { - $('#id_{{ name }}').val(data[1]); + + $('#lookup_{{ name }}').autocomplete('{{ search_path }}').result( + function(event, data, formatted) { + if (data) { + $('#id_{{ name }}').val(data[1]); } }).keyup(function(event){ if (event.keyCode == 27) { diff --git a/django/contrib/admin/templates/widget/m2m_searchinput.html b/django/contrib/admin/templates/widget/m2m_searchinput.html index dae5b88f8b..1dc1b4a7a6 100644 --- a/django/contrib/admin/templates/widget/m2m_searchinput.html +++ b/django/contrib/admin/templates/widget/m2m_searchinput.html @@ -8,22 +8,7 @@ $(document).ready(function() { // Show lookup input $("#lookup_{{ name }}").show(); - function lookup(query) { - $.get('{{ search_path }}', { - 'search_fields': '{{ search_fields }}', - 'app_label': '{{ app_label }}', - 'model_name': '{{ model_name }}', - 'object_pk': query - }, function(data){ - $('#lookup_{{ name }}').val(data); - }); - }; $('#lookup_{{ name }}').autocomplete('{{ search_path }}', { - extraParams: { - 'search_fields': '{{ search_fields }}', - 'app_label': '{{ app_label }}', - 'model_name': '{{ model_name }}' - }, multiple: true, mustMatch: true }).result(function(event, data, formatted) { diff --git a/django/contrib/admin/views/autocomplete.py b/django/contrib/admin/views/autocomplete.py deleted file mode 100644 index fea37dc85a..0000000000 --- a/django/contrib/admin/views/autocomplete.py +++ /dev/null @@ -1,60 +0,0 @@ -import operator -from django.db import models -from django.db.models.query import QuerySet -from django.utils.encoding import smart_str -from django.http import HttpResponse, HttpResponseNotFound -from django.conf import settings -from django.contrib.admin.views.decorators import staff_member_required - -def foreignkey_autocomplete(request, related_string_functions=None): - """ - Searches in the fields of the given related model and returns the - result as a simple string to be used by the jQuery Autocomplete plugin - """ - if related_string_functions is None: - related_string_functions = getattr(settings, - 'DJANGO_EXTENSIONS_FOREIGNKEY_AUTOCOMPLETE_STRING_FUNCTIONS', {}) - query = request.GET.get('q', None) - app_label = request.GET.get('app_label', None) - model_name = request.GET.get('model_name', None) - search_fields = request.GET.get('search_fields', None) - object_pk = request.GET.get('object_pk', None) - try: - to_string_function = related_string_functions[model_name] - except KeyError: - to_string_function = lambda x: unicode(x) - if search_fields and app_label and model_name and (query or object_pk): - def construct_search(field_name): - # use different lookup methods depending on the notation - if field_name.startswith('^'): - return "%s__istartswith" % field_name[1:] - elif field_name.startswith('='): - return "%s__iexact" % field_name[1:] - elif field_name.startswith('@'): - return "%s__search" % field_name[1:] - else: - return "%s__icontains" % field_name - model = models.get_model(app_label, model_name) - queryset = model._default_manager.all() - data = '' - if query: - for bit in query.split(): - or_queries = [models.Q(**{construct_search( - smart_str(field_name)): smart_str(bit)}) - for field_name in search_fields.split(',')] - other_qs = QuerySet(model) - other_qs.dup_select_related(queryset) - other_qs = other_qs.filter(reduce(operator.or_, or_queries)) - queryset = queryset & other_qs - data = ''.join([u'%s|%s\n' % ( - to_string_function(f), f.pk) for f in queryset]) - elif object_pk: - try: - obj = queryset.get(pk=object_pk) - except: - pass - else: - data = to_string_function(obj) - return HttpResponse(data) - return HttpResponseNotFound() -foreignkey_autocomplete = staff_member_required(foreignkey_autocomplete) \ No newline at end of file diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 3cc3fbd0c5..edca0a3acd 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -158,8 +158,6 @@ class ForeignKeySearchInput(ForeignKeyRawIdWidget): """ # Set in subclass to render the widget with a different template widget_template = 'widget/foreignkey_searchinput.html' - # Set this to the path of the search view - search_path = '../../../foreignkey_autocomplete/' class Media: css = { @@ -181,6 +179,9 @@ class ForeignKeySearchInput(ForeignKeyRawIdWidget): self.search_fields = search_fields super(ForeignKeySearchInput, self).__init__(rel, attrs) + def get_search_path(self, name): + return '../autocomplete/%s/' % name + def render(self, name, value, attrs=None): if attrs is None: attrs = {} @@ -206,7 +207,7 @@ class ForeignKeySearchInput(ForeignKeyRawIdWidget): 'url': url, 'related_url': related_url, 'admin_media_prefix': settings.ADMIN_MEDIA_PREFIX, - 'search_path': self.search_path, + 'search_path': self.get_search_path(name), 'search_fields': ','.join(self.search_fields), 'model_name': model_name, 'app_label': app_label, @@ -270,8 +271,6 @@ class ManyToManySearchInput(ManyToManyRawIdWidget): """ # Set in subclass to render the widget with a different template widget_template = 'widget/m2m_searchinput.html' - # Set this to the path of the search view - search_path = '../../../foreignkey_autocomplete/' class Media: css = { @@ -293,6 +292,8 @@ class ManyToManySearchInput(ManyToManyRawIdWidget): objs = self.rel.to._default_manager.filter(**{key + '__in': value.split(',')}) return ','.join([str(o) for o in objs]) + def get_search_path(self, name): + return '../autocomplete/%s/' % name def render(self, name, value, attrs=None): if attrs is None: @@ -323,7 +324,7 @@ class ManyToManySearchInput(ManyToManyRawIdWidget): 'url': url, 'related_url': related_url, 'admin_media_prefix': settings.ADMIN_MEDIA_PREFIX, - 'search_path': self.search_path, + 'search_path': self.get_search_path(name), 'search_fields': ','.join(self.search_fields), 'model_name': model_name, 'app_label': app_label,