Skip to content

Commit

Permalink
Fixed #24146 -- Fixed a missing fields regression in admin checks.
Browse files Browse the repository at this point in the history
This allows using get_field() early in the app loading process.

Thanks to PirosB3 and Tim Graham.
  • Loading branch information
collinanderson authored and timgraham committed Jan 16, 2015
1 parent 8e8daf7 commit e8171da
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 13 deletions.
28 changes: 20 additions & 8 deletions django/db/models/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,12 @@ def _forward_fields_map(self):

@cached_property
def fields_map(self):
return self._get_fields_map()

def _get_fields_map(self):
# Helper method to provide a way to access this without caching it.
# For example, admin checks run before the app cache is ready and we
# need to be able to lookup fields before we cache the final result.
res = {}
fields = self._get_fields(forward=False, include_hidden=True)
for field in fields:
Expand Down Expand Up @@ -531,20 +537,26 @@ def get_field(self, field_name, many_to_many=None):

return field
except KeyError:
# If the app registry is not ready, reverse fields are
# unavailable, therefore we throw a FieldDoesNotExist exception.
if not self.apps.ready:
pass

if m2m_in_kwargs:
# Previous API does not allow searching reverse fields.
raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, field_name))

# If the app registry is not ready, reverse fields are probably
# unavailable, but try anyway.
if not self.apps.ready:
try:
# Don't cache results
return self._get_fields_map()[field_name]
except KeyError:
raise FieldDoesNotExist(
"%s has no field named %r. The app cache isn't ready yet, "
"so if this is an auto-created related field, it won't "
"so if this is an auto-created related field, it might not "
"be available yet." % (self.object_name, field_name)
)

try:
if m2m_in_kwargs:
# Previous API does not allow searching reverse fields.
raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, field_name))

# Retrieve field instance by name from cached or just-computed
# field map.
return self.fields_map[field_name]
Expand Down
16 changes: 11 additions & 5 deletions tests/model_meta/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,24 +169,30 @@ def test_get_generic_relation(self):
self.assertEqual(field_info[1:], (None, True, False))
self.assertIsInstance(field_info[0], GenericRelation)

def test_get_fields_only_searches_forward_on_apps_not_ready(self):
def test_get_fields_when_apps_not_ready(self):
opts = Person._meta
# If apps registry is not ready, get_field() searches over only
# forward fields.
opts.apps.ready = False
# Clear cached data.
opts.__dict__.pop('fields_map', None)
try:
# 'data_abstract' is a forward field, and therefore will be found
self.assertTrue(opts.get_field('data_abstract'))
msg = (
"Person has no field named 'relating_baseperson'. The app "
"Person has no field named 'some_missing_field'. The app "
"cache isn't ready yet, so if this is an auto-created related "
"field, it won't be available yet."
"field, it might not be available yet."
)
# 'data_abstract' is a reverse field, and will raise an exception
with self.assertRaisesMessage(FieldDoesNotExist, msg):
opts.get_field('relating_baseperson')
opts.get_field('some_missing_field')
# Be sure it's not cached
self.assertNotIn('fields_map', opts.__dict__)
finally:
opts.apps.ready = True
# At this point searching a related field would cache fields_map
opts.get_field('relating_baseperson')
self.assertIn('fields_map', opts.__dict__)


class RelationTreeTests(TestCase):
Expand Down

0 comments on commit e8171da

Please sign in to comment.