django-ninja
django-ninja copied to clipboard
Nested routers w/ path parameters?
Use Case
I have several objects that belong to a certain customer, who has a unique customer_id. I want to include "/customers/{customer_id}/" as a prefix for all subsequent routers that handle the objects that belong to that customer. And I need the customer_id to ensure that the user/agent requesting access to these objects belongs to the same customer.
Example
In expense/api.py:
router = Router(tags=["Expense"])
router.add_router("customers/{customer_id}/", products_router)
In products/api.py:
router = Router(tags=["Products"])
@router.get("/products", response={200: List[Products], 400: Error})
def get_products(request: WSGIRequest, customer_id: int):
products = ProductService.get_products()
return 200, products
Error Response:
{
"detail": [
{
"loc": [
"query",
"customer_id"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}
How I want to solve it
I have created a Router object for each Django app, eg app_router, and I am adding each of those routers to the main Router object, eg main_router, using main_router.add_router("customers/{customer_id}", app_router). *(The router names are used just as an example).
Problem
I need to access the customer_id as a path parameter in my views. The problem is that the view does not recognize it as a path parameter, because it belongs to a higher level component, even though the endpoint is created correctly. When I include "/customers/{customer_id}/" directly to the same exact view, it obviously sees the path parameter and it works correctly. But this is an issue when you have a big number of endpoints that you need to include that.
Questions
I saw that FastAPI initially had a similar issue and the path parameters from nested routers were not included in the view level. But then they fixed that and included that functionality in a later release. Is there a way to do that with Django-Ninja already? If not, is it possible to include that to a later version? Is there a reason that this structure would not be advised? Thank you!
Not sure, but Maybe try to create a global auth
router = Router(tags=["Expense"], auth=AuthHere) # <---
router.add_router("customers/{customer_id}/", products_router)
Then inside of this
router = Router(tags=["Products"])
@router.get("/products", response={200: List[Products], 400: Error})
-def get_products(request: WSGIRequest, customer_id: int):
+def get_products(request: WSGIRequest):
+ user_id = request.auth
products = ProductService.get_products()
return 200, products
@eznix86 thanks for the reply, mate. But my issue isn't with the Auth. It's with accessing the path parameter of the prefix defined on the top-level Router. The customer and the user are not the same entity in my case.
Maybe the trick is to move the string
instead of /customers/{customer_id} (base) then /products (route)
Do
/customers (base) then /{customer_id}/products (route)
It is the only way. I guess.
@eznix86 yeah, that works, but it defeats the purpose of the prefix, because I have to append it to every endpoint. But thanks for your help, bud!
I agree with @akotsampaseris that this is a necessary feature for a future release
Hi @akotsampaseris
since router does not really know how it will be included you need to "help" it to understand where to get your customer_id parameter - try this:
from ninja import Path
router = Router(tags=["Products"])
def get_products(request: WSGIRequest, customer_id: int = Path(...)): # <--- Path
...
or customer_id: Path[int] if you use ninja 1.x
I ran into the same issue and can confirm, the solution here fixes it. Tysm!
This seems like something that needs to be in the documentation for nested routers. It is a common use case in REST APIs and unless someone stumbles on this issue it's not obvious at all how to get it to work.