Skip to content

Django Integration Guide

This guide covers how Django DeviceHub integrates with the Django ecosystem: signals, admin, URLs, auto-discovery, and settings.

App Configuration

Django DeviceHub registers itself as django_devicehub in INSTALLED_APPS:

INSTALLED_APPS = [
    ...
    "django_devicehub",
    "myapp",
]

Auto-discovery

When the app starts (AppConfig.ready()), two things happen automatically:

  1. Device type discovery: autodiscover_modules("devices") scans all installed apps for devices.py files and imports them. This triggers the DeviceTypeMeta metaclass, which registers each DeviceType in the global registry.

  2. Admin auto-registration: If AUTO_ADMIN is True (the default), auto_register_admin() is called to register Django admin classes for all device types that have models.

This means you only need to:

  • Define device types in devices.py
  • Call create_models() in models.py
  • Everything else is discovered and wired up automatically

Signals

Django DeviceHub provides six signals for hooking into the device lifecycle. All signals use Django's standard Signal class.

device_data_received

Fired when a device sends data readings (after the reading is stored).

from django.dispatch import receiver
from django_devicehub.signals import device_data_received

@receiver(device_data_received)
def handle_data(sender, device, readings, message, **kwargs):
    """
    sender: DeviceType class (e.g., WeatherStation)
    device: Device model instance
    readings: dict of field_name -> value
    message: Message model instance (raw audit record)
    """
    if readings.get("temperature", 0) > 50:
        send_alert(device, readings)

device_status_changed

Fired when a device's status changes.

from django_devicehub.signals import device_status_changed

@receiver(device_status_changed)
def handle_status(sender, device, old_status, new_status, **kwargs):
    """
    sender: DeviceType class
    device: Device model instance
    old_status: str (previous status value)
    new_status: str (current status value)
    """
    if new_status == "error":
        create_incident(device)

device_connected

Fired when a device connects (via broker auth endpoint).

from django_devicehub.signals import device_connected

@receiver(device_connected)
def handle_connect(sender, device, **kwargs):
    log_event(device, "connected")

device_disconnected

Fired when a device disconnects or its heartbeat times out.

from django_devicehub.signals import device_disconnected

@receiver(device_disconnected)
def handle_disconnect(sender, device, **kwargs):
    log_event(device, "disconnected")

device_command_sent

Fired after a command is published to a device via MQTT.

from django_devicehub.signals import device_command_sent

@receiver(device_command_sent)
def handle_command(sender, device, command, payload, **kwargs):
    """
    sender: DeviceType class
    device: Device model instance
    command: str (command name, e.g., "reboot")
    payload: dict (command payload, may be empty)
    """
    audit_log(device, f"Command sent: {command}", payload)

device_provisioned

Fired after a device is provisioned with credentials (via API or management command).

from django_devicehub.signals import device_provisioned

@receiver(device_provisioned)
def handle_provisioned(sender, device, credentials, **kwargs):
    """
    sender: DeviceType class
    device: Device model instance
    credentials: dict with mqtt_username, mqtt_password, api_key
    """
    # Store credentials in a secure vault
    vault.store(device.device_id, credentials)

Filtering by device type

Use the sender argument to filter signals for a specific device type:

from myapp.devices import WeatherStation

@receiver(device_data_received, sender=WeatherStation)
def handle_weather_data(sender, device, readings, **kwargs):
    # Only called for WeatherStation devices
    ...

URL Configuration

Broker auth + realtime SSE

Include the main django-devicehub URLs:

# urls.py
from django.urls import path, include

urlpatterns = [
    path("iot/", include("django_devicehub.urls")),
]

This registers:

URL View Description
/iot/broker/auth/ BrokerAuthView MQTT broker authentication
/iot/broker/acl/ BrokerACLView MQTT broker ACL
/iot/stream/{type}/ sse_stream SSE stream (all devices of a type)
/iot/stream/{type}/{id}/ sse_stream SSE stream (specific device)
/iot/poll/{type}/ sse_poll Polling fallback (all devices)
/iot/poll/{type}/{id}/ sse_poll Polling fallback (specific device)

Auto-generated REST API

If Django REST Framework is installed and AUTO_API is True, you can generate API endpoints for all registered device types:

from django_devicehub.generators.urls import get_iot_urlpatterns

urlpatterns = [
    ...
    *get_iot_urlpatterns("api"),
]

This creates DRF router URLs for each device type:

URL ViewSet Description
/api/{type}/devices/ {Type}DeviceViewSet CRUD for devices
/api/{type}/devices/{id}/readings/ (action) List readings for a device
/api/{type}/devices/{id}/provision/ (action) Generate MQTT credentials
/api/{type}/readings/ {Type}ReadingViewSet Read-only access to readings

