Skip to content

Conversation

FarhanAliRaza
Copy link
Contributor

@FarhanAliRaza FarhanAliRaza commented Jul 16, 2025

  • Updated Engine.get_template to load a partial template
  • Added partialdef and partial template tags
  • Added documentation for the partials tags
  • Added tests for the partial tags

Trac ticket number

ticket-36410

Branch description

Provide a concise overview of the issue or rationale behind the proposed changes.

Checklist

  • This PR targets the main branch.
  • The commit message is written in past tense, mentions the ticket number, and ends with a period.
  • I have checked the "Has patch" ticket flag in the Trac system.
  • I have added or updated relevant tests.
  • I have added or updated relevant docs, including release notes if applicable.
  • I have attached screenshots in both light and dark modes for any UI changes.

Copy link
Contributor

@nessita nessita left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @FarhanAliRaza, amazing start! 🌟

Thank you for working on this. I have added a first round of initial comments. Let me know if you have any questions.

Looking forward to seeing this progressing!

@FarhanAliRaza
Copy link
Contributor Author

Thanks for the review.
Exciting times ahead. I will start fixing these things as soon as possible.

Copy link
Contributor

@nessita nessita left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@FarhanAliRaza Thank you for the round of fixes, I appreciate the quick turnaround!

I added some more comments, specifically around tests we should use a bit more of subTest, but this is looking great overall.

Then, there is an outstanding design question about where best to place the "template proxy". I like Carlton's suggestions and I have added a few more notes. Also I'm looking forward of @ngnpope input!

Once all the above is resolved, I will do a top-bottom final review to ensure consistency and live testing to check correctness.

@carltongibson
Copy link
Member

@nessita I don't know why GitHub is not showing it on the main Conversations tab, but I replied to your #19643 (comment), and it does show in the Files view.

@FarhanAliRaza
Copy link
Contributor Author

Extra review points are fixed. Now, I'm just waiting for the code-moving bit to be finalized.

@nessita
Copy link
Contributor

nessita commented Jul 23, 2025

@nessita I don't know why GitHub is not showing it on the main Conversations tab, but I replied to your #19643 (comment), and it does show in the Files view.

Thank you for pointing that out, I commented in the same thread.

Copy link
Contributor

@nessita nessita left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Astonishing progress, thank you @FarhanAliRaza and @carltongibson!

@FarhanAliRaza
Copy link
Contributor Author

Added syntax tests. Refactored tests to match other syntax tests like include tests.
Moved TemplateProxy to base.py and renamed it to PartialTemplate.
Moved SubDictionaryWrapper to utils.py

@nessita
Copy link
Contributor

nessita commented Jul 30, 2025

Added syntax tests. Refactored tests to match other syntax tests like include tests. Moved TemplateProxy to base.py and renamed it to PartialTemplate. Moved SubDictionaryWrapper to utils.py

Thank you @FarhanAliRaza for the updates, I'll be restarting work/review on this PR tomorrow morning.

@nessita
Copy link
Contributor

nessita commented Jul 30, 2025

@ngnpope Hello! Is this PR something you wanted/could take a look? Non worries if not, but let me know when you can.

Copy link
Contributor

@nessita nessita left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been doing a deep dive on tests, both the unit and manual tests.

I'll push some tweaks, along with some cleanup, including a squash of all commits. I'm also spent an embarrassing amount of time trying to replace the current mock-heavy test_partial_template_get_exception_info with a test that would be more realistic (more on that below). This has lead to the following potential bug discovery.

Local setup

  • Basic Django project with a testapp app and testapp/urls.py that include:
    path("partialtest/", TemplateView.as_view(template_name="partialtest.html")),
  • partialtest.html looks like this:
<h1>Title</h1>

{% partialdef testtestest %}
    <p>{{ nonexistent|default:"alsonotthere" }}</p>
{% endpartialdef %}

<h2>Sub Title</h2>
{% partial testtestest %}

Bug?

  1. Should path("partialtestpartial/", TemplateView.as_view(template_name="partialtest.html#testestest")) work? Currently is crashes with traceback:
