Skip to content

Commit fb48eb0

Browse files
PirosB3timgraham
authored andcommitted
Fixed #12663 -- Formalized the Model._meta API for retrieving fields.
Thanks to Russell Keith-Magee for mentoring this Google Summer of Code 2014 project and everyone else who helped with the patch!
1 parent 749d232 commit fb48eb0

58 files changed

Lines changed: 2852 additions & 1196 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

django/apps/registry.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,12 @@ def clear_cache(self):
337337
338338
This is mostly used in tests.
339339
"""
340+
# Call expire cache on each model. This will purge
341+
# the relation tree and the fields cache.
340342
self.get_models.cache_clear()
343+
if self.ready:
344+
for model in self.get_models(include_auto_created=True):
345+
model._meta._expire_cache()
341346

342347
### DEPRECATED METHODS GO BELOW THIS LINE ###
343348

django/contrib/admin/checks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,7 @@ def _check_list_editable(self, cls, model):
762762

763763
def _check_list_editable_item(self, cls, model, field_name, label):
764764
try:
765-
field = model._meta.get_field_by_name(field_name)[0]
765+
field = model._meta.get_field(field_name)
766766
except FieldDoesNotExist:
767767
return refer_to_missing_field(field=field_name, option=label,
768768
model=model, obj=cls, id='admin.E121')

django/contrib/admin/options.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ def lookup_allowed(self, lookup, value):
406406
rel_name = None
407407
for part in parts[:-1]:
408408
try:
409-
field, _, _, _ = model._meta.get_field_by_name(part)
409+
field = model._meta.get_field(part)
410410
except FieldDoesNotExist:
411411
# Lookups on non-existent fields are ok, since they're ignored
412412
# later.
@@ -422,7 +422,7 @@ def lookup_allowed(self, lookup, value):
422422
else:
423423
rel_name = None
424424
elif isinstance(field, ForeignObjectRel):
425-
model = field.model
425+
model = field.related_model
426426
rel_name = model._meta.pk.name
427427
else:
428428
rel_name = None
@@ -473,9 +473,12 @@ def to_field_allowed(self, request, to_field):
473473
for inline in admin.inlines:
474474
registered_models.add(inline.model)
475475

476-
for related_object in (opts.get_all_related_objects(include_hidden=True) +
477-
opts.get_all_related_many_to_many_objects()):
478-
related_model = related_object.model
476+
related_objects = (
477+
f for f in opts.get_fields(include_hidden=True)
478+
if (f.auto_created and not f.concrete)
479+
)
480+
for related_object in related_objects:
481+
related_model = related_object.related_model
479482
if (any(issubclass(model, related_model) for model in registered_models) and
480483
related_object.field.rel.get_related_field() == field):
481484
return True

django/contrib/admin/templatetags/admin_list.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ def date_hierarchy(cl):
326326
"""
327327
if cl.date_hierarchy:
328328
field_name = cl.date_hierarchy
329-
field = cl.opts.get_field_by_name(field_name)[0]
329+
field = cl.opts.get_field(field_name)
330330
dates_or_datetimes = 'datetimes' if isinstance(field, models.DateTimeField) else 'dates'
331331
year_field = '%s__year' % field_name
332332
month_field = '%s__month' % field_name

