Django ajax warning on same page after DoesNotExist exception on form POST - ajax

I have a django form attached to a view. In the form a user types in a query which is passed to a Model.objects.get( query ) like so:
def post(self, request):
try:
Model.objects.get(query)
except Model.DoesNotExist:
# something here
Upon exception i'd like to send an ajax request to my template that stops it from refreshing, and displays a warning to the user that there's nothing in the database matching that get request. What would I put in the view and the template?

The http standard response would be a 404 response. Django has a shortcut function for this: get_object_or_404
def post(self, request):
my_object = get_object_or_404(Model, query)
If the lookup fails, django will raise an 404 error, which will result in a 404 http response back to the client. In your javascript ajax handling code, you should check the http status, and handle any 404 responses appropriately.
For example, if you are using the fetch api, the code might look like this.
fetch('/some/url/?query=foobar').then(response => {
if (response.ok) return response.json()
if (response.status == 404) throw new Error('404')
})

Related

Django testing - test the view which handles an ajax call

In testing Django, the view is returning 200 code but not sending any error message related.
def ajax_view(request):
msg = ''
if request.is_ajax():
username = request.POST['username']
user = User.objects.get(username=username)
msg = 'user exists'
return HttpResponse(msg)
In tests.py
response = self.client.post(reverse('ajax_view'), data={'username': 'hello'})
self.assertEqual(200, response.status_code)
self.assertContains(response, 'exist')
It seems it is not going through the request.is_ajax().. How can I mock the ajax call in Django testing?
The docs on the test client mention this; you need to pass the HTTP_X_REQUESTED_WITH header, which you can do as a keyword argument.
Also, if you pass content_type as 'application/json', Django will automatically serialize to JSON. So:
response = self.client.post(
reverse('ajax_view'),
data={'username': 'hello'},
content_type='application/json',
HTTP_X_REQUESTED_WITH='XMLHttpRequest'
)
Not entirely sure this will resolve the entire issue but your method may be expecting the data in a json format:
json_data = json.dumps({'username': 'hello'})
response = self.client.post(reverse('ajax_view'), data=json_data)

How to restrict the ajax call from out side of the browser in django

i am working in a project, there is no user authentication and authorization. Bascially i am calling ajax in client side and it executes a view in django and return a json out. How can i validate this request is only coming from browser and how to restrict the if this not coming from the browser or any manual script?
You can use request.is_ajax() method
HttpRequest.is_ajax()
Returns True if the request was made via an XMLHttpRequest, by checking the HTTP_X_REQUESTED_WITH header for the string 'XMLHttpRequest'. Most modern JavaScript libraries send this header. If you write your own XMLHttpRequest call (on the browser side), you’ll have to set this header manually if you want is_ajax() to work.
If a response varies on whether or not it’s requested via AJAX and you are using some form of caching like Django’s cache middleware, you should decorate the view with vary_on_headers('HTTP_X_REQUESTED_WITH') so that the responses are properly cached.
docs
Im updating my answer to fit what we commented above
In your views
from django.core import signing
from django.views.generic import View, TemplateView
from django.http import HttpResponseBadRequest
class BrowserView(TemplateView):
template_name = 'yourtemplate.html'
def get_context_data(self, **kwargs):
ctx = super(BrowserView, self).get_context_data(**kwargs)
ctx['token'] = signing.dumps(self.request.session_id)
return ctx
class AjaxView(View):
def get(self, *args, **kwargs):
if self.request.is_ajax():
try:
sign = signing.loads(self.request.GET.get('token'), max_age=(20))
if sign == self.request.session_id:
## return ajax
return HttpResponseBadRequest('You are not authorized to see this page')
except signing.BadSignature:
return HttpResponseBadRequest('You are not authorized to see this page')
else:
return HttpResponseBadRequest('You are not authorized to see this page')
In your template
In this case I used a meta tag, but you get the idea
<meta name="validation" content="{{token}}" />
In your javascript
var t = document.querySelector("meta[name='validation']").getAttribute('content');
$.ajax({
url:'yoururl',
data: yourData + '&token=' + t,
type: 'get',
success: function(response){
// do whatever
},
error: function(e){
console.log(e);
}
});
I don't believe it's possible to 100% prevent this, but there are some things you can do:
a set-cookie header w/some unique ID on the page, but not on API responses.
if the cookie isn't received by your API, return a 401.
tracking API calls per unique ID could be a good indicator of "proper" usage.
associate IDs w/IPs.
the tracking metrics can be combined w/a threshold that blocks requests if exceeded.
you can check the referrer header (easy to spoof).
finally, lookup the is_ajax method of Django's, but this just checks for an XMLHttpRequest header (again, easy to spoof).