Internal Server Error: /testapp/partialtestpartial/
Traceback (most recent call last):
  File "/home/nessita/fellowship/django/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/home/nessita/fellowship/django/django/core/handlers/base.py", line 221, in _get_response
    response = response.render()
  File "/home/nessita/fellowship/django/django/template/response.py", line 114, in render
    self.content = self.rendered_content
                   ^^^^^^^^^^^^^^^^^^^^^
  File "/home/nessita/fellowship/django/django/template/response.py", line 90, in rendered_content
    template = self.resolve_template(self.template_name)
  File "/home/nessita/fellowship/django/django/template/response.py", line 72, in resolve_template
    return select_template(template, using=self.using)
  File "/home/nessita/fellowship/django/django/template/loader.py", line 47, in select_template
    raise TemplateDoesNotExist(", ".join(template_name_list), chain=chain)
django.template.exceptions.TemplateDoesNotExist: partialtest.html#testestest
[30/Jul/2025 19:24:50] "GET /testapp/partialtestpartial/ HTTP/1.1" 500 87799
  1. If we change "alsonotthere" with alsonotthere (i.e. make the literal string a missing var in the context), IRL we get the expected exception VariableDoesNotExist and I see PartialTemplate.get_exception_info being called. But I tried to model a test after this and is close but it needs tweaks to fully mimic a real scenario, could you take a look and see what's missing?
from django.template import Context, Engine, Template, VariableDoesNotExist
from django.template.base import Token, TokenType
from django.test import SimpleTestCase, override_settings


class TemplateExceptionInfoTests(SimpleTestCase):

    @override_settings(DEBUG=True)
    def test_get_exception_info_reports_correct_location_locmem(self):
        buggy_template = """<h1>Title</h1>
{% partialdef testtestest %}
    <p>{{ nonexistent|default:alsonotthere }}</p>
{% endpartialdef %}
<h2>Sub Title</h2>
{% partial testtestest %}
"""
        engine = Engine(
            loaders=[
                ("django.template.loaders.locmem.Loader", {"template": buggy_template}),
            ]
        )
        # This does NOT trigger PartialTemplate.get_exception_info AND has an origin.
        template = engine.get_template("template")

        # This DOES trigger PartialTemplate.get_exception_info, but it has NO origin.
        template = Template(buggy_template, origin=template.origin)
        try:
            template.render(Context({}))
        except VariableDoesNotExist as e:
            # Simulate token from the failed parse.
            fake_token = Token(
                token_type=TokenType.VAR,
                contents="nonexistent|default:alsonotthere",
                position=(10, len(buggy_template)),
            )
            exc_info = template.get_exception_info(e, token=fake_token)

        # exc_info has the same content whether `PartialTemplate.get_exception_info`
        # returns something or {}!
        self.assertEqual(exc_info.get("message"), "Failed lookup for key [%s] in %r")

I need to stop for now but as said I will some changes so please pull them before making any further changes. Thanks! 🌟

@nessita nessita force-pushed the template-partials branch from a7a6263 to 16804e4 Compare July 30, 2025 18:55
@carltongibson
Copy link
Member

carltongibson commented Jul 30, 2025

@nessita I don't know if this is the issue but, the partial name in the url is testestest :

path("partialtestpartial/", TemplateView.as_view(template_name="partialtest.html#testestest"))

But the partial in the template is testtestest:

{% partialdef testtestest %}

There's an extra t in the latter one, which would explain the template not found exception.

@nessita
Copy link
Contributor

nessita commented Jul 30, 2025

@nessita I don't know if this is the issue but, the partial name in the url is testestest :

path("partialtestpartial/", TemplateView.as_view(template_name="partialtest.html#testestest"))

But the partial in the template is testtestest:

{% partialdef testtestest %}

There's an extra t in the latter one, which would explain the template not found exception.

Thank you for looking so quickly, that was a typo (caused by one of the manual testing stages to test an undefined partial name). I've swicthed to partialtest for clarity and I still get:

Exception Type: TemplateDoesNotExist at /testapp/partialtestpartial/
Exception Value: partialtest.html#partialtest

Template:

<h1>Title</h1>

{% partialdef partialtest %}
    <p>{{ nonexistent|default:alsonotthere }}</p>
{% endpartialdef %}

<h2>Sub Title</h2>
{% partial partialtest %}

@nessita nessita force-pushed the template-partials branch from 16804e4 to 0024ea2 Compare July 30, 2025 19:21
@carltongibson
Copy link
Member

carltongibson commented Jul 30, 2025

@FarhanAliRaza See if you can reproduce.

Against template-partials (at b6adac3) I get:

  1. TemplateNotFound for a missing partial name: #not-a-partial say. (Expected)
  2. <p>alsonotthere</p> output for <p>{{ nonexistent|default:"alsonotthere" }}</p> (Expected)
  3. VariableDoesNotExist for <p>{{ nonexistent|default:alsonotthere }}</p> (I guess we're saying Expected right? — Didn't think about it fully, but going by what Natalia said.)

