Argument is not getting mapped in REST endpoint method in django - python

I have following code in my MyModuleViewSet:
class MyModuleViewSet(viewsets.ModelViewSet):
# ...
#action(detail=False, permission_classes=(IsOwnerOrReadOnly,))
#debugger_queries
def my_rest_api(self, request, pk=None):
# ...
return HttpResponse(resp, content_type="application/json", status=status.HTTP_200_OK)
It is registered in my urls.py as follows:
router.register(r'mymodule', MyModuleViewSet)
I tried to hit this end point with following link:
http://0.0.0.0:3000/myproject/api/mymodule/1019/my_rest_api/?format=json&param1=7945
But it gave error saying:
Page not found (404)
Request Method: GET
Request URL: http://0.0.0.0:3000/myproject/api/mymodule/1019/my_rest_api/?format=json&param1=7945
Using the URLconf defined in myproject.urls, Django tried these URL patterns, in this order:
1. ...
2. ...
...
37. courseware/ api/ ^mymodule/my_rest_api/$ [name='mymodule-my-rest-api']
38. courseware/ api/ ^mymodule/my_rest_api\.(?P<format>[a-z0-9]+)/?$ [name='mymodule-my-rest-api']
...
As you can see mymodule/my_rest_api is indeed specified in the response on line 37 and 38. So, I tried with different url by removing /1019:
http://0.0.0.0:3000/myproject/api/mymodule/1019/my_rest_api/?format=json&param1=7945
and it hit the breakpoint inside the rest endpoint method. I was thinking that 1019 will get assigned to pk argument inside my_rest_api(self, request,pk=None). But that did not happen. Why is this the case? Did I miss some basic understanding of how DRF works?

if your action looks like this
#action(detail=False, permission_classes=(IsOwnerOrReadOnly,))
your url can't be http://0.0.0.0:3000/myproject/api/mymodule/1019/my_rest_api/
set detail=True and add pk in def my_rest_api(self, pk, request):
or remove id from your url to http://0.0.0.0:3000/myproject/api/mymodule/my_rest_api/

Related

Django REST Framework how to allow DELETE only without pk

I need to create an endpoint:
DELETE /users (deleted ids in the request body)
So, i created a class UserViewSet, that extends mixins.DestroyModelMixin and GenericViewSet and implemented the method def delete(self, request), it works for request DELETE /users, but the problem is that user can send DELETE /users/5 request and then i got an error 'Not found` (user with the current pk is not found, because i don't have it).
I want to forbid send DELETE /users/{id} requests and allow only DELETE /users.
How can i do this?
I tried to use decorator #action(methods=['delete'], detail=True) for the delete method, but its doesn't change anything.
Also i tried to use this signature:
def delete(self, request, pk=None):
if pk:
return Response(data='PK is not allowed in DELETE method')
For some reason it doesn't even match this method with DELETE/{id} and i still get the error 'Not found'.
UPD. I delete mixins.DestroyModelMixin from the extend classes and now DELETE /users works but DELETE/{id} gives me 404 (Page not found) when i try to send DELETE with id.
I would like something like method not allowed
UPD2.
Code:
def destroy(self, request, pk=None):
raise exceptions.MethodNotAllowed('DELETE with PK')
Works for me, but i think this is the bad solution...
You get 404 (Page not found) because you don't have /users/<int:id> in your URLConf.

How to set up URL aliases for dynamically created URLs in DJANGO?

