Set toc_object_entries=True to auto add functions/classes to the page's table of contents?

In Django, toc_object_entries is set to False to disable classes/functions/attributes being added to the page’s table of contents by default (see PR).
Note that toc_object_entries was a feature added to Sphinx 5.2

This is disabled to avoid duplication in the table of contents in pages such as the SchemaEditor page where headers have been added above each class/method to have them added to the table of contents.

Note, this is what the table of contents look like here when we update toc_object_entries to True (the default) without further code changes:

However, Django is not currently consistent with adding headers for each class/method/function/attribute. For example, see the Paginator docs

In general, the docs are inconsistent with some pages having headers and some not.


I think we should have the classes/functions/methods etc appear in the table of contents consistently for two reasons:

  • it improves the on page navigation experience
  • the table of contents is used in djangoproject.com search ranking and so searches with matches in the table of contents are ranked higher

If we agree, there are two options:

  1. add the missing headers to any docs/ref page and add the practice of having the header be defined above the method/class etc into our documentation contributing guide
  2. remove the existing headers that duplicate the function/method/class name and set toc_object_entries to True

My preference is 2 as it is less code and one less thing a contributor/reviewer needs to remember to do.
If we opt for 1, this might be due to having more control over the header text. If this is the case, we should document the format preference of these headers in the documentation contributing guide.

What do we think?

I seem to (half?) recall @smithdc1 looking at this a while ago…

If we opt for 1, this might be due to having more control over the header text.

I suspect that’s probably the most of it. (But not certain)

I’d definitely ask @timgraham’s thoughts for context/history/insight.