I didn't test this branch, but if that's not what we're seeing a difference snuck in, that we'll need to pin down.

@carltongibson
Copy link
Member

carltongibson commented Jul 30, 2025

I didn't test this branch, ...

OK, I tested. I can't reproduce this @nessita. With the "alsonotthere" version, I get the expected output in the browser.

Screenshot 2025-07-30 at 21 43 25

natalia.zip

@nessita
Copy link
Contributor

nessita commented Jul 30, 2025

I didn't test this branch, ...

OK, I tested. I can't reproduce this @nessita. With the "alsonotthere" version, I get the expected output in the browser.

Thank you! I'll debug more tomorrow, will start from scratch with your project.

@nessita
Copy link
Contributor

nessita commented Jul 30, 2025

I didn't test this branch, ...

OK, I tested. I can't reproduce this @nessita. With the "alsonotthere" version, I get the expected output in the browser.

Thank you! I'll debug more tomorrow, will start from scratch with your project.

I can confirm this works, as Carlton pointed out. I have just now tested with a bunch of prints removed, I wonder if that was causing the issues I saw before 🤔

As a side note, and not related to this branch nor to the issue above, a print of exception as the first statement in get_exception_info generates a max recursion error when there is an exception in a template. This is informational since it was very hard to pin point!

@FarhanAliRaza I guess that one thing that would be useful for me is if you could take a look at the test trying to test more realistically PartialTemplate.get_exception_info to, if possible, drop the current mocks.

@FarhanAliRaza
Copy link
Contributor Author

@FarhanAliRaza See if you can reproduce.

@carltongibson I can not reproduce it. Working as expected.

TemplateNotFound for a missing partial name: #not-a-partial say. (Expected)

alsonotthere

output for

{{ nonexistent|default:"alsonotthere" }}

(Expected) VariableDoesNotExist for

{{ nonexistent|default:alsonotthere }}

(I guess we're saying Expected right? — Didn't >think about it fully, but going by what Natalia said.)

The same applies to me for this current branch.

@nessita Working on the test case.

@FarhanAliRaza
Copy link
Contributor Author

@nessita I have added tests for get_exception_info by using the template_debug that I believe is the output of the get_exception_info

with self.assertRaises(VariableDoesNotExist) as cm:
            template.render(context)

exc_info = cm.exception.template_debug

@nessita
Copy link
Contributor

nessita commented Jul 31, 2025

@nessita I have added tests for get_exception_info by using the template_debug that I believe is the output of the get_exception_info

with self.assertRaises(VariableDoesNotExist) as cm:
            template.render(context)

exc_info = cm.exception.template_debug

Thank you! I can't look right away because I'm deep in other review, but could you please double check that if the implementation for PartialTemplate.get_exception_info would get changed to return {}, the proper test fail with meaningful information?

@FarhanAliRaza
Copy link
Contributor Author

@nessita

Yes, I made sure it gave KeyError
Should I add asserts to provide more descriptive errors?

ERROR: test_partial_runtime_error_exception_info (template_tests.syntax_tests.test_partials.PartialTagTests.test_partial_runtime_error_exception_info)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/farhan/code/django/tests/template_tests/syntax_tests/test_partials.py", line 448, in test_partial_runtime_error_exception_info
    self.assertIn("badsimpletag", exc_debug["during"])
                                  ~~~~~~~~~^^^^^^^^^^
KeyError: 'during'

======================================================================
ERROR: test_partial_runtime_exception_has_debug_info (template_tests.syntax_tests.test_partials.PartialTagTests.test_partial_runtime_exception_has_debug_info)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/farhan/code/django/tests/template_tests/syntax_tests/test_partials.py", line 303, in test_partial_runtime_exception_has_debug_info
    self.assertEqual(exc_info["during"], "{{ nonexistent|default:alsonotthere }}")
                     ~~~~~~~~^^^^^^^^^^
KeyError: 'during'

======================================================================
FAIL: test_partial_template_get_exception_info_delegation (template_tests.syntax_tests.test_partials.PartialTagTests.test_partial_template_get_exception_info_delegation)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/farhan/code/django/tests/template_tests/syntax_tests/test_partials.py", line 335, in test_partial_template_get_exception_info_delegation
    self.assertIn("message", exc_info)
    ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
AssertionError: 'message' not found in {}

