Skip to content

Creating Panels

Learn how to create custom panels for Django Control Room.

Official Site: djangocontrolroom.com.

The fastest way to create a new panel is using our official cookiecutter template:

cookiecutter-dj-control-room-plugin

This template generates a complete, production-ready panel structure with:

  • Django admin integration
  • Docker Compose development setup
  • Test suite with pytest
  • Documentation with MkDocs
  • Modern Python packaging (pyproject.toml)
  • Example project for testing

Using the Template

pip install cookiecutter  # requires cookiecutter>=2.0.0
cookiecutter https://github.com/yassi/cookiecutter-dj-control-room-plugin

The template will prompt you for project details and generate everything you need to start building your panel immediately.

Quick Start (Manual Setup)

A panel is a Python package that implements a simple interface. Your panel will appear in the Control Room dashboard alongside official panels:

Panel Grid

Here's the minimum you need:

# my_panel/panel.py
class MyPanel:
    name = "My Panel"
    description = "My awesome panel for monitoring X"
    icon = "chart"
# pyproject.toml
[project.entry-points."dj_control_room.panels"]
my_panel = "my_panel.panel:MyPanel"

That's it! Your panel will be automatically discovered by Django Control Room.

Panel Interface

How the Registry ID Works

Django Control Room derives a unique registry key for your panel automatically from your PyPI distribution name (the name field in pyproject.toml), normalizing hyphens to underscores. For example, a package named my-panel gets the registry key my_panel.

This means you never need to declare an id on your panel class — and if you do, it is silently ignored. Two different panel authors can never accidentally clobber each other's panels by picking the same string.

Required Attributes

Every panel must define these three attributes:

name (str)

Display name shown in the Control Room dashboard.

name = "My Panel"  # Shown to users

description (str)

Brief description (1-2 sentences) explaining what your panel does.

description = "Monitor system health and performance metrics"

icon (str)

Icon identifier for visual representation in the dashboard.

icon = "chart"  # One of the available icons

Available icons: - database - Database/storage related - layers - Caching/stacking related - link - URL/routing related - chart - Analytics/monitoring related - radio - Signals/events related - cog - Settings/configuration related

Optional Attributes

app_name (str)

The Django app label used in INSTALLED_APPS and as the URL namespace in your urls.py. Defaults to the normalized distribution name (hyphens replaced with underscores), which is typically the same as your Python package name. Only set this explicitly if your app label differs from your dist name.

app_name = "my_panel"  # Only needed if it differs from your PyPI dist name

Django Control Room uses this value to check INSTALLED_APPS and to resolve your panel's URL via reverse(f'{panel.app_name}:{url_name}'). It must match the app_name declared in your urls.py.

package (str)

Your PyPI package name. When set, enables the install/configure page with pip install instructions.

package = "my-panel"

docs_url / pypi_url (str)

Optional links shown on the install/configure page.

docs_url = "https://github.com/yourname/my-panel"
pypi_url = "https://pypi.org/project/my-panel/"

Optional Methods

get_url_name()

Returns the URL name for your panel's main entry point (defaults to "index").

def get_url_name(self):
    return "index"  # Or "dashboard", "home", etc.

Django Control Room will resolve your panel's URL using: reverse(f'{panel.app_name}:{url_name}')

Complete Panel Structure

Here's a complete panel package structure:

my-panel/
├── my_panel/
│   ├── __init__.py
│   ├── panel.py          # Panel class
│   ├── apps.py           # Django app config
│   ├── models.py         # Placeholder model for admin
│   ├── admin.py          # Admin registration
│   ├── urls.py           # URL patterns
│   ├── views.py          # Views
│   ├── templates/
│   │   └── admin/
│   │       └── my_panel/
│   │           ├── base.html
│   │           └── index.html
│   └── static/
│       └── my_panel/
│           └── css/
│               └── styles.css
├── tests/
├── pyproject.toml
└── README.md

Step-by-Step Guide

1. Create Package Structure

Use the cookiecutter template:

cookiecutter https://github.com/yassi/cookiecutter-dj-control-room-plugin

Or manually create the structure shown above.

2. Define Panel Class

# my_panel/panel.py
class MyPanel:
    """
    My awesome panel for Django Control Room.
    """

    # Required
    name = "My Panel"
    description = "Monitor and manage XYZ"
    icon = "chart"

    # Optional: only needed if your app label differs from your PyPI dist name
    # app_name = "my_panel"

    # Optional: enables the install/configure page
    # package = "my-panel"

    # Optional: customize URL name (defaults to "index")
    def get_url_name(self):
        return "index"

3. Create Django App Config

# my_panel/apps.py
from django.apps import AppConfig

class MyPanelConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'my_panel'
    verbose_name = 'My Panel'

4. Define URL Patterns

# my_panel/urls.py
from django.urls import path
from . import views

app_name = 'my_panel'  # Must match panel.app_name (defaults to normalized dist name)

urlpatterns = [
    path('', views.index, name='index'),  # Main entry point
    path('detail/<str:pk>/', views.detail, name='detail'),
]

Important: app_name in your urls.py must match the app_name on your panel class (which defaults to the normalized PyPI distribution name). For a package named my-panel, both should be my_panel.

5. Create Views

# my_panel/views.py
from django.contrib.admin.views.decorators import staff_member_required
from django.shortcuts import render
from django.contrib import admin

