|
12 | 12 | from django.conf import settings |
13 | 13 | from django.http import QueryDict |
14 | 14 | from django.utils import timezone |
| 15 | +from django.utils.datastructures import DeferredSubDict |
15 | 16 | from django.utils.html import conditional_escape, escape, format_html |
16 | 17 | from django.utils.lorem_ipsum import paragraphs, words |
17 | 18 | from django.utils.safestring import mark_safe |
|
29 | 30 | VARIABLE_TAG_START, |
30 | 31 | Node, |
31 | 32 | NodeList, |
| 33 | + PartialTemplate, |
32 | 34 | TemplateSyntaxError, |
33 | 35 | VariableDoesNotExist, |
34 | 36 | kwarg_re, |
@@ -408,6 +410,31 @@ def render(self, context): |
408 | 410 | return formatted |
409 | 411 |
|
410 | 412 |
|
| 413 | +class PartialDefNode(Node): |
| 414 | + def __init__(self, partial_name, inline, nodelist): |
| 415 | + self.partial_name = partial_name |
| 416 | + self.inline = inline |
| 417 | + self.nodelist = nodelist |
| 418 | + |
| 419 | + def render(self, context): |
| 420 | + return self.nodelist.render(context) if self.inline else "" |
| 421 | + |
| 422 | + |
| 423 | +class PartialNode(Node): |
| 424 | + def __init__(self, partial_name, partial_mapping): |
| 425 | + # Defer lookup in `partial_mapping` and nodelist to runtime. |
| 426 | + self.partial_name = partial_name |
| 427 | + self.partial_mapping = partial_mapping |
| 428 | + |
| 429 | + def render(self, context): |
| 430 | + try: |
| 431 | + return self.partial_mapping[self.partial_name].render(context) |
| 432 | + except KeyError: |
| 433 | + raise TemplateSyntaxError( |
| 434 | + f"Partial '{self.partial_name}' is not defined in the current template." |
| 435 | + ) |
| 436 | + |
| 437 | + |
411 | 438 | class ResetCycleNode(Node): |
412 | 439 | def __init__(self, node): |
413 | 440 | self.node = node |
@@ -1174,6 +1201,75 @@ def now(parser, token): |
1174 | 1201 | return NowNode(format_string, asvar) |
1175 | 1202 |
|
1176 | 1203 |
|
| 1204 | +@register.tag(name="partialdef") |
| 1205 | +def partialdef_func(parser, token): |
| 1206 | + """ |
| 1207 | + Declare a partial that can be used in the template. |
| 1208 | +
|
| 1209 | + Usage:: |
| 1210 | +
|
| 1211 | + {% partialdef partial_name %} |
| 1212 | + Content goes here. |
| 1213 | + {% endpartialdef %} |
| 1214 | +
|
| 1215 | + Store the nodelist in the context under the key "partials". It can be |
| 1216 | + retrieved using the ``{% partial %}`` tag. |
| 1217 | +
|
| 1218 | + The optional ``inline`` argument renders the partial's contents |
| 1219 | + immediately, at the point where it is defined. |
| 1220 | + """ |
| 1221 | + match token.split_contents(): |
| 1222 | + case "partialdef", partial_name, "inline": |
| 1223 | + inline = True |
| 1224 | + case "partialdef", partial_name, _: |
| 1225 | + raise TemplateSyntaxError( |
| 1226 | + "The 'inline' argument does not have any parameters; either use " |
| 1227 | + "'inline' or remove it completely." |
| 1228 | + ) |
| 1229 | + case "partialdef", partial_name: |
| 1230 | + inline = False |
| 1231 | + case ["partialdef"]: |
| 1232 | + raise TemplateSyntaxError("'partialdef' tag requires a name") |
| 1233 | + case _: |
| 1234 | + raise TemplateSyntaxError("'partialdef' tag takes at most 2 arguments") |
| 1235 | + |
| 1236 | + # Parse the content until the end tag. |
| 1237 | + valid_endpartials = ("endpartialdef", f"endpartialdef {partial_name}") |
| 1238 | + nodelist = parser.parse(valid_endpartials) |
| 1239 | + endpartial = parser.next_token() |
| 1240 | + if endpartial.contents not in valid_endpartials: |
| 1241 | + parser.invalid_block_tag(endpartial, "endpartialdef", valid_endpartials) |
| 1242 | + |
| 1243 | + # Store the partial nodelist in the parser.extra_data attribute. |
| 1244 | + partials = parser.extra_data.setdefault("partials", {}) |
| 1245 | + if partial_name in partials: |
| 1246 | + raise TemplateSyntaxError( |
| 1247 | + f"Partial '{partial_name}' is already defined in the " |
| 1248 | + f"'{parser.origin.name}' template." |
| 1249 | + ) |
| 1250 | + partials[partial_name] = PartialTemplate(nodelist, parser.origin, partial_name) |
| 1251 | + |
| 1252 | + return PartialDefNode(partial_name, inline, nodelist) |
| 1253 | + |
| 1254 | + |
| 1255 | +@register.tag(name="partial") |
| 1256 | +def partial_func(parser, token): |
| 1257 | + """ |
| 1258 | + Render a partial previously declared with the ``{% partialdef %}`` tag. |
| 1259 | +
|
| 1260 | + Usage:: |
| 1261 | +
|
| 1262 | + {% partial partial_name %} |
| 1263 | + """ |
| 1264 | + match token.split_contents(): |
| 1265 | + case "partial", partial_name: |
| 1266 | + extra_data = parser.extra_data |
| 1267 | + partial_mapping = DeferredSubDict(extra_data, "partials") |
| 1268 | + return PartialNode(partial_name, partial_mapping=partial_mapping) |
| 1269 | + case _: |
| 1270 | + raise TemplateSyntaxError("'partial' tag requires a single argument") |
| 1271 | + |
| 1272 | + |
1177 | 1273 | @register.simple_tag(name="querystring", takes_context=True) |
1178 | 1274 | def querystring(context, *args, **kwargs): |
1179 | 1275 | """ |
|
0 commit comments