Django url collisions - django

Using Django==2.2.11, djangorestframework==3.8.1
Thank you for reading!
The urls I am using that have the collision:
urlpatterns = [
. . .
url(
r'^some-path$',
views.MyViewSet.as_view({'get': 'list'})
),
url(
r'^some-path$',
views.MyViewSet.as_view({'post': 'create'}),
),
...
]
I am using postman to test each path, and it seems like there is a collision between these two urls.
Using this url with a GET, would work:
http://my_domain.com:8000/some-path
But POST with same url (and with a valid payload) would throw error:
WARNING 2020-03-28 19:13:57,288 "POST /some-path HTTP/1.1" 405 41
And response:
{"detail": "Method \"POST\" not allowed."}
I urls are swapped in order, then the POST would work, and GET would throw a similar error.
I looked at this post:
405 POST method not allowed
I would gladly add the view code - but I am pretty sure the issue is with the urls, since they each work when swapped order.
Will add it upon request.
Thank you!
EDIT: I confused the urls- added the retrieve instead of list sorry!

If you are pointing to same end-point, ie /some-path, you should add your extra actions as,
urlpatterns = [
url(r'^some-path$', MusicianViewset.as_view({'post': 'create', 'get': 'list'})),
]

Related

Django REST Framework tutorial sends Page Not Found on part 1

I'm new to Django and API developing, so I started following Django REST Framework tutorial, and I have an error in the first part of it.
When I go to "127.0.0.1:2000/" on my browser, the Api Root page appears (I hosted it on port 2000), so it seems to be working, but when I try to go to "127.0.0.1:2000/snippets/", I get a Page Not Found error.
I imagine this is a very simple thing I'm just overlooking, but I'm kind of stuck right now, and would appreciate the help. Should my "tutorial/urls.py" include the snippets in any way? I followed the tutorial from the beginning, and re-viewed it, so I don't think so, but its a hypothesis.
According to the tutorial, my tutorial\urls.py (tutorial being the project name) looks like this:
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
And the app's (snippets\urls.py) like this:
urlpatterns = [
path('snippets/', views.snippet_list),
path('snippets/<int:pk>/', views.snippet_detail),
]
Did you add your URLs from the app you created to the main URLs .py?

Misconfigured custom HTTP error templates in Django project (404,500)

I am in Django HTTP error codes hell. Would be great if an expert can help me out of my misconfiguration.
My Django project runs with nginx as a reverse proxy coupled to a gunicorn application server.
Requirement:
I want a custom Page not found template to render (i.e. 404) when a url pattern is entered that doesn't exist in my urls.py. Sounds simple enough, and is well documented.
I have already gone ahead and implemented this.
The Problem:
Assume example.com is my live project.
1) If I try to access https://example.com/asdfasdf (i.e. unmatched, random gibberish) on my production server, it displays the 500 template instead of 404.
2) Next, if I try to curl the said url pattern via curl -I https://example.com/asdfasdf/, I see 200 OK instead of 404 or 500. Wth?
3) Moreover, if I try the same behavior with Debug = True on localhost, 404 is returned correctly (both template and HTTP error code are in consonance).
These 3 behaviors are quite perplexing.
My configuration:
I created error_views.py and inserted it in the folder where I keep my regular views.py. This error file contains:
from django.shortcuts import render
def server_error(request):
return render(request, '500.html')
def not_found(request):
return render(request, '404.html')
def permission_denied(request):
return render(request, '404.html')
def bad_request(request):
return render(request, '404.html')
In my urls.py (kept in the same folder as settings.py), I added the following after all url patterns:
handler404 = 'my_app.error_views.not_found'
handler500 = 'my_app.error_views.server_error'
handler403 = 'my_app.error_views.permission_denied'
handler400 = 'my_app.error_views.bad_request'
I created 404.html and 500.html, and inserted them in the default /templates/ directory.
In settings.py, I have ALLOWED_HOSTS = ['*']
Lastly, my nginx conf dealing with this is as follows (placed within the server block in the virtual host file):
# Error pages
error_page 500 502 503 504 /500.html;
location = /500.html {
root /home/ubuntu/this_proj/project_dir/templates/;
}
location = /too_bad.svg {
root /home/ubuntu/this_proj/project_dir/static/img/;
}
All of this is fairly regular stuff and I'm missing what I've misconfigured here. Can an expert guide me out of this mess?
Thanks in advance, and please ask for more information in case warranted.
Note: I tried solutions provided in similar questions on SO here and here. Needless to say, those misconfigurations were very different, displaying none of the symptoms I'm seeing.
If you use a custom handler, you have to explicitly set the proper http status for the response object. If you don't set the status, the default is 200 OK.
def not_found(request):
return render(request, '404.html', status=404)

