Skip to main content

DRF Dynamic Navigation

A reusable Django REST Framework package for building dynamic, role-based navigation systems.

drf-dynamic-nav allows you to manage menus, submenus, pages, widgets, and permissions dynamically from the database instead of hardcoding navigation logic in your frontend.

It is designed for applications where different users or roles need access to different parts of the application.


Features

  • Dynamic navigation management

  • Menu → SubMenu → Page → Widget hierarchy

  • Role-based navigation permissions

  • SubRole-based permissions

  • External User and Role IDs supported

  • No hard dependency on Django's built-in auth_user

  • Custom authentication support

  • Django REST Framework integration

  • Database-driven navigation

  • Active/inactive navigation items

  • API-based navigation retrieval

  • Easy integration with existing Django projects

  • Customizable database table names

  • Suitable for admin dashboards, enterprise applications, and SaaS applications


Navigation Hierarchy

The package follows this structure:

Menu
 └── SubMenu
      └── Page
           └── Widget

Example:

Dashboard (Menu)
├── Reports  (Submenu)
│   ├── Sales Report (Page)
│   │   ├── Export Button  (Widget)
│   │   └── Filter Button
│   └── Inventory Report
│       └── Export Button
│
└── Settings
    ├── User Management
    └── Role Management

Permissions can be assigned at the required navigation level.


Requirements

  • Python 3.10+

  • Django 4.0+

  • Django REST Framework 3.14+


Models

The package provides navigation models based on the following hierarchy:

Menu
SubMenu
Page
Widget

Permissions are maintained separately using external identifiers.

For example:

class NavPermission(models.Model):
    id = models.BigAutoField(primary_key=True)

    role_id = models.BigIntegerField(
        db_index=True,
        help_text="External Role ID",
    )

    subrole_id = models.BigIntegerField(
        db_index=True,
        help_text="External SubRole ID",
    )

Using external IDs makes the package suitable for applications that already have their own:

  • User model

  • Role model

  • SubRole model

  • Authentication system

The package does not require your existing role tables to be directly related through Django foreign keys.


Authentication

The package is designed to work with existing authentication systems.

You can integrate it with:

  • JWT/Outh authentication

  • Custom JWT authentication

  • Django authentication

  • Custom UserMaster

  • External authentication services

For example, if your project already uses a custom user model:

class UserMaster:
    id = ...
    role_id = ...
    subrole_id = ...

the navigation package can use those identifiers to determine the user's available navigation.


Permissions

Navigation permissions can be controlled using:

Role
  ↓
SubRole
  ↓
Navigation Permission
  ↓
Menu / SubMenu / Page / Widget

This allows different users to receive different navigation structures.

Example:

Admin
 ├── Dashboard
 ├── Reports
 ├── User Management
 └── Settings

Manager
 ├── Dashboard
 └── Reports

Operator
 └── Dashboard

The frontend does not need to hardcode these rules.

Instead, it can request the navigation from the backend.


Follow these steps to use this package in your project

Installation

Install the package using pip:

pip install drf-dynamic-nav

Or with uv:

uv add drf-dynamic-nav

Paste the following path in projects urls.py file

path(
        "api/navigation/",
        include("drf_dynamic_nav.urls"),
    ),

for example:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path(
        "api/navigation/",
        include("drf_dynamic_nav.urls"),
    ),
]

Configuration in settings.py file

Configuration

Add the application to INSTALLED_APPS:

INSTALLED_APPS = [
    # Django apps
    "django.contrib.auth",
    "django.contrib.contenttypes",

    # Third-party apps
    "rest_framework",

    # DRF Dynamic Navigation
    "drf_dynamic_nav",
]
DYNAMIC_NAV = {
    "NAVIGATION_ITEM_TABLE_NAME": "test_navigation_item",  # if you want to rename the table name
    "NAV_PERMISSION_TABLE_NAME": "test_nav_permission",  # if you want to rename the table name
    "USER_MODEL": "users.UserMaster",   # path of your usermaster table
    "USER_ID_FIELD": "id", # user id primary or unique field of your usermaster table
    "ADMIN_ROLE_IDS": [1], # define admin role ids here for admin only accessible api
}

Run migrations:

python manage.py makemigrations
python manage.py migrate

API ENDPOINTS

