django-ninja
django-ninja copied to clipboard
[BUG] url_name not accessible
Describe the bug After implementing two endpoints with same url-path (one for GET, one for POST), the first one is not included in urls. In tests, I can not call reverse on the name from the first one.
File: api.py
from ninja import NinjaAPI
api = NinjaAPI(urls_namespace="frontend:api")
@api.get("recordings", url_name="recording_list")
def recording_list(request):
pass
@api.post("recordings", url_name="recording_create")
def recording_create(request):
pass
Current behavior:
reverse("api:recording_create")
'/api/recordings'
This works, but:
reverse_lazy("recording_list")
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/bogus/Dev/greenstreams/django-ninja-reverse/.venv/lib/python3.10/site-packages/django/urls/base.py", line 88, in reverse
return resolver._reverse_with_prefix(view, prefix, *args, **kwargs)
File "/home/bogus/Dev/greenstreams/django-ninja-reverse/.venv/lib/python3.10/site-packages/django/urls/resolvers.py", line 802, in _reverse_with_prefix
raise NoReverseMatch(msg)
django.urls.exceptions.NoReverseMatch: Reverse for 'recording_list' not found. 'recording_list' is not a valid view function or pattern name.
raise an exception.
Expected behavior:
Both url_names are possible.
reverse("api:recording_create")
'/api/recordings'
reverse_lazy("recording_list")
'/api/recordings'
Versions:
- Python version: 3.10.3
- Django version: 4.0.4
- Django-Ninja version: 0.17.0
Package to reproduce
Can somewhat confirm. Something with URLs and namespaces is off it feels like. I cannot get any URLs to work correctly.
from ninja import NinjaAPI
api = NinjaAPI(urls_namespace="api")
@api.post("/test_fx", url_name="post")
def test_fx(request):
return {"post success": 200}
@api.get("/test_fx", url_name="get")
def test_fx(request):
return {"get success": 200}
>>> from django.urls import reverse_lazy
>>> reverse_lazy("api:post")
>>> Traceback (most recent call last):
>>> ...
>>> reverse_lazy("api:get")
>>> '/api/test_fx'
And btw: in my templates.html this wouldn't work at all (not specifying the url_name-attribute of course):
<button href = {% url 'api:test_fx' %}>MyButton </button>
I am finding this be an issue and somewhat counter intuitive when designing API's. I sense it's related to how the paths are registered as part of the Dict object in router.py. Ideally though the outcome should be that I should be able to declare my API like this:
@router.get('/', url_name="users-list")
@router.post('/', url_name="users-create")
@router.get('/{id}/', url_name="users-detail")
@router.put('/{id}/', url_name="users-update")
@router.delete('/{id}/', url_name="users-delete")
And all available url_names are available for reversal, not just the last one.
@limugob @fantasticle @bencleary
Well.. django-ninja uses django url resolver under the hood, which does not allow to set more than one name to url
so whenever you use THE SAME url for different http methods - only last one is applied
@router.get('/{id}/', url_name="users-detail")
@router.put('/{id}/', url_name="users-update")
@router.delete('/{id}/', url_name="users-delete")
as you can se here ^ all 3 operations are perfomed to one url /id/
@vitalik thanks for the response, i was sure i had the same URL configured for some views in Django turns out i don't so you can ignore me. It would be really nice if there was a way, but i guess that is a Django change not Ninja change. I withdraw my comments!
Hello @vitalik, thanks for this interesting lib, we use it already!
The described bug can not come directly from django url resolver, because in django it is possible to give the same url two separate names. In the given package above, there is a working example for this also.
@limugob
because in django it is possible to give the same url two separate names.
no, as I see it's there is only one name string argument for path function:
path(route, view, kwargs=None, name=None)
and no - you cannot create multiple same urls and give diferent names - because only first will match with resolver
Thanks, I see now, that even in django-rest-framework it is the same.