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:
Auto-discovery¶
When the app starts (AppConfig.ready()), two things happen automatically:
-
Device type discovery:
autodiscover_modules("devices")scans all installed apps fordevices.pyfiles and imports them. This triggers theDeviceTypeMetametaclass, which registers eachDeviceTypein the global registry. -
Admin auto-registration: If
AUTO_ADMINisTrue(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()inmodels.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 StateFieldslist_filter: status, is_activesearch_fields: name, device_idreadonly_fields: mqtt_username, mqtt_password_hash, api_key, last_seen, created_at, updated_at
Reading admin:
list_display: device, timestamp, plus all Reading fieldslist_filter: devicedate_hierarchy: timestamp
Message admin:
list_display: device, topic, processing_status, received_atlist_filter: processing_statusreadonly_fields: all fields (messages are read-only audit records)
Disabling auto-admin¶
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, withidandreceived_atas 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):
Management Commands¶
iot_listen¶
Start the MQTT message listener:
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):
| 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:
See Provisioning Guide for details.