The prefix argument to get_iot_urlpatterns() controls the URL prefix. For example, get_iot_urlpatterns("api/things") produces /api/things/weatherstation/devices/.

WebSocket routing (Django Channels)

For WebSocket support, add the routing to your ASGI application:

# asgi.py
from django_devicehub.realtime.routing import websocket_urlpatterns

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": AuthMiddlewareStack(
        URLRouter(websocket_urlpatterns)
    ),
})

Admin Customization

Auto-generated admin

When AUTO_ADMIN is True, Django DeviceHub registers admin classes for every device type that has models. The generated admin includes:

Device admin:

  • list_display: name, device_id, status, battery_level, last_seen, is_active, created_at, plus any StateFields
  • list_filter: status, is_active
  • search_fields: name, device_id
  • readonly_fields: mqtt_username, mqtt_password_hash, api_key, last_seen, created_at, updated_at

Reading admin:

  • list_display: device, timestamp, plus all Reading fields
  • list_filter: device
  • date_hierarchy: timestamp

Message admin:

  • list_display: device, topic, processing_status, received_at
  • list_filter: processing_status
  • readonly_fields: all fields (messages are read-only audit records)

Disabling auto-admin

DJANGO_DEVICEHUB = {
    "AUTO_ADMIN": False,
}

Custom admin classes

To customize the admin, disable auto-admin and register your own:

# myapp/admin.py
from django.contrib import admin
from myapp.models import WeatherStationDevice, WeatherStationReading

@admin.register(WeatherStationDevice)
class WeatherStationDeviceAdmin(admin.ModelAdmin):
    list_display = ["name", "device_id", "status", "temperature_latest", "last_seen"]
    list_filter = ["status", "is_active"]
    search_fields = ["name", "device_id"]
    readonly_fields = ["mqtt_username", "mqtt_password_hash", "api_key"]

    def temperature_latest(self, obj):
        reading = obj.readings.order_by("-timestamp").first()
        return f"{reading.temperature}C" if reading else "-"
    temperature_latest.short_description = "Latest Temp"

Manual admin registration

You can also use the generator to create admin classes, then customize them:

from django_devicehub.generators.admin import create_admin
from myapp.devices import WeatherStation

DeviceAdmin, ReadingAdmin, MessageAdmin = create_admin(WeatherStation)

Auto-generated REST API Details

Serializers

The create_serializers() function generates two DRF serializers per device type:

  • DeviceSerializer: exposes all fields, with sensitive fields (mqtt_username, mqtt_password_hash, api_key, status, battery_level, last_seen, timestamps) as read-only
  • ReadingSerializer: exposes id, device, timestamp, received_at, and all Reading fields, with id and received_at as read-only

ViewSets

The create_viewsets() function generates two DRF ViewSets:

  • DeviceViewSet (ModelViewSet): full CRUD with two extra actions:
    • readings (GET): returns the last N readings for a device (default 100, max 1000)
    • provision (POST): generates MQTT credentials and returns them with topic information
  • ReadingViewSet (ReadOnlyModelViewSet): read-only access to readings

Both ViewSets require authentication (IsAuthenticated).

Settings

All settings live under the DJANGO_DEVICEHUB dict in your Django settings. See the Settings Reference for the complete list with defaults.

Minimal configuration:

DJANGO_DEVICEHUB = {
    "BROKERS": {
        "default": {
            "HOST": "localhost",
            "PORT": 1883,
        }
    },
    "TOPIC_PREFIX": "myproject",
}

Settings are accessed internally via devicehub_settings:

from django_devicehub.conf import devicehub_settings

prefix = devicehub_settings.TOPIC_PREFIX
brokers = devicehub_settings.BROKERS

Settings are cached on first access. To force a reload (useful in tests):

devicehub_settings.invalidate_cache()

Management Commands

iot_listen

Start the MQTT message listener:

python manage.py iot_listen
python manage.py iot_listen --broker lorawan

The listener subscribes to all registered device type topics and routes incoming messages to the MessageRouter.

iot_simulate

Simulate devices publishing data (no hardware needed):

python manage.py iot_simulate weatherstation --count 5 --interval 3 --realistic
Option Default Description
device_type (required) Registered DeviceType name
--count 1 Number of simulated devices
--interval 5 Publish interval in seconds
--broker "default" Which broker to publish to
--duration 0 (infinite) Run for N seconds then stop
--realistic False Generate values within Reading ranges
--prefix "sim-" Device ID prefix

iot_provision

Bulk provision devices with credentials:

python manage.py iot_provision weatherstation --count 10 --output creds.json

See Provisioning Guide for details.