Skip to content

Commit 1c46bc7

Browse files
authored
Improve handling of non-string values for 'override_tag's 'default_html' argument (#224)
1 parent e41b502 commit 1c46bc7

File tree

5 files changed

+104
-3
lines changed

5 files changed

+104
-3
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
- Add support for Django 5.0 ([#241](https://github.com/torchbox/django-pattern-library/pull/241))
88

9+
### Changed
10+
11+
- From Django >= 4.0, calls to `Node.render()` must always return a string, but this app previously allowed non-string values to be passed in the `default_html` parameter to `override_tag`. Passing a non-string now raises a `TypeError` when using Django >= 4.0, and raises a warning for older versions ([issue #211](https://github.com/torchbox/django-pattern-library/issues/211)).
12+
913
## [1.1.0](https://github.com/torchbox/django-pattern-library/releases/tag/v1.1.0) - 2023-10-25
1014

1115
### Added

pattern_library/monkey_utils.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import inspect
12
import logging
2-
from typing import Optional
3+
import typing
4+
import warnings
35

46
import django
57
from django.template.library import SimpleNode
@@ -11,7 +13,9 @@
1113

1214

1315
def override_tag(
14-
register: django.template.Library, name: str, default_html: Optional[str] = None
16+
register: django.template.Library,
17+
name: str,
18+
default_html: typing.Optional[typing.Any] = UNSPECIFIED,
1519
):
1620
"""
1721
An utility that helps you override original tags for use in your pattern library.
@@ -79,6 +83,23 @@ def node_render(context):
7983
# See https://github.com/torchbox/django-pattern-library/issues/166.
8084
return str(result)
8185
elif default_html is not UNSPECIFIED:
86+
# Ensure default_html is a string.
87+
if not isinstance(default_html, str):
88+
# Save the caller for the override tag in case it's needed for error reporting.
89+
trace = inspect.stack()[1]
90+
if django.VERSION < (4, 0):
91+
warnings.warn(
92+
"default_html argument to override_tag should be a string to ensure compatibility "
93+
'with Django >= 4.0 (line %s in "%s")'
94+
% (trace.lineno, trace.filename),
95+
Warning,
96+
)
97+
else:
98+
raise TypeError(
99+
'default_html argument to override_tag must be a string (line %s in "%s")'
100+
% (trace.lineno, trace.filename)
101+
)
102+
82103
# Render provided default;
83104
# if no stub data supplied.
84105
return default_html
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{% load test_tags_invalid %}
2+
3+
MARMA{% default_html_tag_invalid empty_string %}LADE01
4+
MARMA{% default_html_tag_invalid none %}LADE02
5+
MARMA{% default_html_tag_invalid dict %}LADE03
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from django import template
2+
3+
from pattern_library.monkey_utils import override_tag
4+
5+
register = template.Library()
6+
7+
8+
@register.simple_tag()
9+
def default_html_tag_invalid(arg=None):
10+
"Just raise an exception, never do anything"
11+
raise Exception("default_tag raised an exception")
12+
13+
14+
# Test overriding tag with a default_html that's not valid in Django >= 4.0
15+
override_tag(register, "default_html_tag_invalid", default_html=[1, 2, 3])

tests/tests/test_tags.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
from django.test import SimpleTestCase
1+
from unittest.mock import patch
2+
3+
from django.shortcuts import render
4+
from django.test import RequestFactory, SimpleTestCase
5+
6+
from pattern_library import get_pattern_context_var_name
27

38
from .utils import reverse
49

@@ -43,3 +48,54 @@ def test_falsey_default_html_overide(self):
4348
self.assertContains(response, "POTATO1")
4449
self.assertContains(response, "POTANoneTO2")
4550
self.assertContains(response, "POTA0TO3")
51+
52+
53+
class TagsTestFailCase(SimpleTestCase):
54+
def test_bad_default_html_warning(self):
55+
"""
56+
Test that the library raises a warning when passing a non-string `default_html` argument to `override_tag`
57+
in Django < 4.0
58+
"""
59+
with patch("django.VERSION", (3, 2, 0, "final", 0)):
60+
with self.assertWarns(Warning) as cm:
61+
template_name = (
62+
"patterns/atoms/tags_test_atom/invalid_tags_test_atom.html.fail"
63+
)
64+
request = RequestFactory().get("/")
65+
66+
# Rendering the template with a non-string `default_html` argument will cause Django >= 4 to raise
67+
# a `TypeError`, which we need to catch and ignore in order to check that the warning is raised
68+
try:
69+
render(
70+
request,
71+
template_name,
72+
context={get_pattern_context_var_name(): True},
73+
)
74+
except TypeError:
75+
pass
76+
77+
self.assertIn(
78+
"default_html argument to override_tag should be a string to ensure compatibility with Django",
79+
str(cm.warnings[0]),
80+
)
81+
82+
def test_bad_default_html_error(self):
83+
"""
84+
Test that the library raises a TypeError when passing a non-string `default_html` argument to `override_tag`
85+
in Django >= 4.0
86+
"""
87+
with patch("django.VERSION", (4, 2, 0, "final", 0)):
88+
with self.assertRaises(TypeError) as cm:
89+
template_name = (
90+
"patterns/atoms/tags_test_atom/invalid_tags_test_atom.html.fail"
91+
)
92+
request = RequestFactory().get("/")
93+
render(
94+
request,
95+
template_name,
96+
context={get_pattern_context_var_name(): True},
97+
)
98+
self.assertIn(
99+
"default_html argument to override_tag must be a string",
100+
str(cm.exception),
101+
)

0 commit comments

Comments
 (0)