2 Slightly modified version of the django admin component that handles filters and searches
4 from django.contrib.admin.filterspecs import FilterSpec
5 from django.contrib.admin.options import IncorrectLookupParameters
6 from django.core.paginator import Paginator, InvalidPage, EmptyPage
7 from django.db import models
8 from django.db.models.query import QuerySet
9 from django.utils.encoding import smart_str
10 from django.utils.http import urlencode
14 MAX_SHOW_ALL_ALLOWED = 20000
25 class ChangeList(object):
26 def __init__(self, request, model, list_filter, search_fields, list_per_page, queryset=None):
28 self.opts = model._meta
29 self.lookup_opts = self.opts
31 self.root_query_set = model.objects.all()
33 self.root_query_set = queryset
34 self.list_display = []
35 self.list_display_links = None
36 self.list_filter = list_filter
38 self.search_fields = search_fields
39 self.list_select_related = None
40 self.list_per_page = list_per_page
41 self.model_admin = None
44 self.page_num = int(request.GET.get(PAGE_VAR,'0'))
47 self.show_all = 'all' in request.GET
48 self.params = dict(request.GET.items())
49 if PAGE_VAR in self.params:
50 del self.params[PAGE_VAR]
51 if ERROR_FLAG in self.params:
52 del self.params[ERROR_FLAG]
54 self.multi_page = True
55 self.can_show_all = False
57 self.order_field, self.order_type = self.get_ordering()
58 self.query = request.GET.get(SEARCH_VAR, '')
59 self.query_set = self.get_query_set()
60 self.get_results(request)
61 self.filter_specs, self.has_filters = self.get_filters(request)
63 #self.result_count = 'result count'
64 #self.full_result_count = 'full result count'
66 def get_filters(self, request):
69 filter_fields = [self.lookup_opts.get_field(field_name) for field_name in self.list_filter]
70 for f in filter_fields:
71 spec = FilterSpec.create(f, request, self.params, self.model, self.model_admin)
72 if spec and spec.has_output():
73 filter_specs.append(spec)
74 return filter_specs, bool(filter_specs)
76 def get_query_string(self, new_params=None, remove=None):
77 if new_params is None: new_params = {}
78 if remove is None: remove = []
79 p = self.params.copy()
84 for k, v in new_params.items():
90 return '?%s' % urlencode(p)
92 def get_results(self, request):
93 paginator = Paginator(self.query_set, self.list_per_page)
94 # Get the number of objects, with admin filters applied.
95 result_count = paginator.count
97 # Get the total number of objects, with no admin filters applied.
98 # Perform a slight optimization: Check to see whether any filters were
99 # given. If not, use paginator.hits to calculate the number of objects,
100 # because we've already done paginator.hits and the value is cached.
101 if not self.query_set.query.where:
102 full_result_count = result_count
104 full_result_count = self.root_query_set.count()
106 can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED
107 multi_page = result_count > self.list_per_page
109 # Get the list of objects to display on this page.
110 if (self.show_all and can_show_all) or not multi_page:
111 result_list = list(self.query_set)
114 result_list = paginator.page(self.page_num+1).object_list
118 self.result_count = result_count
119 self.full_result_count = full_result_count
120 self.result_list = result_list
121 self.can_show_all = can_show_all
122 self.multi_page = multi_page
123 self.paginator = paginator
125 def get_ordering(self):
126 lookup_opts, params = self.lookup_opts, self.params
127 # For ordering, first check the "ordering" parameter in the admin
128 # options, then check the object's default ordering. If neither of
129 # those exist, order descending by ID by default. Finally, look for
130 # manually-specified ordering from the query string.
131 ordering = lookup_opts.ordering or ['-' + lookup_opts.pk.name]
133 if ordering[0].startswith('-'):
134 order_field, order_type = ordering[0][1:], 'desc'
136 order_field, order_type = ordering[0], 'asc'
137 if ORDER_VAR in params:
139 field_name = self.list_display[int(params[ORDER_VAR])]
141 f = lookup_opts.get_field(field_name)
142 except models.FieldDoesNotExist:
143 # See whether field_name is a name of a non-field
144 # that allows sorting.
146 if callable(field_name):
148 elif hasattr(self.model_admin, field_name):
149 attr = getattr(self.model_admin, field_name)
151 attr = getattr(self.model, field_name)
152 order_field = attr.admin_order_field
153 except AttributeError:
157 except (IndexError, ValueError):
158 pass # Invalid ordering specified. Just use the default.
159 if ORDER_TYPE_VAR in params and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
160 order_type = params[ORDER_TYPE_VAR]
161 return order_field, order_type
163 def get_query_set(self):
164 qs = self.root_query_set
165 lookup_params = self.params.copy() # a dictionary of the query string
166 for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
167 if i in lookup_params:
169 for key, value in lookup_params.items():
170 if not isinstance(key, str):
171 # 'key' will be used as a keyword argument later, so Python
172 # requires it to be a string.
173 del lookup_params[key]
174 lookup_params[smart_str(key)] = value
176 # if key ends with __in, split parameter into separate values
177 if key.endswith('__in'):
178 lookup_params[key] = value.split(',')
180 # Apply lookup parameters from the query string.
182 qs = qs.filter(**lookup_params)
183 # Naked except! Because we don't have any other way of validating "params".
184 # They might be invalid if the keyword arguments are incorrect, or if the
185 # values are not in the correct type, so we might get FieldError, ValueError,
186 # ValicationError, or ? from a custom field that raises yet something else
187 # when handed impossible data.
190 raise IncorrectLookupParameters
192 # Use select_related() if one of the list_display options is a field
193 # with a relationship.
194 if self.list_select_related:
195 qs = qs.select_related()
197 for field_name in self.list_display:
199 f = self.lookup_opts.get_field(field_name)
200 except models.FieldDoesNotExist:
203 if isinstance(f.rel, models.ManyToOneRel):
204 qs = qs.select_related()
209 qs = qs.order_by('%s%s' % ((self.order_type == 'desc' and '-' or ''), self.order_field))
211 # Apply keyword searches.
212 def construct_search(field_name):
213 if field_name.startswith('^'):
214 return "%s__istartswith" % field_name[1:]
215 elif field_name.startswith('='):
216 return "%s__iexact" % field_name[1:]
217 elif field_name.startswith('@'):
218 return "%s__search" % field_name[1:]
220 return "%s__icontains" % field_name
222 if self.search_fields and self.query:
223 for bit in self.query.split():
224 or_queries = [models.Q(**{construct_search(field_name): bit}) for field_name in self.search_fields]
225 other_qs = QuerySet(self.model)
226 other_qs.dup_select_related(qs)
227 other_qs = other_qs.filter(reduce(operator.or_, or_queries))
229 for field_name in self.search_fields:
230 if '__' in field_name:
234 if self.opts.one_to_one_field:
235 qs = qs.complex_filter(self.opts.one_to_one_field.rel.limit_choices_to)