Method Endpoint Purpose
GET /get-nav-permissions/ Get navigation permissions
POST /permissions/create/ Create role-based navigation permissions
POST /permissions/update/ Update role-based navigation permissions

Admin control only apis

Method Endpoint Purpose
GET GET /get-nav-items/?role_id=1&subrole_id=1 Get navigation items available for a role/user
POST /nav-items/create/ Create navigation items
POST /nav-items/update/ Update navigation items

For navitems have these following item_type choices

 MENU = "menu", "Menu"
 SUBMENU = "submenu", "SubMenu"
 PAGE = "page", "Page"
 WIDGET = "widget", "Widget"



CURLS

create navigation items (menu, submenu, page, and widgets) data

curl --location 'http://127.0.0.1:8000/api/navigation/nav-items/create/' \

--header 'Authorization: Bearer YOUR_JWT_TOKEN' \

--header 'Content-Type: application/json' \

--data '{

"name": "User Management",

"code": "user_management",

"item_type": "menu",

"parent_id": 1,

"url": null,

"icon": "users",

"sort_order": 1,

"is_active": true,

"metadata": {}

}'

Response:

{

"is_success": true,

"message": "Navigation item created successfully.",

"data": {

"id": 2,

"name": "User Management",

"code": "user_management",

"item_type": "submenu",

"parent_id": 1,

"parent_name": "Dashboard",

"url": null,

"icon": "users",

"sort_order": 1,

"is_active": true,

"metadata": {},

"created_at": "2026-09-15T12:28:09.068010Z",

"updated_at": "2026-09-15T12:28:09.068010Z"

}

}

Update the navigation items data

curl --location 'http://127.0.0.1:8000/api/navigation/nav-items/update/' \

--header 'Content-Type: application/json' \

--header 'Authorization: Bearer YOUR_JWT_TOKEN' \

--data '{

"nav_item_id": 1,

"name": "Main Dashboard",

"code": "main_dashboard",

"item_type": "menu",

"parent_id": null,

"url": null,

"icon": "dashboard",

"sort_order": 1,

"is_active": true,

"metadata": {}

}'

Response:

{

"is_success": true,

"message": "Navigation item updated successfully.",

"data": {

"id": 1,

"name": "Main Dashboard",

"code": "main_dashboard",

"item_type": "menu",

"parent_id": null,

"url": null,

"icon": "dashboard",

"sort_order": 1,

"is_active": true,

"metadata": {}

}

}

Get active or inactive menu,submenu,pages or widgets based on role or subrole

curl --location 'http://127.0.0.1:8000/api/navigation/get-role-nav-items/?role_id=1&subrole_id=2&limit=10&offset=0' \

--header 'Authorization: Bearer YOUR_JWT_TOKEN'

Response:
{

"is_success": true,

"message": "Role navigation list fetched successfully.",

"data": {

"role_id": 3,

"subrole_id": 3,

"menus": [

{

"navigation_item_id": 1,

"name": "Main Dashboard",

"code": "main_dashboard",

"item_type": "menu",

"url": null,

"icon": "dashboard",

"sort_order": 1,

"is_active": true,

"permission_active": false,

"permissions": {

"can_view": false,

"can_create": false,

"can_update": false,

"can_delete": false

},

"children": [

{

"navigation_item_id": 2,

"name": "User Management",

"code": "user_management",

"item_type": "submenu",

"url": null,

"icon": "users",

"sort_order": 1,

"is_active": true,

"permission_active": false,

"permissions": {

"can_view": false,

"can_create": false,

"can_update": false,

"can_delete": false

},

"children": []

}

]

},

{

"navigation_item_id": 3,

"name": "reports Management",

"code": "reports",

"item_type": "menu",

"url": null,

"icon": "users",

"sort_order": 1,

"is_active": true,

"permission_active": false,

"permissions": {

"can_view": false,

"can_create": false,

"can_update": false,

"can_delete": false

},

"children": []

}

]

},

"total_count": 2

}

assign permissions based on role and subrole
curl --location 'http://127.0.0.1:8000/api/navigation/permissions/create/' \

--header 'Content-Type: application/json' \

--header 'Authorization: Bearer YOUR_JWT_TOKEN' \

--data '{

"role_id": 1,

