Django REST Framework router prepending period on url reverse - django

I have a ModelViewSet registered in a router
from django.conf.urls import url, include
from rest_framework import routers
from . import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
]
So i try to reverse the url with a primary key
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
user = User.objects.get(pk=1)
url_with_args = reverse('user-list', args=[user.pk])
url_with_kwargs = reverse('user-list', kwargs={'format':user.pk})
but the value of the urls is
url_with_args == '/api/users/.1'
url_with_kwargs == '/api/users/.1'
why is there a period in the primary key value?
I have also tried the same process with
rest_framework.reverse.reverse
but the values returned values are:
'/api/users/.1'

The issue here was that you were using user-list instead of user-detail for the named URL. user-list is meant to go to the list view /api/resource while user-detail is meant to go to the detail view /api/resource/1.
The reason why user-list was giving you /api/users/.1 is because the regex that it is matching is /api/users/.(?<format>) where format is the optional string that can be things like xml or json and control what renderer is used`.
The regex for user-detail is /api/users/(?<pk>) where pk is a non-optional parameter used for matching the primary key when retrieving the object.

Related

Django ulr does not accept query string

I have a view in Django that redirects a user to some url with aditional query string:
return redirect('search/?print_id='+str(pk))
In my urls.py, the url pattern looks like this:
path('search/', views.search, name='search'),
And the problem is when Django tries to redirect, I get the following error:
Reverse for 'search/?print_id=36' not found. 'search/?print_id=36' is not a valid view function or pattern name.
How can I modify the url pattern to accept a query string?
redirect will try to look for a view with that name. You should work with a :
from django.http import HttpResponseRedirect, QueryDict
from django.urls import reverse
# …
qd = QueryDict(mutable=True)
qd['print_id'] = pk
return HttpResponseRedirect(f"{reverse('search')}?{qd.urlencode()}")

<int:pk> not working inside router in Django REST framework

I've the following code inside urls.py:
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import SentenceListViewSet, SentenceViewSet
router = DefaultRouter()
router.register('lists', SentenceListViewSet, basename='SentenceList')
router.register('lists/<int:pk>/sentences/', SentenceViewSet, basename='Sentence')
app_name = 'api_app'
urlpatterns = [
path('', include(router.urls), name='lists')
]
It's the second router registry that's causing problem. I get "page not found" if I navigate to localhost:8000/lists/8/sentences. However, I can access localhost:8000/lists/<int:pk>/sentences.
How can I make DRF capture 8 as a URL parameter instead of int:pk getting treated as a literal part of the URL?

Django - reverse nested url with drf-nested-routers

I configured my api url as
localhost:port/app_name/students/{student_id}/macro/{macro_id}/lto
using drf-nested-routers extension. Basically, each students has some macro categories assigned, that in turns have some Long Term Objectives (LTOs). I've tested it using curl and Postman and everything seems to work.
Now I need to write a more precise test case for my LTO model.
This is my urls.py
from django.urls import path, re_path
from django.conf.urls import include
from rest_framework import routers
from app_name.views.views import UserViewSet, StudentViewSet, MacroViewSet, LTOViewSet, MacroAssignmentViewSet
from rest_framework_nested import routers as nested_routers
# application namespace
app_name = 'app_name'
router = routers.DefaultRouter()
router.register(r'users', UserViewSet, basename='user')
router.register(r'macro', MacroViewSet, basename='macro')
router.register(r'macro-assignments', MacroAssignmentViewSet, basename='macro-assignment')
student_router = routers.DefaultRouter()
student_router.register(r'students', StudentViewSet, basename='student')
lto_router = nested_routers.NestedSimpleRouter(student_router, r'students', lookup='student')
lto_router.register(r'macro/(?P<macro_pk>.+)/lto', LTOViewSet, basename='lto')
urlpatterns = [
re_path('^', include(router.urls)),
re_path('^', include(student_router.urls)),
re_path('^', include(lto_router.urls)),
]
The issue is that I cannot use the reverse() method correctly to get the url of my LTOViewSet to test it.
self.url = reverse('app_name:student-detail:lto', {getattr(self.student, 'id'), getattr(self.macro, 'id')})
This gives the following error
django.urls.exceptions.NoReverseMatch: 'student-detail' is not a registered namespace inside 'app_name'
In other test cases, I use very similar sentences and those work fine
self.list_url = reverse('app_name:student-list')
reverse('app_name:student-detail', {post_response.data['id']})
So here's the minimally reproducible example:
# main/viewsets.py
from rest_framework.viewsets import ModelViewSet
from django.contrib.auth.models import User, Group
class StudentViewSet(ModelViewSet):
model = User
class LTOViewSet(ModelViewSet):
model = Group
# main/urls.py
from django.urls import re_path, include
from rest_framework import routers
from rest_framework_nested import routers as nested_routers
from .viewsets import StudentViewSet, LTOViewSet
# application namespace
app_name = "main"
student_router = routers.DefaultRouter()
student_router.register(r"students", StudentViewSet, basename="student")
lto_router = nested_routers.NestedSimpleRouter(
student_router, r"students", lookup="student"
)
lto_router.register(r"macro/(?P<macro_pk>.+)/lto", LTOViewSet, basename="lto")
urlpatterns = [
re_path("^", include(student_router.urls)),
re_path("^", include(lto_router.urls)),
]
reverse('main:lto-detail', args=(1,1,1))
Out[5]: '/api/students/1/macro/1/lto/1/'
So indeed your error was passing just the router basename not a final endpoint to reverse and because of the nesting we were thrown off by student-detail not reversing (which I still don't get).

Django Rest Framework GET request with params

I'm trying to make a get request with params (srcFilename) in Django Rest Framework. I'm pretty confused about where to add the "req.query.srcFilename" (as it would be in javascript) in django. I read I have to add the complete url with the params in "<>" as shown in the code below, but it wont find the url.
views.py:
#api_view(['GET'])
def api_generate_signed_url(request, srcFilename):
print(f'srcFilename: {srcFilename}')
bucket = storage_client.bucket(bucket_name)
blob = bucket.blob(srcFilename)
if request.method == 'GET':
url = blob.generate_signed_url(
version="v4",
# This URL is valid for 15 minutes
expiration=datetime.timedelta(minutes=15),
# Allow GET requests using this URL.
method="GET",
)
print(f"Generated GET signed URL: {url}")
return Response(url)
urls.py:
from django.urls import include, path
from rest_framework import routers
from .views import api_generate_signed_url
router = routers.DefaultRouter()
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path(r'signedurl?srcFilename=<srcFilename>', api_generate_signed_url),
]
When trying this in Postman I get the following error:
The current path, signedurl, didn't match any of these.
Postman screenshot
You have to change your path as below...
path('signedurl', api_generate_signed_url),
No need to write ?srcFilename=<srcFilename>. Just remove it.
Then in your view access your request parameters through the QueryDict request.query_params.get('srcFilename').

How to reverse path for custom user when testing Django Rest Framework

I'm building a Django 3 app using a Custom User. I'm trying to test the Custom User, but I can't get the url using reverse. I'm using Django Rest Framework. I'm using Django Rest Auth.
myapp.api.urls.py:
app_name = 'myApp'
from django.urls import include, path, re_path
urlpatterns = [
...
path('users/', include('users.urls'), name='UsersURLS'),
]
myapp.users.urls.py
from django.urls import include, path
from . import api
app_name = 'users' # namespace for reverse
urlpatterns = [
path('', api.UserListView.as_view(), name='UsersPath'),
]
myapp.users.api.py
class UserListView(generics.ListCreateAPIView):
queryset = models.CustomUser.objects.all()
serializer_class = serializers.UserSerializer
authentication_classes = (TokenAuthentication,)
And in test_users_api.py:
from users.api import UserListView
user_detail_url = reverse('myApp:UsersURLS-list')
...
def test_user_detail(self):
self.client.force_authenticate(user=self.user)
response = self.client.post(user_detail_url, {'pk': self.user.id }, format='json')
Whatever I try for the reverse, I get an error that is it not a valid view or function name. So far I have tried:
reverse('myApp:UsersURLS-list')
reverse('users:UsersPath-list')
reverse(UserListView)
reverse('UsersPath')
I'd be grateful for any idea what I'm doing wrong, and how to get the reverse URL. I have it working for the rest of my endpoints which use router, but I can't see what's needed for my Custom User, which I set up following a tutorial and it works fine otherwise.
I found an answer in the rest-auth source code, at venv36/lib/python3.6/site-packages/rest_auth/tests/test_api.py, and mixins.py in the same folder. I don't know why my attempts to reverse fail, but this works:
user_detail_url = reverse('rest_user_details')
All names for reverse can be found in the mixins.py file.