This series has covered extending detail, list, and card views through the same declare-don’t-subclass pattern: define an *Extension class, point it to the target view’s dotted path, and let Horilla CRM‘s registry and composer build the extended class automatically.

Kanban views follow the same approach through KanbanExtension and _inherit_kanban. There is one important difference, though: kanban views are not only resolved when the page is rendered. They are also looked up when a user drags a card from one column to another.

Understanding that second lookup is important when extending kanban views.

The core idea

A kanban extension can be defined like this:

# horilla_crm/compliance/kanban_extensions.py

from horilla.extension.kanban import KanbanExtension


class LeadKanbanComplianceExtension(KanbanExtension):
    """Adds compliance information to the Lead kanban board."""


    _inherit_kanban = "horilla_crm.leads.views.LeadKanbanView"


    columns_append = [("Compliance", "compliance_status")]
    exclude_kanban_fields_append = ["internal_notes"]


    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["flagged_count"] = self.get_queryset().filter(
            is_compliance_flagged=True
        ).count()
        return context

Setting _inherit_kanban is enough to identify the target view. When the extension class is created, KanbanExtension.__init_subclass__ captures the extension specification and, when the target is available, recomposes the view.

There is no need to import LeadKanbanView, modify its source code, change urls.py, or call a separate registration function.

What can be extended?

register_kanban_extension_class() separates the extension into three main categories.

Layout attributes

These include:

  • columns_insert
  • columns_append
  • bulk_update_fields_append
  • export_exclude_append
  • exclude_columns_append
  • actions_append
  • custom_bulk_actions_append
  • additional_action_button_append
  • exclude_quick_filter_fields_append
  • exclude_columns_from_sorting_append
  • exclude_kanban_fields_append

The last attribute is specific to kanban views. It controls fields that should not appear on the kanban card itself.

Method overrides

Normal methods can be overridden in the extension class:

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    # Custom logic
    return context

The composer places the extension mixin before the original view in the method resolution order, so super() continues through the extension chain and eventually reaches the original implementation.

Scalar overrides

Kanban views support the common list-view configuration attributes, along with three kanban-specific ones:

  • group_by_field
  • kanban_order_by
  • height_kanban

This is possible because HorillaKanbanView is built on top of HorillaListView. It therefore reuses much of the existing queryset, filtering, column, and bulk-action functionality while adding kanban-specific grouping and drag-and-drop behavior.

How the kanban class is composed

compose_kanban_view_class() follows the same general pattern used by the other view composers.

First, each registered extension becomes a dynamically created mixin containing its methods and non-layout attributes.

The composer then merges the layout attributes with the target view. For example, columns_insert can place a column after a specific field, while columns_append adds columns to the end.

The other append-style attributes are merged with their corresponding target attributes:

bulk_update_fields
export_exclude
exclude_columns
actions
custom_bulk_actions
additional_action_button
exclude_quick_filter_fields
exclude_columns_from_sorting

Kanban fields are handled separately through merge_exclude_kanban_fields(). This is intentional because exclude_kanban_fields controls fields displayed on the kanban card, whereas exclude_columns is part of the generic view layout system.

Finally, the composer creates a new class using type():

ComposedName(Mixin1, Mixin2, Target)

This gives extension methods precedence over the original view while preserving normal super() behavior.

The composed class is then marked with __horilla_kanban_composed__ = True and stored in KANBAN_COMPOSED_MAP.

Why the view registry matters

The registry is especially important for kanban views because it is used in two different parts of the request flow.

The normal page request resolves the composed kanban class so that the board itself uses the extension.

But drag-and-drop has another lookup path.

When a user moves a card to another kanban column, the frontend sends information such as the item ID, new column, application label, model name, and class name to the move handler.

The handler resolves the view class from the kanban view registry:

view_class = HorillaKanbanView._view_registry.get(self.model)

This is why registering the composed class is important.

During composition, compose_kanban_view_class() calls _register_kanban_view_registry() with the composed class and its target. The registry therefore points to the extended class rather than the original view.

For example, suppose an extension overrides get_queryset():

def get_queryset(self):
    queryset = super().get_queryset()
    return queryset.filter(is_active=True)

The normal kanban page will use that override because it resolves the composed class.

The drag-and-drop handler can also use that same composed class because the registry points to it.

Without that registry update, the drag-and-drop path could resolve the original LeadKanbanView instead. The page would appear correctly extended, while the drag-and-drop operation could silently execute against the unextended view.

That is one of the most important reasons to use KanbanExtension instead of directly patching the target view.

Why urls.py does not need to change

Kanban extensions do not require new URLs.

HorillaKanbanView inherits as_view() from HorillaListView. During request handling, the view type is detected and resolved appropriately:

if issubclass(cls, HorillaKanbanView):
    resolved = resolve_kanban_view_class(cls)
    ...
elif issubclass(cls, HorillaCardView):
    resolved = resolve_card_view_class(cls)
    ...
else:
    resolved = resolve_list_view_class(cls)

The resolved class is then used to handle the request.

The extension system also tracks the current extension registry state. When the registry changes, the cached resolution is invalidated, allowing a newly loaded extension to take effect on the next request.

This means an extension app can be loaded after the CRM app’s URLs have already been registered without requiring changes to the existing URL configuration.

Kanban-specific scalar overrides

Three attributes are particularly useful when an extension needs to change the behavior or appearance of the board itself.

group_by_field

Controls the field used to determine which column a record belongs to.

For example, an extension could change a board from grouping by the default status field to a custom pipeline_stage field.

kanban_order_by

Controls how cards are ordered inside each kanban column.

An extension can use this when the default card ordering does not match the requirements of a particular CRM workflow.

height_kanban

Controls the rendered height of the kanban board.

This is useful when an extension needs to provide more or less vertical space for the board.

Multiple extensions and priority

More than one application can extend the same kanban view.

KanbanExtension supports _inherit_kanban_priority, which defaults to 0.

When multiple extensions target the same view, their priorities determine their ordering in the composed class’s MRO. This makes method and scalar conflicts deterministic rather than dependent on import order.

For example:

class FirstExtension(KanbanExtension):
    _inherit_kanban = "horilla_crm.leads.views.LeadKanbanView"
    _inherit_kanban_priority = 10

A second extension can use a different priority to control where it appears in the extension chain.

This is particularly useful when multiple extension apps customize the same CRM board.

Extending a kanban board follows the same declare-don’t-subclass pattern used throughout Horilla’s extension system.

Define a KanbanExtension, set _inherit_kanban to the target view’s dotted path, use the available layout attributes for additions, and override methods normally with super().

The important kanban-specific detail is the view registry.

The composed kanban class is used not only when the board is rendered, but also when the drag-and-drop move handler resolves the view by model. Registering the composed class therefore keeps both paths consistent: the page uses the extension, and drag-and-drop uses the same extended behavior.

That registry-based resolution is what allows kanban extensions to remain isolated in a separate application without modifying the original CRM view directly.

Share this article