"subrole_id": 1,

"navigation_item_id": 1,

"can_view": true,

"can_create": true,

"can_update": true,

"can_delete": false

}'

Response:

{

"is_success": true,

"message": "Navigation permission created successfully.",

"data": {

"id": 1,

"role_id": 1,

"subrole_id": 1,

"navigation_item_id": 1,

"navigation_item_name": "Main Dashboard",

"navigation_item_code": "main_dashboard",

"permissions": {

"can_view": true,

"can_create": true,

"can_update": true,

"can_delete": false

}

}

}

update permissions based on role and subrole

curl --location 'http://127.0.0.1:8000/api/navigation/permissions/update/' \

--header 'Authorization: Bearer YOUR_JWT_TOKEN' \

--header 'Content-Type: application/json' \

--data '{

"role_id": 1,

"subrole_id": 1,

"permissions": [

{

"navigation_item_id": 1,

"can_view": true,

"can_create": true,

"can_update":true,

"can_delete": true

},

{

"navigation_item_id": 2,

"can_view": true,

"can_create": true,

"can_update": false,

"can_delete": true

}

]

}'

Response:

{

"is_success": true,

"message": "Role navigation permissions updated successfully.",

"data": {

"role_id": 1,

"subrole_id": 1,

"deleted_permissions": 1,

"created_permissions": 2,

"permissions": [

{

"id": 4,

"navigation_item_id": 1,

"navigation_item_name": "Main Dashboard",

"navigation_item_code": "main_dashboard",

"item_type": "menu",

"permissions": {

"can_view": true,

"can_create": true,

"can_update": true,

"can_delete": true

}

},

{

"id": 5,

"navigation_item_id": 2,

"navigation_item_name": "User Management",

"navigation_item_code": "user_management",

"item_type": "submenu",

"permissions": {

"can_view": true,

"can_create": true,

"can_update": false,

"can_delete": true

}

}

]

}

}

Get navigation permissions based on role or subrole

note: call this api after login api

curl --location 'http://127.0.0.1:8000/api/navigation/get-nav-permissions/' \

--header 'Authorization: Bearer YOUR_JWT_TOKEN'

Response:

{

"is_success": true,

"message": "Navigation permissions fetched successfully.",

"data": [

{

"role_id": 1,

"subrole_id": 1,

"nav_item_id": 1,

"nav_item_name": "Main Dashboard",

"nav_item_code": "main_dashboard",

"item_type": "menu",

"url": null,

"icon": "dashboard",

"sort_order": 1,

"permissions": {

"can_view": true,

"can_create": true,

"can_update": true,

"can_delete": true

},

"children": [

{

"role_id": 1,

"subrole_id": 1,

"nav_item_id": 2,

"nav_item_name": "User Management",

"nav_item_code": "user_management",

"item_type": "submenu",

"url": null,

"icon": "users",

"sort_order": 1,

"permissions": {

"can_view": true,

"can_create": true,

"can_update": false,

"can_delete": true

},

"children": []

}

]

}

]

}

Release files for drf-dynamic-nav 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for drf-dynamic-nav 0.1.0
File Size Uploaded
drf_dynamic_nav-0.1.0.tar.gz 29.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for drf-dynamic-nav 0.1.0
File Interpreter ABI Platform
drf_dynamic_nav-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 45.0 kB

Release files / drf_dynamic_nav-0.1.0.tar.gz

Download URL drf_dynamic_nav-0.1.0.tar.gz
Size 29.3 kB
Tags Source
SHA-256 checksum
How to use checksums
1fbe0cde759b92cbddfbc1472f22d740ddee2214b72c7a85981ec5378d4be14a
BLAKE2b-256 checksum
How to use checksums
6618a63e9eae97a4931772bc294e1a9eb84587387201a71be10edcd17a82c304
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.0

Release files / drf_dynamic_nav-0.1.0-py3-none-any.whl

Download URL drf_dynamic_nav-0.1.0-py3-none-any.whl
Size 15.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
55c3b30c3f145a4eee466270c7189657882503f1ecf92b141a4d54e8fb34083e
BLAKE2b-256 checksum
How to use checksums
3d325fb233b0b65477381a5f923dbdbb5a56a477598f82e0b8be2621590c3c07
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.0

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page