In my site, I have pages that are created on the fly using primary keys (for privacy/security reasons using uuid.uuid4()) as the URLs. I end up with .../reports/e657334b-75e2-48ce-8571-211251f1b341/ Is there a way to make aliases for all of these dynamically created sites to something like .../reports/report/.
Right now my urls.py includes the following:
path("reports/<str:pk>/", views.report_detail, name="report")
I tried changing it to:
re_path('reports/<str:pk>/', RedirectView.as_view(url='/reports/report/'), name="report"),
path("reports/report/", views.report_detail),
But I go to the site that has the links to the long URLs, I get the following error:
NoReverseMatch at /reports/Reverse for 'report' with arguments '('e657334b-75e2-48ce-8571-211251f1b341',)' not found. 1 pattern(s) tried: ['reports/str:pk/']
The template for that site includes:
<a class="card-title" href="{% url 'report' report.pk%}">
I also tried the following for urls:
path("reports/report/", views.report_detail),
path('reports/<str:pk>/', RedirectView.as_view(url='reports/report/'), name="report"),
Which allowed the previous site to load, but when I clicked on the link got the following 404 error:
Request URL: http://127.0.0.1:8000/reports/e657334b-75e2-48ce-8571-211251f1b341/reports/report/
I am trying to have one alias for multiple pages - essentially removing/replacing the long uuid with a single word.
Without trying to make an alias, the site works fine.
If you really don't want to pass the pk/uuid of the report to the shortened url, you could pass it in the session
Create a custom RedirectView that saves the pk to the session and then read that pk in the target view
class ReportRedirect(RedirectView):
def get(self, request, pk):
request.session['report_pk'] = pk
return super().get(request, pk)
def report_detail(request):
report_pk = request.session['report_pk']
...
You use the custom RedirectView just like the built-in one
path("reports/report/", views.report_detail),
path('reports/<str:pk>/', views.ReportRedirect.as_view(url='/reports/report/'), name="report"),

Django: redirecting a view and using include()

I'm essentially trying to make my URLs more readable by incorporating a slug that is not used for lookup purposes in my views. Previously, I had this in my main URLconf:
urls.py
path('<int:team_pk>/', TeamDetails.as_view(), name='team_details'),
path('<int:team_pk>/', include('tracker.urls', namespace='tracker')),
So I had urls like mysite/1/stuff and mysite/1/more/stuff.
Then I added a slug field to my Team model. The behavior I want is for any url that only includes a team_pk to be redirected to an expanded URL that incorporates the slug. For example, mysite/1/ should redirect to mysite/1/team_1_slug and mysite/1/stuff should redirect to mysite/1/team_1_slug/stuff. It's this latter behavior that I'm having trouble with.
I modified my URLconf to this:
path('<int:team_pk>/', TeamRedirectView.as_view(), name='team_details'),
path('<int:team_pk>/<slug:team_slug>/', TeamDetails.as_view(), name='team_details_redirected'),
path('<int:team_pk>/<slug:team_slug>/', include('tracker.urls', namespace='tracker')),
Then I have a simple view to redirect:
# views.py
class TeamRedirectView(generic.RedirectView):
def get_redirect_url(self, *args, **kwargs):
team = get_object_or_404(models.Team, pk=self.kwargs['team_pk'])
return reverse('team_details_redirected', kwargs={'team_pk': team.pk, 'team_slug': team.slug})
This works for redirecting mysite/1/ to mysite/1/team_1_slug, but any url tag that points to a url inside the tracker app throws a reverse error because I'm not supplying team_slug to the url tag.
So I'm basically trying to get my RedirectView to be a little more universal and add this slug to any url without having to manually supply it to every url tag. Is that doable?

Django (DRF) fails to POST data to url with similar path

In my url conf I have two similar patterns:
urlpatterns = [
path('chat/', views.chat), # create chat
path('chat/message/', views.message), # create message
]
The second path works as expect, however, when I try to POST data to chat/ I get error 405 and {"detail":"Method \"POST\" not allowed."} error message. The code in the view works, if I modify chat/ to something more specific like chat/create/ then everything works fine. However, this is not what I want to do. I thought django would match the first URL that matches the request. Why is this happening? It this bug or expected behavior?
I have run into a similar issue. I created a new nested_route decorator that acts a lot like list_route and detail_route. I usually redirect to another viewset to handle the nested path. The issue was that the stream was being read too early (by the parent viewset dispatch), so I needed to ensure that the initialize_request function was only called once for a given request.
Working off of #rsalmaso's comment above, I overrode the initialize_request method in the children viewsets with the following.
def initialize_request(self, request, *args, **kwargs):
if not isinstance(request, Request):
request = super().initialize_request(request, *args, **kwargs)
return request
This works fine, but I think having some sort of attribute for either ignoring the initialize_request function within dispatch or having a global check to only run initialize_request iff isinstance(request, rest_framework.request.Request) == False. I'm happy to prepare the PR with tests if that could be acceptable.

