Skip to content

Commit 9d5593b

Browse files
Naggafinboxed
andauthored
Made 'default' filter bypass FastDev template exceptions (#57)
--------- Co-authored-by: Anders Hovmöller <anders.hovmoller@dryft.se>
1 parent cb0f944 commit 9d5593b

File tree

4 files changed

+98
-5
lines changed

4 files changed

+98
-5
lines changed

README.rst

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ Features
1010
Error on non-existent template variables
1111
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1212

13-
Django templates by default hide errors, and when it does show an error it's often not very helpful. This app will change this so that if you do:
13+
Django templates by default hide errors, and when it does show an error it's often not very helpful. This app changes this behavior to provide more informative error messages. For example, if you use:
1414

1515
.. code:: html
1616

1717
{{ does_not_exist }}
1818

19-
instead of rendering that as an empty string, this app will give you an error message:
19+
instead of rendering that as an empty string, this app will raise a :code:`FastDevVariableDoesNotExist` error with a detailed message:
2020

2121
.. code::
2222
@@ -34,18 +34,30 @@ instead of rendering that as an empty string, this app will give you an error me
3434
request
3535
user
3636
37-
There are more specialized error messages for when you try to access the contents of a :code:`dict`, and attributes of an object a few levels deep like :code:`foo.bar.baz` (where baz doesn't exist).
37+
There are more specialized error messages for accessing non-existent keys in a :code:`dict` or attributes of an object several levels deep, such as :code:`foo.bar.baz` (where :code:`baz` doesn't exist).
3838

39-
By default, :code:`django-fastdev` only checks templates that exist within your project directory. If you want it to check ALL templates, including stock django templates and templates from third party libraries, add :code:`FASTDEV_STRICT_TEMPLATE_CHECKING = True` to your project :code:`settings.py`.
39+
**Handling `default` and `default_if_none` Filters**
4040

41-
Variable access inside :code:`{% if %}` will not crash unless the setting :code:`FASTDEV_STRICT_IF` is set to :code:`True`. If you use this setting and want to check for existence of a variable, use :code:`{% ifexists %}` from the fastdev template tag library.
41+
When using the :code:`default` or :code:`default_if_none` filters, :code:`django-fastdev` will not raise an exception for non-existent variables. Instead, it behaves as one might intuitively expect by populating the context variable with the result of the filter operation. For example:
42+
43+
.. code:: html
44+
45+
{{ does_not_exist|default:"N/A" }}
46+
{{ does_not_exist|default_if_none:"" }}
47+
48+
In these cases:
49+
* If :code:`does_not_exist` is undefined, :code:`default:"N/A"` will render as :code:`N/A`, and :code:`default_if_none:""` will render as an empty string (:code:`""`).
50+
* This ensures that templates using these filters handle missing variables gracefully, aligning with Django's built-in behavior while maintaining :code:`django-fastdev`'s strict checking for other cases.
51+
52+
By default, :code:`django-fastdev` only checks templates that exist within your project directory. To check ALL templates, including stock Django templates and templates from third-party libraries, add :code:`FASTDEV_STRICT_TEMPLATE_CHECKING = True` to your project :code:`settings.py`.
4253

4354

4455
Improved TemplateDoesNotExist errors
4556
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4657

4758
Good suggestions for what you wanted to do, and a complete list of all valid values makes it very easy to fix `TemplateDoesNotExist` errors.
4859

60+
4961
NoReverseMatch errors
5062
~~~~~~~~~~~~~~~~~~~~~
5163

@@ -105,6 +117,7 @@ Django will silently throw away `hello!` because you wrote :code:`contents` inst
105117
of :code:`content`. :code:`django-fastdev` will turn this into an error which lists the
106118
invalid and valid block names in alphabetical order.
107119

120+
108121
Better error messages for reverse
109122
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
110123

django_fastdev/apps.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
IfNode,
3636
ForNode
3737
)
38+
from django.template.defaultfilters import (
39+
default,
40+
default_if_none,
41+
)
3842
from django.template.loader_tags import (
3943
BlockNode,
4044
ExtendsNode,
@@ -354,6 +358,14 @@ def resolve_override(self, context, ignore_failures=False, ignore_failures_for_r
354358
except FastDevVariableDoesNotExist:
355359
raise
356360
except VariableDoesNotExist as e:
361+
# If the filter includes default or default_if_none, suppress
362+
# the exception and return None
363+
if any(
364+
filter == default
365+
for filter, args in self.filters
366+
):
367+
return orig_resolve(self, context)
368+
357369
if not strict_template_checking():
358370
# worry only about templates inside our project dir; if they
359371
# exist elsewhere, then go to standard django behavior
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{ nonexistent_var|default:"fallback" }}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import pytest
2+
from django.template import Context, Template
3+
from django.template.loader import get_template
4+
from django.test import TestCase
5+
from django_fastdev.apps import FastDevVariableDoesNotExist
6+
from unittest.mock import patch
7+
8+
9+
context = Context({"existing_var": "test_value"})
10+
11+
12+
def test_nonexistent_variable_with_default_filter():
13+
template = Template('{{ nonexistent_var|default:"fallback" }}')
14+
result = template.render(context)
15+
assert result == "fallback", "Expected fallback value for None with default filter"
16+
17+
18+
def test_nonexistent_variable_with_default_if_none_filter():
19+
template = Template('{{ nonexistent_var|default_if_none:"fallback" }}')
20+
with pytest.raises(FastDevVariableDoesNotExist) as cm:
21+
result = template.render(context)
22+
assert "nonexistent_var does not exist in context" in str(cm.value)
23+
24+
25+
def test_nonexistent_variable_without_filters():
26+
template = Template("{{ nonexistent_var }}")
27+
with pytest.raises(FastDevVariableDoesNotExist) as cm:
28+
template.render(context)
29+
assert "nonexistent_var does not exist in context" in str(cm.value)
30+
31+
32+
def test_existing_variable_with_default_filter():
33+
template = Template('{{ existing_var|default:"fallback" }}')
34+
result = template.render(context)
35+
assert result == "test_value", "Expected existing variable value with default filter"
36+
37+
38+
def test_existing_variable_with_default_if_none_filter():
39+
"""Test that an existing variable with |default_if_none filter returns its value."""
40+
template = Template('{{ existing_var|default_if_none:"fallback" }}')
41+
result = template.render(context)
42+
assert result == "test_value", "Expected existing variable value with default_if_none filter"
43+
44+
45+
def test_nested_nonexistent_variable_with_default_filter():
46+
template = Template('{{ obj.nonexistent_field|default:"fallback" }}')
47+
context = Context({"obj": {"existing_field": "value"}})
48+
result = template.render(context)
49+
assert result == "fallback", "Expected fallback value for None with nested default filter"
50+
51+
52+
def test_ignored_template_with_filter():
53+
template = get_template('ignored/test_ignored_template_with_filter.html')
54+
result = template.render().strip()
55+
assert result == "fallback", "Expected Django default behavior for ignored template"
56+
57+
58+
def test_nonexistent_variable_with_multiple_filters():
59+
template = Template('{{ nonexistent_var|upper|default:"fallback" }}')
60+
result = template.render(context)
61+
assert result == "fallback", "Expected fallback value for None with multiple filters including default"
62+
63+
64+
def test_nonexistent_variable_with_multiple_filters2():
65+
template = Template('{{ nonexistent_var|default:"fallback"|upper }}')
66+
result = template.render(context)
67+
assert result == "FALLBACK", "Expected fallback value for None with multiple filters including default"

0 commit comments

Comments
 (0)