Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ pub enum Tag {
Load,
SimpleTag(SimpleTag),
Url(Url),
CsrfToken,
}

#[derive(PartialEq, Eq)]
Expand Down Expand Up @@ -1046,6 +1047,7 @@ impl<'t, 'l, 'py> Parser<'t, 'l, 'py> {
};
Ok(match self.template.content(tag.at) {
"url" => Either::Left(self.parse_url(at, parts)?),
"csrf_token" => Either::Left(TokenTree::Tag(Tag::CsrfToken)),
"load" => Either::Left(self.parse_load(at, parts)?),
"autoescape" => Either::Left(self.parse_autoescape(at, parts)?),
"endautoescape" => Either::Right(EndTag {
Expand Down
33 changes: 33 additions & 0 deletions src/render/tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,39 @@ impl Render for Tag {
Self::Load => Cow::Borrowed(""),
Self::SimpleTag(simple_tag) => simple_tag.render(py, template, context)?,
Self::Url(url) => url.render(py, template, context)?,
Self::CsrfToken => match context.get("csrf_token") {
Some(token) => {
let bound_token = token.bind(py);
if bound_token.is_truthy().unwrap_or(false) {
if bound_token.eq("NOTPROVIDED")? {
Cow::Borrowed("")
} else {
Cow::Owned(format!(
r#"<input type="hidden" name="csrfmiddlewaretoken" value="{}">"#,
html_escape::encode_quoted_attribute(bound_token.str()?.to_str()?)
))
}
} else {
Cow::Borrowed("")
}
}
None => {
let debug = py
.import("django.conf")?
.getattr("settings")?
.getattr("DEBUG")?
.is_truthy()?;

if debug {
py.import("warnings")?.call_method1(
"warn",
("A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext.",)
)?;
}

Cow::Borrowed("")
}
},
})
}
}
Expand Down
133 changes: 133 additions & 0 deletions tests/tags/test_csrf_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import warnings
from django.template import engines
from django.test import override_settings


def test_csrf_token_basic():
template = "{% csrf_token %}"

django_template = engines["django"].from_string(template)
rust_template = engines["rusty"].from_string(template)

context = {"csrf_token": "test123"}
expected = '<input type="hidden" name="csrfmiddlewaretoken" value="test123">'
Comment on lines +12 to +13
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the main tests should add csrf_token via the context processor.

This means these tests will need some refactoring to ensure the context processors are run for both Python and Rust engines.

We should also have extra tests that also set it explicitly like this.


assert django_template.render(context) == expected
assert rust_template.render(context) == expected


def test_csrf_token_not_provided():
template = "{% csrf_token %}"

django_template = engines["django"].from_string(template)
rust_template = engines["rusty"].from_string(template)

context = {"csrf_token": "NOTPROVIDED"}

assert django_template.render(context) == ""
assert rust_template.render(context) == ""


def test_csrf_token_missing():
template = "{% csrf_token %}"

django_template = engines["django"].from_string(template)
rust_template = engines["rusty"].from_string(template)

assert django_template.render({}) == ""
assert rust_template.render({}) == ""


def test_csrf_token_escaping():
template = "{% csrf_token %}"

django_template = engines["django"].from_string(template)
rust_template = engines["rusty"].from_string(template)

context = {"csrf_token": 'test"with<quotes>&amp;'}

django_result = django_template.render(context)
rust_result = rust_template.render(context)

assert django_result == rust_result
assert 'value="test&quot;with&lt;quotes&gt;&amp;amp;"' in rust_result


def test_csrf_token_empty_string():
template = "{% csrf_token %}"

django_template = engines["django"].from_string(template)
rust_template = engines["rusty"].from_string(template)

context = {"csrf_token": ""}

assert django_template.render(context) == ""
assert rust_template.render(context) == ""


def test_csrf_token_none_value():
template = "{% csrf_token %}"

django_template = engines["django"].from_string(template)
rust_template = engines["rusty"].from_string(template)

context = {"csrf_token": None}

assert django_template.render(context) == ""
assert rust_template.render(context) == ""


def test_csrf_token_numeric_value():
template = "{% csrf_token %}"
django_template = engines["django"].from_string(template)
rust_template = engines["rusty"].from_string(template)

context = {"csrf_token": 12345}
expected = '<input type="hidden" name="csrfmiddlewaretoken" value="12345">'

assert django_template.render(context) == expected
assert rust_template.render(context) == expected


def test_csrf_token_zero_value():
template = "{% csrf_token %}"

django_template = engines["django"].from_string(template)
rust_template = engines["rusty"].from_string(template)

context = {"csrf_token": 0}

assert django_template.render(context) == ""
assert rust_template.render(context) == ""


def test_csrf_token_false_value():
template = "{% csrf_token %}"

django_template = engines["django"].from_string(template)
rust_template = engines["rusty"].from_string(template)

context = {"csrf_token": False}

assert django_template.render(context) == ""
assert rust_template.render(context) == ""


@override_settings(DEBUG=True)
def test_csrf_token_missing_debug_warning():
template = "{% csrf_token %}"

django_template = engines["django"].from_string(template)
rust_template = engines["rusty"].from_string(template)

expected = "A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext."

with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
assert django_template.render({}) == ""
assert str(w[0].message) == expected

with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
assert rust_template.render({}) == ""
assert str(w[0].message) == expected
Loading