DEV Community

Cover image for Building a Scalable Sidebar Architecture in Django Using Decorators, Registries, and Context Processors
Sreethul
Sreethul

Posted on

Building a Scalable Sidebar Architecture in Django Using Decorators, Registries, and Context Processors

When building large Django applications, one problem slowly becomes painful:

sidebar management
Enter fullscreen mode Exit fullscreen mode

Especially in modular systems like:

  • HRMS
  • CRM
  • ERP
  • Admin panels
  • Multi-app dashboards

At first, updating the sidebar feels simple.

But once your project grows to:

  • 10+ apps
  • multiple contributors
  • role-based permissions
  • dynamic navigation
  • HTMX interactions

your sidebar.html quickly becomes a maintenance nightmare.


The Problem I Faced

In my Django CRM project, every new app required updating:

sidebar.html
sub_sidebar.html
Enter fullscreen mode Exit fullscreen mode

Example:

<li>
    <a href="/accounts/">Accounts</a>
</li>

<li>
    <a href="/contacts/">Contacts</a>
</li>
Enter fullscreen mode Exit fullscreen mode

This caused several problems:

  • Every contributor had to modify core sidebar templates
  • Frequent Git merge conflicts
  • Sidebar logic became centralized and difficult to maintain
  • Permission handling became repetitive
  • Adding new apps required touching unrelated files

The bigger the project became, the worse it felt.


The Idea

Instead of manually editing central sidebar templates, I wanted:

Each app should register its own menu items.
Enter fullscreen mode Exit fullscreen mode

So I created a small registry-based menu system using:

  • decorators
  • registries
  • context processors
  • app auto-discovery

Now every app owns its own sidebar configuration.


Final Project Structure

Example:

horilla_crm/
├── accounts/
│   ├── menu.py
│   ├── views.py
│   ├── urls.py
│   └── apps.py
│
├── activity/
│   ├── menu.py
│   ├── views.py
│   ├── urls.py
│   └── apps.py
│
├── project/
│   ├── menus/
│   │   ├── main.py
│   │   ├── sub.py
│   │   └── context_processors.py
Enter fullscreen mode Exit fullscreen mode

Step 1 — Creating Menu Registries

Main Sidebar Registry

# project/menus/main.py

main_section_menu = []

def register(cls):
    main_section_menu.append(cls)
    return cls
Enter fullscreen mode Exit fullscreen mode

Sub Sidebar Registry

# project/menus/sub.py

sub_section_menu = []

def register(cls):
    sub_section_menu.append(cls)
    return cls
Enter fullscreen mode Exit fullscreen mode

Step 2 — Registering Menus Inside Apps

Now every app can define its own menus.

Example:

# accounts/menu.py

from django.utils.translation import gettext_lazy as _
from django.urls import reverse_lazy

MAIN_CONTENT_HX_ATTRS = {
    "hx-boost": "true",
    "hx-target": "#mainContent",
    "hx-select": "#mainContent",
    "hx-swap": "outerHTML",
}
Enter fullscreen mode Exit fullscreen mode

Main Section

@main_section_menu.register
class AccountsSection:
    section = "accounts"
    name = _("Accounts")
    icon = "/assets/icons/accounts.svg"
    position = 1
Enter fullscreen mode Exit fullscreen mode

Sub Section

@sub_section_menu.register
class AccountsSubSection:
    section = "accounts"
    app_label = "accounts"

    verbose_name = _("Accounts")
    icon = "/assets/icons/accounts.svg"

    position = 1

    url = reverse_lazy("accounts:list")
    attrs = MAIN_CONTENT_HX_ATTRS

    perm = [
        "accounts.view_accounts",
        "accounts.view_own_accounts",
    ]
Enter fullscreen mode Exit fullscreen mode

Now the app becomes completely self-contained.


Step 3 — Dynamic Menu Loading

I created an AppLauncher system to auto-import modules like:

  • menu
  • signals
  • dashboard
  • registrations

Example:

class AccountsConfig(AppLauncher):

    auto_import_modules = [
        "registration",
        "signals",
        "menu",
        "dashboard",
    ]
Enter fullscreen mode Exit fullscreen mode

This automatically imports menu.py during startup.

So developers never need to manually import menus.


Step 4 — Building Menu Objects

I created helper functions like:

def get_sub_section_menu(request=None):
Enter fullscreen mode Exit fullscreen mode

which:

  • validates permissions
  • groups menus by section
  • sorts menus using position
  • validates missing sections
  • prepares final menu dictionaries

Example validation:

raise ImproperlyConfigured(
    f"Sub section uses section '{section_name}' "
    f"but no matching main section exists."
)
Enter fullscreen mode Exit fullscreen mode

This prevented silent configuration errors.


Step 5 — Using Context Processors

Then I exposed menus globally using a context processor.

def menu_context_processor(request):

    return {
        "main_section_menu": get_main_section_menu(request),
        "sub_section_menu": get_sub_section_menu(request),
    }
Enter fullscreen mode Exit fullscreen mode

Now every template automatically receives menu data.


Step 6 — Rendering Menus in Templates

Main Sidebar

{% for item in main_section_menu %}
    <a href="{{ item.url }}">
        {{ item.name }}
    </a>
{% endfor %}
Enter fullscreen mode Exit fullscreen mode

Sub Sidebar

{% for item in current_items %}
    <a href="{{ item.url }}">
        {{ item.label }}
    </a>
{% endfor %}
Enter fullscreen mode Exit fullscreen mode

Now templates became extremely small and clean.


Why This Architecture Helped

This solved multiple problems immediately.

Contributors No Longer Modify Core Sidebar Templates

They only create:

menu.py
Enter fullscreen mode Exit fullscreen mode

inside their app.


No More Merge Conflicts

Huge improvement for collaborative development.


Permissions Became Centralized

Each menu controls its own permissions.


HTMX Integration Became Easy

Each menu can define custom HTMX attributes:

attrs = MAIN_CONTENT_HX_ATTRS
Enter fullscreen mode Exit fullscreen mode

Apps Became Plug-and-Play

A new app can register itself automatically.


Things I Learned

While building this, I realized:

Sidebar systems are actually plugin systems.
Enter fullscreen mode Exit fullscreen mode

Once apps can contribute:

  • menus
  • dashboard widgets
  • settings pages
  • navigation items

your architecture becomes much more scalable.


Future Improvements

Some ideas I plan to add later:

  • menu caching
  • badge counts
  • nested menus
  • feature flags
  • dynamic visibility
  • app-level menu ordering
  • reusable Django package

Final Thoughts

This started as:

"I don't want to edit sidebar.html repeatedly."
Enter fullscreen mode Exit fullscreen mode

But eventually evolved into a modular registry-based navigation architecture.

For large Django projects, this approach made the codebase:

  • cleaner
  • contributor-friendly
  • scalable
  • easier to maintain

especially in multi-app systems like HRMS and CRM platforms.


Tech Stack

  • Django
  • HTMX
  • Tailwind CSS
  • Python decorators
  • Context processors
  • Dynamic registries

Top comments (1)

Collapse
 
ksreethul profile image
Sreethul

How do you usually handle sidebar and navigation scaling in large Django projects?

Do you prefer:

  • template-based menus
  • database-driven menus
  • registry/decorator approaches
  • plugin architectures
  • context processors
  • frontend-managed navigation

Would love to hear how other developers solve this problem in multi-app Django systems 👀