Redirects clashing with URL patterns Django

I'm trying to get Django's redirects app working, it works with some of the redirects I have in the database, but not all of them.
The ones that match with one of my url patterns 404 without redirecting, the rest are fine.
urlpatterns = [
...
url(r'^(?P<category>[-\w]+)/$', views.SubCategoryListView.as_view(),
name='category_list'),
url(r'^(?P<category>[-\w]+)/(?P<slug>[-\w]+)/$',
views.content_or_sub_category, name='choice')
...
]
For example, say the URL 'example.com/foo/bar' is supposed to redirect. It would match the second url pattern above, get sent to content_or_sub_category, which checks whether 'foo' is a valid category or not, and if 'bar' is a valid slug. Upon finding that they're not, it 404s (as in the code below) and doesn't redirect.
In the docs it says
Each time any Django application raises a 404 error, this middleware checks the redirects database for the requested URL as a last resort.
https://docs.djangoproject.com/en/1.8/ref/contrib/redirects/
But instead of redirects kicking in any time a 404 is raised it appears to only happen if Django doesn't find a matching pattern.
Is that how it's supposed to be behaving?
I found this question Raise 404 and continue the URL chain about how to resume evaluating the rest of the url patterns if one 404s, but it looks like that's either something you can't or shouldn't do.
The only answer on that question suggested putting some logic in your urls.py, but that would need to check the validity of the URL, and then make the urlpatterns list according to whether it's valid or not. I googled and couldn't find a way to do that from within urls.py.
I'm using Django 1.8.
class SubCategoryListView(ListView):
model = Content
queryset = Content.objects.published()
paginate_by = 15
def get_queryset(self):
qs = super(SubCategoryListView, self).get_queryset()
if 'category' in self.kwargs:
if self.kwargs['category'] is not None:
qs = qs.filter(
categories__slug__contains=self.kwargs['category'])
return qs
def get_context_data(self, **kwargs):
context = super(SubCategoryListView, self).get_context_data(**kwargs)
if 'category' in self.kwargs and self.kwargs['category'] is not None:
context['category'] = get_object_or_404(Category,
slug=self.kwargs[
'category'])
return context
...
def content_or_sub_category(self, **kwargs):
sub_category = get_object_or_false(Category.objects.sub_categories(),
slug=kwargs['slug'])
content = get_object_or_false(Content.objects.published(),
slug=kwargs['slug'])
if content:
return ContentDetailView.as_view()(self, **kwargs)
if sub_category:
return ContentListView.as_view()(self,
**{'category': kwargs['category'],
'sub_category': kwargs['slug']})
raise Http404
Some redirects that don't work:
/46421 --> /economy/has-prime-minister-broken-promise-tax-credits/
/are-living-standards-on-the-rise --> /economy/are-living-standards-rise/
/articles/nhs_budget_cut-28646 --> /economy/has-nhs-budget-been-cut/
But something like this does work:
/health/live/2015/jan/number_12_hour_wait_hospital_bed_accident_emergency-38409 --> /health/spike-numbers-waiting-12-hours-e-hospital-bed/
Not sure what return ContentDetailView.as_view()(self, **kwargs) is, but in general if you want to redirect, you would use something along the lines of:
return redirect(reverse('category_list', args=[ARG1, ARG2]))
Where "category_list" is the name of the url pattern, and ARG1 & ARG2 are the arguments you want in the url.

Categories

Resources