Skip to main content
iVentureTeam

slide_category_one2many

The content tab of an eLearning course is one relation pretending to be two: sections and lessons. This widget is the renderer that keeps them apart.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 27, 2026Updated August 27, 20266 min read
Technical nameslide_category_one2many
Field typesone2many, many2many
Viewsform, with an embedded list and an optional kanban
Modulewebsite_slides, the eLearning app
Used in core1 occurrence, the Content tab of the course form in website_slides
VersionsOdoo 20.0, Odoo 19.0, Odoo 18.0, Odoo 17.0, Odoo 16.0
No-code setupNo. It is a view XML widget and depends on a discriminating boolean on the child model
Alternativesone2many, question_page_one2many, section_one2many, handle

What the course content list does

An eLearning course in Odoo is a single ordered list of content rows, where some rows are section headings and the rest are the actual lessons, videos, quizzes and documents. Storing both in one relation keeps ordering simple; showing both in one plain list makes the course structure invisible.

This widget fixes the display half. It swaps the embedded list's renderer for one that knows which rows are sections, renders those bold and lets their name stretch across the columns the lessons use. The drag handle column stays beside the title so sections can still be reordered.

It also splits how rows are created. The Add Section button carries a context flag, and the renderer only turns on inline editing when it sees that flag. So a section is typed straight into the list, while Add Content opens the lesson form, which is the right shape because a lesson has a dozen fields and a section has one.

What this means for your team

Course structure is what makes an online course finishable. Learners abandon a flat list of thirty videos and complete the same thirty grouped into six modules, and the person building the course needs that structure to be cheap to create and easy to reorder. Typing a section title directly into the list, then dragging lessons under it, is about as cheap as it gets.

The pattern is also the honest answer to a question that comes up in most content projects: should sections be their own model? Odoo's answer here is no, keep one ordered relation and mark the structural rows, because that keeps drag-and-drop ordering across the whole course working with no special cases. The cost is that the display has to do the work, which is what this widget is.

Supported options in Odoo 19

The widget declares no supported options, and neither does the x2many descriptor it spreads: the options dictionary is forwarded to the embedded list as CRUD settings. The controls that matter are attributes and companion fields, listed below and verified against the Odoo 19.0 source and the core course form.

OptionTypeWhat it does
is_categoryfield on the child modelHardcoded discriminator. Rows where it is true render as sections. On Odoo 19 the embedded list must load it; on the development branch the widget injects it.
namefield on the child modelHardcoded title column. On a section row it is given a colspan covering every non-handle column.
default_is_categorycontext key on the create controlSet on a <create> control's context. When true, and the screen is not small, the new row is added inline; otherwise the row opens in a dialog.
editablestring (list attribute)The widget defaults the embedded list to editable at the bottom. Only section rows are actually inline editable.(default: bottom)
modestring (field attribute)Which subviews are available. Core uses list,kanban so course content can also be browsed as cards.

The section field name is hardcoded. The renderer uses is_category as its discriminator and name as the title column, both written literally in the source. In Odoo 19 the embedded list must load is_category itself, which the core view does with a column-invisible field; the development branch injects it as a related field instead.

Working examples

The core usage, trimmed

<field name="slide_ids" widget="slide_category_one2many"
       mode="list,kanban">
  <list decoration-bf="is_category" editable="bottom">
    <field name="sequence" widget="handle"/>
    <field name="name"/>
    <field name="is_category" column_invisible="True"/>
  </list>
</field>

The column-invisible field is what makes the section detection work on Odoo 19.

The two create controls

<control>
  <create name="add_slide_section" string="Add Section"
          context="{'default_is_category': True}"/>
  <create name="add_slide_lesson" string="Add Content"/>
</control>

The context key on the first control is the entire difference between inline editing and a dialog.

Hiding lesson columns on section rows

<field name="completion_time" widget="float_time"
       invisible="slide_category == 'category'"/>

The renderer collapses section rows, but core also hides lesson-only values individually so nothing stray shows through.

Why a section row is only a handle and a title

The widget itself is short: swap the list renderer, default the embedded list to editable at the bottom, and allow rows to be opened. Everything characteristic lives in the renderer.

Its section handling is deliberately narrow. getSectionColumns keeps only the handle columns and then appends the title column with a colspan covering everything else, so a section row is exactly a drag handle and a stretched name. That is simpler than the survey equivalent, which preserves an extra count column, and it is why an eLearning section row shows nothing but its title.

Inline editing is restricted twice over. The renderer's create override only enables it when the create control's context sets the section default and the screen is not small, and a separate check declares that only section rows are inline editable at all. So a lesson row can never be typed into directly; clicking it opens the lesson form.

There is one keyboard behavior specific to this renderer. Enter, Tab and Shift+Tab in edit mode leave edit mode rather than moving to the next cell or the next row. On an ordinary editable list Enter creates another row, which for section titles would produce a stream of empty sections; here it closes the editor after the title is typed, which is what you want.

