AJAX¶
Checking authentication¶
Django’s built in login_required() decorator is not well suited to AJAX requests. If the user making the request is not authenticated, it issues a redirect to the configured login view. This is not particularly useful to an AJAX client.
Djem provides a simple alternate decorator, ajax_login_required(), to use on views that are the target of AJAX requests. Instead of issuing a redirect if the user is not authenticated, it returns a HttpForbiddenResponse.
New in version 0.7: The ajax_login_required() decorator.
Responding to AJAX requests¶
Django provides JsonResponse to aid in responding to data-centric AJAX requests (as opposed to those that return rendered HTML to be injected directly into the page). Djem provides a simple extension of JsonResponse that adds some additional features: AjaxResponse.
AjaxResponse automatically adds any messages in the Django messages framework store to the response body.
This allows views that are called via AJAX and return JSON-encoded data to still make use of the messages framework and have those messages automatically embedded in the response and removed from the message store.
The messages are added to the parent JsonResponse’s data dictionary using the “messages” key. The messages themselves are dictionaries containing the following:
- message: The message string.
- tags: A string of the tags applied to the message, space separated.
If there are no messages to add, the “messages” key will not be added to the data dictionary at all (i.e. it will not be added as an empty list).
Using AjaxResponse differs from JsonResponse in the following ways:
- The first positional argument should be a Django
HttpRequestinstance. This argument is required - it is used to retrieve messages from the message framework store. - The
dataargument is optional. If provided, it must always be adictinstance, and messages will be added to this dictionary. If not provided, messages will be added to a newdictinstance, passing it to the parentJsonResponse. Using thesafeargument ofJsonResponseto JSON-encode other types is not supported (see the documentation for thesafeargument ofJsonResponse). - The optional argument
successcan be set to add a “success” attribute to thedatadictionary. The “success” attribute will always be added as a boolean value, regardless of what was passed to thesuccessargument (though it will not be added at all if nothing was passed). As with messages, this will be added to thedatadictionary passed to the parentJsonResponseregardless of whether one was provided toAjaxResponseitself or not.
With the exception of safe, as noted above, AjaxResponse accepts and supports all arguments of JsonResponse.
Usage¶
This simple example demonstrates how AjaxResponse can be used with the messages framework:
from django.contrib import messages
from djem import AjaxResponse
def my_view(request):
# do something...
messages.success(request, 'Did something!')
return AjaxResponse(request)
This will give a JSON-encoded response body that looks like this:
{
"messages":[{
"message":"Did something!",
"tags":"success"
}]
}
The following is a more complete example, based on the “polls” application created in the Django tutorial. This view records a vote on a given Choice:
from django.contrib import messages
from djem import AjaxResponse
from polls.models import Choice
def vote_on_question(request, choice_id):
try:
choice = Choice.objects.get(pk=choice_id)
except Choice.DoesNotExist:
messages.error(request, 'Invalid choice.')
return AjaxResponse(request, success=False)
choice.votes += 1
choice.save()
messages.success(request, 'Vote recorded.')
return AjaxResponse(request, {'votes': choice.votes}, success=True)
Note
This example is for illustrative purposes only. It does not represent a good way to increment a counter in the database - such an operation should be performed atomically.
CSRF¶
As Django notes in its own documentation, adding the CSRF token to AJAX POST requests can be done on each individual request, or you can use JavaScript framework features to add it to all outgoing POST requests, using the X-CSRFToken header.
Djem’s csrfify_ajax template tag does exactly that in a single line:
{% load djem %}
...
{% csrfify_ajax %}
...
The tag injects a <script> tag containing library-specific code to add X-CSRFToken headers to all outgoing requests that require it.
New in version 0.6: The csrfify_ajax template tag.
Note
As with Django’s standard {% csrf_token %} tag, to use {% csrfify_ajax %} the view must use RequestContext to render the template, e.g. using the render() shortcut function.
Note
As the <script> tag rendered by the tag contains library-specific code, it needs to be included after the library itself.
Library support¶
By default, Djem ships with support for jQuery, but it is simple to add additional libraries. Extra libraries may be included by default in future releases.
The <script> tag, and its contents, that csrfify_ajax renders is stored in a template under the djem/csrfify_ajax/ directory, named after the library. E.g. the included jQuery template is at djem/csrfify_ajax/jquery.html. As with any Django app templates, you can override those that Djem includes or add your own by including your own djem/csrfify_ajax/ directory somewhere on your configured template path. See the Django documentation for overriding templates.
By providing your own template, you can use csrfify_ajax with any library of your choosing. To specify which library template the tag should use, simply provide the name of the template (without the .html) as an argument:
{% load djem %}
...
{% csrfify_ajax 'some_other_lib' %}
...
A default value of 'jquery' is used when no argument is provided.
These templates have access to the CSRF token via the {{ csrf_token }} template variable.
XSS¶
Django’s template system automatically escapes all template variables by default. This helps prevent user-generated content from messing up the rendered HTML, or worse, exposing your users to a security vulnerability. This kind of injection is known as cross site scripting or XSS.
If including user-generated content in the JSON body of an AJAX response (that is, not returning rendered HTML), these autoescaping protections do not apply. If that content is then injected directly into the page when the response is received, similar XSS vulnerabilities exist.
You should be wary of XSS threats when returning data in this way. Either ensure all returned data has been properly escaped on the server side, or do not inject it as HTML (e.g. use jQuery’s .text() method instead of .html())
Djem’s AjaxResponse class helps mitigate one potential vector for XSS - messages added via the Django messages framework. All messages included in the response body are escaped, unless marked as safe.
# View
from django.contrib import messages
from django.template.defaultfilters import mark_safe
from djem.ajax import AjaxResponse
def view(request):
some_user_input = '<strong>BAD</strong> user input'
messages.error(request, 'Invalid input: {0}.'.format(some_user_input))
messages.info(request, mark_safe('This is a message with <em>safe</em> HTML.'))
return AjaxResponse(request)
# Response Body
{
"messages": [{
"message": "Invalid input: <strong>BAD</strong> user input.",
"tags": "error"
}, {
"message": "This is a message with <em>safe</em> HTML.",
"tags": "info"
}]
}
The messages framework¶
As noted above, the AjaxResponse class can automatically include messages stored in Django’s builtin messages framework. But whether or not you are using AjaxResponse, the messages framework is not perfectly suited for use in AJAX requests.
It is designed to make it simple to store one-time notification messages throughout the lifecycle of a request, then display them together at some later point, typically when rendering a template. Importantly, it offers persistent storage of these messages, allowing them to be retrieved and displayed on a later request. This is particularly useful when issuing browser redirects to display subsequent pages, and needing the messages from the current request to be available after the redirect.
As Django notes in its documentation, this persistence between requests can cause issues if multiple requests are issued in parallel. The wrong request can read from the persistent messages store, display the messages in the wrong context, and prevent the messages being shown where they were intended. When dealing with full requests for entire pages, this situation only occurs when a user is issuing requests from multiple browser tabs/windows at the same time, and thus is only a minor problem. But on a site using AJAX requests, the risk increases. Depending on the nature of the site and the AJAX requests it issues, the chances of multiple requests running simultaneously can increase dramatically, making the problem of requests stealing messages from one another much worse.
One solution is to avoid using the messages framework in views used by AJAX requests. After all, an AJAX request doesn’t require the same redirect hopping that might otherwise be required - any messages that it generates can be returned immediately, making the persistent storage offered by the framework unnecessary. But some views can be used by both AJAX and non-AJAX requests, plus a consistent method of handling message passing regardless of the view would be nice.
Djem provides a MessageMiddleware class that acts as a drop-in replacement for Django’s own. Simply replace the django.contrib.messages.middleware.MessageMiddleware string in your MIDDLEWARE setting with djem.middleware.MessageMiddleware.
# before
MIDDLEWARE = [
...
'django.contrib.messages.middleware.MessageMiddleware'
...
]
# after
MIDDLEWARE = [
...
'djem.middleware.MessageMiddleware'
...
]
Djem’s MessageMiddleware is nearly identical to Django’s, and usage of the messages framework itself is completely unchanged. The middleware has only one minor difference: it disables persistent message storage for AJAX requests. On standard requests, the storage backend configured via the MESSAGE_STORAGE setting is still used as per usual. But on AJAX requests, a separate storage backend is used - one that keeps the messages in memory only, making them inaccessible to simultaneous requests.
New in version 0.6: MessageMiddleware
Important
MessageMiddleware uses the HttpRequest object’s is_ajax() method to differentiate between AJAX and non-AJAX requests. Your XMLHttpRequest call must use the appropriate headers in order to be correctly detected. Most modern JavaScript libraries do so.
Note
Using a memory-only storage backend for messages in AJAX requests also makes them unavailable to subsequent requests. If using Djem’s MessageMiddleware, be sure to read the messages from the storage as part of the same request and include them in the response. AjaxResponse does this automatically.
Note
Using Djem’s MessageMiddleware doesn’t change any of the other requirements for using the messages framework. For instance, django.contrib.messages still needs to be listed in the INSTALLED_APPS setting and, if using SessionStorage, SessionMiddleware still needs to be listed before MessageMiddleware in the MIDDLEWARE setting.