How to register DRF router url patterns in django 2 - django

My DRF routers specify a namespace so that I can reverse my urls:
urls.py:
router = DefaultRouter()
router.register('widget/', MyWidgetViewSet, base_name='widgets')
urlpatterns =+ [
url(r'/path/to/API/', include(router.urls, namespace='widget-api'),
]
Which, when upgrading to django 2, gives:
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.
Django 2 now requires app_name if the namespace kwarg is specified when using include. What's the right way to specify app_name when the url patterns are constructed by a DRF url router? I don't think the documentation is up-to-date for django 2 on this subject.

You need to put app_name = 'x' in your application's url.py file. This is a little buried in the documentation:
https://docs.djangoproject.com/en/2.0/topics/http/urls/#id5
For example, if in /project/project/urls.py you have:
path('', include('app.urls', namespace='app'))
Then in the corresponding url file (in /project/app/urls.py) you need to specify the app_name parameter with:
app_name = 'app' #the weird code
urlpatterns = [
path('', views.index, name = 'index'), #this can be anything
]

It's just necessary to use '{basename}-list' in reverse function.
In your case, it's going to be: reverse('widgets-list')

You need to include the router.urls as a tuple and add the app name to the tuple instead of only include router.urls
According to your example you should try with something like:
router = DefaultRouter()
router.register('widget/', MyWidgetViewSet, base_name='widgets')
urlpatterns =+ [
url(r'/path/to/API/', include((router.urls, 'my_app_name'), namespace='widget-api'),
]

The recommended approach is
from django.conf.urls import url, include
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'widget/', MyWidgetViewSet)
urlpatterns = [
url(r'^path/to/API/', include('rest_framework.urls', namespace='widget-api'))
]
See http://www.tomchristie.com/rest-framework-2-docs/tutorial/quickstart#urls

Related

How do you implement Django namespaces

I have started my journey with Django and I am thoroughly loving it. I do have a question about namespaces which I hope someone will be able to help me with and explain why it works like that.
I understand namespaces are used to make sure that if you have two pages with the same name that the url and reverse function points to the right page. I have been trying to implement namespaces in a test app I am writing but have not been able to do it.
Here is what I have so far (This is without namespaces as I haven't been able to get it to work.
Extract from app urls.py
from django.urls import path, re_path
from . import views
urlpatterns = [
path('', views.index, name = "index"),
]
project urls.py
from django.contrib import admin
from django.urls import path, include, re_path
import gallery
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('django.contrib.auth.urls')),
path('index/', include('gallery.urls')),
]
And lastly, this is my view.py file in my app folder
from django.shortcuts import render
from django.urls import reverse
# Create your views here.
def index(request):
return render(request,"gallery/index.html")
Any help will be appreciated
URL namespaces allow you to uniquely reverse named URL patterns even if different applications use the same URL names. It’s a good practice for third-party apps to always use namespaced URLs (as we did in the tutorial). Similarly, it also allows you to reverse URLs if multiple instances of an application are deployed. In other words, since multiple instances of a single application will share named URLs, namespaces provide a way to tell these named URLs apart. See URL namespaces
In your case:
project urls.py
from django.contrib import admin
from django.urls import path, include, re_path
import gallery
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('django.contrib.auth.urls', namespace='your-namespace')),
path('index/', include('gallery.urls',namespace='your-namespace')),
]
Extract from app urls.py
from django.urls import path, re_path
from . import views
app_name = 'app'
urlpatterns = [
path('', views.index, name = "index"),
]
in the template:
{% url 'app:index' %}

Provide the namespace argument to include() instead on django 3.0

In Django 3.0 this gives the error:
django.core.exceptions.ImproperlyConfigured: Passing a 3-tuple to include() is not supported. Pass a 2-tuple containing the list of patterns and app_name, and provide the namespace argument to include() instead.
How should I change url(r'^admin/', include(admin.site.urls))? I tried to look at the documentation,
from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^hello/', include(myapp.views)),
]
remove include from admin urls and use path
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('hello/', include('myapp.views')),
]
refer this django 3 doc.
The problem is with include(myapp.views):
First you should include a string (include('myapp.views')), not the actual module. That's what is causing the error.
But second, you don't include views, you include other url patterns. So unless myapp.views contains a top-level urlpatterns, you'll get an error as well.
Finally, as #c.grey pointed out, use path(), not url().

Replacing default forms in django-postman via urlpatterns and views

django-postman docs say that you can replace the default forms in views with this:
urlpatterns = patterns('postman.views',
# ...
url(r'^write/(?:(?P<recipients>[^/#]+)/)?$',
WriteView.as_view(form_classes=(MyCustomWriteForm, MyCustomAnonymousWriteForm)),
)
But what is patterns? Where do I import that from, and where would this code go? In the project's urls.py?
My project level urls.py currently includes django-postman, as recommended in the docs, like this:
urlpatterns = [
...
url("r'messages/', include('postman.urls', namespace='postman'),
]
So the custom url pattern should be overwriting the default that will already be included in urls.py.
Yes, this is a code for urls.py. However, it's quite outdated.
The modern version would look like:
from django.urls import re_path
urlpatterns = [
re_path(r'^write/(?:(?P<recipients>[^/#]+)/)?$',
WriteView.as_view(form_classes=(MyCustomWriteForm, MyCustomAnonymousWriteForm)),
]
Edit
I guess you're including postman urls in your root urls.py, then you can do something like this to overwrite one of them:
urlpatterns = [
...
re_path(r'^messages/write/(?:(?P<recipients>[^/#]+)/)?$',
WriteView.as_view(form_classes=(MyCustomWriteForm, MyCustomAnonymousWriteForm)),
path('messages/', include('postman.urls')),
]

How to fix "error path not found" in django rest framework

I am trying to build an API of my blogging website using Django rest framework, but my URL is not matching.
I am trying Django Rest framework for the first time so I am not quite able to fix this. But I think I mess this up in url_patterns.
Here is my URL code from the main directory(the directory which contains settings.py) .
`
from django.conf.urls import url,include
from django.contrib import admin
from django.urls import path, include
from blog import views
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'apipost',views.PostViewSet)
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'',include('blog.urls')),
path('api-auth/',include('rest_framework.urls',namespace='rest_framework')),
]
`
I am trying url http://127.0.0.1:8000/apipost and expect to get value in json format.
You need to add router.urls to your urlpatterns.
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'',include('blog.urls')),
path('api-auth/',include('rest_framework.urls',namespace='rest_framework')),
]
urlpatterns += router.urls
Django REST Framework Won't magically register your router in urlpatterns, you have to do it by yourself. You can use urlpatterns += router.urls if you want to add them to the root of your urlpatterns, or url(r'^api/', include((router.urls, 'app_name'))), if you want to set subpath for them.

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