Router not displaying correct URL in Django RestFramework? - django

This is how I defined urls.py file of my app
router = DefaultRouter()
router.register('hello-viewset', views.HelloViewSet, base_name='hello-viewset')
router.register('profiles', views.UserProfileViewSet)
router.register('schema', views.SchemaViewSet)
router.register('creddefination', views.CredDefViewSet)
router.register('overalltable', views.OverallViewSet)
urlpatterns = [
path('', include(router.urls)),
]
urls.py of Project:
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('DIAPI.urls')),
]
I am not getting correct address for creddefination. But when i manually go to http://127.0.0.1:7000/api/creddefination/ it is working. It is just not displaying correctly. What might be reason for this

I guess views.CredDefViewSet and views.OverallViewSet are using the same model.
If that's true, then the default register's basename will be named after that model and used as name in a call to Django's reverse url construction. Since the API Root view will be trying to resolve both views with the same name, it'll lead to the same url.
Workaround is to explicitly add a basename to one of the view:
router.register('creddefination', views.CredDefViewSet, basename='creddeef')

Related

How to write a urls.py in django so that I can do something like */page

Here is the problem:
I have an app with the following models: project, position, outreach
A position is connected to a project and project only with a Foreign key
An outreach is connected to a position and a position only with a Foreign key
I can create a new project from almost anywhere in my app (same for the other objects). Currently I wrote that a new project is created from the url dashboard/newjobproject but I would to make it so that depending on the page that I am, the url simply becomes something like www.myapp.com/..../newproject
What's a way to write the urls.py to achieve that?
from django.urls import path
from action import views
app_name = 'action'
urlpatterns = [
# ex: /action/
path('', views.login, name='login'),
path('dashboard/', views.dashboard, name='dashboard'),
path('contacts/', views.contacts, name='contacts'),
path('projects/', views.project, name='project'),
path('contacts/newcontact', views.new_contact, name='new_contact'),
path('projects/newjobproject', views.new_outreach, name='new_outreach'),
path('dashboard/newjobproject', views.new_jobproject, name='new_jobproject'),
path('projects/<uuid>/newjobposition', views.new_jobposition, name='new_jobposition'),
]
However,
Try adding this to the bottom of urlpatterns:
path('<path:p>/newjobproject', views.new_jobproject, name='whatever-name-you-want'),
and in your views.py:
def new_jobproject(request, p):
Tbh though, this is sort of a hacky way to do it. It'll break in a few locations. If you have a main urlpatterns array in which you're including the urls for this 'action' app as APIs, this solution won't work outside the API urls.
For eg. if you have:
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('action.urls')),
]
And you access your url like this -> www.myapp.com/api/v1/....../newjobproject, then the code will work. If you try to access www.myapp.com/..../newjobproject or www.myapp.com/admin/..../newjobproject then the code will break and it will not match any of the paths. I'm not sure how you'd get it to work in that case.
If the above scenario is not an issue, if you're not going to be using these views as APIs, and your urlpatterns looks something like this:
urlpatterns = [
path('admin', admin.site.urls),
path('/', include('action.urls')),
]
then the code should work for all urls except for the admin/.../newjobproject case.

Django Rest Framework: incorrect hyperlink on second viewset of same model