django/contrib/admin/utils.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def lookup_needs_distinct(opts, lookup_path):
2525
Returns True if 'distinct()' should be used to query the given lookup path.
2626
"""
2727
field_name = lookup_path.split('__', 1)[0]
28-
field = opts.get_field_by_name(field_name)[0]
28+
field = opts.get_field(field_name)
2929
if hasattr(field, 'get_path_info') and any(path.m2m for path in field.get_path_info()):
3030
return True
3131
return False
@@ -265,7 +265,7 @@ def model_ngettext(obj, n=None):
265265
def lookup_field(name, obj, model_admin=None):
266266
opts = obj._meta
267267
try:
268-
f = opts.get_field(name)
268+
f = _get_non_gfk_field(opts, name)
269269
except FieldDoesNotExist:
270270
# For non-field values, the value is either a method, property or
271271
# returned via a callable.
@@ -291,6 +291,17 @@ def lookup_field(name, obj, model_admin=None):
291291
return f, attr, value
292292

293293

294+
def _get_non_gfk_field(opts, name):
295+
"""
296+
For historical reasons, the admin app relies on GenericForeignKeys as being
297+
"not found" by get_field(). This could likely be cleaned up.
298+
"""
299+
field = opts.get_field(name)
300+
if field.is_relation and field.one_to_many and not field.related_model:
301+
raise FieldDoesNotExist()
302+
return field
303+
304+
294305
def label_for_field(name, model, model_admin=None, return_attr=False):
295306
"""
296307
Returns a sensible label for a field name. The name can be a callable,
@@ -301,7 +312,7 @@ def label_for_field(name, model, model_admin=None, return_attr=False):
301312
"""
302313
attr = None
303314
try:
304-
field = model._meta.get_field_by_name(name)[0]
315+
field = _get_non_gfk_field(model._meta, name)
305316
try:
306317
label = field.verbose_name
307318
except AttributeError:
@@ -349,11 +360,10 @@ def label_for_field(name, model, model_admin=None, return_attr=False):
349360
def help_text_for_field(name, model):
350361
help_text = ""
351362
try:
352-
field_data = model._meta.get_field_by_name(name)
363+
field = _get_non_gfk_field(model._meta, name)
353364
except FieldDoesNotExist:
354365
pass
355366
else:
356-
field = field_data[0]
357367
if hasattr(field, 'help_text'):
358368
help_text = field.help_text
359369
return smart_text(help_text)
@@ -425,19 +435,21 @@ def reverse_field_path(model, path):
425435
parent = model
426436
pieces = path.split(LOOKUP_SEP)
427437
for piece in pieces:
428-
field, model, direct, m2m = parent._meta.get_field_by_name(piece)
438+
field = parent._meta.get_field(piece)
429439
# skip trailing data field if extant:
430440
if len(reversed_path) == len(pieces) - 1: # final iteration
431441
try:
432442
get_model_from_relation(field)
433443
except NotRelationField:
434444
break
435-
if direct:
445+
446+
# Field should point to another model
447+
if field.is_relation and not (field.auto_created and not field.concrete):
436448
related_name = field.related_query_name()
437449
parent = field.rel.to
438450
else:
439451
related_name = field.field.name
440-
parent = field.model
452+
parent = field.related_model
441453
reversed_path.insert(0, related_name)
442454
return (parent, LOOKUP_SEP.join(reversed_path))
443455

@@ -458,7 +470,7 @@ def get_fields_from_path(model, path):
458470
parent = get_model_from_relation(fields[-1])
459471
else:
460472
parent = model
461-
fields.append(parent._meta.get_field_by_name(piece)[0])
473+
fields.append(parent._meta.get_field(piece))
462474
return fields
463475

464476

django/contrib/admin/validation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ def validate_list_editable(self, cls, model):
346346
check_isseq(cls, 'list_editable', cls.list_editable)
347347
for idx, field_name in enumerate(cls.list_editable):
348348
try:
349-
field = model._meta.get_field_by_name(field_name)[0]
349+
field = model._meta.get_field(field_name)
350350
except FieldDoesNotExist:
351351
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
352352
"field, '%s', not defined on %s.%s."

django/contrib/admindocs/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ def get_context_data(self, **kwargs):
262262
})
263263

264264
# Gather related objects
265-
for rel in opts.get_all_related_objects() + opts.get_all_related_many_to_many_objects():
265+
for rel in opts.related_objects:
266266
verbose = _("related `%(app_label)s.%(object_name)s` objects") % {
267267
'app_label': rel.opts.app_label,
268268
'object_name': rel.opts.object_name,

django/contrib/contenttypes/fields.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,32 @@ class GenericForeignKey(object):
2121
Provides a generic relation to any object through content-type/object-id
2222
fields.
2323
"""
24+
# Field flags
25+
auto_created = False
26+
concrete = False
27+
editable = False
28+
hidden = False
29+
30+
is_relation = True
31+
many_to_many = False
32+
many_to_one = False
33+
one_to_many = True
34+
one_to_one = False
35+
related_model = None
2436