On Odoo 19 the renderer reads the section flag straight from the row's loaded data, so the field has to be in the subview. The development branch adds it as an injected related field on the descriptor, which removes that requirement and is the only functional change in the widget for Odoo 20.

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentNot released. The development branch injects the section flag as a related field, removing the subview requirement.
Odoo 19.0VerifiedVerified against the shipped source. The embedded list must load the section flag itself.
Odoo 18.0VerifiedIdentical behavior.
Odoo 17.0VerifiedIdentical behavior. This is where the registration became a descriptor.
Odoo 16.0VerifiedSame renderer and behavior, but the component class was registered directly rather than a descriptor.

Upgrade note. No XML change is needed between Odoo 16 and Odoo 19; the renderer and the behavior are the same throughout, and only the registration style changed, from registering the component class in Odoo 16 to a descriptor from Odoo 17 onwards. Custom JavaScript that imported the class on Odoo 16 needs adjusting; views do not.

What is changing in Odoo 20

Odoo 20 is expected at Odoo Experience in Brussels, 24 to 26 September 2026. The development branch is unstable and this may still change; we re-verify the page after the release.

One functional improvement. The descriptor gains an injected related field for the section flag, declared writable, so the widget no longer depends on the embedded list loading is_category. A custom subview that forgot that field on Odoo 19 starts working on Odoo 20.

The rest of the diff is the migration to the new Owl props syntax, which the widget performs by inlining the x2many props because the base is not yet converted, with a source comment saying so. That affects patches, not views.

Common problems and fixes

SymptomCause and fix
Section rows look like ordinary rowsThe embedded list does not load the section flag, which the renderer reads literally. Add the field with column_invisible="True", as the core view does.
Add Section opens a dialogThe create control's context does not set the section default, or the screen is treated as small. Add context="{'default_is_category': True}" to that control. On a phone the dialog is intentional.
A lesson row will not edit inlineIntended. Only section rows are inline editable in this renderer. Click the row to open the lesson form.
Pressing Enter does not create another rowIntended. This renderer leaves edit mode on Enter, Tab and Shift+Tab. Use Add Section again for the next section.
Lesson columns show values on section rowsThe renderer collapses the row, but a column left visible can still print. Add an invisible expression on lesson-only columns, as the core view does.
Reordering does not stickThe sequence field is missing or not rendered with the handle widget. Include the sequence field with widget="handle" in the embedded list.

Course content list vs the alternatives

WidgetBest forKey difference
slide_category_one2manyCourse content where one ordered relation carries sections and lessons togetherSection rows collapse to a drag handle plus a stretched title, and only sections edit inline
one2manyAn ordinary list of child recordsNo section rendering and no split between inline and dialog creation
question_page_one2manyThe same pattern in the Survey appKeeps an extra column on section rows and forces a save of the parent on every child edit
section_one2manyGeneric sectioned relationsA different renderer with its own field conventions
handleReordering rowsA single column widget rather than a list renderer

A plain one2many is right when every row is the same kind of thing. Reach for a sectioned renderer only when a single ordered relation genuinely mixes structure and content. Odoo has three of these renderers with slightly different rules, and this one is the most minimal: sections collapse to a title, and nothing else survives on the row.

Frequently asked questions

Why can I type a section title inline but not a lesson?+
The renderer enables inline editing only when the create control's context sets the section default, and separately declares that only section rows are inline editable. Lessons have far more fields, so they open in a dialog.
Why do my section rows look ordinary?+
The renderer reads a field named is_category directly from the row's data. On Odoo 19 the embedded list has to load it, which the core view does with a column-invisible field.
Does it support any options?+
None of its own. It spreads the x2many descriptor, which declares no options and forwards the whole dictionary to the embedded list as CRUD settings.
Why does Enter not add another row?+
This renderer overrides the keyboard handling so Enter, Tab and Shift+Tab leave edit mode. On a section list, the standard behavior would create a run of empty sections.
What changes in Odoo 20?+
The development branch injects the section flag as a related field, so a subview that omits it still works. Everything else is the Owl props migration.

Building courses your learners actually finish?

Course structure, progress tracking and the admin screens behind them decide whether eLearning gets used after week one. We build and extend Odoo eLearning, including custom content models and the views that keep them usable.

Book a free consultation

How this page was produced

The renderer swap, the section column collapse, the inline-editing restrictions and the keyboard handling were read from slide_category_one2many_field.js and slide_category_list_renderer.js on the Odoo 19.0 branch, with the usage taken from website_slides/views/slide_channel_views.xml. The version table comes from the same files on 16.0, 17.0 and 18.0, and the Odoo 20 notes from the public development branch. Spotted an error? Tell us and we will correct the page.