Repository: wagtail/wagtail
File: wagtail/models/reference_index.py (Python)

Code before:
                ):
                    return field
            raise
        else:
            return field

    @cached_property
    def related_field(self):
        # The field stored on the reference index can be a related field or a
        # reverse related field, depending on whether the reference was extracted
        # directly from a ForeignKey or through a parent ClusterableModel. This
        # property normalises to the related field.
        if isinstance(self.source_field, models.ForeignObjectRel):
            return self.source_field.remote_field
        return self.source_field

    @cached_property
    def reverse_related_field(self):
        # This property normalises to the reverse related field, which is where
        # the on_delete attribute is stored.
        return self.related_field.remote_field

    def get_label(self, field_name, index):
        if index > 2:
            return f" → {capfirst(field_name)}"
        return capfirst(field_name)

    def describe_source_field(self):
        """
        Returns a string describing the field that this reference was extracted from.

        For StreamField, this returns the label of the block that contains the reference.
        For other fields, this returns the verbose name of the field.
        """
        field = self.source_field
        model_path_components = self.model_path.split(".")

        # ManyToOneRel (reverse accessor for ParentalKey) does not have a verbose name. So get the name of the child field instead
        if isinstance(field, models.ManyToOneRel):
            label = ""
            idx = 2
            child_field = field.related_model._meta.get_field(
                model_path_components[idx]
            )
            while isinstance(child_field, models.ManyToOneRel):
                label += self.get_label(
                    child_field.related_model._meta.verbose_name, idx
                )
                idx += 2
                child_field = field.related_model._meta.get_field(
                    model_path_components[idx]

Review comment (refactor):
I don't think it's worth extracting into a method. We could instead collect the model names into a list and do a `.join()`. Also, we'll likely replace the right arrow with something more proper that works for RTL languages too, e.g. an icon, a translatable string, or some other trick.

Code after:
        The field from which the reference was extracted.
        This may be a related field (e.g. ForeignKey), a reverse related field
        (e.g. ManyToOneRel), a StreamField, or any other field that defines
        extract_references().
        """
        field_name = self.model_path_components[0]
        model_class = self._content_type.model_class()
        try:
            field = model_class._meta.get_field(field_name)
        except FieldDoesNotExist:
            # For related fields, get_field() only works with the
            # related_query_name, but we store the reference information using
            # get_accessor_name() which uses the related_name. In the case where
            # the two are different, we use get_fields() to find the field
            # that matches the related_name.
            for field in model_class._meta.get_fields():
                if (
                    isinstance(field, models.ForeignObjectRel)
                    and field.related_name == field_name
                ):
                    return field
            raise
        else:
            return field

    @cached_property
    def related_field(self):
        """
        The final field in which the reference exists.

        The field stored on the reference index can be a related field or a
        reverse related field, depending on whether the reference was extracted
        directly from a ``ForeignKey`` or through a parent ``ClusterableModel``.
        This property normalizes to the related field.
        """
        # Drill through nested inline models to find where the reference exists.
        # Increment idx by 2 each time to skip over the `.item.` component.
        field = self.source_field
        idx = 2
        while isinstance(field, models.ForeignObjectRel):
            related_model_opts = field.related_model._meta
            field = related_model_opts.get_field(self.model_path_components[idx])
            idx += 2
        return field

    @cached_property
    def reverse_related_field(self):
        # This property normalises to the reverse related field, which is where
        # the on_delete attribute is stored.
        return self.related_field.remote_field