# Buggy code
def _follow_field_source(model, path: List[str]):
    """
    navigate through root model via given navigation path. supports forward/reverse relations.
    """
    field_or_property = getattr(model, path[0], None)

    if len(path) == 1:
        # end of traversal
        if isinstance(field_or_property, property):
            return field_or_property.fget
        elif isinstance(field_or_property, cached_property):
            return field_or_property.func
        elif callable(field_or_property):
            return field_or_property
        else:
            field = model._meta.get_field(path[0])
            if isinstance(field, ForeignObjectRel):
                # resolve DRF internal object to PK field as approximation
                return field.get_related_field()  # type: ignore
            else:
                return field
    else:
        if isinstance(field_or_property, property) or callable(field_or_property):
            if isinstance(field_or_property, property):
                target_model = typing.get_type_hints(field_or_property.fget).get(
                    "return"
                )
            else:
                target_model = typing.get_type_hints(field_or_property).get("return")
            if not target_model:
                raise UnableToProceedError(
                    f'could not follow field source through intermediate property "{path[0]}" '
                    f"on model {model}. please add a type hint on the model's property/function "
                    f'to enable traversal of the source path "{".".join(path)}".'
                )
            return _follow_field_source(target_model, path[1:])
        else:
            target_model = model._meta.get_field(path[0]).related_model
            return _follow_field_source(target_model, path[1:])


# Traceback (AssertionError)
Warning #1: could not resolve field on model <class 'test_spectacular.models.TestModel'> with path "reltd_set". this is likely a custom field that does some unknown magic. maybe consider annotating the field/property? defaulting to "string". (Exception: TestModel has no field named 'reltd_set')
Traceback (most recent call last):
File "./manage.py", line 22, in <module>
main()
File "./manage.py", line 18, in main
execute_from_command_line(sys.argv)
File "test_spectacular/venv/lib/python3.8/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
utility.execute()
File "test_spectacular/venv/lib/python3.8/site-packages/django/core/management/__init__.py", line 395, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "test_spectacular/venv/lib/python3.8/site-packages/django/core/management/base.py", line 330, in run_from_argv
self.execute(*args, **cmd_options)
File "test_spectacular/venv/lib/python3.8/site-packages/django/core/management/base.py", line 371, in execute
output = self.handle(*args, **options)
File "test_spectacular/venv/lib/python3.8/site-packages/drf_spectacular/management/commands/spectacular.py", line 50, in handle
schema = generator.get_schema(request=None, public=True)
File "test_spectacular/venv/lib/python3.8/site-packages/drf_spectacular/generators.py", line 219, in get_schema
paths=self.parse(request, public),
File "test_spectacular/venv/lib/python3.8/site-packages/drf_spectacular/generators.py", line 196, in parse
operation = view.schema.get_operation(path, path_regex, method, self.registry)
File "test_spectacular/venv/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 83, in get_operation
operation['responses'] = self._get_response_bodies()
File "test_spectacular/venv/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 969, in _get_response_bodies
return {'200': self._get_response_for_code(response_serializers, '200')}
File "test_spectacular/venv/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 1007, in _get_response_for_code
component = self.resolve_serializer(serializer, 'response')
File "test_spectacular/venv/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 1146, in resolve_serializer
component.schema = self._map_serializer(serializer, direction)
File "test_spectacular/venv/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 669, in _map_serializer
schema = self._map_basic_serializer(serializer, direction)
File "test_spectacular/venv/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 736, in _map_basic_serializer
schema = self._map_serializer_field(field, direction)
File "test_spectacular/venv/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 488, in _map_serializer_field
schema = self._map_serializer_field(field.child_relation, direction)
File "test_spectacular/venv/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 516, in _map_serializer_field
schema = self._map_model_field(model_field, direction)
File "test_spectacular/venv/lib/python3.8/site-packages/drf_spectacular/openapi.py", line 387, in _map_model_field
assert isinstance(model_field, models.Field)
AssertionError

# Fixed code
def _follow_field_source(model, path: List[str]):
    """
    navigate through root model via given navigation path. supports forward/reverse relations.
    """
    field_or_property = getattr(model, path[0], None)

    if len(path) == 1:
        # end of traversal
        if isinstance(field_or_property, property):
            return field_or_property.fget
        elif isinstance(field_or_property, cached_property):
            return field_or_property.func
        elif callable(field_or_property):
            return field_or_property
        elif isinstance(field_or_property, ManyToManyDescriptor):
            if field_or_property.reverse:
                return field_or_property.rel.target_field  # m2m reverse
            else:
                return field_or_property.field.target_field  # m2m forward
        elif isinstance(field_or_property, ReverseOneToOneDescriptor):
            return field_or_property.related.target_field  # o2o reverse
        elif isinstance(field_or_property, ReverseManyToOneDescriptor):
            return field_or_property.rel.target_field  # type: ignore # foreign reverse
        elif isinstance(field_or_property, ForwardManyToOneDescriptor):
            return field_or_property.field.target_field  # type: ignore # o2o & foreign forward
        else:
            field = model._meta.get_field(path[0])
            if isinstance(field, ForeignObjectRel):
                # case only occurs when relations are traversed in reverse and
                # not via the related_name (default: X_set) but the model name.
                return field.target_field
            else:
                return field
    else:
        if isinstance(field_or_property, property) or callable(field_or_property):
            if isinstance(field_or_property, property):
                target_model = typing.get_type_hints(field_or_property.fget).get(
                    "return"
                )
            else:
                target_model = typing.get_type_hints(field_or_property).get("return")
            if not target_model:
                raise UnableToProceedError(
                    f'could not follow field source through intermediate property "{path[0]}" '
                    f"on model {model}. please add a type hint on the model's property/function "
                    f'to enable traversal of the source path "{".".join(path)}".'
                )
            return _follow_field_source(target_model, path[1:])
        else:
            target_model = model._meta.get_field(path[0]).related_model
            return _follow_field_source(target_model, path[1:])