Skip to content

Commit 46a04e6

Browse files
committed
Merge branch 'robwa/master'
2 parents 4f1ca05 + b045f1a commit 46a04e6

File tree

4 files changed

+76
-36
lines changed

4 files changed

+76
-36
lines changed

django_unicorn/errors.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,7 @@ class MissingComponentElement(Exception):
2020

2121
class MissingComponentViewElement(Exception):
2222
pass
23+
24+
25+
class ComponentNotValid(Exception):
26+
pass

django_unicorn/templatetags/unicorn.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
from django import template
44
from django.conf import settings
5+
from django.template.base import FilterExpression
56

67
import shortuuid
78

89
from django_unicorn.call_method_parser import InvalidKwarg, parse_kwarg
10+
from django_unicorn.errors import ComponentNotValid
911

1012

1113
register = template.Library()
@@ -42,15 +44,7 @@ def unicorn(parser, token):
4244
"%r tag requires at least a single argument" % token.contents.split()[0]
4345
)
4446

45-
tag_name = contents[0]
46-
component_name = contents[1]
47-
48-
if not (
49-
component_name[0] == component_name[-1] and component_name[0] in ('"', "'")
50-
):
51-
raise template.TemplateSyntaxError(
52-
"%r tag's argument should be in quotes" % tag_name
53-
)
47+
component_name = parser.compile_filter(contents[1])
5448

5549
kwargs = {}
5650

@@ -61,11 +55,11 @@ def unicorn(parser, token):
6155
except InvalidKwarg:
6256
pass
6357

64-
return UnicornNode(component_name[1:-1], kwargs)
58+
return UnicornNode(component_name, kwargs)
6559

6660

6761
class UnicornNode(template.Node):
68-
def __init__(self, component_name: str, kwargs: Dict = {}):
62+
def __init__(self, component_name: FilterExpression, kwargs: Dict = {}):
6963
self.component_name = component_name
7064
self.kwargs = kwargs
7165
self.component_key = ""
@@ -107,9 +101,16 @@ def render(self, context):
107101

108102
component_id = None
109103

104+
try:
105+
component_name = self.component_name.resolve(context)
106+
except AttributeError:
107+
raise ComponentNotValid(
108+
f"Component template is not valid: {self.component_name}."
109+
)
110+
110111
if self.parent:
111112
# Child components use the parent for part of the `component_id`
112-
component_id = f"{self.parent.component_id}:{self.component_name}"
113+
component_id = f"{self.parent.component_id}:{component_name}"
113114

114115
if self.component_key:
115116
component_id = f"{component_id}:{self.component_key}"
@@ -140,7 +141,7 @@ def render(self, context):
140141

141142
view = UnicornView.create(
142143
component_id=component_id,
143-
component_name=self.component_name,
144+
component_name=component_name,
144145
component_key=self.component_key,
145146
parent=self.parent,
146147
kwargs=resolved_kwargs,

tests/templatetags/test_unicorn.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
1-
from django.template.base import Token, TokenType
1+
from django.template.base import Parser, Token, TokenType
22

33
from django_unicorn.templatetags.unicorn import unicorn
44

55

66
def test_unicorn():
77
token = Token(TokenType.TEXT, "unicorn 'todo'")
8-
unicorn_node = unicorn(None, token)
8+
unicorn_node = unicorn(Parser([]), token)
99

10-
assert unicorn_node.component_name == "todo"
10+
assert unicorn_node.component_name.resolve({}) == "todo"
1111
assert len(unicorn_node.kwargs) == 0
1212

1313

1414
def test_unicorn_kwargs():
1515
token = Token(TokenType.TEXT, "unicorn 'todo' blob='blob'")
16-
unicorn_node = unicorn(None, token)
16+
unicorn_node = unicorn(Parser([]), token)
1717

1818
assert unicorn_node.kwargs == {"blob": "blob"}
1919

2020

2121
def test_unicorn_args_and_kwargs():
2222
# args after the component name get ignored
2323
token = Token(TokenType.TEXT, "unicorn 'todo' '1' 2 hello='world' test=3 '4'")
24-
unicorn_node = unicorn(None, token)
24+
unicorn_node = unicorn(Parser([]), token)
2525

2626
assert unicorn_node.kwargs == {"hello": "world", "test": 3}

tests/templatetags/test_unicorn_render.py

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
import re
22

33
from django.template import Context
4-
from django.template.base import Token, TokenType
4+
from django.template.base import Parser, Token, TokenType
55

66
import pytest
77

88
from django_unicorn.components import UnicornView
9+
from django_unicorn.errors import ComponentNotValid
910
from django_unicorn.templatetags.unicorn import unicorn
1011
from django_unicorn.utils import generate_checksum
1112
from example.coffee.models import Flavor
1213

1314

15+
class FakeComponent(UnicornView):
16+
template_name = "templates/test_component.html"
17+
18+
1419
class FakeComponentParent(UnicornView):
1520
template_name = "templates/test_component_parent.html"
1621

@@ -66,7 +71,7 @@ def test_unicorn_render_kwarg():
6671
TokenType.TEXT,
6772
"unicorn 'tests.templatetags.test_unicorn_render.FakeComponentKwargs' test_kwarg='tested!'",
6873
)
69-
unicorn_node = unicorn(None, token)
74+
unicorn_node = unicorn(Parser([]), token)
7075
context = {}
7176
actual = unicorn_node.render(Context(context))
7277