2537
def __init__(self, ct_field="content_type", fk_field="object_id", for_concrete_model=True):
2638
self.ct_field = ct_field
2739
self.fk_field = fk_field
2840
self.for_concrete_model = for_concrete_model
2941
self.editable = False
3042
self.rel = None
43+
self.column = None
3144

3245
def contribute_to_class(self, cls, name, **kwargs):
3346
self.name = name
3447
self.model = cls
3548
self.cache_attr = "_%s_cache" % name
36-
cls._meta.add_virtual_field(self)
49+
cls._meta.add_field(self, virtual=True)
3750

3851
# Only run pre-initialization field assignment on non-abstract models
3952
if not cls._meta.abstract:
@@ -243,6 +256,13 @@ def __set__(self, instance, value):
243256

244257
class GenericRelation(ForeignObject):
245258
"""Provides an accessor to generic related objects (e.g. comments)"""
259+
# Field flags
260+
auto_created = False
261+
262+
many_to_many = False
263+
many_to_one = True
264+
one_to_many = False
265+
one_to_one = False
246266

247267
def __init__(self, to, **kwargs):
248268
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
@@ -303,8 +323,7 @@ def _check_generic_foreign_key_existence(self):
303323

304324
def resolve_related_fields(self):
305325
self.to_fields = [self.model._meta.pk.name]
306-
return [(self.rel.to._meta.get_field_by_name(self.object_id_field_name)[0],
307-
self.model._meta.pk)]
326+
return [(self.rel.to._meta.get_field(self.object_id_field_name), self.model._meta.pk)]
308327

309328
def get_path_info(self):
310329
opts = self.rel.to._meta
@@ -345,7 +364,7 @@ def get_content_type(self):
345364
for_concrete_model=self.for_concrete_model)
346365

347366
def get_extra_restriction(self, where_class, alias, remote_alias):
348-
field = self.rel.to._meta.get_field_by_name(self.content_type_field_name)[0]
367+
field = self.rel.to._meta.get_field(self.content_type_field_name)
349368
contenttype_pk = self.get_content_type().pk
350369
cond = where_class()
351370
lookup = field.get_lookup('exact')(Col(remote_alias, field, field), contenttype_pk)

django/contrib/gis/db/models/query.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,7 @@ def _geocol_select(self, geo_field, field_name):
758758
elif geo_field not in opts.local_fields:
759759
# This geographic field is inherited from another model, so we have to
760760
# use the db table for the _parent_ model instead.
761-
tmp_fld, parent_model, direct, m2m = opts.get_field_by_name(geo_field.name)
761+
parent_model = geo_field.model._meta.concrete_model
762762
return self.query.get_compiler(self.db)._field_column(geo_field, parent_model._meta.db_table)
763763
else:
764764
return self.query.get_compiler(self.db)._field_column(geo_field)

django/contrib/gis/db/models/sql/compiler.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,10 @@ def get_default_columns(self, with_aliases=False, col_aliases=None,
118118
seen = self.query.included_inherited_models.copy()
119119
if start_alias:
120120
seen[None] = start_alias
121-
for field, model in opts.get_concrete_fields_with_model():
121+
for field in opts.concrete_fields:
122+
model = field.model._meta.concrete_model
123+
if model is opts.model:
124+
model = None
122125
if from_parent and model is not None and issubclass(from_parent, model):
123126
# Avoid loading data for already loaded parents.
124127
continue

0 commit comments

Comments
 (0)