Ticket #19755: 19755.diff
File 19755.diff, 15.9 KB (added by , 12 years ago) |
---|
-
django/contrib/admin/filters.py
diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py index ff66d3e..c4f7ca7 100644
a b class ListFilter(object): 54 54 """ 55 55 raise NotImplementedError 56 56 57 def limit_lookup_choices(self, queryset): 58 pass 59 57 60 58 61 class SimpleListFilter(ListFilter): 59 62 # The parameter that should be used in the query string for that filter. … … class FieldListFilter(ListFilter): 118 121 self.field = field 119 122 self.field_path = field_path 120 123 self.title = getattr(field, 'verbose_name', field_path) 124 self.limited_qs = None 121 125 super(FieldListFilter, self).__init__( 122 126 request, params, model, model_admin) 123 127 for p in self.expected_parameters(): … … class FieldListFilter(ListFilter): 134 138 except ValidationError as e: 135 139 raise IncorrectLookupParameters(e) 136 140 141 def limit_lookup_choices(self, queryset): 142 self.limited_qs = queryset 143 137 144 @classmethod 138 145 def register(cls, test, list_filter_class, take_priority=False): 139 146 if take_priority: … … class FieldListFilter(ListFilter): 157 164 158 165 class RelatedFieldListFilter(FieldListFilter): 159 166 def __init__(self, field, request, params, model, model_admin, field_path): 160 other_model = get_model_from_relation(field)167 self.other_model = get_model_from_relation(field) 161 168 if hasattr(field, 'rel'): 162 169 rel_name = field.rel.get_related_field().name 163 170 else: 164 rel_name = other_model._meta.pk.name171 rel_name = self.other_model._meta.pk.name 165 172 self.lookup_kwarg = '%s__%s__exact' % (field_path, rel_name) 166 173 self.lookup_kwarg_isnull = '%s__isnull' % field_path 167 174 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 168 175 self.lookup_val_isnull = request.GET.get( 169 176 self.lookup_kwarg_isnull, None) 170 self. lookup_choices = field.get_choices(include_blank=False)177 self._lookup_choices = None 171 178 super(RelatedFieldListFilter, self).__init__( 172 179 field, request, params, model, model_admin, field_path) 173 180 if hasattr(field, 'verbose_name'): 174 181 self.lookup_title = field.verbose_name 175 182 else: 176 self.lookup_title = other_model._meta.verbose_name183 self.lookup_title = self.other_model._meta.verbose_name 177 184 self.title = self.lookup_title 178 185 179 186 def has_output(self): … … class RelatedFieldListFilter(FieldListFilter): 183 190 extra = 1 184 191 else: 185 192 extra = 0 186 return len(self.lookup_choices) + extra > 1193 return len(self.lookup_choices) + extra > 0 187 194 188 195 def expected_parameters(self): 189 196 return [self.lookup_kwarg, self.lookup_kwarg_isnull] 190 197 198 def _get_lookup_choices(self): 199 if self._lookup_choices is None: 200 if self.limited_qs is None: 201 self._lookup_choices = self.field.get_choices(include_blank=False) 202 else: 203 pks = (self.limited_qs 204 .distinct() 205 .values_list(self.field_path, flat=True)) 206 self._lookup_choices = [(x._get_pk_val(), smart_text(x)) for x in 207 self.other_model.objects.filter(pk__in=pks)] 208 return self._lookup_choices 209 lookup_choices = property(_get_lookup_choices) 210 191 211 def choices(self, cl): 192 212 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE 193 213 yield { … … class BooleanFieldListFilter(FieldListFilter): 233 253 return [self.lookup_kwarg, self.lookup_kwarg2] 234 254 235 255 def choices(self, cl): 236 for lookup, title in ( 237 (None, _('All')), 238 ('1', _('Yes')), 239 ('0', _('No'))): 256 lookup, title = None, _('All') 257 yield { 258 'selected': self.lookup_val == lookup and not self.lookup_val2, 259 'query_string': cl.get_query_string({ 260 self.lookup_kwarg: lookup, 261 }, [self.lookup_kwarg2]), 262 'display': title, 263 } 264 choices = self.lookup_choices() 265 for choice in choices: 266 if choice is None: 267 continue 268 elif choice: 269 lookup, title = '1', _('Yes') 270 else: 271 lookup, title = '0', _('No') 240 272 yield { 241 273 'selected': self.lookup_val == lookup and not self.lookup_val2, 242 274 'query_string': cl.get_query_string({ … … class BooleanFieldListFilter(FieldListFilter): 244 276 }, [self.lookup_kwarg2]), 245 277 'display': title, 246 278 } 247 if isinstance(self.field, models.NullBooleanField):279 if None in choices: 248 280 yield { 249 281 'selected': self.lookup_val2 == 'True', 250 282 'query_string': cl.get_query_string({ … … class BooleanFieldListFilter(FieldListFilter): 253 285 'display': _('Unknown'), 254 286 } 255 287 288 def lookup_choices(self): 289 if self.limited_qs is None: 290 if isinstance(self.field, models.NullBooleanField): 291 choices = (True, False, None) 292 else: 293 choices = (True, False) 294 else: 295 choices = (self.limited_qs 296 .distinct() 297 .values_list(self.field_path, flat=True)) 298 choices = tuple(ch for ch in (True, False, None) if ch in choices) 299 return choices 300 256 301 FieldListFilter.register(lambda f: isinstance(f, 257 302 (models.BooleanField, models.NullBooleanField)), BooleanFieldListFilter) 258 303 … … class ChoicesFieldListFilter(FieldListFilter): 261 306 def __init__(self, field, request, params, model, model_admin, field_path): 262 307 self.lookup_kwarg = '%s__exact' % field_path 263 308 self.lookup_val = request.GET.get(self.lookup_kwarg) 309 self._lookup_choices = None 264 310 super(ChoicesFieldListFilter, self).__init__( 265 311 field, request, params, model, model_admin, field_path) 266 312 313 def has_output(self): 314 return len(self.lookup_choices) > 0 315 267 316 def expected_parameters(self): 268 317 return [self.lookup_kwarg] 269 318 … … class ChoicesFieldListFilter(FieldListFilter): 273 322 'query_string': cl.get_query_string({}, [self.lookup_kwarg]), 274 323 'display': _('All') 275 324 } 276 for lookup, title in self. field.flatchoices:325 for lookup, title in self.lookup_choices: 277 326 yield { 278 327 'selected': smart_text(lookup) == self.lookup_val, 279 328 'query_string': cl.get_query_string({ … … class ChoicesFieldListFilter(FieldListFilter): 281 330 'display': title, 282 331 } 283 332 333 def _get_lookup_choices(self): 334 if self._lookup_choices is None: 335 if self.limited_qs is None: 336 self._lookup_choices = self.field.flatchoices 337 else: 338 choices_dict = dict(self.field.flatchoices) 339 choices = (self.limited_qs 340 .distinct() 341 .order_by(self.field_path) 342 .values_list(self.field_path, flat=True)) 343 self._lookup_choices = [] 344 for choice in choices: 345 if choice is not None: 346 title = force_text(choices_dict.get(choice, choice), 347 strings_only=True) 348 self._lookup_choices.append((choice, title)) 349 return self._lookup_choices 350 lookup_choices = property(_get_lookup_choices) 351 284 352 FieldListFilter.register(lambda f: bool(f.choices), ChoicesFieldListFilter) 285 353 286 354 … … class DateFieldListFilter(FieldListFilter): 302 370 today = now.date() 303 371 tomorrow = today + datetime.timedelta(days=1) 304 372 373 self.today = today 374 self.tomorrow = tomorrow 375 305 376 self.lookup_kwarg_since = '%s__gte' % field_path 306 377 self.lookup_kwarg_until = '%s__lt' % field_path 307 self.links = ( 308 (_('Any date'), {}), 309 (_('Today'), { 310 self.lookup_kwarg_since: str(today), 311 self.lookup_kwarg_until: str(tomorrow), 312 }), 313 (_('Past 7 days'), { 314 self.lookup_kwarg_since: str(today - datetime.timedelta(days=7)), 315 self.lookup_kwarg_until: str(tomorrow), 316 }), 317 (_('This month'), { 318 self.lookup_kwarg_since: str(today.replace(day=1)), 319 self.lookup_kwarg_until: str(tomorrow), 320 }), 321 (_('This year'), { 322 self.lookup_kwarg_since: str(today.replace(month=1, day=1)), 323 self.lookup_kwarg_until: str(tomorrow), 324 }), 325 ) 378 self._links = None 326 379 super(DateFieldListFilter, self).__init__( 327 380 field, request, params, model, model_admin, field_path) 328 381 382 def has_output(self): 383 return len(self.links) > 1 384 329 385 def expected_parameters(self): 330 386 return [self.lookup_kwarg_since, self.lookup_kwarg_until] 331 387 … … class DateFieldListFilter(FieldListFilter): 338 394 'display': title, 339 395 } 340 396 397 def _get_links(self): 398 if self._links is None: 399 self._links = [] 400 qs = self.limited_qs 401 queries = ( 402 (_('Today'), ( 403 (self.lookup_kwarg_since, self.today), 404 (self.lookup_kwarg_until, self.tomorrow), 405 )), 406 (_('Past 7 days'), ( 407 (self.lookup_kwarg_since, self.today - datetime.timedelta(days=7)), 408 (self.lookup_kwarg_until, self.tomorrow), 409 )), 410 (_('This month'), ( 411 (self.lookup_kwarg_since, self.today.replace(day=1)), 412 (self.lookup_kwarg_until, self.tomorrow), 413 )), 414 (_('This year'), ( 415 (self.lookup_kwarg_since, self.today.replace(day=1, month=1)), 416 (self.lookup_kwarg_until, self.tomorrow), 417 )), 418 ) 419 self._links.append((_('Any date'), {})) 420 for title, query in queries: 421 if qs is None or qs.filter(**dict(query)).exists(): 422 self._links.append((title, dict((k, str(v)) for k, v in query))) 423 return self._links 424 links = property(_get_links) 425 426 341 427 FieldListFilter.register( 342 428 lambda f: isinstance(f, models.DateField), DateFieldListFilter) 343 429 … … class AllValuesFieldListFilter(FieldListFilter): 352 438 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 353 439 self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull, 354 440 None) 355 parent_model, reverse_path = reverse_field_path(model, field_path)441 parent_model, self.reverse_path = reverse_field_path(model, field_path) 356 442 queryset = parent_model._default_manager.all() 357 443 # optional feature: limit choices base on existing relationships 358 444 # queryset = queryset.complex_filter( 359 445 # {'%s__isnull' % reverse_path: False}) 360 446 limit_choices_to = get_limit_choices_to_from_path(model, field_path) 361 447 queryset = queryset.filter(limit_choices_to) 448 self.field_qs = queryset 362 449 363 self.lookup_choices = (queryset 364 .distinct() 365 .order_by(field.name) 366 .values_list(field.name, flat=True)) 450 self._lookup_choices = None 367 451 super(AllValuesFieldListFilter, self).__init__( 368 452 field, request, params, model, model_admin, field_path) 369 453 454 def has_output(self): 455 return len(self.lookup_choices) > 0 456 370 457 def expected_parameters(self): 371 458 return [self.lookup_kwarg, self.lookup_kwarg_isnull] 372 459 … … class AllValuesFieldListFilter(FieldListFilter): 401 488 'display': EMPTY_CHANGELIST_VALUE, 402 489 } 403 490 491 def _get_lookup_choices(self): 492 if self._lookup_choices is None: 493 if self.limited_qs is None: 494 qs = self.field_qs 495 field_name = self.field.name 496 else: 497 qs = self.limited_qs 498 field_name = self.field_path 499 self._lookup_choices = (qs 500 .distinct() 501 .order_by(field_name) 502 .values_list(field_name, flat=True)) 503 return self._lookup_choices 504 lookup_choices = property(_get_lookup_choices) 505 404 506 FieldListFilter.register(lambda f: True, AllValuesFieldListFilter) -
django/contrib/admin/options.py
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index b35f100..ee8759f 100644
a b class ModelAdmin(BaseModelAdmin): 345 345 list_display = ('__str__',) 346 346 list_display_links = () 347 347 list_filter = () 348 list_filter_incremental = False 348 349 list_select_related = False 349 350 list_per_page = 100 350 351 list_max_show_all = 200 -
django/contrib/admin/validation.py
diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py index a02bb7a..1efbcd7 100644
a b def validate(cls, model): 163 163 if hasattr(cls, "readonly_fields"): 164 164 check_readonly_fields(cls, model, opts) 165 165 166 # list_filter_incremental = False 166 167 # list_select_related = False 167 168 # save_as = False 168 169 # save_on_top = False 169 for attr in ('list_ select_related', 'save_as', 'save_on_top'):170 for attr in ('list_filter_incremental', 'list_select_related', 'save_as', 'save_on_top'): 170 171 if not isinstance(getattr(cls, attr), bool): 171 172 raise ImproperlyConfigured("'%s.%s' should be a boolean." 172 173 % (cls.__name__, attr)) -
django/contrib/admin/views/main.py
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 050d477..9b78561 100644
a b class ChangeList(six.with_metaclass(RenameChangeListMethods)): 158 158 use_distinct = (use_distinct or 159 159 lookup_needs_distinct(self.lookup_opts, 160 160 field_path)) 161 if spec and spec.has_output(): 161 incremental = self.model_admin.list_filter_incremental 162 if spec and (incremental or spec.has_output()): 162 163 filter_specs.append(spec) 163 164 164 165 # At this point, all the parameters used by the various ListFilters … … class ChangeList(six.with_metaclass(RenameChangeListMethods)): 330 331 331 332 def get_queryset(self, request): 332 333 # First, we collect all the declared list filters. 333 ( self.filter_specs, self.has_filters, remaining_lookup_params,334 (filter_specs, self.has_filters, remaining_lookup_params, 334 335 use_distinct) = self.get_filters(request) 335 336 336 337 # Then, we let every list filter modify the queryset to its liking. 337 338 qs = self.root_queryset 338 for filter_spec in self.filter_specs:339 for filter_spec in filter_specs: 339 340 new_qs = filter_spec.queryset(request, qs) 340 341 if new_qs is not None: 341 342 qs = new_qs 342 343 344 # incremental list filter, limit lookup choices 345 if self.model_admin.list_filter_incremental: 346 for filter_spec in filter_specs: 347 filter_spec.limit_lookup_choices(qs) 348 349 # only filters with output are saved 350 self.filter_specs = [fs for fs in filter_specs if fs.has_output()] 351 343 352 try: 344 353 # Finally, we apply the remaining lookup parameters from the query 345 354 # string (i.e. those that haven't already been processed by the