A practical, operations-first backend for tracking physical units, their lifecycle, their event history, and the business metrics they drive. 📊
Asset Management System is a Django REST backend that models real-world physical assets (e.g., a hotel and its rooms, or a fleet and its vehicles) as software objects with:
- Stable IDs you can put on a physical label (print as a QR/barcode using any generator)
- A lifecycle (inactive → active → expired) that can be automated
- An event history sourced from devices/edge systems (MQTT telemetry)
- A monetization loop (Paystack payments update revenue, unlock access, then auto-expire)
- Operational controls (send “lock/unlock”, “ignition on/off”, etc. via MQTT)
The result is a practical reference system for building digital histories for individual “units” (a room, a vehicle, or any other uniquely-identifiable physical item).
Many businesses manage physical units that have to be:
- identified unambiguously
- monitored in near-real time
- activated/deactivated based on policy (time, payment, authorization)
- audited (who did what, when, and to which physical unit)
- measured for performance (revenue, utilization, yield)
This backend demonstrates those primitives end-to-end, using Hotels/Rooms and Fleets/Vehicles as concrete asset types.
-
Physical asset mapping (unit-level tracking)
Assetrepresents a top-level “container” (a hotel or a fleet) with a generatedasset_number.HotelRoomandVehiclerepresent individual physical units under that asset.
-
Lifecycle + automation
- When a payment is verified, the system activates the sub-asset and sets an expiry timestamp.
- Celery schedules an expiry job that can re-lock / disable access when time is up.
-
Event history (append-only audit trail)
AssetEventrecords telemetry and control actions against a specific sub-asset via aGenericForeignKey.- MQTT topics are structured so every message cleanly maps to: asset → unit → event type.
-
Remote control plane (HTTP → MQTT)
- Authenticated API calls publish MQTT commands like:
- Room: electricity / access (lock/unlock)
- Vehicle: ignition (turn_on/turn_off)
- Each command is also written to
AssetEventfor auditability.
- Authenticated API calls publish MQTT commands like:
-
Payments & revenue accounting (Nigeria-ready rails)
- Paystack card payment initialization + verification.
- Transfer initiation scaffolding and webhook signature verification (HMAC-SHA512).
- All revenue aggregates per asset (
Asset.total_revenue) and powers analytics.
-
Analytics you can act on
- “Index” stats: total assets, total revenue, daily revenue graph by asset type.
- “Status” stats: occupancy/utilization and expected yield from events.
┌───────────────────────────────┐
│ Clients │
│ Web / Mobile / Admin tools │
└───────────────┬───────────────┘
│ HTTPS (JWT)
v
┌───────────────────────┐ ┌───────────────────────────────┐
│ Payment Provider │<--->│ Django REST API │
│ (Paystack/Flutterwave) │ │ core + assets + analytics + │
└───────────────────────┘ │ mqtt_handler │
└───────┬───────────┬────────────┘
│ │
│ │ publish control commands
│ v
│ ┌───────────────────────────┐
│ │ MQTT Broker │
│ │ (topics encode unit IDs) │
│ └───────────┬───────────────┘
│ │ telemetry + state
│ v
│ ┌───────────────────────────┐
│ │ Edge/IoT Devices │
│ │ locks, sensors, trackers │
│ └───────────────────────────┘
│
│ persist domain state + event log
v
┌───────────────────────────────┐
│ PostgreSQL │
│ Assets / Sub-assets / Events │
│ Roles / Transactions / Metrics │
└───────────────────────────────┘
async jobs (expiry, automation)
┌───────────────────────────────┐
│ Celery Worker │
└───────────────┬───────────────┘
│ broker/result backend
v
┌───────────┐
│ Redis │
└───────────┘
How to read this diagram
- Telemetry (device → system): Devices publish MQTT telemetry (e.g., occupancy, location). The subscriber writes
AssetEventrows (and may update the sub-asset state, e.g., vehicle location). - Control (system → device): Authorized users call HTTP endpoints. The API publishes MQTT commands (retain=true) and also logs the action as an
AssetEvent. - Lifecycle automation: Payments (and direct controls) can schedule Celery tasks that enforce expiry policies (e.g., re-lock / disable after a time window).
- Business visibility: Analytics reads from the same source of truth—transactions + events—to compute revenue graphs and utilization/yield-style indicators.
-
Asset (
core.models.Asset)
A hotel or fleet with anasset_number, location, bank details, andtotal_revenue. -
HotelRoom / Vehicle (
core.models.HotelRoom,core.models.Vehicle)
Sub-assets with status + activation/expiry timestamps. Vehicles track location + total distance. -
AssetEvent (
core.models.AssetEvent)
Event log attached to either a room or a vehicle (viaGenericForeignKey). -
Role (
core.models.Role)
Role-based access per asset: admin / manager / viewer. -
Transaction (
core.models.Transaction)
Payment records, linked to an asset and a sub-asset number.
core/: authentication, payments (init/verify), core models, and shared helpersassets/: asset/sub-asset CRUD, role association (“invite/kick”), transaction historymqtt_handler/: control endpoints (publish MQTT), status endpoints, and MQTT subscriber bootstrapanalytics/: dashboard-style aggregated metricsrooms/,vehicles/: additional endpoints (older/alternate API style); data models still live incore.models
-
Device/edge publishes telemetry → MQTT broker
Example topic pattern:rooms/{asset_number}/{room_number}/occupancyvehicles/{asset_number}/{vehicle_number}/location
-
Subscriber ingests →
AssetEvent(+ optional location update)
MQTT subscriber is started inmqtt_handler.apps.MqttHandlerConfig.ready()(disabled during tests).
- Client calls API → publishes MQTT command (retain=true) → logs
AssetEvent
Control endpoints validate:- the asset exists
- the unit exists under that asset
- the user has permission (admin or system user)
All API routes are mounted under '/api/' (see hotel_demo/urls.py).
POST /api/auth/register/POST /api/token/(JWT)POST /api/token/refresh/GET /api/auth/me/
GET /api/assets/POST /api/assets/GET /api/assets/{asset_number}/PATCH /api/assets/{asset_number}/DELETE /api/assets/{asset_number}/
-
Rooms
GET /api/assets/{asset_number}/rooms/POST /api/assets/{asset_number}/rooms/GET /api/assets/{asset_number}/rooms/{room_number}/
-
Vehicles
GET /api/assets/{asset_number}/vehicles/POST /api/assets/{asset_number}/vehicles/GET /api/assets/{asset_number}/vehicles/{vehicle_number}/
GET /api/assets/{asset_number}/users/POST /api/assets/invite/{asset_number}/(assign role by email)POST /api/assets/kick/{asset_number}/(remove role by email)
-
POST /api/payment/init/
Creates a pendingTransactionand returns a Paystack checkout link. -
GET /api/payment/verify/?trxref={transaction_ref}
Verifies with Paystack, then:- marks transaction verified
- updates asset revenue
- activates the sub-asset and schedules expiry via Celery
POST /api/assets/{asset_number}/control/{sub_asset_id}/POST /api/assets/{asset_number}/direct_control/{sub_asset_id}/GET /api/assets/{asset_number}/status/GET /api/assets/{asset_number}/status/{sub_asset_id}/
GET /api/analytics/index/?year=YYYY&month=MM
GET|POST /api/vehicles/GET|PUT|DELETE /api/vehicles/{id}/GET /api/vehicles/{id}/status/GET|POST /api/hotels/{hotel_id}/rooms/GET|PUT|DELETE /api/hotels/rooms/{id}/
The system expects MQTT messages whose topic encodes identity:
-
Rooms
rooms/{asset_number}/{room_number}/occupancy(payload like1or0)rooms/{asset_number}/{room_number}/electricity(payload is arbitrary command/state)rooms/{asset_number}/{room_number}/access(payload likelock/unlock)
-
Vehicles
vehicles/{asset_number}/{vehicle_number}/location(payload:"lat,lon")vehicles/{asset_number}/{vehicle_number}/ignition(payload liketurn_on/turn_off)vehicles/{asset_number}/{vehicle_number}/passenger_countvehicles/{asset_number}/{vehicle_number}/tamperingvehicles/{asset_number}/{vehicle_number}/payment
Notes:
- Location payloads are validated (lat/lon range, and
0,0is rejected). - Each received message becomes an
AssetEvent. For location, theVehicleobject is also updated.
This codebase treats analytics as a first-class feature rather than a “report later” afterthought:
- Revenue is computed and stored (
Asset.total_revenue) and can be re-aggregated. - Daily revenue graph is computed from
Transactionhistory (grouped by date and asset type). - Operational yield indicators are derived from event streams:
- For hotels: occupancy events → expected yield (sum of room prices for occupied rooms).
- For vehicles: per-day activity inferred from events (vehicles with events, active vehicles, etc.).
The backend is API-first, but it’s designed to power a practical operations dashboard—asset overview, unit status/control, and transaction audit in one place.
Hotel operations dashboard
Vehicle operations dashboard
Centralized “business constants” live in conf.yml, including:
- roles and payment statuses
- supported asset types and event types
- transfer policy (min/max amounts, charge bands, confirmation expiry window)
- frontend redirect URL + transaction reference prefix
Required (or effectively required) in hotel_demo/settings.py:
-
Core
SECRET_KEYDEBUG(true/false)SYSTEM_USER_REQUEST_DOMAIN(defaults tolocalhost:8000)
-
Database (PostgreSQL)
DB_NAME,DB_USER,DB_PASSWORD,DB_HOST,DB_PORT
-
Redis / Celery
REDIS_URL(defaults toredis://localhost:6379/0)
-
Payments
PAYSTACK_SECRET_KEY_DEV,PAYSTACK_SECRET_KEY_LIVEFLW_PUBLIC_KEY,FLW_SECRET_KEY,FLW_SECRET_HASH(Flutterwave helpers exist)
- Python 3.x
- PostgreSQL
- Redis (for Celery broker/results)
pip install -r requirements.txtpython manage.py migratepython manage.py createsuperuserIn separate terminals:
celery -A hotel_demo worker -l infoIf you use scheduled tasks (celery beat):
celery -A hotel_demo beat -l infoThe MQTT subscriber thread is started automatically when mqtt_handler is installed (via AppConfig.ready()).
In tests, the app is removed from INSTALLED_APPS to prevent background threads during test execution.
POST /api/payment/init/creates a pendingTransactionfor a specificasset_number+room_number.GET /api/payment/verify/verifies the transaction and:- marks it
completed - increments
Asset.total_revenue - sets
HotelRoom.status = True - sets/extends
HotelRoom.expiry_timestamp - publishes an access command (unlock)
- schedules expiry to re-lock and deactivate
- marks it
Same pattern as hotel rooms, except the control domain is “ignition” rather than “access”.
- JWT authentication for core API access.
- Role-based access enforced per asset (
Rolemodel). - Paystack webhook signature verification uses HMAC-SHA512 over the raw request body.
- QR code generation is not implemented inside this repo, but all identifiers (
asset_number,room_number,vehicle_number) are label-friendly and can be encoded into QR/barcodes externally. - Some payment/transfer flows are still evolving (e.g.,
FinalizeTransferViewis a stub). - MQTT broker defaults to a public broker (
broker.emqx.io) for demonstration; production should use a private broker + authentication + TLS.
This project is shared for showcasing and evaluation. See LICENSE.

