2011-05-03 18:44:23 +08:00
|
|
|
import operator
|
|
|
|
|
2010-12-23 11:44:38 +08:00
|
|
|
from django.core.exceptions import SuspiciousOperation
|
2011-03-03 09:54:33 +08:00
|
|
|
from django.core.paginator import InvalidPage
|
2006-05-02 09:31:56 +08:00
|
|
|
from django.db import models
|
2011-06-03 00:18:47 +08:00
|
|
|
from django.utils.datastructures import SortedDict
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
from django.utils.encoding import force_unicode, smart_str
|
2011-03-02 18:44:48 +08:00
|
|
|
from django.utils.translation import ugettext, ugettext_lazy
|
2008-07-19 07:54:34 +08:00
|
|
|
from django.utils.http import urlencode
|
2011-05-03 18:44:23 +08:00
|
|
|
|
|
|
|
from django.contrib.admin import FieldListFilter
|
|
|
|
from django.contrib.admin.options import IncorrectLookupParameters
|
|
|
|
from django.contrib.admin.util import quote, get_fields_from_path
|
2005-07-13 09:25:57 +08:00
|
|
|
|
2006-05-02 09:31:56 +08:00
|
|
|
# The system will display a "Show all" link on the change list only if the
|
|
|
|
# total result count is less than or equal to this setting.
|
|
|
|
MAX_SHOW_ALL_ALLOWED = 200
|
2005-11-26 05:20:09 +08:00
|
|
|
|
2006-05-02 09:31:56 +08:00
|
|
|
# Changelist settings
|
2005-11-26 05:20:09 +08:00
|
|
|
ALL_VAR = 'all'
|
|
|
|
ORDER_VAR = 'o'
|
|
|
|
ORDER_TYPE_VAR = 'ot'
|
|
|
|
PAGE_VAR = 'p'
|
|
|
|
SEARCH_VAR = 'q'
|
2008-09-02 06:43:38 +08:00
|
|
|
TO_FIELD_VAR = 't'
|
2005-11-26 05:20:09 +08:00
|
|
|
IS_POPUP_VAR = 'pop'
|
2006-05-31 23:25:23 +08:00
|
|
|
ERROR_FLAG = 'e'
|
2005-11-26 05:20:09 +08:00
|
|
|
|
2011-05-03 18:44:23 +08:00
|
|
|
IGNORED_PARAMS = (
|
|
|
|
ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR, TO_FIELD_VAR)
|
|
|
|
|
2006-05-02 09:31:56 +08:00
|
|
|
# Text to display within change-list table cells if the value is blank.
|
2011-03-02 18:44:48 +08:00
|
|
|
EMPTY_CHANGELIST_VALUE = ugettext_lazy('(None)')
|
2005-07-13 09:25:57 +08:00
|
|
|
|
2011-04-23 11:55:21 +08:00
|
|
|
def field_needs_distinct(field):
|
|
|
|
if ((hasattr(field, 'rel') and
|
|
|
|
isinstance(field.rel, models.ManyToManyRel)) or
|
|
|
|
(isinstance(field, models.related.RelatedObject) and
|
|
|
|
not field.field.unique)):
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2006-05-02 09:31:56 +08:00
|
|
|
class ChangeList(object):
|
2011-05-03 18:44:23 +08:00
|
|
|
def __init__(self, request, model, list_display, list_display_links,
|
|
|
|
list_filter, date_hierarchy, search_fields, list_select_related,
|
|
|
|
list_per_page, list_editable, model_admin):
|
2006-05-02 09:31:56 +08:00
|
|
|
self.model = model
|
|
|
|
self.opts = model._meta
|
|
|
|
self.lookup_opts = self.opts
|
2008-07-19 07:54:34 +08:00
|
|
|
self.root_query_set = model_admin.queryset(request)
|
|
|
|
self.list_display = list_display
|
|
|
|
self.list_display_links = list_display_links
|
|
|
|
self.list_filter = list_filter
|
|
|
|
self.date_hierarchy = date_hierarchy
|
|
|
|
self.search_fields = search_fields
|
|
|
|
self.list_select_related = list_select_related
|
|
|
|
self.list_per_page = list_per_page
|
|
|
|
self.model_admin = model_admin
|
2006-05-02 09:31:56 +08:00
|
|
|
|
|
|
|
# Get search parameters from the query string.
|
|
|
|
try:
|
|
|
|
self.page_num = int(request.GET.get(PAGE_VAR, 0))
|
|
|
|
except ValueError:
|
|
|
|
self.page_num = 0
|
2007-04-26 21:30:48 +08:00
|
|
|
self.show_all = ALL_VAR in request.GET
|
|
|
|
self.is_popup = IS_POPUP_VAR in request.GET
|
2008-09-02 06:43:38 +08:00
|
|
|
self.to_field = request.GET.get(TO_FIELD_VAR)
|
2006-05-02 09:31:56 +08:00
|
|
|
self.params = dict(request.GET.items())
|
2007-04-26 21:30:48 +08:00
|
|
|
if PAGE_VAR in self.params:
|
2006-05-02 09:31:56 +08:00
|
|
|
del self.params[PAGE_VAR]
|
2007-04-26 21:30:48 +08:00
|
|
|
if ERROR_FLAG in self.params:
|
2006-05-31 23:25:23 +08:00
|
|
|
del self.params[ERROR_FLAG]
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2011-01-02 09:32:40 +08:00
|
|
|
if self.is_popup:
|
|
|
|
self.list_editable = ()
|
|
|
|
else:
|
|
|
|
self.list_editable = list_editable
|
2011-06-12 21:04:53 +08:00
|
|
|
self.ordering = self.get_ordering(request)
|
2006-05-02 09:31:56 +08:00
|
|
|
self.query = request.GET.get(SEARCH_VAR, '')
|
2011-05-03 18:44:23 +08:00
|
|
|
self.query_set = self.get_query_set(request)
|
2006-05-02 09:31:56 +08:00
|
|
|
self.get_results(request)
|
2011-05-03 18:44:23 +08:00
|
|
|
if self.is_popup:
|
|
|
|
title = ugettext('Select %s')
|
|
|
|
else:
|
|
|
|
title = ugettext('Select %s to change')
|
|
|
|
self.title = title % force_unicode(self.opts.verbose_name)
|
2006-05-02 09:31:56 +08:00
|
|
|
self.pk_attname = self.lookup_opts.pk.attname
|
|
|
|
|
2011-05-03 18:44:23 +08:00
|
|
|
def get_filters(self, request, use_distinct=False):
|
2006-05-02 09:31:56 +08:00
|
|
|
filter_specs = []
|
2011-05-03 18:44:23 +08:00
|
|
|
cleaned_params, use_distinct = self.get_lookup_params(use_distinct)
|
2008-08-16 03:15:20 +08:00
|
|
|
if self.list_filter:
|
2011-06-22 20:20:52 +08:00
|
|
|
for list_filter in self.list_filter:
|
|
|
|
if callable(list_filter):
|
2011-05-03 18:44:23 +08:00
|
|
|
# This is simply a custom list filter class.
|
2011-06-22 20:20:52 +08:00
|
|
|
spec = list_filter(request, cleaned_params,
|
2011-05-03 18:44:23 +08:00
|
|
|
self.model, self.model_admin)
|
|
|
|
else:
|
|
|
|
field_path = None
|
2011-06-22 20:20:52 +08:00
|
|
|
if isinstance(list_filter, (tuple, list)):
|
2011-05-24 17:17:55 +08:00
|
|
|
# This is a custom FieldListFilter class for a given field.
|
2011-06-22 20:20:52 +08:00
|
|
|
field, field_list_filter_class = list_filter
|
2011-05-24 17:17:55 +08:00
|
|
|
else:
|
2011-05-03 18:44:23 +08:00
|
|
|
# This is simply a field name, so use the default
|
|
|
|
# FieldListFilter class that has been registered for
|
|
|
|
# the type of the given field.
|
2011-06-22 20:20:52 +08:00
|
|
|
field, field_list_filter_class = list_filter, FieldListFilter.create
|
2011-05-03 18:44:23 +08:00
|
|
|
if not isinstance(field, models.Field):
|
|
|
|
field_path = field
|
|
|
|
field = get_fields_from_path(self.model, field_path)[-1]
|
|
|
|
spec = field_list_filter_class(field, request, cleaned_params,
|
|
|
|
self.model, self.model_admin, field_path=field_path)
|
2006-05-02 09:31:56 +08:00
|
|
|
if spec and spec.has_output():
|
|
|
|
filter_specs.append(spec)
|
|
|
|
return filter_specs, bool(filter_specs)
|
|
|
|
|
2006-06-03 21:37:34 +08:00
|
|
|
def get_query_string(self, new_params=None, remove=None):
|
|
|
|
if new_params is None: new_params = {}
|
|
|
|
if remove is None: remove = []
|
2006-05-02 09:31:56 +08:00
|
|
|
p = self.params.copy()
|
|
|
|
for r in remove:
|
|
|
|
for k in p.keys():
|
|
|
|
if k.startswith(r):
|
|
|
|
del p[k]
|
|
|
|
for k, v in new_params.items():
|
2008-07-19 07:54:34 +08:00
|
|
|
if v is None:
|
|
|
|
if k in p:
|
|
|
|
del p[k]
|
|
|
|
else:
|
2006-05-02 09:31:56 +08:00
|
|
|
p[k] = v
|
2008-07-19 07:54:34 +08:00
|
|
|
return '?%s' % urlencode(p)
|
2006-05-02 09:31:56 +08:00
|
|
|
|
|
|
|
def get_results(self, request):
|
2010-12-22 03:19:04 +08:00
|
|
|
paginator = self.model_admin.get_paginator(request, self.query_set, self.list_per_page)
|
2006-05-02 09:31:56 +08:00
|
|
|
# Get the number of objects, with admin filters applied.
|
2008-10-23 07:09:35 +08:00
|
|
|
result_count = paginator.count
|
2006-05-02 09:31:56 +08:00
|
|
|
|
|
|
|
# Get the total number of objects, with no admin filters applied.
|
|
|
|
# Perform a slight optimization: Check to see whether any filters were
|
|
|
|
# given. If not, use paginator.hits to calculate the number of objects,
|
|
|
|
# because we've already done paginator.hits and the value is cached.
|
Merged the queryset-refactor branch into trunk.
This is a big internal change, but mostly backwards compatible with existing
code. Also adds a couple of new features.
Fixed #245, #1050, #1656, #1801, #2076, #2091, #2150, #2253, #2306, #2400, #2430, #2482, #2496, #2676, #2737, #2874, #2902, #2939, #3037, #3141, #3288, #3440, #3592, #3739, #4088, #4260, #4289, #4306, #4358, #4464, #4510, #4858, #5012, #5020, #5261, #5295, #5321, #5324, #5325, #5555, #5707, #5796, #5817, #5987, #6018, #6074, #6088, #6154, #6177, #6180, #6203, #6658
git-svn-id: http://code.djangoproject.com/svn/django/trunk@7477 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2008-04-27 10:50:16 +08:00
|
|
|
if not self.query_set.query.where:
|
2006-05-02 09:31:56 +08:00
|
|
|
full_result_count = result_count
|
|
|
|
else:
|
2008-07-19 07:54:34 +08:00
|
|
|
full_result_count = self.root_query_set.count()
|
2006-05-02 09:31:56 +08:00
|
|
|
|
|
|
|
can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED
|
2008-07-19 07:54:34 +08:00
|
|
|
multi_page = result_count > self.list_per_page
|
2006-05-02 09:31:56 +08:00
|
|
|
|
|
|
|
# Get the list of objects to display on this page.
|
|
|
|
if (self.show_all and can_show_all) or not multi_page:
|
2009-04-23 06:16:32 +08:00
|
|
|
result_list = self.query_set._clone()
|
2006-05-02 09:31:56 +08:00
|
|
|
else:
|
|
|
|
try:
|
2008-03-25 13:20:55 +08:00
|
|
|
result_list = paginator.page(self.page_num+1).object_list
|
2006-05-02 09:31:56 +08:00
|
|
|
except InvalidPage:
|
2010-12-13 06:58:47 +08:00
|
|
|
raise IncorrectLookupParameters
|
2006-05-02 09:31:56 +08:00
|
|
|
|
|
|
|
self.result_count = result_count
|
|
|
|
self.full_result_count = full_result_count
|
|
|
|
self.result_list = result_list
|
|
|
|
self.can_show_all = can_show_all
|
|
|
|
self.multi_page = multi_page
|
|
|
|
self.paginator = paginator
|
|
|
|
|
2011-06-03 19:54:29 +08:00
|
|
|
def _get_default_ordering(self):
|
|
|
|
ordering = []
|
|
|
|
if self.model_admin.ordering:
|
|
|
|
ordering = self.model_admin.ordering
|
|
|
|
elif self.lookup_opts.ordering:
|
|
|
|
ordering = self.lookup_opts.ordering
|
|
|
|
return ordering
|
|
|
|
|
2011-06-12 21:04:53 +08:00
|
|
|
def get_ordering(self, request):
|
2011-06-03 19:54:29 +08:00
|
|
|
params = self.params
|
2011-06-12 21:04:53 +08:00
|
|
|
# For ordering, first check the if exists the "get_ordering" method
|
|
|
|
# in model admin, then check "ordering" parameter in the admin
|
2011-06-03 00:18:47 +08:00
|
|
|
# options, then check the object's default ordering. Finally, a
|
|
|
|
# manually-specified ordering from the query string overrides anything.
|
2011-06-12 21:04:53 +08:00
|
|
|
ordering = self.model_admin.get_ordering(request) or self._get_default_ordering()
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2007-04-26 21:30:48 +08:00
|
|
|
if ORDER_VAR in params:
|
2011-06-03 00:18:47 +08:00
|
|
|
# Clear ordering and used params
|
|
|
|
ordering = []
|
|
|
|
order_params = params[ORDER_VAR].split('.')
|
|
|
|
for p in order_params:
|
2006-05-02 09:31:56 +08:00
|
|
|
try:
|
2011-06-03 00:18:47 +08:00
|
|
|
none, pfx, idx = p.rpartition('-')
|
|
|
|
field_name = self.list_display[int(idx)]
|
2007-02-26 13:37:24 +08:00
|
|
|
try:
|
2011-06-03 19:54:29 +08:00
|
|
|
f = self.lookup_opts.get_field(field_name)
|
2011-06-03 00:18:47 +08:00
|
|
|
except models.FieldDoesNotExist:
|
|
|
|
# See whether field_name is a name of a non-field
|
|
|
|
# that allows sorting.
|
|
|
|
try:
|
|
|
|
if callable(field_name):
|
|
|
|
attr = field_name
|
|
|
|
elif hasattr(self.model_admin, field_name):
|
|
|
|
attr = getattr(self.model_admin, field_name)
|
|
|
|
else:
|
|
|
|
attr = getattr(self.model, field_name)
|
|
|
|
field_name = attr.admin_order_field
|
|
|
|
except AttributeError:
|
|
|
|
continue # No 'admin_order_field', skip it
|
|
|
|
else:
|
|
|
|
field_name = f.name
|
|
|
|
|
|
|
|
ordering.append(pfx + field_name)
|
|
|
|
|
|
|
|
except (IndexError, ValueError):
|
|
|
|
continue # Invalid ordering specified, skip it.
|
|
|
|
|
|
|
|
return ordering
|
|
|
|
|
2011-06-03 19:54:29 +08:00
|
|
|
def get_ordering_field_columns(self):
|
|
|
|
# Returns a SortedDict of ordering field column numbers and asc/desc
|
|
|
|
|
|
|
|
# We must cope with more than one column having the same underlying sort
|
|
|
|
# field, so we base things on column numbers.
|
|
|
|
ordering = self._get_default_ordering()
|
2011-06-03 00:18:47 +08:00
|
|
|
ordering_fields = SortedDict()
|
2011-06-03 19:54:29 +08:00
|
|
|
if ORDER_VAR not in self.params:
|
|
|
|
# for ordering specified on ModelAdmin or model Meta, we don't know
|
|
|
|
# the right column numbers absolutely, because there might be more
|
|
|
|
# than one column associated with that ordering, so we guess.
|
|
|
|
for f in ordering:
|
|
|
|
if f.startswith('-'):
|
|
|
|
f = f[1:]
|
|
|
|
order_type = 'desc'
|
|
|
|
else:
|
|
|
|
order_type = 'asc'
|
|
|
|
i = None
|
|
|
|
try:
|
|
|
|
# Search for simply field name first
|
|
|
|
i = self.list_display.index(f)
|
|
|
|
except ValueError:
|
|
|
|
# No match, but their might be a match if we take into account
|
|
|
|
# 'admin_order_field'
|
|
|
|
for j, attr in enumerate(self.list_display):
|
|
|
|
if getattr(attr, 'admin_order_field', '') == f:
|
|
|
|
i = j
|
|
|
|
break
|
|
|
|
if i is not None:
|
|
|
|
ordering_fields[i] = order_type
|
|
|
|
else:
|
|
|
|
for p in self.params[ORDER_VAR].split('.'):
|
|
|
|
none, pfx, idx = p.rpartition('-')
|
|
|
|
try:
|
|
|
|
idx = int(idx)
|
|
|
|
except ValueError:
|
|
|
|
continue # skip it
|
|
|
|
ordering_fields[idx] = 'desc' if pfx == '-' else 'asc'
|
2011-06-03 00:18:47 +08:00
|
|
|
return ordering_fields
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2011-05-03 18:44:23 +08:00
|
|
|
def get_lookup_params(self, use_distinct=False):
|
2006-05-02 09:31:56 +08:00
|
|
|
lookup_params = self.params.copy() # a dictionary of the query string
|
2011-05-03 18:44:23 +08:00
|
|
|
|
|
|
|
for ignored in IGNORED_PARAMS:
|
|
|
|
if ignored in lookup_params:
|
|
|
|
del lookup_params[ignored]
|
|
|
|
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
for key, value in lookup_params.items():
|
|
|
|
if not isinstance(key, str):
|
|
|
|
# 'key' will be used as a keyword argument later, so Python
|
|
|
|
# requires it to be a string.
|
|
|
|
del lookup_params[key]
|
|
|
|
lookup_params[smart_str(key)] = value
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2011-02-14 06:51:40 +08:00
|
|
|
if not use_distinct:
|
|
|
|
# Check if it's a relationship that might return more than one
|
|
|
|
# instance
|
|
|
|
field_name = key.split('__', 1)[0]
|
|
|
|
try:
|
2011-05-03 18:44:23 +08:00
|
|
|
field = self.lookup_opts.get_field_by_name(field_name)[0]
|
|
|
|
use_distinct = field_needs_distinct(field)
|
2011-02-14 06:51:40 +08:00
|
|
|
except models.FieldDoesNotExist:
|
2011-05-03 18:44:23 +08:00
|
|
|
# It might be a custom NonFieldFilter
|
|
|
|
pass
|
2011-02-14 06:51:40 +08:00
|
|
|
|
2008-08-30 00:13:17 +08:00
|
|
|
# if key ends with __in, split parameter into separate values
|
|
|
|
if key.endswith('__in'):
|
2011-01-28 22:08:25 +08:00
|
|
|
value = value.split(',')
|
|
|
|
lookup_params[key] = value
|
2008-08-30 00:13:17 +08:00
|
|
|
|
2010-03-17 03:01:40 +08:00
|
|
|
# if key ends with __isnull, special case '' and false
|
|
|
|
if key.endswith('__isnull'):
|
|
|
|
if value.lower() in ('', 'false'):
|
2011-01-28 22:08:25 +08:00
|
|
|
value = False
|
2010-03-17 03:01:40 +08:00
|
|
|
else:
|
2011-01-28 22:08:25 +08:00
|
|
|
value = True
|
|
|
|
lookup_params[key] = value
|
2010-03-17 03:01:40 +08:00
|
|
|
|
2011-01-28 22:08:25 +08:00
|
|
|
if not self.model_admin.lookup_allowed(key, value):
|
2011-05-03 18:44:23 +08:00
|
|
|
raise SuspiciousOperation("Filtering by %s not allowed" % key)
|
|
|
|
|
|
|
|
return lookup_params, use_distinct
|
|
|
|
|
|
|
|
def get_query_set(self, request):
|
|
|
|
lookup_params, use_distinct = self.get_lookup_params(use_distinct=False)
|
|
|
|
self.filter_specs, self.has_filters = self.get_filters(request, use_distinct)
|
|
|
|
|
|
|
|
# Let every list filter modify the qs and params to its liking
|
|
|
|
qs = self.root_query_set
|
|
|
|
for filter_spec in self.filter_specs:
|
|
|
|
new_qs = filter_spec.queryset(request, qs)
|
|
|
|
if new_qs is not None:
|
|
|
|
qs = new_qs
|
|
|
|
for param in filter_spec.used_params():
|
|
|
|
try:
|
|
|
|
del lookup_params[param]
|
|
|
|
except KeyError:
|
|
|
|
pass
|
2010-12-23 11:44:38 +08:00
|
|
|
|
2011-05-03 18:44:23 +08:00
|
|
|
# Apply the remaining lookup parameters from the query string (i.e.
|
|
|
|
# those that haven't already been processed by the filters).
|
2008-10-23 07:09:35 +08:00
|
|
|
try:
|
|
|
|
qs = qs.filter(**lookup_params)
|
|
|
|
# Naked except! Because we don't have any other way of validating "params".
|
|
|
|
# They might be invalid if the keyword arguments are incorrect, or if the
|
|
|
|
# values are not in the correct type, so we might get FieldError, ValueError,
|
2010-12-23 11:44:38 +08:00
|
|
|
# ValicationError, or ? from a custom field that raises yet something else
|
2008-10-23 07:09:35 +08:00
|
|
|
# when handed impossible data.
|
2011-05-03 18:44:23 +08:00
|
|
|
except Exception, e:
|
|
|
|
raise IncorrectLookupParameters(e)
|
2006-05-02 09:31:56 +08:00
|
|
|
|
|
|
|
# Use select_related() if one of the list_display options is a field
|
2009-05-14 23:09:33 +08:00
|
|
|
# with a relationship and the provided queryset doesn't already have
|
|
|
|
# select_related defined.
|
|
|
|
if not qs.query.select_related:
|
|
|
|
if self.list_select_related:
|
|
|
|
qs = qs.select_related()
|
|
|
|
else:
|
|
|
|
for field_name in self.list_display:
|
|
|
|
try:
|
2011-05-03 18:44:23 +08:00
|
|
|
field = self.lookup_opts.get_field(field_name)
|
2009-05-14 23:09:33 +08:00
|
|
|
except models.FieldDoesNotExist:
|
|
|
|
pass
|
|
|
|
else:
|
2011-05-03 18:44:23 +08:00
|
|
|
if isinstance(field.rel, models.ManyToOneRel):
|
2009-05-14 23:09:33 +08:00
|
|
|
qs = qs.select_related()
|
|
|
|
break
|
2006-05-02 09:31:56 +08:00
|
|
|
|
|
|
|
# Set ordering.
|
2011-06-03 00:18:47 +08:00
|
|
|
if self.ordering:
|
|
|
|
qs = qs.order_by(*self.ordering)
|
2006-05-02 09:31:56 +08:00
|
|
|
|
|
|
|
# Apply keyword searches.
|
2006-08-18 10:48:34 +08:00
|
|
|
def construct_search(field_name):
|
|
|
|
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
|
2006-08-19 05:39:29 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
if self.search_fields and self.query:
|
2011-03-01 10:04:35 +08:00
|
|
|
orm_lookups = [construct_search(str(search_field))
|
|
|
|
for search_field in self.search_fields]
|
2006-05-02 09:31:56 +08:00
|
|
|
for bit in self.query.split():
|
2011-03-01 10:04:35 +08:00
|
|
|
or_queries = [models.Q(**{orm_lookup: bit})
|
|
|
|
for orm_lookup in orm_lookups]
|
2009-05-07 20:52:43 +08:00
|
|
|
qs = qs.filter(reduce(operator.or_, or_queries))
|
2011-02-14 06:51:40 +08:00
|
|
|
if not use_distinct:
|
2011-03-01 10:04:35 +08:00
|
|
|
for search_spec in orm_lookups:
|
|
|
|
field_name = search_spec.split('__', 1)[0]
|
2011-02-14 06:51:40 +08:00
|
|
|
f = self.lookup_opts.get_field_by_name(field_name)[0]
|
2011-04-23 11:55:21 +08:00
|
|
|
if field_needs_distinct(f):
|
2011-02-14 06:51:40 +08:00
|
|
|
use_distinct = True
|
|
|
|
break
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2011-02-14 06:51:40 +08:00
|
|
|
if use_distinct:
|
|
|
|
return qs.distinct()
|
|
|
|
else:
|
|
|
|
return qs
|
2006-05-02 09:31:56 +08:00
|
|
|
|
|
|
|
def url_for_result(self, result):
|
|
|
|
return "%s/" % quote(getattr(result, self.pk_attname))
|