Commit e8171daf authored by Collin Anderson's avatar Collin Anderson Committed by Tim Graham
Browse files

Fixed #24146 -- Fixed a missing fields regression in admin checks.

This allows using get_field() early in the app loading process.

Thanks to PirosB3 and Tim Graham.
parent 8e8daf7c
Loading
Loading
Loading
Loading
+20 −8
Original line number Diff line number Diff line
@@ -487,6 +487,12 @@ class Options(object):

    @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:
@@ -531,20 +537,26 @@ class Options(object):

            return field
        except KeyError:
            # If the app registry is not ready, reverse fields are
            # unavailable, therefore we throw a FieldDoesNotExist exception.
            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]
+11 −5
Original line number Diff line number Diff line
@@ -169,24 +169,30 @@ class GetFieldByNameTests(OptionsBaseTests):
        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):