@staff_member_required
def index(request):
    """Main panel view."""
    context = admin.site.each_context(request)
    context.update({
        'title': 'My Panel',
        # Your data here
    })
    return render(request, 'admin/my_panel/index.html', context)

6. Create Templates

{# my_panel/templates/admin/my_panel/base.html #}
{% extends "admin/base_site.html" %}
{% load i18n admin_urls static %}

{% block title %}{{ title }} | My Panel{% endblock %}

{% block extrahead %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'my_panel/css/styles.css' %}">
{% endblock %}

{% block branding %}
<h1 id="site-name">
  <a href="{% url 'my_panel:index' %}">My Panel</a>
</h1>
{% endblock %}

{% block breadcrumbs %}
<div class="breadcrumbs">
  <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  &rsaquo; My Panel
</div>
{% endblock %}

{% block content %}{% endblock %}
{# my_panel/templates/admin/my_panel/index.html #}
{% extends "admin/my_panel/base.html" %}

{% block content %}
<div class="module">
    <h2>My Panel Dashboard</h2>
    <p>Your content here</p>
</div>
{% endblock %}

7. Add Entry Point

# pyproject.toml
[project.entry-points."dj_control_room.panels"]
my_panel = "my_panel.panel:MyPanel"

8. Create Placeholder Model

# my_panel/models.py
from django.db import models

class MyPanelPlaceholder(models.Model):
    """Placeholder model for admin integration."""

    class Meta:
        managed = False
        verbose_name = "My Panel"
        verbose_name_plural = "My Panel"
        app_label = "my_panel"
# my_panel/admin.py
from django.contrib import admin
from django.http import HttpResponseRedirect
from django.urls import reverse
from .models import MyPanelPlaceholder

@admin.register(MyPanelPlaceholder)
class MyPanelAdmin(admin.ModelAdmin):
    def changelist_view(self, request, extra_context=None):
        return HttpResponseRedirect(reverse('my_panel:index'))

    def has_add_permission(self, request):
        return False

    def has_change_permission(self, request, obj=None):
        return request.user.is_staff

    def has_delete_permission(self, request, obj=None):
        return False

Note: Django Control Room will automatically unregister this placeholder model and replace it with its own proxy model under the "Django Control Room" section (unless configured otherwise).

Once your panel is installed and configured, it will appear in the admin sidebar under Django Control Room:

Admin Sidebar

Publishing Your Panel

1. Complete pyproject.toml

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "my-panel"
version = "0.1.0"
description = "My awesome panel for Django Control Room"
readme = "README.md"
license = {text = "MIT"}
authors = [
    {name = "Your Name", email = "you@example.com"},
]
requires-python = ">=3.9"
classifiers = [
    "Framework :: Django",
    "Framework :: Django :: 4.2",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
]
keywords = ["django", "admin", "panel"]
dependencies = [
    "Django>=4.2",
]

[project.entry-points."dj_control_room.panels"]
my_panel = "my_panel.panel:MyPanel"

[project.urls]
Homepage = "https://github.com/yourusername/my-panel"
Documentation = "https://github.com/yourusername/my-panel"
Repository = "https://github.com/yourusername/my-panel"

[tool.setuptools.packages.find]
exclude = ["tests*"]

[tool.setuptools.package-data]
"my_panel" = ["templates/**/*", "static/**/*"]

2. Build Package

pip install build
python -m build

3. Publish to PyPI

pip install twine
twine upload dist/*

Best Practices

1. Use Admin Context

Always include Django admin context for proper styling and navigation:

context = admin.site.each_context(request)
context.update({
    'title': 'Your Title',
    # Your data
})

2. Require Staff Permission

Protect your views with the staff member decorator:

from django.contrib.admin.views.decorators import staff_member_required

@staff_member_required
def my_view(request):
    # ...

3. Follow Django Admin Styling

Extend Django admin templates and use admin CSS classes for consistency:

{% extends "admin/base_site.html" %}

<div class="module">
    <h2>Module Title</h2>
    <table class="table">
        <!-- Use admin table styles -->
    </table>
</div>

4. Handle Errors Gracefully

Provide helpful error messages:

try:
    data = fetch_data()
except Exception as e:
    messages.error(request, f"Error fetching data: {e}")
    data = []

5. Document Your Panel

Include comprehensive README with: - Installation instructions - Configuration options - Screenshots - Usage examples

Testing Your Panel

Local Development

  1. Install your panel in editable mode:

    pip install -e /path/to/my-panel
    

  2. Add to INSTALLED_APPS:

    INSTALLED_APPS = [
        # ...
        'dj_control_room',
        'my_panel',
    ]
    

  3. Include URLs:

    urlpatterns = [
        path('admin/my-panel/', include('my_panel.urls')),
        path('admin/dj-control-room/', include('dj_control_room.urls')),
        path('admin/', admin.site.urls),
    ]
    

  4. Check panel registration:

    from dj_control_room.registry import registry
    registry.autodiscover()
    print([p._registry_id for p in registry.get_panels()])
    

Write Tests

# tests/test_panel.py
from django.test import TestCase
from my_panel.panel import MyPanel

class PanelTestCase(TestCase):
    def test_panel_attributes(self):
        panel = MyPanel()
        self.assertEqual(panel.name, 'My Panel')
        self.assertTrue(panel.description)
        self.assertTrue(panel.icon)

    def test_url_name(self):
        panel = MyPanel()
        self.assertEqual(panel.get_url_name(), 'index')

Examples

Check out these official panels for reference:

Getting Help

Resources

Next Steps