@@ -78,7 +83,7 @@ def test_unicorn_render_context_variable():
7883
TokenType.TEXT,
7984
"unicorn 'tests.templatetags.test_unicorn_render.FakeComponentKwargs' test_kwarg=test_var.nested",
8085
)
81-
unicorn_node = unicorn(None, token)
86+
unicorn_node = unicorn(Parser([]), token)
8287
context = {"test_var": {"nested": "variable!"}}
8388
actual = unicorn_node.render(Context(context))
8489

@@ -90,7 +95,7 @@ def test_unicorn_render_with_invalid_html():
9095
TokenType.TEXT,
9196
"unicorn 'tests.templatetags.test_unicorn_render.FakeComponentKwargsWithHtmlEntity' test_kwarg=test_var.nested",
9297
)
93-
unicorn_node = unicorn(None, token)
98+
unicorn_node = unicorn(Parser([]), token)
9499
context = {"test_var": {"nested": "variable!"}}
95100
actual = unicorn_node.render(Context(context))
96101

@@ -103,7 +108,7 @@ def test_unicorn_render_parent(settings):
103108
TokenType.TEXT,
104109
"unicorn 'tests.templatetags.test_unicorn_render.FakeComponentKwargs' parent=view",
105110
)
106-
unicorn_node = unicorn(None, token)
111+
unicorn_node = unicorn(Parser([]), token)
107112
view = FakeComponentParent(component_name="test", component_id="asdf")
108113
context = {"view": view}
109114
unicorn_node.render(Context(context))
@@ -121,7 +126,7 @@ def test_unicorn_render_parent_with_key(settings):
121126
TokenType.TEXT,
122127
"unicorn 'tests.templatetags.test_unicorn_render.FakeComponentKwargs' parent=view key='blob'",
123128
)
124-
unicorn_node = unicorn(None, token)
129+
unicorn_node = unicorn(Parser([]), token)
125130
view = FakeComponentParent(component_name="test", component_id="asdf")
126131
context = {"view": view}
127132
unicorn_node.render(Context(context))
@@ -138,7 +143,7 @@ def test_unicorn_render_parent_with_id(settings):
138143
TokenType.TEXT,
139144
"unicorn 'tests.templatetags.test_unicorn_render.FakeComponentKwargs' parent=view id='flob'",
140145
)
141-
unicorn_node = unicorn(None, token)
146+
unicorn_node = unicorn(Parser([]), token)
142147
view = FakeComponentParent(component_name="test", component_id="asdf")
143148
context = {"view": view}
144149
unicorn_node.render(Context(context))
@@ -155,7 +160,7 @@ def test_unicorn_render_parent_with_pk(settings):
155160
TokenType.TEXT,
156161
"unicorn 'tests.templatetags.test_unicorn_render.FakeComponentKwargs' parent=view pk=99",
157162
)
158-
unicorn_node = unicorn(None, token)
163+
unicorn_node = unicorn(Parser([]), token)
159164
view = FakeComponentParent(component_name="test", component_id="asdf")
160165
context = {"view": view}
161166
unicorn_node.render(Context(context))
@@ -172,7 +177,7 @@ def test_unicorn_render_parent_with_model_id(settings):
172177
TokenType.TEXT,
173178
"unicorn 'tests.templatetags.test_unicorn_render.FakeComponentKwargs' parent=view model=model",
174179
)
175-
unicorn_node = unicorn(None, token)
180+
unicorn_node = unicorn(Parser([]), token)
176181
view = FakeComponentParent(component_name="test", component_id="asdf")
177182

178183
# Fake a model that only has an id
@@ -199,7 +204,7 @@ def test_unicorn_render_parent_with_model_pk(settings):
199204
TokenType.TEXT,
200205
"unicorn 'tests.templatetags.test_unicorn_render.FakeComponentKwargs' parent=view model=model",
201206
)
202-
unicorn_node = unicorn(None, token)
207+
unicorn_node = unicorn(Parser([]), token)
203208
view = FakeComponentParent(component_name="test", component_id="asdf")
204209