ExtJS 5 application + Django rest framework CORS error when changing URL of store

I am developing a ExtJS application that uses a Django-rest-framework service. I am using CORS headers to allow fetching the data from the service (https://github.com/OttoYiu/django-cors-headers).
What happens is that at a point in time I want to change the URL from the store. And when I do that I get the following error:
XMLHttpRequest cannot load http://10.98.0.241:8000/reacsearch/as?_dc=1418831884352&page=1&start=0&limit=25. The request was redirected to 'http://10.98.0.241:8000/reacsearch/as/?_dc=1418831884352&page=1&start=0&limit=25', which is disallowed for cross-origin requests that require preflight.
In the settings.oy I define the following properties for the CORS
CORS_ALLOW_METHODS = (
'GET',
'OPTIONS'
)
CORS_ORIGIN_ALLOW_ALL = True
This works fine when I use URLs to list all the elements in my database, however when I change the store for another URL I get the error above. Also the link works fine in the browser.
The store url change is made this way:
var store = Ext.getStore(storeName);
store.getProxy().setUrl(newURL);
store.load();
The difference between the views, is that the two that work on the application are viewsets, while the other is just a generic list
class Example1viewset(viewsets.ModelViewSet):
"""
API endpoing that allows metabolites to be viewed.
"""
queryset = examples1.objects.all()
serializer_class = Example1Serializer
class Example1SearchList(generics.ListAPIView):
serializer_class = Example1Serializer
def get_queryset(self):
queryset = Example.objects.all()
if 'attr' in self.kwargs:
queryset = queryset.filter(Q(attribute1__contains=self.kwargs['attr']) | Q(attribute2__contains=self.kwargs['abbr']))
return queryset
Like I mentioned both examples work fine in the browser (even accessing through other computers in the network), however in the application when changing the URL of the store I get the CORS error. Does anyone has any idea why this is happening?
Thank you.
Edit:
Just for clarification, the problem is not in changing the url of the store. As I tried to set those urls as defaults, but they are not working when accessing from the application.
My urls.py file:
router = routers.DefaultRouter()
router.register(r'example', views.Example1ViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^reacsearch/(?P<attr>.+)/$', Example1SearchList.as_view()),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
Can it be that the problem is related with the fact that I am not adding the search list to the router?
Edit2
Problem solved since I was trying to fetch data from a different domain. I changed the type of store to jsonp in Extjs, and I also allowed my rest service to render data as jsonp.
Just a reminder if anyone comes accross this same problem, it is necessary to add ?format=jsonp to the store url:
http://my/url/?format=jsonp
Since it looks like an alternate solution was found, I'll explain what the issue appeared to be as well as why the alternative works.
XMLHttpRequest cannot load first url. The request was redirected to 'second url', which is disallowed for cross-origin requests that require preflight.
The issue here is that you are telling Django to enforce the trailing slash, which makes it automatically redirect urls without a trailing slash to urls with a trailing slash, assuming that one exists. This is why, as stated in the error, the request was redirected to the second url, which you can tell has the missing trailing slash. This is controlled by the APPEND_SLASH Django setting which is True by default.
The problem is that when CORS is doing a preflight request, which is what allows it to determine if the request can be made, there must be a valid response at the requested URL. Because you are redirecting the request, the preflight request fails and you're stuck without your information.
You can fix this by adding the trailing slash in your code. There appear to be a few solutions for doing this with ext, but I personally can't recommend a specific one. You can also manually set the url to use the trailing slash, which sounds like what you were doing previously.
Or you can use JSONP...
You've found the alternative solution, which is to use JSONP to make the request instead of relying on CORS. This gets around the preflight issue and works in all major browsers, but there are some drawbacks to consider. You can find more information on CORS vs JSONP by looking around.
You're going to need CORS if you want to push any changes to your API, as JSONP only supports GET requests. There are other advantages, such as the ability to abort requests, that also comes with CORS.

How to make a POST simple JSON using Django REST Framework? CSRF token missing or incorrect

Would appreciate someone showing me how to make a simple POST request using JSON with Django REST framework. I do not see any examples of this in the tutorial anywhere?
Here is my Role model object that I'd like to POST. This will be a brand new Role that I'd like to add to the database but I'm getting a 500 error.
{
"name": "Manager",
"description": "someone who manages"
}
Here is my curl request at a bash terminal prompt:
curl -X POST -H "Content-Type: application/json" -d '[
{
"name": "Manager",
"description": "someone who manages"
}]'
http://localhost:8000/lakesShoreProperties/role
The URL
http://localhost:8000/lakesShoreProperties/roles
DOES work with a GET request, and I can pull down all the roles in the database, but I can not seem to create any new Roles. I have no permissions set. I'm using a standard view in views.py
class RoleDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Role.objects.all()
serializer_class = RoleSerializer
format = None
class RoleList(generics.ListCreateAPIView):
queryset = Role.objects.all()
serializer_class = RoleSerializer
format = None
And in my urls.py for this app, the relevant url - view mappings are correct:
url(r'^roles/$', views.RoleList.as_view()),
url(r'^role/(?P<pk>[0-9]+)/$', views.RoleDetail.as_view()),
Error message is:
{
"detail": "CSRF Failed: CSRF token missing or incorrect."
}
What is going on here and what is the fix for this? Is localhost a cross site request? I have added #csrf_exempt to RoleDetail and RoleList but it doesn't seem to change anything. Can this decorator even be added to a class, or does it have to be added to a method?
Adding the #csrf_exempt decorate, my error becomes:
Request Method: POST
Request URL: http://127.0.0.1:8000/lakeshoreProperties/roles/
Django Version: 1.5.1
Exception Type: AttributeError
Exception Value:
'function' object has no attribute 'as_view'
Then I disabled CSRF throughtout the entire app, and I now get this message:
{"non_field_errors": ["Invalid data"]} when my JSON object I know is valid json. It's a non-field error, but I'm stuck right here.
Well, it turns out that my json was not valid?
{
"name": "admin",
"description": "someone who administrates"
}
vs
[
{
"name": "admin",
"description": "someone who administrates"
}
]
Having the enclosing brackets [], causes the POST request to fail. But using the jsonlint.com validator, both of my json objects validate.
Update: The issue was with sending the POST with PostMan, not in the backend. See https://stackoverflow.com/a/17508420/203312
CSRF is exempted by default in Django REST Framework. Therefore, curl POST request works fine. POSTMAN request call returned CSRF incorrect because POSTMAN included csrf token if it is found in Cookies. You can solve this by cleaning up Cookies.
It's from your REST Framework settings. in your settings.py file, your REST_FRAMEWORK should have the following.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
}
This will set your REST Framework to use token authentication instead of csrf authentication. And by setting the permission to AllowAny, you can authenticate only where you want to.
You probably need to send along the CSRF token with your request. Check out https://docs.djangoproject.com/en/1.7/ref/contrib/csrf/#csrf-ajax
Update: Because you've already tried exempting CSRF, maybe this could help (depending on which version of Django you're using): https://stackoverflow.com/a/14379073/977931
OK, well now of course I take back what I said. CSRF does work as intended.
I was making a POST request using a chrome plugin called POSTMAN.
My POST request fails with CSRF enabled.
But a curl POST request using
curl -X POST -H "Content-Type: application/json" -d '
{
"name": "Manager",
"description": "someone who manages"
}' http://127.0.0.1:8000/lakeshoreProperties/roles/
works fine...
I had to take off the braces, i.e., [], and make sure there is a slash after the 's' in roles, i.e., roles/, and csrf enabled did not throw any errors.
I'm not sure what the difference between calling using POSTMAN is vs using curl, but POSTMAN is run in the web browser which is the biggest difference. That said, I disabled csrf for the entire class RoleList but one identical request works with Curl, but fails with POSTMAN.
To give an update on current status, and sum up a few answers:
AJAX requests that are made within the same context as the API they are interacting with will typically use SessionAuthentication. This ensures that once a user has logged in, any AJAX requests made can be authenticated using the same session-based authentication that is used for the rest of the website.
AJAX requests that are made on a different site from the API they are communicating with will typically need to use a non-session-based authentication scheme, such as TokenAuthentication.
Therefore, answers recommending to replace SessionAuthentication with TokenAuthentication may solve the issue, but are not necessarily totally correct.
To guard against these type of attacks, you need to do two things:
Ensure that the 'safe' HTTP operations, such as GET, HEAD and OPTIONS cannot be used to alter any server-side state.
Ensure that any 'unsafe' HTTP operations, such as POST, PUT, PATCH and DELETE, always require a valid CSRF token.
If you're using SessionAuthentication you'll need to include valid CSRF tokens for any POST, PUT, PATCH or DELETE operations.
In order to make AJAX requests, you need to include CSRF token in the HTTP header, as described in the Django documentation.
Therefore, it is important that csrf is included in header, as for instance this answer suggests.
Reference: Working with AJAX, CSRF & CORS, Django REST framework documentation.
As you said your URL was
http://localhost:8000/lakesShoreProperties/roles
Postman has some issues with localhost.
Sending the POST to 127.0.0.1:8000/your-api/endpoint instead did the trick for me.
the old Postman is having a problem with csrf tokens because it does not working with cookies.
I suggest for you to switch to the new version of postman, it works with cookies and you will not face this problem again.
if you have set AllowAny permission and you facing with csrf issue
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny'
]
}
then placing following in the settings.py will resolve the issue
REST_SESSION_LOGIN = False

