django-ninja icon indicating copy to clipboard operation
django-ninja copied to clipboard

Nested routers w/ path parameters?

Open akotsampaseris opened this issue 1 year ago • 9 comments

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!

akotsampaseris avatar Jan 18 '24 10:01 akotsampaseris

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 avatar Jan 18 '24 22:01 eznix86

@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.

akotsampaseris avatar Jan 22 '24 08:01 akotsampaseris

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 avatar Jan 22 '24 08:01 eznix86

@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!

akotsampaseris avatar Jan 22 '24 10:01 akotsampaseris

I agree with @akotsampaseris that this is a necessary feature for a future release

almeida-raphael avatar Jan 29 '24 21:01 almeida-raphael

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
    ...

vitalik avatar Jan 30 '24 09:01 vitalik

or customer_id: Path[int] if you use ninja 1.x

vitalik avatar Jan 30 '24 09:01 vitalik

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.

Marclev78 avatar Mar 06 '24 23:03 Marclev78