1 | Index: django/contrib/admin/validation.py
|
---|
2 | ===================================================================
|
---|
3 | --- django/contrib/admin/validation.py (revision 12223)
|
---|
4 | +++ django/contrib/admin/validation.py (working copy)
|
---|
5 | @@ -1,5 +1,6 @@
|
---|
6 | from django.core.exceptions import ImproperlyConfigured
|
---|
7 | from django.db import models
|
---|
8 | +from django.db.models.sql.constants import LOOKUP_SEP
|
---|
9 | from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model,
|
---|
10 | _get_foreign_key)
|
---|
11 | from django.contrib.admin.options import flatten_fieldsets, BaseModelAdmin
|
---|
12 | @@ -32,8 +33,11 @@
|
---|
13 | try:
|
---|
14 | opts.get_field(field)
|
---|
15 | except models.FieldDoesNotExist:
|
---|
16 | - raise ImproperlyConfigured("%s.list_display[%d], %r is not a callable or an attribute of %r or found in the model %r."
|
---|
17 | - % (cls.__name__, idx, field, cls.__name__, model._meta.object_name))
|
---|
18 | + try:
|
---|
19 | + get_closest_relation(model, field)
|
---|
20 | + except AttributeError, models.FieldDoesNotExist:
|
---|
21 | + raise ImproperlyConfigured("%s.list_display[%d], %r is not a callable or an attribute of %r or found in the model %r."
|
---|
22 | + % (cls.__name__, idx, field, cls.__name__, model._meta.object_name))
|
---|
23 | else:
|
---|
24 | # getattr(model, field) could be an X_RelatedObjectsDescriptor
|
---|
25 | f = fetch_attr(cls, model, opts, "list_display[%d]" % idx, field)
|
---|
26 | @@ -349,3 +353,28 @@
|
---|
27 | except AttributeError:
|
---|
28 | raise ImproperlyConfigured("'%s.%s' refers to '%s' that is neither a field, method or property of model '%s'."
|
---|
29 | % (cls.__name__, label, field, model.__name__))
|
---|
30 | +
|
---|
31 | +def get_closest_relation(model,relation):
|
---|
32 | + # Recursively go down all forward relations, then all backward relations with this model to see if the relation exists.
|
---|
33 | + if not LOOKUP_SEP in relation:
|
---|
34 | + if hasattr(model,'base') and relation in model.base.field.rel.to._meta.get_all_field_names():
|
---|
35 | + """
|
---|
36 | + If this model has a base class and the field is really on it,
|
---|
37 | + return the actual base class.
|
---|
38 | + """
|
---|
39 | + model = model.base.field.rel.to
|
---|
40 | + return model._meta.get_field_by_name(relation) # at this point we should have our field, or it raises FieldDoesNotExist
|
---|
41 | +
|
---|
42 | + this_module = relation.split(LOOKUP_SEP,1)
|
---|
43 | +
|
---|
44 | + if this_module[0] in model._meta.get_all_field_names(): # forward relations
|
---|
45 | + rel = model._meta.get_field_by_name(this_module[0])[0].rel
|
---|
46 | + if isinstance(rel,models.fields.related.OneToOneRel) or isinstance(rel,models.fields.related.ManyToOneRel):
|
---|
47 | + return get_closest_relation(rel.to,this_module[1])
|
---|
48 | +
|
---|
49 | + for rel in model._meta.get_all_related_objects(): # backward relations
|
---|
50 | + if this_module[0] in (rel.name, rel.field.related_query_name) and isinstance(rel,models.fields.related.OneToOneRel):
|
---|
51 | + return get_closest_relation(rel.model,this_module[1])
|
---|
52 | +
|
---|
53 | + raise AttributeError("%s relation not found on %s." % (this_module[0],model._meta.verbose_name))
|
---|
54 | Index: django/contrib/admin/util.py
|
---|
55 | ===================================================================
|
---|
56 | --- django/contrib/admin/util.py (revision 12223)
|
---|
57 | +++ django/contrib/admin/util.py (working copy)
|
---|
58 | @@ -1,5 +1,6 @@
|
---|
59 | from django.core.exceptions import ObjectDoesNotExist
|
---|
60 | from django.db import models
|
---|
61 | +from django.db.models.sql.constants import LOOKUP_SEP
|
---|
62 | from django.utils import formats
|
---|
63 | from django.utils.html import escape
|
---|
64 | from django.utils.safestring import mark_safe
|
---|
65 | @@ -229,9 +230,13 @@
|
---|
66 | try:
|
---|
67 | f = opts.get_field(name)
|
---|
68 | except models.FieldDoesNotExist:
|
---|
69 | - # For non-field values, the value is either a method, property or
|
---|
70 | - # returned via a callable.
|
---|
71 | - if callable(name):
|
---|
72 | + # For non-field values, the value is either a relation, method, property or
|
---|
73 | + # returned via a callable
|
---|
74 | + if type(name) == str and LOOKUP_SEP in name:
|
---|
75 | + value = obj
|
---|
76 | + for attr in name.split(LOOKUP_SEP):
|
---|
77 | + value = getattr(value,attr)
|
---|
78 | + elif callable(name):
|
---|
79 | attr = name
|
---|
80 | value = attr(obj)
|
---|
81 | elif (model_admin is not None and hasattr(model_admin, name) and
|
---|
82 | @@ -259,6 +264,8 @@
|
---|
83 | label = force_unicode(model._meta.verbose_name)
|
---|
84 | elif name == "__str__":
|
---|
85 | label = smart_str(model._meta.verbose_name)
|
---|
86 | + elif type(name) == str and LOOKUP_SEP in name:
|
---|
87 | + label = name.split(LOOKUP_SEP)[-1].replace('_',' ')
|
---|
88 | else:
|
---|
89 | if callable(name):
|
---|
90 | attr = name
|
---|
91 | @@ -286,7 +293,6 @@
|
---|
92 | else:
|
---|
93 | return label
|
---|
94 |
|
---|
95 | -
|
---|
96 | def display_for_field(value, field):
|
---|
97 | from django.contrib.admin.templatetags.admin_list import _boolean_icon
|
---|
98 | from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
|
---|
99 |
|
---|