django-components: {% block %} does not work inside a slot
I’m really enjoying using this library but I’m running into an issue when I come to factor out common component hierarchies and I’m not sure if I’m doing something wrong, if it’s not supported, or a bug.
I’m commonly placing the following (simplified a bit for this example) hierarchy of components in several of my templates:
{% component_block "container" %}
{% slot "content" %}
{% component_block "card-container" %}
{% slot "panels" %}
{% component_block "card" %}
{% slot "sections" %}
{% component_block "card-section" %}
{% slot "content" %}
{% component_block "heading" %}{% endcomponent_block %}
{% endslot %}
{% endcomponent_block %}
{% endslot %}
{% endcomponent_block %}
<form action="{{ action }}" method="POST">
{% component_block "card" %}
{% slot "sections" %}
<!-- content here -->
{% endslot %}
{% endcomponent_block %}
{% csrf_token %}
</form>
{% endslot %}
{% endcomponent_block %}
{% endslot %}
{% endcomponent_block %}
Normally (without using django-components) I’d factor out the HTML (that corresponds to the above) into a base template and use {% extends %} and {% block %} to place page specific content in place of <!-- content here -->. However it appears that the inbuilt block template tag only works if it’s not nested inside another template tag (e.g. not inside a component slot).
I also tried creating a component whose template contained the above hierarchy of components and placed a {% slot "innercontent" %}temp{% endslot %} in place of <!-- content here --> but temp never got replace by the externally provided slot content. I’m guessing it can’t differentiate between the slots in the template providing data to other components and the slots it should be exposing when the component is being used (especially when they are nested within each other).
Is there something I’m missing, or another way to do what I want so I don’t have to duplicate the block of nested components over and over again in each template? If not, is it something that can be added easily?
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 17 (14 by maintainers)
I would actually put it a bit more strongly–that code unambiguously renders the
cardcomponent with an emptycontentsslot.@philipstarkey Thanks so much for the extremely detailed example. I’m heading out on a short vacation today, so I should have some time to delve in over the next few days.
You’re right about the
extendstag being the workhorse. As I recall, it basically hijacks the parser, grabs all theblocktags in the current template, then rewrites the parent template with them. So I would not expect things to work if the extendingblocktag is nested inside anything else, becauseextendswon’t be able to find it. I haven’t looked at the reverse situation, where the sourceblockis nested. I would think that ought to work generally, but I can imagine that it might not with this library, because we also do some on-the-fly template rewriting, which might step on Django’s toes.Let me play around with your examples so I have a better understanding of your use case, and I’ll report back.
Ok, I’ve put together (a probably over the top) example project derived from cookiecutter-django.
Relevant files (since it has a giant amount of boilerplate) are:
Component:
Base templates:
Templates:
The two templates match the two scenarios mentioned in my previous post (simple template inheritance and a more complex template inheritance). Both show the default content of the block inside the component and not the content of the block as defined in the templates.
Let me know if I can do anything more to help!
@philipstarkey, just to make sure I understand your first approach, do you have the whole component tree in the root parent template, with a
{% block sections %}tag where the<!-- content here -->placeholder is? I don’t think there’s a good reason that shouldn’t work. That said, Django’s template inheritance system is very complicated, so I won’t be very surprised if we’re screwing up the context in some way that interferes with it.You’re right that the second approach doesn’t work right now. It used to, but it creates a risk of infinite recursions if there are naming collisions, so components currently only get slots from their immediate parent. An easy fix would be to add an option to allow unsafe global slot searching, but that’s not optimal. I’ll think about whether there’s a way to do that safely.
It’s a bit unwieldy, but you ought to be able to insert an
{% include content_template %}tag where you want the content, then pass the parent component the path to another template with the content as a prop.