Login Required Middleware gets HTTP/1.1" 302

I've done a simple login that works fine while the authenticated user is redirected to a page with url:
url(r'^(?P<user_id>\d+)/$', 'auth.views.main', name='main'),
Now I'm trying to use a LoginRequired-Middleware but when I do and I try to login I get:
"POST /login/ HTTP/1.1" 302 0
"GET /1000/ HTTP/1.1" 302 0
and I remain to the initial login-page.
I use a common snippet for doing that with
LOGIN_URL = ( '/login/' )
What's going wrong?
Unfortunately your provided code is insufficient to determine your error, so I can only give you pointers:
Make sure you aren't actually still caching an HTTP 302 REDIRECT site, from earlier experimenting. See here for useful hints.
Reset your browser-cache. If you are using Chrome, you can get several cache clearance options by pressing CTRL+SHIFT+J (starting the Developer Tools), and long pressing the Reload button next to the top navigation url-bar.
For Firefox, see also these useful suggestions.
http://support.mozilla.org/es/questions/848678
https://superuser.com/questions/23134/how-to-turn-off-firefox-cache
Did you just upgrade Django from version <1.3.x? I came across a situation where passwords were rewritten with a new default-Hasher, and thus one could no longer log in. Check password-hasher consistency directly in your database, within the auth_users - table. For instance SHA1 password-hashes start with sha1...
Make sure to setup the list PASSWORD_HASHERS in your settings.py, is in an order that retains the original project's primary Hasher on top. i.e. the hashing-algorithm that was used to initially hash the user passwords.
Is the url(r'...'...) in the root urls.py of your project?
Make sure 'django.contrib.auth' is listed in your INSTALLED_APPS section of settings.py (though you should get error's if that is not the case) and apps that must be loaded beforehand, are actually placed uppermost.
Check the setup of your TEMPLATE_CONTEXT_PROCESSORS, MIDDLEWARE_CLASSES in settings.py according to your snippet's comments and make sure it is compatible with the SessionMiddleware of your Django version.
Check all codelines in your project that make redirects:
Check for:
from django.views.generic.base import RedirectView
url(r'^.*$', RedirectView.as_view(url='<url_to_view>', permanent=False), name='index')
or
from django.http import HttpResponsePermanentRedirect
HttpResponsePermanentRedirect('url...')
Note that in Django 1.5 the deprecated redirect_to function has been replaced by RedirectView, according to their generic view paradigm.
redirect(...)
url('^pattern/$', lambda _: redirect('/redirecttourl/'))
url(r'^pattern$', redirect('example.com')),
url(r'^pattern$', redirect(projectname.views.home)),
(r'^accounts/profile/$', 'redirect_to', {'url': 'generic_account_url'}),
HttpResponseRedirect(....)
HttpResponseRedirect(request.META.get('HTTP_REFERER','/'))
Django's built-in authentication login page includes a next query string. next= determines the the page to return to after login:
See: http://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.decorators.login_required
Check your server's HTTP access logs. Is the content-size of the login-site after the redirect the same as the login before the redirect?
It helps providing the entire HTTP access history during the login process. As you do not hang in an redirect-loop, more HTTP's than the two mentioned must follow.