----------------------------------------------------------------------
Ran 25 tests in 0.016s

Copy link
Member

@ngnpope ngnpope left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @carltongibson and @nessita. I managed to do a review this evening - hope it helps. This is shaping up to be very nice and feels like it'll provide a lot of power with very little code to implement it - thanks for your efforts @FarhanAliRaza! 🥇

@FarhanAliRaza
Copy link
Contributor Author

Implemented the suggestion from @ngnpope .
Handled nested partials handling by updating find_partial_source to use a single regex and handle search in nested case.
Refactored SubDictionaryWrapper to move error handling out of it to RenderPartialNode
and moved it to django/utils/datastructures.py
And other refactors.

@carltongibson
Copy link
Member

@FarhanAliRaza @nessita on the new-features repo there's a suggestion to move _render() and render() into a common base mixin, that we might want to pick up here?

Copy link
Contributor

@nessita nessita left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @FarhanAliRaza! This is shaping nicely and with a delight precision. 🌟

Today I focused mostly on the new syntax tests. Overall I would like to see more meaningful names used for the tests and the templates, and also more of subTest when semantically adequate. I added some suggestions for both of these points.

Then, a minor nitpick: I think that the partial name used for testing could be partial-name or testing-name to signal clearly to readers that this is a name identifier.

Thinking about performance, we may need one or two tests with big chunky templates (using html test files) making heavy use of the new tags to see if the regexes show any obvious catastrophic side-effect.

@carltongibson
Copy link
Member

I'm not seeing the failure here locally. eb8c218
eed7f44 passes. The CI run looks to have passed too (the two failures look unrelated).

@FarhanAliRaza
Copy link
Contributor Author

FarhanAliRaza commented Aug 12, 2025

@carltongibson
I ll retest.

I'm not seeing the failure here locally. eb8c218

The first time that happened to me, too. Can you check if that specific test is running because it has _ in front of it? So it did not run for me for first try.

@carltongibson
Copy link
Member

The first time that happened to me, too. Can you check if that specific test is running because it has _ in front of it? So it did not run for me for first try.

Thanks. Got it.

@carltongibson
Copy link
Member

carltongibson commented Aug 12, 2025

@FarhanAliRaza Yep, so you're bang on. It's that the @setup helper is using the base Engine directly, rather than the DjangoTemplates backend (and then each backend, Django/Jinja/..., extends Engine).

We setup the loaders on Engine to use locmem and then it has no awareness of partials. Hence the error.

Engine.get_template method to make it aware of partials.

I don't think that's right: Partials are being added to the Django Backend, not the backend agnostic template functionality per se.

Change the loading logic in the Include tag to somehow load using the django backend.

No, include is fine. It's the test that's at fault here.

I think we just need to not use the setup helper here, and test the Django backend itself (which works).

Update:

Engine.get_template method to make it aware of partials.

I guess we could create a mini Engine subclass just with the necessary change for the setup helper to use. It would only need to pull off the fragment, call the super implementation, and then check for the partial at the end. (As the loader does in template-partials.)

Personally I'd rewrite the test but 🤷

@FarhanAliRaza
Copy link
Contributor Author

FarhanAliRaza commented Aug 12, 2025

I tried without @setup decorator also .
It gives the same error .
I changed the test to load using template files using filesystem loader and also by creating custom django backend in the test function, it still fails.

Because in include the loading happens through context.template loading that somehow does not use the Django backend. I am not sure how exactly but I added print statement loading during include, it does not Django backend for that.

@carltongibson
Copy link
Member

@FarhanAliRaza Can you push your rewritten test?

This pattern...

         "MAIN TEMPLATE START\n"
          "{% include 'partial_included.html#included-partial' %}\n"
          "MAIN TEMPLATE END"

... has been working since the package was created.

We've had tests on that since ≈ day one no? Suddenly there's a failure: it's unlikely to now need a change to include. (It's a test artefact is first suspect.)

@FarhanAliRaza
Copy link
Contributor Author

I only tested the usage of partial inside the included HTML file. Not tested partial loading like this {% include 'partial_included.html#included-partial' %} that is failing now.

only tested this about include

@setup(
        {
            "partial_with_include": (
                "MAIN TEMPLATE START\n"
                "{% include 'partial_included.html' %}\n"
                "MAIN TEMPLATE END"
            )
        },
        partial_templates,
    )
    def test_partial_in_included_template(self):
        output = self.engine.render_to_string("partial_with_include")
        expected = (
            "MAIN TEMPLATE START\nINCLUDED TEMPLATE START\n\n\n"
            "Now using the partial: \n"
            "THIS IS CONTENT FROM THE INCLUDED PARTIAL\n\n\n"
            "INCLUDED TEMPLATE END\n\nMAIN TEMPLATE END"
        )
        self.assertEqual(output, expected)