WebApi response codes

So let's say I have a WebApi controller called UsersController. Let's look at the following examples:
1.Navigating to /Users/1 returns JSON of a user with Id = 1. HTTP response code will be 200.
2.Navigating to /User/1 (note I misspelled the URL!) will return response code 404. I do not even need to do anything, my web server will return code 404 for me.
Now the question: what response code (200 or 404) should be returned by the URL /Users/2, if user with Id = 2 does not exist in the database? And why.
You should return NotFound (404), because the url is valid but the required resource doesn't exists. check this.

Django POST data dictionary is empty when posting from test client

I am trying to test and AJAX view in my Django Project. When submit the post from JQuery the data is correctly accessible in the Django View but when I try to make queries for the Django test client it is emplty.
Here is the code I use:
The view
def add_item(request):
if request.is_ajax() and request.method == 'POST':
post_data = request.POST
print post_data ## <----- THIS IS EMPTY
name = post_data.get('name')
# Render the succes response
json_data = json.dumps({"success":1})
return HttpResponse(json_data, content_type="application/json")
else:
raise Http404
And the test
class TestAddItem(TestCase):
def test_some_test(self):
data = {
'description':"description",
}
response = self.client.post('theurl', data, content_type='application/json')
Any Idea what I might be doing wrong?
I tried also without content type and also using plain url like thurl/?name=name without succes.
Any help will be appreciated.
Olivier
After trying different combinations of parameter formating, content types, etc..
I found one solution that works :
response = self.client.post(reverse('theurl'), data,
**{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'})
And I get the following dictionary on the POST parameters of the request:
<QueryDict: {u'name': [u'name']}>
Edit I love testing :)

login_required decorator on ajax views to return 401 instead of 302

While writing some views to respond to ajax requests i find it somewhat strange that the login_required decorator always returns a 302 status code for not authenticated users. As these views are ajax views, this seems somewhat inappropriate. I do not want the user to log in in such a case, but i want Django to tell the client that authentication is required to access such a view (a 401 should be the right status code, i think).
To achieve this, i started to write my own decorator login_required_ajax, but somehow this is beyond my skills. This is what i have come up with so far:
def login_required_ajax(function=None,redirect_field_name=None):
"""
Just make sure the user is authenticated to access a certain ajax view
Otherwise return a HttpResponse 401 - authentication required
instead of the 302 redirect of the original Django decorator
"""
def _decorator(view_func):
def _wrapped_view(request, *args, **kwargs):
if request.user.is_authenticated():
return view_func(request, *args, **kwargs)
else:
return HttpResponse(status=401)
if function is None:
return _decorator
else:
return _decorator(function)
When using this decorator on a view, i get a ViewDoesNotExist exception as soon as i try to access any page on the site.
I first thought that the problem could be the direct return of an HttpResponse when a user is not authenticated, because a response object is not a callable. But then the decorator should work as long as i do not try to access the view in question, shouldn't it? And if this really is the crux, how can i write a decorator that returns a HttpResponse with a status code of 401?
That's a pretty good attempt. Here's a couple of problems I spotted:
Your _decorator function should return _wrapped_view.
The indentation for your if function is None block is a bit off -- the login_required_ajax function needs to return the decorated function.
Here's the decorator with those changes made:
def login_required_ajax(function=None,redirect_field_name=None):
"""
Just make sure the user is authenticated to access a certain ajax view
Otherwise return a HttpResponse 401 - authentication required
instead of the 302 redirect of the original Django decorator
"""
def _decorator(view_func):
def _wrapped_view(request, *args, **kwargs):
if request.user.is_authenticated():
return view_func(request, *args, **kwargs)
else:
return HttpResponse(status=401)
return _wrapped_view
if function is None:
return _decorator
else:
return _decorator(function)

Resources