When building large Django applications, one problem slowly becomes painful:
sidebar management
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
Example:
<li>
<a href="/accounts/">Accounts</a>
</li>
<li>
<a href="/contacts/">Contacts</a>
</li>
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.
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
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
Sub Sidebar Registry
# project/menus/sub.py
sub_section_menu = []
def register(cls):
sub_section_menu.append(cls)
return cls
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",
}
Main Section
@main_section_menu.register
class AccountsSection:
section = "accounts"
name = _("Accounts")
icon = "/assets/icons/accounts.svg"
position = 1
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",
]
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",
]
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):
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."
)
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),
}
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 %}
Sub Sidebar
{% for item in current_items %}
<a href="{{ item.url }}">
{{ item.label }}
</a>
{% endfor %}
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
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
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.
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."
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)
How do you usually handle sidebar and navigation scaling in large Django projects?
Do you prefer:
Would love to hear how other developers solve this problem in multi-app Django systems 👀