@FarhanAliRaza
Copy link
Contributor Author

test.zip
experimental project.

@FarhanAliRaza
Copy link
Contributor Author

This solution works if we change the loading in the include tag.

Another method is refactoring get_template.

 diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py
  index 3a0d054e62..4e76f1a44c 100644
  --- a/django/template/loader_tags.py
  +++ b/django/template/loader_tags.py
  @@ -4,6 +4,7 @@ from collections import defaultdict
   from django.utils.safestring import mark_safe

   from .base import Node, Template, TemplateSyntaxError, TextNode, Variable,
  token_kwargs
  +from .exceptions import TemplateDoesNotExist
   from .library import Library

   register = Library()
  @@ -197,7 +198,31 @@ class IncludeNode(Node):
               cache = context.render_context.dicts[0].setdefault(self, {})
               template = cache.get(template_name)
               if template is None:
  -                template = context.template.engine.select_template(template_name)
  +                partial_template = None
  +                for name in template_name:
  +                    if "#" in name:
  +                        base_name, _, partial_name = name.partition("#")
  +                        try:
  +                            base_template = context.template.engine.get_template(
  +                                base_name
  +                            )
  +                            extra_data = getattr(base_template, "extra_data", {})
  +                            if (
  +                                "template-partials" in extra_data
  +                                and partial_name in 
  extra_data["template-partials"]
  +                            ):
  +                                partial_template = 
  extra_data["template-partials"][
  +                                    partial_name
  +                                ]
  +                                partial_template.engine = context.template.engine
  +                                break
  +                        except TemplateDoesNotExist:
  +                            continue
  +
  +                if partial_template:
  +                    template = partial_template
  +                else:
  +                    template = 
  context.template.engine.select_template(template_name)
                   cache[template_name] = template
           # Use the base.Template of a backends.django.Template.
           elif hasattr(template, "template"):

@carltongibson
Copy link
Member

carltongibson commented Aug 12, 2025

OK, we've just inserted the partial lookup in the wrong place. Confusing names strike again. django.template.backends.base.BaseEngine is what everything subclasses. django.template.engine.Engine is Django specific — so not a problem adjusting it — and where the partial lookup needs to live.

@FarhanAliRaza If you restore django/template/backends/django.py and implement the logic there in django/template/engine.py we'll be in the right place, I think.