205210
flavor = Flavor(pk=187)
@@ -218,7 +223,7 @@ def test_unicorn_render_id_use_pk():
218223
TokenType.TEXT,
219224
"unicorn 'tests.templatetags.test_unicorn_render.FakeComponentModel' model_id=model.id",
220225
)
221-
unicorn_node = unicorn(None, token)
226+
unicorn_node = unicorn(Parser([]), token)
222227
context = {"model": {"pk": 123}}
223228
actual = unicorn_node.render(Context(context))
224229

@@ -231,7 +236,7 @@ def test_unicorn_render_component_one_script_tag(settings):
231236
TokenType.TEXT,
232237
"unicorn 'tests.templatetags.test_unicorn_render.FakeComponentKwargs'",
233238
)
234-
unicorn_node = unicorn(None, token)
239+
unicorn_node = unicorn(Parser([]), token)
235240
context = {}
236241
html = unicorn_node.render(Context(context))
237242

@@ -245,7 +250,7 @@ def test_unicorn_render_child_component_no_script_tag(settings):
245250
TokenType.TEXT,
246251
"unicorn 'tests.templatetags.test_unicorn_render.FakeComponentKwargs' parent=view",
247252
)
248-
unicorn_node = unicorn(None, token)
253+
unicorn_node = unicorn(Parser([]), token)
249254
view = FakeComponentParent(component_name="test", component_id="asdf")
250255
context = {"view": view}
251256
html = unicorn_node.render(Context(context))
@@ -259,7 +264,7 @@ def test_unicorn_render_parent_component_one_script_tag(settings):
259264
TokenType.TEXT,
260265
"unicorn 'tests.templatetags.test_unicorn_render.FakeComponentParent'",
261266
)
262-
unicorn_node = unicorn(None, token)
267+
unicorn_node = unicorn(Parser([]), token)
263268
context = {}
264269
html = unicorn_node.render(Context(context))
265270

@@ -273,7 +278,7 @@ def test_unicorn_render_calls(settings):
273278
TokenType.TEXT,
274279
"unicorn 'tests.templatetags.test_unicorn_render.FakeComponentCalls'",
275280
)
276-
unicorn_node = unicorn(None, token)
281+
unicorn_node = unicorn(Parser([]), token)
277282
context = {}
278283
html = unicorn_node.render(Context(context))
279284

@@ -288,7 +293,7 @@ def test_unicorn_render_calls_with_arg(settings):
288293
TokenType.TEXT,
289294
"unicorn 'tests.templatetags.test_unicorn_render.FakeComponentCalls2'",
290295
)
291-
unicorn_node = unicorn(None, token)
296+
unicorn_node = unicorn(Parser([]), token)
292297
context = {}
293298
html = unicorn_node.render(Context(context))
294299

@@ -303,7 +308,7 @@ def test_unicorn_render_calls_no_mount_call(settings):
303308
TokenType.TEXT,
304309
"unicorn 'tests.templatetags.test_unicorn_render.FakeComponentParent'",
305310
)
306-
unicorn_node = unicorn(None, token)
311+
unicorn_node = unicorn(Parser([]), token)
307312
context = {}
308313
html = unicorn_node.render(Context(context))
309314

@@ -318,7 +323,7 @@ def test_unicorn_render_hash(settings):
318323
TokenType.TEXT,
319324
"unicorn 'tests.templatetags.test_unicorn_render.FakeComponentParent'",
320325
)
321-
unicorn_node = unicorn(None, token)
326+
unicorn_node = unicorn(Parser([]), token)
322327
context = {}
323328
html = unicorn_node.render(Context(context))
324329

@@ -331,3 +336,33 @@ def test_unicorn_render_hash(settings):
331336
rendered_content = html[:script_idx]
332337
expected_hash = generate_checksum(rendered_content)
333338
assert f'"hash":"{expected_hash}"' in html
339+
340+
341+
def test_unicorn_render_with_component_name_from_context():
342+
token = Token(
343+
TokenType.TEXT,
344+
"unicorn component_name",
345+
)
346+
unicorn_node = unicorn(Parser([]), token)
347+
context = {"component_name": "tests.templatetags.test_unicorn_render.FakeComponent"}
348+
html = unicorn_node.render(Context(context))
349+
350+
assert '<script type="module"' in html
351+
assert len(re.findall('<script type="module"', html)) == 1
352+
353+
354+
def test_unicorn_render_with_invalid_component_name_from_context():
355+
token = Token(
356+
TokenType.TEXT,
357+
"unicorn bad_component_name",
358+
)
359+
unicorn_node = unicorn(Parser([]), token)
360+
context = {"component_name": "tests.templatetags.test_unicorn_render.FakeComponent"}
361+
362+
with pytest.raises(ComponentNotValid) as e:
363+
unicorn_node.render(Context(context))
364+
365+
assert (
366+
e.exconly()
367+
== "django_unicorn.errors.ComponentNotValid: Component template is not valid: bad_component_name."
368+
)

0 commit comments

Comments
 (0)