If we set toc_object_entries = True, is there a way to prevent the class name from being prefixed to each attribute/method in the toc. The BaseDatabaseSchemaEditor screenshot shows how unwieldy that can appear. I thought the class name would be omitted if the directives omitted them (e.g. .. method:: create_model(model) [which requires indenting much of the page… and then regular headings in indented text (like “Methods”) don’t appear in the toc] instead of .. method:: BaseDatabaseSchemaEditor/create_model(model) but as far as I tested, that’s not the case.

I agree that the need to add a redundant header isn’t ideal. On the negative side, I have a feeling this would be a large patch that will either be difficult to backport, or if not backported, will make future backports painful. But now could be the time while there isn’t too much new in 6.0 and 5.1 docs will soon be abandoned for most backports.

This audit seems likely to reveal other inconsistencies, like the fact that Paginator has some attributes documented under Page.__init__() (when they corresponding to the parameters) and others under the “Attributes” heading.

Looks like we can set toc_object_entries_show_parents = "hide" to achieve this.

If I remove the headers in the schema editor page, it then looks like:

[which requires indenting much of the page… and then regular headings in indented text (like “Methods”) don’t appear in the toc]

The indenting is a good point. In the SchemaEditor case, the indenting looks alright because as I kept the “Methods” and “Attributes” headings (which then indents the methods/attributes).

On pages where the methods/attributes for classes are indented, the result is how you would expect.

I have a feeling this would be a large patch that will either be difficult to backport, or if not backported, will make future backports painful. But now could be the time while there isn’t too much new in 6.0 and 5.1 docs will soon be abandoned for most backports.

(thank you, these are all good points)

Now that I’ve seen the “Methods”/“Attributes” headers, I feel this could be a way for us to avoid adding indents in the docs but still have reasonable indents in the table of contents for docs pages where the methods/attributes are not already indented. This wouldn’t achieve complete consistency, but it would be a smaller diff, and possibly less disruptive for backports.

Been reflecting and I think the @accessibility team should consult here

Folks who use assistive technologies/screen-readers would have to navigate the page differently if we remove headers (they would navigate via lists/list items instead <dl>, <dt>…).

I also think the indents impact where the information is structured in the HTML and we should perhaps get guidance on this.

Hence, I would love the accessibility team to review the options available as to how we can structure the reference docs and advise us on the best result in terms of a good table of contents and a good screen reader experience.

We can then add that to the contributing guide and work towards it. :+1:

1 Like

Note that when thinking about this some more, I had forgotten that of course the table of contents will be available for navigation.
I think having this be accessible is a mixture of djangoproject.com page design (see Use <nav> in the docs page for table of contents, breadcrumbs (prev/next) etc · Issue #1956 · django/djangoproject.com · GitHub) and feedback to Sphinx itself.

In which case, I feel we could procede here removing the titles and updating our default settings.

I’ve set up a few one-off previews of the docs with and without toc_object_entries, and with a manual change to headings, to make it easier to compare the options:

main docs main RTD toc_object_entries Add headings
SchemaEditor SchemaEditor SchemaEditor (skipped)
Paginator Paginator Paginator (skipped)
File storage API File storage API File storage API (skipped)
Models Models Models Models

Reviewing those examples, it seems pretty clear toc_object_entries is a win, with the caveats already mentioned that sometimes there’s been headings manually added, and the auto-generated TOC entry label is very verbose. However, for screen reader users it’s much better if there are actual headings in the content.

With actual headings, they’d be able to:

  • Use screen reader support to navigate the page by headings, which is the most common form of navigation as reported by screen reader users.
  • More consistently know when a given sub-section starts and how deep they are.

I’m fuzzy on the details but my understanding is most/all of Sphinx’s directives that rely on indentation will output description lists markup (dl with dt and dd), with the description term enhanced to act as a heading (heading styles, anchor link, and now TOC entries). While some usage of description lists is fine, I think it becomes problematic when they get used for overall page sections. They behave like headings for sighted users but come up short for screen reader users.

The issue is pretty clear for example in the Sphinx docs, in The Python Domain. About two thirds of that page’s content have no actual heading elements. For screen reader users the page is reported as having 5 headings, despite there being 67 entries in the table of contents. Those two numbers should probably match in an ideal world.

What to do

My recommendation for Django docs (and any other Sphinx project using those directives):

  1. See if it’s possible for those directives to create heading levels. If so, check whether it’d be best to do that and description lists markup, or just headings.
  2. Assuming that’s not possible / in the meantime, switch to manually creating headings instead with vanilla reStructuredText.
  3. If that’s not possible either / in the meantime, toc_object_entries. It’s a good improvement but doesn’t solve the screen reader heading navigation problem.

Would love to do that whenever there’s a chance. I’ve not had much luck finding accessibility considerations / discussions in their project before, but can also be due to my lack of familiarity with it. I’m never too sure as far as markup what comes from docutils vs. Sphinx.

Currently taking a look at the related ticket and have some observations. I’m not sure what specific feedback the accessibility team gave, so if anything below conflicts with them would be keen to hear it.

Overall, adding duplicate headings for each class, function, etc directive is having some implications in practice:

  1. For anyone screenreading a page top to bottom it adds duplicate elements to move through
  2. Visually every element also appears twice. On already long pages that equates to noise and extra scrolling
  3. This approach bleeds through to the PDF documentation as well
  4. Indented class definitions aren’t playing well with headings. Indent the headings and they don’t show in the TOC. Leave them left aligned and the content doesn’t format well

Taking all this as a sign we might be swimming against the current, I’d suggest a different approach.

Rather than add additional headings in the rst, the suggestion would be to switch on toc_object_entries and post-process the html output to include the h? etc tags we want for accessible navigation.

Will proceed with this approach to see if it is workable. Though if this would never fly from a tech debt POV - adding a docs dependency like beautifulsoup and a custom python script - please let me know.

Tried a few different options, and the approach below seems to work reasonably well:

  1. Set toc_object_entries = True
  2. Remove majority of duplicated manual headings
  3. Hook into Sphinx to set aria heading attributes for class names, etc

It does make page TOCs generally more verbose, and possibly include entries doc authors didn’t anticipate.

Removing the manual headings does change the visual layout too, though there’s the argument that when everything has a heading nothing does (visually at least).

PR 21858 created to illustrate a possible implementation with a couple of example pages for comparison.

@medmunds would welcome your thoughts.

2 Likes

@smallsaucepan’s PR seems to neatly and automatically address @thibaudcolas’s concerns about toc_object_entries not having corresponding headings in the main text. It applies role="heading" with the appropriate aria-level to a span in the Sphinx-generated <dt> that serves (visually) as the object’s heading.

I would make one change to resolve the duplicate heading problem: rather than remove existing headings that would duplicate objects now in the ToC, I’d add the :no-contents-entry: option on the objects to keep them out of the ToC:

``SomeClass`` class
===================

.. class:: SomeClass
    :no-contents-entry:

Provides some methods. And attributes.

Methods
-------

``do_something()``
~~~~~~~~~~~~~~~~~~

.. method:: SomeClass.do_something(self, log=False)
    :no-contents-entry:

Does something. Tells you about it if ``log`` is ``True``.

Those manually authored headings (and non-indented content) render as semantically clean html that should work well with screen readers (nested <section>s each with a heading tag and content). Removing them would feel like a step backward. It could also break external links into Django’s docs, as each heading provides an exposed ¶ anchor.

Contrast that with docs that use indented domain directives and content:

.. class:: SomeClass

    Provides some methods. And attributes.

    .. method: do_something(self, log=False)
        :param log: Tells you about it if ``True``.

        Does something.

Sphinx renders that as nested <dl>s, with a <dt> for each declaration and indented content in the <dd>—but currently no ToC entries or navigable headings. Enabling toc_object_entries and @smallsaucepan’s PR address those shortcomings. (The larger issue, that definition lists and screen readers don’t really get along, is something Sphinx would have to solve.)

Django’s docs have a mixture of the two approaches (plus some things in between). I don’t think we should try to standardize one or the other. But improving the results for the non-manual-heading cases seems like a worthy and achievable goal.

1 Like