(When migrating the partial lookup logic from the loader in template-partials, we've moved it up two levels to the backend, rather than just one to engine. Pushing it down again will give us what was intended.)

@nessita
Copy link
Contributor

nessita commented Aug 12, 2025

@FarhanAliRaza Can you push your rewritten test?

This pattern...

         "MAIN TEMPLATE START\n"
          "{% include 'partial_included.html#included-partial' %}\n"
          "MAIN TEMPLATE END"

... has been working since the package was created.

We've had tests on that since ≈ day one no?

Since I started reviewing, I did not see a test for this specific case. I think we may have missed it since the start (at least in this PR, unsure about template-partials).

Suddenly there's a failure: it's unlikely to now need a change to include. (It's a test artefact is first suspect.)

A little bit more of info around this: I added the tests because the include wasn't working for me in a real (test) project. Amazing that you both could debugged it so quickly!

@carltongibson
Copy link
Member

... unsure about template-partials

It turned out not. Well, except in my work project of course. 😅 Since the loader (where it's implemented in template-partials) is inside the engine it works. The issue here was we'd move the logic outside the engine, which meant it only worked via the usual public API, but not via the reference to the engine that the include tag uses.

@nessita
Copy link
Contributor

nessita commented Aug 12, 2025

django.template.backends.base.BaseEngine is what everything subclasses. django.template.engine.Engine is Django specific — so not a problem adjusting it — and where the partial lookup needs to live.

To double check that we are in the same page, let me share what I think this means:

  • Revert changes to django/template/backends/django.py.
  • Extend the implementation of Engine.get_template to detect partials and try to server them when possible.

Is that correct?

@FarhanAliRaza
Copy link
Contributor Author

Moved get_template partial implementation from DjangoTemplates.get_template to Engine.get_template. Added tests that @nessita suggested. Thank you, it was a great find.

Fixed test mocks for the new implementation.

@nessita nessita force-pushed the template-partials branch from 78c22bd to b75bbcf Compare August 14, 2025 02:16
Copy link
Contributor

@nessita nessita left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are super close to the final line! 🏁

I have made a full-full review today. I renamed the new utility for the deferred dict access and added a full test case for it. I went thru the docs and pushed what I think is hopefully a slightly clearer wording, and I also made all the examples in ref/templates/language.txt consistent with the User/Author example from the "reuse" section.

I'll give a final pass tomorrow and likely merge 🌟

@smithdc1
Copy link
Member

The docs mention HTMX. 🤔

I get that HTMX is popular right now but mentioning it in Django's docs seems surprising to me. Typically, the docs avoid mentioning a particular package -- to the extent that the SC have now launched the ecosystem page on the website.

@carltongibson
Copy link
Member

carltongibson commented Aug 14, 2025

I don't think we have to pretend Django is the only software that exists. I think it's plain that the vast majority of users interested in template partials are using it with HTMX. The weird thing would be to not mention it.

The docs contain mentions of jQuery, for example, and it's perfectly natural. (There are many similar. That's just one.)

@nessita
Copy link
Contributor

nessita commented Aug 14, 2025

@carltongibson @FarhanAliRaza @ngnpope Last night I dreamt about partials (no kidding), and I woke up thinking that the extra_data internal dict key might be better named simply partials (instead of template-partials), since the code where this key is used is already within a "template" context.

If you think this is inaccurate or a bad idea, please let me know!

@carltongibson
Copy link
Member

@nessita no problem, yes 👍 -- it's gotta be close if you're dreaming about it 🛌 -- thanks for all your sterling work! 🎩

@FarhanAliRaza
Copy link
Contributor Author

I also think your dream should be fulfilled. @nessita 😅
🎉

@nessita
Copy link
Contributor

nessita commented Aug 14, 2025

I also think your dream should be fulfilled. @nessita 😅 🎉

Amazing, thank you both. I'll push that change and merge once CI is green. This is very exciting!

@nessita nessita force-pushed the template-partials branch from b75bbcf to 3c510f8 Compare August 14, 2025 12:43
Copy link
Contributor

@nessita nessita left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is it! Amazing work @FarhanAliRaza and @carltongibson 🌟

Thank you @ngnpope for you essential review and feedback!

Copy link
Contributor

@sarahboyce sarahboyce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looked through briefly, think this looks great 👍

case "partialdef", partial_name:
inline = False
case _:
raise TemplateSyntaxError("'partialdef' tag requires 2-3 arguments")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
raise TemplateSyntaxError("'partialdef' tag requires 2-3 arguments")
raise TemplateSyntaxError("'partialdef' tag requires 1-2 arguments")

raise TemplateSyntaxError("'partialdef' tag requires 2-3 arguments")

# Parse the content until the end tag.
valid_endpartials = ("endpartialdef", f"endpartialdef {partial_name}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the spacing element is inconsistently strict considering you can do partialdef button (2 spaces) but not endpartialdef button (2 spaces)

However, this is already the case with other tags like block. So at least we're consistent

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL 😅 I never hit that.

(I agree it seems a bit draconian.) separate clean up, can I suggest (at most)

@nessita
Copy link
Contributor

nessita commented Aug 14, 2025

I can push the "2-3" string correction soon (I have in mind a slightly more specific error message, one for no params and other for the default match case). I'll do that once I complete this 3 hours debugging session the failing test in mysql + jenkins 🤯

…plate Language.

Introduced `{% partialdef %}` and `{% partial %}` template tags to
define and render reusable named fragments within a template file.
Partials can also be accessed using the `template_name#partial_name`
syntax via `get_template()`, `render()`, `{% include %}`, and other
template-loading tools.

Adjusted `get_template()` behavior to support partial resolution, with
appropriate error handling for invalid names and edge cases. Introduced
`PartialTemplate` to encapsulate partial rendering behavior.

Includes tests and internal refactors to support partial context
binding, exception reporting, and tag validation.

Co-authored-by: Carlton Gibson <[email protected]>
Co-authored-by: Natalia <[email protected]>
Co-authored-by: Nick Pope <[email protected]>
@nessita nessita force-pushed the template-partials branch from 3c510f8 to 6bd7bc8 Compare August 14, 2025 20:51
@nessita nessita merged commit 5e06b97 into django:main Aug 15, 2025
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants