=== modified file 'django/contrib/admin/views/main.py'
|
|
|
8 | 8 | from django.core.paginator import ObjectPaginator, InvalidPage |
9 | 9 | from django.shortcuts import get_object_or_404, render_to_response |
10 | 10 | from django.db import models |
11 | | from django.db.models.query import handle_legacy_orderlist, QuerySet |
| 11 | from django.db.models.query import handle_legacy_orderlist, QuerySet, LOOKUP_SEPARATOR |
12 | 12 | from django.http import Http404, HttpResponse, HttpResponseRedirect |
13 | 13 | from django.utils.html import escape |
14 | 14 | from django.utils.text import capfirst, get_text_list |
… |
… |
|
543 | 543 | "admin/object_history.html"], extra_context, context_instance=template.RequestContext(request)) |
544 | 544 | history = staff_member_required(never_cache(history)) |
545 | 545 | |
| 546 | class FollowForeignKey(object): |
| 547 | """ |
| 548 | A class to represent a ForeignKey that spans multiple foreign keys |
| 549 | over multiple model classes. |
| 550 | """ |
| 551 | def __init__(self, field, lookups): |
| 552 | # Traverse the relationships to reach the final model class. |
| 553 | for field_name in lookups[1:]: |
| 554 | model = field.rel.to |
| 555 | new_field = model._meta.get_field(field_name) |
| 556 | if not new_field.rel: break |
| 557 | field = new_field |
| 558 | self.verbose_name = field.verbose_name |
| 559 | self.rel = field.rel |
| 560 | # Rejoin the lookups using the separator so that the |
| 561 | # RelatedFilterSpec uses the correct lookup_kwarg. |
| 562 | self.name = LOOKUP_SEPARATOR.join(lookups) |
| 563 | |
546 | 564 | class ChangeList(object): |
547 | 565 | def __init__(self, request, model): |
548 | 566 | self.model = model |
… |
… |
|
574 | 592 | def get_filters(self, request): |
575 | 593 | filter_specs = [] |
576 | 594 | if self.lookup_opts.admin.list_filter and not self.opts.one_to_one_field: |
577 | | filter_fields = [self.lookup_opts.get_field(field_name) \ |
578 | | for field_name in self.lookup_opts.admin.list_filter] |
579 | | for f in filter_fields: |
| 595 | for field_name in self.lookup_opts.admin.list_filter: |
| 596 | field_lookups = field_name.split(LOOKUP_SEPARATOR) |
| 597 | f = self.lookup_opts.get_field(field_lookups[0]) |
| 598 | # If the field name spans multiple models, wrap the field in a |
| 599 | # FollowForeignKey. |
| 600 | if len(field_lookups) > 1 and f.rel: |
| 601 | f = FollowForeignKey(f, field_lookups) |
580 | 602 | spec = FilterSpec.create(f, request, self.params, self.model) |
581 | 603 | if spec and spec.has_output(): |
582 | 604 | filter_specs.append(spec) |
=== modified file 'django/core/management/validation.py'
|
|
|
21 | 21 | from django.db import models, connection |
22 | 22 | from django.db.models.loading import get_app_errors |
23 | 23 | from django.db.models.fields.related import RelatedObject |
| 24 | from django.db.models.query import LOOKUP_SEPARATOR |
24 | 25 | |
25 | 26 | e = ModelErrorCollection(outfile) |
26 | 27 | |
… |
… |
|
170 | 171 | e.add(opts, '"admin.list_filter", if given, must be set to a list or tuple.') |
171 | 172 | else: |
172 | 173 | for fn in opts.admin.list_filter: |
| 174 | field_lookups = fn.split(LOOKUP_SEPARATOR) |
173 | 175 | try: |
174 | | f = opts.get_field(fn) |
| 176 | f = opts.get_field(field_lookups[0]) |
| 177 | if len(field_lookups) > 1: |
| 178 | if not f.rel: |
| 179 | e.add(opts, '"admin.list_filter" refers to %r which containts a non-related field.' % fn) |
| 180 | for field_name in field_lookups[1:]: |
| 181 | model = f.rel.to |
| 182 | f = model._meta.get_field(field_name) |
| 183 | if not f.rel: |
| 184 | e.add(opts, '"admin.list_filter" refers to %r which containts a non-related field.' % fn) |
175 | 185 | except models.FieldDoesNotExist: |
176 | 186 | e.add(opts, '"admin.list_filter" refers to %r, which isn\'t a field.' % fn) |
177 | 187 | # date_hierarchy |