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

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.

Related

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

Django DRF with NameSpace api versioning. Fix to NoReverseMatch or ImproperlyConfigured error

I want to version my api, but cannot make reverse function work.
I am following the namespace versioning schema proposed in the DRF website : namespaceversioning
I have one app called authentication and inside authentication folder I have :
authentication/
|
-- models.py, apps.py, admin.py
-- api_v1/
|
-- views.py
-- serializers.py
-- urls.py
In my main urls.py, I've defined
urlpatterns = [
url(r'admin/', admin.site.urls),
url(r'^api/v1/',include
('my_project.apps.authentication.api_v1.urls',namespace='v1'),
('my_project.apps.posts.api_v1.urls',namespace='v1')
),
url(r'^$', lambda _: redirect('http://localhost:8888/')),
]
This is authentication/api_v1/urls.py
from rest_framework.routers import DefaultRouter
from authentication.api_v1.views import UserViewSet
app_name = 'authentication'
router = DefaultRouter()
router.register(r'users', UserViewSet, base_name='authentication-user')
urlpatterns = router.urls
And when I execute
./manage.py show_urls
/api/v1/users/ authentication.api_v1.views.UserViewSet v1:authentication-user-list
/api/v1/users/<pk>/ authentication.api_v1.views.UserViewSet v1:authentication-user-detail
When I am trying to reverse for example from shell, I have the following error:
> reverse('v1:authentication-user-detail', kwargs={'pk': '5'})
NoReverseMatch: Reverse for 'authentication-user-detail' not found. 'authentication-user-detail' is not a valid view function or pattern name.
> reverse('authentication:v1:posts-job-detail', kwargs={'pk': '5'})
NoReverseMatch: 'v1' is not a registered namespace inside 'authentication'
> reverse('v1:authentication:posts-job-detail', kwargs={'pk': '5'})
NoReverseMatch: 'authentication' is not a registered namespace inside 'v1'
BUT, if I do not put namespace='v1' in the app urls, like this:
url(r'^api/v1/',include('my_project.apps.authentication.api_v1.urls')
Then the reverse functions works
> reverse('authentication:authentication-user-detail', kwargs={'pk':5})
> '/api/v1/users/5/'
I think I am calling the reverse in the wrong way, or maybe some configuration?
Because if I call the api for example via postman, the endpoint is working ok.
UPDATE:
I think the problem is I have same namespace for two entries in my main urls.py
I will late check it. I think the solution is have only one entry in my main urls, and move all the rest to another file.
UPDATE 2
I think (i am not sure if its correct) I make it work like this.
In my main urls.py
api_v1 =[
url(r'^api/v1/',
include('my_project.apps.agreements.api_v1.urls')),
url(r'^api/v1/',
include('my_project.apps.authentication.api_v1.urls')) ]
urlpatterns = [
url(r'', include((api_v1,'v1'), namespace="v1")),
]
If instead, in the url patterns I include like this:
urlpatterns = [
url(r'', include(api_v1, namespace="v1")),
]
I have the following error:
'Specifying a namespace in include() without providing an app_name '
django.core.exceptions.ImproperlyConfigured: Specifying a namespace in include() without providing an app_name is not supported. Set the app_name attribute in the included module, or pass a 2-tuple containing the list of patterns and app_name instead.
The thing is that 'v1' as the second argument, the "app_name", in fact its not an app name, I just put it there to not have the error....
Also now, the reverse I have to make it like this (it works):
reverse('v1:authentication:authentication-user-detail', kwargs={'pk': '5'})
'/api/v1/users/5/'
And not like I wanted that is:
reverse('v1:authentication-user-detail' . .
UPDATE 3
For the last problem, I solved it commenting app_name inside the url file from the specific app. So it would be
# app_name = 'authentication'
router = DefaultRouter()
router.register(r'users', UserViewSet, base_name='authentication-user')

Changing a old Django URL to the new paths

So I am making a new site in Django 2.0 and was following this tutorial on making a user registration form with an activation email and my understanding of the new Django 2 is not good enough so was asking what would be the Django 2 equivalent of this URL
url(r'^activate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', views.activate, name='activate'),
There is no straight conversion for your path you could either use a converter as stated in the docs to convert the token. Here the example from the docs:
class FourDigitYearConverter:
regex = '[0-9]{4}'
def to_python(self, value):
return int(value)
def to_url(self, value):
return '%04d' % value
register the converter
from django.urls import path, register_converter
from . import converters, views
register_converter(converters.FourDigitYearConverter, 'yyyy')
urlpatterns = [
path('articles/2003/', views.special_case_2003),
path('articles/<yyyy:year>/', views.year_archive),
...
]
or you can just regex the path like you currently are:
from django.urls import path, re_path
from . import views
urlpatterns = [
path('articles/2003/', views.special_case_2003),
re_path(r'^activate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', views.activate, name='activate')
]
I would just stick to the regex using re_path since you know it works and its already done.
Here is the link to the docs:
https://docs.djangoproject.com/en/2.0/topics/http/urls/

404 Error while accessing API with tastypie

Just started with TastyPie to expose the data. Trying to tie together resources using tastypie.Api for urls.py.
But I get this error when I try to access them through localhost:api/**resource.
my api.py:
from tastypie.resources import ModelResource
from idg.models.molecule_dictionary import MoleculeDictionary
class MoleculeDictionaryResource(ModelResource):
class Meta:
queryset = MoleculeDictionary.objects.all()
# resource_name = 'moleculedictionary'
my url.py:
from django.conf.urls import url, include, patterns
from idg.api import MoleculeDictionaryResource
from django.contrib import admin
from tastypie.api import Api
from . import views
dictionary_resource = MoleculeDictionaryResource()
# private_api = Api(api_name='private')
# private_api.register(MoleculeDictionaryResource(), canonical=True)
urlpatterns = [
url(r'^$', views.index, name='index'),
# url(r'^exports/', include('data_exports.urls', namespace='data_exports')),
url(r'^api/', include(dictionary_resource.urls)),
]
Error:
Page not found (404)
Request Method: GET
Request URL: http://127.0.0.1:8000/api/moleculedictionary/?format=json
Using the URLconf defined in django_root.urls, Django tried these URL patterns, in this order:
^__debug__/
xadmin/
^idg/
^comments/
^admin/
The current URL, api/moleculedictionary/, didn't match any of these.
You're seeing this error because you have DEBUG = True in your Django settings file. Change that to False, and Django will display a standard 404 page.
Any suggestions ? I am not sure where I made a mistake. I have followed the tutorial (https://django-tastypie.readthedocs.org/en/latest/tutorial.html)
Uncomment line
resource_name = 'moleculedictionary'
in Your api.py file.
Try this:
private_api = Api(api_name='v1')
private_api.register(MoleculeDictionaryResource(), canonical=True)
url(r'^api/', include(private_api.urls)),
Then you should be able to access it with /api/v1/