I'm trying to provide two distinct APIs using DRF but I'm unable to get the second app to stop creating
hyperlinked references based on the first. It's essentially the same problem as Django Rest Framework with multiple Viewsets and Routers for the same object but I'm unable to get it working.
app1/urls.py:
router = SimpleRouter(trailing_slash=False)
router.register(prefix=r'article', viewset=app1.ArticleViewSet, basename=r'article')
urlpatterns = [path(f'', include(router.urls)]
app2/urls.py:
router = SimpleRouter(trailing_slash=False)
router.register(prefix=r'published', viewset=app2.ArticleViewSet, basename=r'published')
urlpatterns = [path(f'', include(router.urls)]
site/urls.py:
urlpatterns = [
path('app1/', include('app1.urls')),
path('app2/', include('app2.urls')),
]
While both viewsets are of the same model, the queryset & serializer for each is different.
When I GET an item from /app2/published, it has an app1 URL:
"url": "http://localhost:8000/app1/article/5461"
What I'm wanting is for items retrieved via app2 to have:
"url": "http://localhost:8000/app2/published/5461"
From looking at the docs, it appears that providing basename should do what I want, but I'm not having any luck with getting it to work.
Try the following code in your site/urls.py:
from app1.urls import router as app1_router
from app2.urls import router as app2_router
router = routers.DefaultRouter()
router.registry.extend(app1_router.registry)
router.registry.extend(app2_router.registry)
urlpatterns = [
path('', include(router.urls)), # default page to show api
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
You can see an example here, which has same structure as you need.

Django url/route order not maintained

I have the following in my root URLconf module (there's more, but not important, so left out):
urlpatterns = [
re_path(r'^password-reset-redirect-view/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
password_reset_redirect,
name = 'password_reset_confirm'),
path('', include('search.urls')),
path('', include('customer_portal.urls')),
path('rest-auth/', include('rest_auth.urls')),
path('rest-auth/registration/', include('rest_auth.registration.urls')),
Here's the customer_portal.urls:
urlpatterns = [
path('customer/contact/', views.contact),
path('', views.home),
re_path(r"^confirm-email/(?P<key>[-:\w]+)/$", views.email_verification,
name="account_confirm_email"),
]
Here's the rest_auth.registration.urls:
urlpatterns = [
url(r'^$', RegisterView.as_view(), name='rest_register'),
url(r'^verify-email/$', VerifyEmailView.as_view(), name='rest_verify_email'),
url(r'^account-confirm-email/(?P<key>[-:\w]+)/$', TemplateView.as_view(),
name='account_confirm_email'),
]
As you can see both included urls.py urlpatterns have a view named 'account_confirm_email'.
Somewhere in the code this is ran:
url = reverse(
"account_confirm_email",
args=[emailconfirmation.key])
Since customer_portal.urls is included before rest_auth.registration.urls, I expect the route account_confirm_email in customer_portal.urls to be returned by the above reverse method. But instead I get the rest_auth.registration.urls route URL.
Just to be sure I commented out the route in rest_auth.registration.urls, and then I did get the correct URL (customer_portal URL) returned.
It is filled into an email, I check that email and see that I have the wanted url: http://127.0.0.1:8000/confirm-email/......./, instead of: http://127.0.0.1:8000/rest-auth/registration/account-confirm-email/...../
Can anyone tell me why the customer_portal URL isn't the one being reversed in both cases?
Django docs say:
Django runs through each URL pattern, in order, and stops at the first one that matches the requested URL.

Is it okay to have multiple url patterns with the same view?

I have an app that shows a list of "Issues". The main urls.py file sends /issues/ to the urls.py file in the "issues" app.
urlpatterns = [
path('', RedirectView.as_view(url='/issues/')),
path('admin/', admin.site.urls),
path('issues/', include('issues.urls')),
]
In the issues app's urls.py file I have:
path('', views.IssueListView.as_view(), name='issue-list'),
That calls the IssueListView which is a generic ListView view:
class IssueListView(generic.ListView):
model = Issue
Now, I want to add a sidebar menu with links users can click to sort the issues list by category. I understand that I can rewrite the get_queryset() method of the IssueListView to accept a kwarg, and just load all the issues if that kwarg is missing (with an if statement that checks for the presence of the kwarg, right?), but I think to do this I need to have two urlpatterns that point to the same view like:
path('', views.IssueListView.as_view(), name='issue-list'),
path('<category>', views.IssueListView.as_view(), name='issue-category-list'),
But I am wondering if that's the normal "Django way" to do it.
Thank you.
In the case you have specified, you can use the same view for both url pattern. There is nothing wrong in that.
path('', views.IssueListView.as_view(), name='issue-list'),
path('<category>', views.IssueListView.as_view(), name='issue-category-list'),

How to register DRF router url patterns in django 2

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