made Browsable API base template cachable: omit CSRF token when unnecessary (#7717)

HTML responses generated by the Browsable API otherwise generate
inconsistent ETAGs -- due to the presence of CSRF tokens in the response
-- even when the API is read-only, (and as such when the response
contains no resource-modifying forms, i.e. neither POST nor PUT forms,
which might require the CSRF token).

While the template was appropriately including CSRF tokens only within
POST and PUT forms, its AJAX overlay included the CSRF token in *every*
response, regardless of whether it would be needed.

This change brings the logic of the `script` block into line with that
of the rest of the template -- and such that read-only APIs (and really
the Browsable API pages of *any* read-only resources) will not
needlessly include the CSRF token, and will now be safely cachable -- by
both back-end systems and by the user agent.
This commit is contained in:
Jesse London 2021-03-16 08:25:21 -05:00 committed by GitHub
parent b0ca248d88
commit 9c9ffb18f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 15 additions and 7 deletions

View File

@ -290,7 +290,7 @@
<script>
window.drf = {
csrfHeaderName: "{{ csrf_header_name|default:'X-CSRFToken' }}",
csrfToken: "{% if request %}{{ csrf_token }}{% endif %}"
csrfToken: "{% if request %}{% if post_form or put_form %}{{ csrf_token }}{% endif %}{% endif %}"
};
</script>
<script src="{% static "rest_framework/js/jquery-3.5.1.min.js" %}"></script>

View File

@ -3,15 +3,23 @@ import re
from django.shortcuts import render
def test_base_template_with_context():
context = {'request': True, 'csrf_token': 'TOKEN'}
result = render({}, 'rest_framework/base.html', context=context)
assert re.search(r'\bcsrfToken: "TOKEN"', result.content.decode())
def test_base_template_with_no_context():
# base.html should be renderable with no context,
# so it can be easily extended.
result = render({}, 'rest_framework/base.html')
# note that this response will not include a valid CSRF token
assert re.search(r'\bcsrfToken: ""', result.content.decode())
def test_base_template_with_simple_context():
context = {'request': True, 'csrf_token': 'TOKEN'}
result = render({}, 'rest_framework/base.html', context=context)
# note that response will STILL not include a CSRF token
assert re.search(r'\bcsrfToken: ""', result.content.decode())
def test_base_template_with_editing_context():
context = {'request': True, 'post_form': object(), 'csrf_token': 'TOKEN'}
result = render({}, 'rest_framework/base.html', context=context)
# response includes a CSRF token in support of the POST form
assert re.search(r'\bcsrfToken: "TOKEN"', result.content.decode())