@@ -99,14 +99,14 @@ def _underline_token_in_template(
99
99
"""
100
100
101
101
template_before_token = token .template [: token .start_position ]
102
- if ( skipped_lines := template_before_token .count ("\n " ) - lines_around ) > 0 :
102
+ if skipped_lines := template_before_token .count ("\n " ) - lines_around :
103
103
template_before_token = (
104
104
f"{ cls ._skipped_lines_message (skipped_lines )} \n "
105
105
+ "\n " .join (template_before_token .split ("\n " )[- (lines_around + 1 ) :])
106
106
)
107
107
108
108
template_after_token = token .template [token .end_position :]
109
- if ( skipped_lines := template_after_token .count ("\n " ) - lines_around ) > 0 :
109
+ if skipped_lines := template_after_token .count ("\n " ) - lines_around :
110
110
template_after_token = (
111
111
"\n " .join (template_after_token .split ("\n " )[: (lines_around + 1 )])
112
112
+ f"\n { cls ._skipped_lines_message (skipped_lines )} "
@@ -264,12 +264,12 @@ def _find_block(template: str):
264
264
return _BLOCK_PATTERN .search (template )
265
265
266
266
267
- def _find_include (template : str ):
268
- return _INCLUDE_PATTERN .search (template )
267
+ def _find_endblock (template : str , name : str = r"\w+?" ):
268
+ return re .search (r"{% endblock " + name + r" %}" , template )
269
269
270
270
271
- def _find_named_endblock (template : str , name : str ):
272
- return re .search (r"{% endblock " + name + r" %}" , template )
271
+ def _find_include (template : str ):
272
+ return _INCLUDE_PATTERN .search (template )
273
273
274
274
275
275
def _exists_and_is_file (path : str ) -> bool :
@@ -303,11 +303,14 @@ def _resolve_includes_blocks_and_extends(template: str):
303
303
304
304
# Processing nested child templates
305
305
while (extends_match := _find_extends (template )) is not None :
306
- extended_template_name = extends_match .group (0 )[12 :- 4 ]
306
+ extended_template_path = extends_match .group (0 )[12 :- 4 ]
307
+
308
+ if not _exists_and_is_file (extended_template_path ):
309
+ raise OSError (f"Template file not found: { extended_template_path } " )
307
310
308
311
# Load extended template
309
312
with open (
310
- extended_template_name , "rt" , encoding = "utf-8"
313
+ extended_template_path , "rt" , encoding = "utf-8"
311
314
) as extended_template_file :
312
315
extended_template = extended_template_file .read ()
313
316
@@ -316,13 +319,35 @@ def _resolve_includes_blocks_and_extends(template: str):
316
319
# Resolve includes
317
320
template = _resolve_includes (template )
318
321
322
+ # Check for any stacked extends
323
+ if stacked_extends_match := _find_extends (template [extends_match .end () :]):
324
+ raise TemplateSyntaxError (
325
+ "Incorrect use of {% extends ... %}" ,
326
+ Token (
327
+ template ,
328
+ extends_match .end () + stacked_extends_match .start (),
329
+ extends_match .end () + stacked_extends_match .end (),
330
+ ),
331
+ )
332
+
319
333
# Save block replacements
320
334
while (block_match := _find_block (template [offset :])) is not None :
321
335
block_name = block_match .group (0 )[9 :- 3 ]
322
336
323
- endblock_match = _find_named_endblock (template [offset :], block_name )
337
+ # Check for any unopened endblock tags before current block
338
+ if unopened_endblock_match := _find_endblock (
339
+ template [offset : offset + block_match .start ()]
340
+ ):
341
+ raise TemplateSyntaxError (
342
+ "No matching {% block %}" ,
343
+ Token (
344
+ template ,
345
+ offset + unopened_endblock_match .start (),
346
+ offset + unopened_endblock_match .end (),
347
+ ),
348
+ )
324
349
325
- if endblock_match is None :
350
+ if not ( endblock_match := _find_endblock ( template [ offset :], block_name )) :
326
351
raise TemplateSyntaxError (
327
352
"No matching {% endblock %}" ,
328
353
Token (
@@ -370,7 +395,7 @@ def _replace_blocks_with_replacements(template: str, replacements: "dict[str, st
370
395
block_name = block_match .group (0 )[9 :- 3 ]
371
396
372
397
# Self-closing block tag without default content
373
- if (endblock_match := _find_named_endblock (template , block_name )) is None :
398
+ if (endblock_match := _find_endblock (template , block_name )) is None :
374
399
replacement = replacements .get (block_name , "" )
375
400
376
401
template = (
@@ -636,6 +661,14 @@ def _create_template_rendering_function( # pylint: disable=,too-many-locals,too
636
661
637
662
nested_autoescape_modes .pop ()
638
663
664
+ # Token is a endblock in top-level template
665
+ elif token .content .startswith (r"{% endblock " ):
666
+ raise TemplateSyntaxError ("No matching {% block ... %}" , token )
667
+
668
+ # Token is a extends in top-level template
669
+ elif token .content .startswith (r"{% extends " ):
670
+ raise TemplateSyntaxError ("Incorrect use of {% extends ... %}" , token )
671
+
639
672
else :
640
673
raise TemplateSyntaxError (f"Unknown token: { token .content } " , token )
641
674
0 commit comments