# djangorestframework-hybridrouter
A router for ViewSets and APIViews, with a better browsable API and support for nested routers!
***Inspired by [this topic](https://stackoverflow.com/questions/18817988/using-django-rest-frameworks-browsable-api-with-apiviews/78459183#78459183).***
## Overview
The `HybridRouter` class is an extension of Django REST Framework's `DefaultRouter` that allows you to register both `ViewSet`s and `APIView`s. It provides more flexibility in managing your URL routes, offers a better browsable API experience, and supports nested routers.
## Features
- Register both `ViewSet`s and `APIView`s using a unified interface.
- Simplified URL patterns for better readability.
- Automatic creation of intermediate API views for grouped endpoints (configurable).
- Support for nested routers.
- Automatic conflict resolution for basenames.
## Installation
```bash
pip install djangorestframework-hybridrouter
```
## Usage
Here’s an example of how to use the `HybridRouter`:
```python
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
from django.urls import path, include
from hybridrouter import HybridRouter
class ServerConfigViewSet(ViewSet):
def list(self, request):
return Response({'a': 'b'})
class ServerConfigView(APIView):
def get(self, request):
return Response({'config': 'server'})
class ClientModsView(APIView):
def get(self, request):
return Response({'mods': 'client'})
class ServerModsView(APIView):
def get(self, request):
return Response({'mods': 'server'})
router = HybridRouter()
router.include_intermediate_views = True # Enable intermediate API views
# Register APIViews
router.register('server-config', ServerConfigView, basename='server-config')
router.register('mods/client', ClientModsView, basename='mods-client')
router.register('mods/server', ServerModsView, basename='mods-server')
# Register a ViewSet
router.register('coucou', ServerConfigViewSet, basename='coucou')
# Register more APIViews under 'coucou' prefix
router.register('coucou/client', ClientModsView, basename='coucou-client')
router.register('coucou/server', ServerModsView, basename='coucou-server')
urlpatterns = [
path('', include(router.urls)),
]
```
This configuration will generate URLs for both APIViews and ViewSets, and include intermediate API views for grouped endpoints.
## Documentation
**HybridRouter**
- `register(prefix, view, basename=None)`
Registers an `APIView` or `ViewSet` with the specified prefix.
- `prefix`: URL prefix for the view or viewset.
- `view`: The `APIView `or `ViewSet` class.
- `basename`: The base name for the view or viewset (optional). If not provided, it will be automatically generated.
- `register_nested_router(prefix, router)`
Registers a nested router under a specific prefix.
- `prefix`: URL prefix under which the nested router will be registered.
- `router`: The DRF router instance to be nested.
**Attributes**
- `include_intermediate_views` (default True)
Controls whether intermediate API views are automatically created for grouped endpoints. When set to True, the router will generate intermediate views that provide a browsable API listing of all endpoints under a common prefix.
**Notes**
- Automatic Basename Conflict Resolution
The `HybridRouter` automatically handles conflict resolution for `basenames`. If you register multiple `views` or `viewsets` with the same `basename`, it will assign unique `basenames` and log a warning.
- Trailing Slash
The `HybridRouter` uses a configurable trailing_slash attribute, defaulting to "/?" to match DRF’s `SimpleRouter` behavior.
## Advanced Features
### Custom Intermediary API Views
The `HybridRouter` can automatically create custom intermediary API views for grouped endpoints. This feature improves the organization of your API and provides a cleaner browsable interface.
**Example:**
```python
router = HybridRouter()
router.include_intermediate_views = True # Enable intermediate API views
router.register('server-config', ServerConfigView, basename='server-config')
router.register('server-config/map', ServerConfigView, basename='server-config')
router.register('server-config/health', ServerConfigView, basename='server-config')
router.register('mods/client', ClientModsView, basename='mods-client')
router.register('mods/server', ServerModsView, basename='mods-server')
```
With `include_intermediate_views` set to True, the router will create intermediate views at the `mods/` prefix, providing a browsable API that lists both `client` and `server` endpoints under `mods/`. Also note that here for `server-config/` endpoints an intermediate view will not be created because it's already registered.
### Nested Routers
You can register nested routers under a specific prefix using `register_nested_router`. This allows you to include routers from other apps or create a complex URL structure.
**Example:**
```python
from rest_framework.routers import DefaultRouter
nested_router = DefaultRouter()
nested_router.register('items', ItemViewSet, basename='item')
router = HybridRouter()
router.register_nested_router('nested/', nested_router)
urlpatterns = [
path('', include(router.urls)),
]
```
In this example, all routes from nested_router will be available under the `nested/` prefix.
## Experimental Features
The automatic creation of intermediary API views is a feature that improves the browsable API experience. This feature is still in development and may not work as expected in all cases. Please report any issues or suggestions.
```python
router = HybridRouter(enable_intermediate_apiviews=False)
router.register('server-config', ServerConfigView, name='server-config')
router.register('mods/client', ClientModsView, name='mods-client')
router.register('mods/server', ServerModsView, name='mods-server')
router.register('coucou/client', ClientModsView, name='coucou-client')
router.register('coucou/server', ServerModsView, name='coucou-server')
router.register('coucou', ServerConfigViewSet, basename='coucou')
```
With this configuration of the router with `enable_intermediate_apiviews`set to `False`, the intermediary API views will not be created. So the browsable API will look like on a `DefaultRouter` :
![image](./docs/imgs/Before.png)
But if you set `enable_intermediate_apiviews` to `True`, the intermediary API views will be created and the browsable API will look like this:
```python
router = HybridRouter(enable_intermediate_apiviews=True)
```
![image](./docs/imgs/After_1.png)
![image](./docs/imgs/After_2.png)
This improves the readability and the logic of the browsable API and provides a better user experience.
And as you can see that will not interfere with other already existing views. **Here, the `ServerConfigViewSet` is still accessible through the `coucou` endpoint and as not been overridden by an intermediary API view.**
## Testing
The package includes comprehensive tests to ensure reliability. Here are some highlights:
- Registering Views and ViewSets
Tests registering both APIViews and ViewSets, ensuring that URLs are correctly generated and accessible.
- Intermediate Views
Tests the creation of intermediate views when include_intermediate_views is enabled or disabled.
- Nested Routers
Tests registering nested routers and ensuring that their routes are correctly included under the specified prefix.
- Basename Conflict Resolution
Tests automatic conflict resolution when multiple views or viewsets are registered with the same basename.
## Notes
- Compatibility
The HybridRouter is designed to work seamlessly with Django REST Framework and is compatible with existing DRF features like schema generation.
- Spectacular Support
Will be added in the future. *After some manual test, it seems that the `spectacular` package is compatible with the `HybridRouter`. But that may need some more testing.*
Raw data
{
"_id": null,
"home_page": null,
"name": "djangorestframework-hybridrouter",
"maintainer": null,
"docs_url": null,
"requires_python": "<4,>=3.8",
"maintainer_email": null,
"keywords": "django, rest, framework, router, hybrid, views and viewsets",
"author": "enzo_frnt",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/2f/5e/955f7ee262adfc3f047ab4730c51074ed7ea30fc53fbf2f74b1abcb9d809/djangorestframework_hybridrouter-1.0.2.tar.gz",
"platform": null,
"description": "# djangorestframework-hybridrouter\n\nA router for ViewSets and APIViews, with a better browsable API and support for nested routers!\n\n***Inspired by [this topic](https://stackoverflow.com/questions/18817988/using-django-rest-frameworks-browsable-api-with-apiviews/78459183#78459183).***\n\n## Overview\n\nThe `HybridRouter` class is an extension of Django REST Framework's `DefaultRouter` that allows you to register both `ViewSet`s and `APIView`s. It provides more flexibility in managing your URL routes, offers a better browsable API experience, and supports nested routers.\n\n## Features\n\n- Register both `ViewSet`s and `APIView`s using a unified interface.\n- Simplified URL patterns for better readability.\n- Automatic creation of intermediate API views for grouped endpoints (configurable).\n- Support for nested routers.\n- Automatic conflict resolution for basenames.\n\n## Installation\n\n```bash\npip install djangorestframework-hybridrouter\n```\n\n## Usage\n\nHere\u2019s an example of how to use the `HybridRouter`:\n\n```python\nfrom rest_framework.views import APIView\nfrom rest_framework.response import Response\nfrom rest_framework.viewsets import ViewSet\nfrom django.urls import path, include\nfrom hybridrouter import HybridRouter\n\n\nclass ServerConfigViewSet(ViewSet):\n def list(self, request):\n return Response({'a': 'b'})\n\nclass ServerConfigView(APIView):\n def get(self, request):\n return Response({'config': 'server'})\n\nclass ClientModsView(APIView):\n def get(self, request):\n return Response({'mods': 'client'})\n\nclass ServerModsView(APIView):\n def get(self, request):\n return Response({'mods': 'server'})\n\nrouter = HybridRouter()\nrouter.include_intermediate_views = True # Enable intermediate API views\n\n# Register APIViews\nrouter.register('server-config', ServerConfigView, basename='server-config')\nrouter.register('mods/client', ClientModsView, basename='mods-client')\nrouter.register('mods/server', ServerModsView, basename='mods-server')\n\n# Register a ViewSet\nrouter.register('coucou', ServerConfigViewSet, basename='coucou')\n\n# Register more APIViews under 'coucou' prefix\nrouter.register('coucou/client', ClientModsView, basename='coucou-client')\nrouter.register('coucou/server', ServerModsView, basename='coucou-server')\n\nurlpatterns = [\n path('', include(router.urls)),\n]\n```\n\nThis configuration will generate URLs for both APIViews and ViewSets, and include intermediate API views for grouped endpoints.\n\n## Documentation\n\n**HybridRouter**\n\n- `register(prefix, view, basename=None)`\n\n Registers an `APIView` or `ViewSet` with the specified prefix.\n\n - `prefix`: URL prefix for the view or viewset.\n - `view`: The `APIView `or `ViewSet` class.\n - `basename`: The base name for the view or viewset (optional). If not provided, it will be automatically generated.\n- `register_nested_router(prefix, router)`\n\n Registers a nested router under a specific prefix.\n\n - `prefix`: URL prefix under which the nested router will be registered.\n - `router`: The DRF router instance to be nested.\n\n**Attributes**\n\n- `include_intermediate_views` (default True)\n\n Controls whether intermediate API views are automatically created for grouped endpoints. When set to True, the router will generate intermediate views that provide a browsable API listing of all endpoints under a common prefix.\n\n**Notes**\n\n- Automatic Basename Conflict Resolution\n\n The `HybridRouter` automatically handles conflict resolution for `basenames`. If you register multiple `views` or `viewsets` with the same `basename`, it will assign unique `basenames` and log a warning.\n\n- Trailing Slash\n\n The `HybridRouter` uses a configurable trailing_slash attribute, defaulting to \"/?\" to match DRF\u2019s `SimpleRouter` behavior.\n\n\n## Advanced Features\n\n### Custom Intermediary API Views\n\nThe `HybridRouter` can automatically create custom intermediary API views for grouped endpoints. This feature improves the organization of your API and provides a cleaner browsable interface.\n\n**Example:**\n\n```python\nrouter = HybridRouter()\nrouter.include_intermediate_views = True # Enable intermediate API views\n\nrouter.register('server-config', ServerConfigView, basename='server-config')\nrouter.register('server-config/map', ServerConfigView, basename='server-config')\nrouter.register('server-config/health', ServerConfigView, basename='server-config')\nrouter.register('mods/client', ClientModsView, basename='mods-client')\nrouter.register('mods/server', ServerModsView, basename='mods-server')\n```\n\nWith `include_intermediate_views` set to True, the router will create intermediate views at the `mods/` prefix, providing a browsable API that lists both `client` and `server` endpoints under `mods/`. Also note that here for `server-config/` endpoints an intermediate view will not be created because it's already registered.\n\n### Nested Routers\n\nYou can register nested routers under a specific prefix using `register_nested_router`. This allows you to include routers from other apps or create a complex URL structure.\n\n**Example:**\n\n```python\nfrom rest_framework.routers import DefaultRouter\n\nnested_router = DefaultRouter()\nnested_router.register('items', ItemViewSet, basename='item')\n\nrouter = HybridRouter()\nrouter.register_nested_router('nested/', nested_router)\n\nurlpatterns = [\n path('', include(router.urls)),\n]\n```\n\n\nIn this example, all routes from nested_router will be available under the `nested/` prefix.\n\n## Experimental Features\n\nThe automatic creation of intermediary API views is a feature that improves the browsable API experience. This feature is still in development and may not work as expected in all cases. Please report any issues or suggestions.\n\n```python\nrouter = HybridRouter(enable_intermediate_apiviews=False)\n\nrouter.register('server-config', ServerConfigView, name='server-config')\nrouter.register('mods/client', ClientModsView, name='mods-client')\nrouter.register('mods/server', ServerModsView, name='mods-server')\nrouter.register('coucou/client', ClientModsView, name='coucou-client')\nrouter.register('coucou/server', ServerModsView, name='coucou-server')\nrouter.register('coucou', ServerConfigViewSet, basename='coucou')\n```\n\nWith this configuration of the router with `enable_intermediate_apiviews`set to `False`, the intermediary API views will not be created. So the browsable API will look like on a `DefaultRouter` :\n\n![image](./docs/imgs/Before.png)\n\nBut if you set `enable_intermediate_apiviews` to `True`, the intermediary API views will be created and the browsable API will look like this:\n\n```python\nrouter = HybridRouter(enable_intermediate_apiviews=True)\n```\n\n![image](./docs/imgs/After_1.png)\n![image](./docs/imgs/After_2.png)\n\nThis improves the readability and the logic of the browsable API and provides a better user experience.\n\nAnd as you can see that will not interfere with other already existing views. **Here, the `ServerConfigViewSet` is still accessible through the `coucou` endpoint and as not been overridden by an intermediary API view.**\n\n## Testing\n\nThe package includes comprehensive tests to ensure reliability. Here are some highlights:\n\n- Registering Views and ViewSets\nTests registering both APIViews and ViewSets, ensuring that URLs are correctly generated and accessible.\n\n- Intermediate Views\nTests the creation of intermediate views when include_intermediate_views is enabled or disabled.\n\n- Nested Routers\nTests registering nested routers and ensuring that their routes are correctly included under the specified prefix.\n\n- Basename Conflict Resolution\nTests automatic conflict resolution when multiple views or viewsets are registered with the same basename.\n\n## Notes\n\n- Compatibility\n\n The HybridRouter is designed to work seamlessly with Django REST Framework and is compatible with existing DRF features like schema generation.\n\n- Spectacular Support\n\n Will be added in the future. *After some manual test, it seems that the `spectacular` package is compatible with the `HybridRouter`. But that may need some more testing.*\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Django app that provides a hybrid router for Django Rest Framework",
"version": "1.0.2",
"project_urls": {
"Documentation": "https://github.com/enzofrnt/djangorestframework-hybridrouter/blob/main/README.md",
"Issues": "https://github.com/enzofrnt/djangorestframework-hybridrouter/issues",
"Source Code": "https://github.com/enzofrnt/djangorestframework-hybridrouter"
},
"split_keywords": [
"django",
" rest",
" framework",
" router",
" hybrid",
" views and viewsets"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "8da98bd2730dc097d7e641f1a88616f1253ff238fe3fc5861ec1c08c968f0cfa",
"md5": "dbacb1913d09afd6ba8015d9dbb659b7",
"sha256": "90eff07858ea2dd4158862da15968b8a33d8f3cd471a267b2e31a0162e3d9405"
},
"downloads": -1,
"filename": "djangorestframework_hybridrouter-1.0.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "dbacb1913d09afd6ba8015d9dbb659b7",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4,>=3.8",
"size": 9158,
"upload_time": "2024-12-12T12:15:06",
"upload_time_iso_8601": "2024-12-12T12:15:06.133406Z",
"url": "https://files.pythonhosted.org/packages/8d/a9/8bd2730dc097d7e641f1a88616f1253ff238fe3fc5861ec1c08c968f0cfa/djangorestframework_hybridrouter-1.0.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "2f5e955f7ee262adfc3f047ab4730c51074ed7ea30fc53fbf2f74b1abcb9d809",
"md5": "479dfb6f91b9ebb6b3a0c26362031841",
"sha256": "de8ac5791daa1edbefe6d62f1d30535dd7764a7ceb5e13ef100349b167e09805"
},
"downloads": -1,
"filename": "djangorestframework_hybridrouter-1.0.2.tar.gz",
"has_sig": false,
"md5_digest": "479dfb6f91b9ebb6b3a0c26362031841",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4,>=3.8",
"size": 10909,
"upload_time": "2024-12-12T12:15:08",
"upload_time_iso_8601": "2024-12-12T12:15:08.197921Z",
"url": "https://files.pythonhosted.org/packages/2f/5e/955f7ee262adfc3f047ab4730c51074ed7ea30fc53fbf2f74b1abcb9d809/djangorestframework_hybridrouter-1.0.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-12-12 12:15:08",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "enzofrnt",
"github_project": "djangorestframework-hybridrouter",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"tox": true,
"lcname": "djangorestframework-hybridrouter"
}