# Feature proposal: Simple block tag

**URL:** <https://forum.djangoproject.com/t/feature-proposal-simple-block-tag/32229>\
**Category:** Django Internals\
**Created:** [June 19, 2024, 9:54am UTC](https://forum.djangoproject.com/t/feature-proposal-simple-block-tag/32229 "2024-06-19T09:54:42Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![theorangeone](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/theorangeone/32/21264_2.png) [@theorangeone](https://forum.djangoproject.com/u/theorangeone)\
**Post date:** [June 19, 2024, 9:54am UTC](https://forum.djangoproject.com/t/feature-proposal-simple-block-tag/32229/1 "2024-06-19T09:54:42Z")

</div>

Django’s template system allows creating custom tags, but they don’t allow taking content:

```auto
{% mysimpletag foo="bar" %}

```

However there are plenty of use-cases where it’d be useful to also collect content along with the tag, turning it into a “block” tag:

```auto
{% mysimpletag foo="bar" %}
  This content gets captured too
{% endmysimpletag %}

```

This currently requires using complex internals of the template system (some documented, some not) to achieve. The [cache tag](https://github.com/django/django/blob/main/django/templatetags/cache.py) is a good example of this kind of “simple” block, which requires some parser internals.

I propose adding a `@register.simple_block` method to allow easy registering of custom block tags. The API would be similar to `simple_tag`, however with a required `content` argument containing the (rendered) template content:

```python
@register.simple_block(takes_context=True)
def mysimpletag(context: Context, content: str, foo: str) -> str:
    return f"foo = {foo}"

```

I raised [a ticket](https://code.djangoproject.com/ticket/35535) about this, but was pointed here as a better way of collecting interest. I already have a working demo of this working.

---

<div class="post-metadata">

**Author:** ![carltongibson](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/carltongibson/32/267_2.png) [@carltongibson](https://forum.djangoproject.com/u/carltongibson)\
**Post date:** [June 19, 2024, 10:17am UTC](https://forum.djangoproject.com/t/feature-proposal-simple-block-tag/32229/2 "2024-06-19T10:17:26Z")

</div>

I’m +1 in the general direction here. (There’s are any number of historical threads asking for related ideas.)

FWIW, assuming some `as` integration, I think it’s [#6378 (Capture arbitrary output as a template variable) – Django](https://code.djangoproject.com/ticket/6378) revisited 😅

---

<div class="post-metadata">

**Author:** ![michael-yin](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/michael-yin/32/105_2.png) [@michael-yin](https://forum.djangoproject.com/u/michael-yin)\
**Post date:** [June 19, 2024, 2:49pm UTC](https://forum.djangoproject.com/t/feature-proposal-simple-block-tag/32229/3 "2024-06-19T14:49:52Z")

</div>

You can take a look at [https://github.com/rails-inspire-django/django-viewcomponent](https://github.com/rails-inspire-django/django-viewcomponent)

With the `slot` feature, you can pass huge HTML to the component and then do some operation.

It has helped me solved similar problems in some projects.

---

<div class="post-metadata">

**Author:** ![carltongibson](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/carltongibson/32/267_2.png) [@carltongibson](https://forum.djangoproject.com/u/carltongibson)\
**Post date:** [June 19, 2024, 3:00pm UTC](https://forum.djangoproject.com/t/feature-proposal-simple-block-tag/32229/4 "2024-06-19T15:00:52Z")

</div>

Yes… — that’s a nice package @michael-yin.

There are several in this space. The danger is we get into a never ending discussion about which features to add. (This happens a lot 😅)

Simple, to the point, covers the 80% case is probably enough for an addition to Django itself here, leaving space in the ecosystem for folks to explore more advanced options.

---

<div class="post-metadata">

**Author:** ![adamchainz](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/adamchainz/32/26_2.png) [@adamchainz](https://forum.djangoproject.com/u/adamchainz)\
**Post date:** [July 4, 2024, 12:46pm UTC](https://forum.djangoproject.com/t/feature-proposal-simple-block-tag/32229/5 "2024-07-04T12:46:46Z")

</div>

+1 from me. I have missed this in the past.

Presumably it will allow both positional and keyword arguments, like `simple_tag`. They would need to be checked for collision with the name `content`.

I suggest the name `simple_block_tag` to retain the naming scheme of `simple_tag` and `inclusion_tag`.

> [@theorangeone](#):
>
> I already have a working demo of this working.

Where?

> [@carltongibson](#):
>
> Simple, to the point, covers the 80% case is probably enough for an addition to Django itself here, leaving space in the ecosystem for folks to explore more advanced options.

Indeed, this would be called “simple” for that reason.

---

<div class="post-metadata">

**Author:** ![theorangeone](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/theorangeone/32/21264_2.png) [@theorangeone](https://forum.djangoproject.com/u/theorangeone)\
**Post date:** [July 4, 2024, 1:04pm UTC](https://forum.djangoproject.com/t/feature-proposal-simple-block-tag/32229/6 "2024-07-04T13:04:50Z")

</div>

> [@adamchainz](#):
>
> Presumably it will allow both positional and keyword arguments, like `simple_tag`.

Absolutely! We can reuse much of the implementation of `simple_tag` to keep many of the same semantics. Validating the overlap of the `content` argument is something I’d missed, but assuming `simple_tag` does the same, I should be able to share some of the implementation.

> [@adamchainz](#):
>
> Where?

It was in a private repo when I opened the ticket, but I recently pushed up a branch to start integrating it with Django core. It’s still _very_ rough, and missing “important” things like tests, but the basics are there: [GitHub - RealOrangeOne/django at 35535-simple-block-tag](https://github.com/RealOrangeOne/django/tree/35535-simple-block-tag). Hoping to get around to finishing it off and writing the related in the next week or so to get a draft PR up.

---

<div class="post-metadata">

**Author:** ![tom](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/tom/32/6818_2.png) [@tom](https://forum.djangoproject.com/u/tom)\
**Post date:** [July 5, 2024, 2:08pm UTC](https://forum.djangoproject.com/t/feature-proposal-simple-block-tag/32229/7 "2024-07-05T14:08:18Z")

</div>

I’m late but also +1.

It might be useful to document an example use-case for it, so people have an idea when to use it.

---

<div class="post-metadata">

**Author:** ![theorangeone](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/theorangeone/32/21264_2.png) [@theorangeone](https://forum.djangoproject.com/u/theorangeone)\
**Post date:** [July 5, 2024, 2:48pm UTC](https://forum.djangoproject.com/t/feature-proposal-simple-block-tag/32229/8 "2024-07-05T14:48:59Z")

</div>

et voilà: [Fixed #35535 -- Add simple block tag by RealOrangeOne · Pull Request #18343 · django/django · GitHub](https://github.com/django/django/pull/18343)

---

<div class="post-metadata">

**Author:** ![matiaschn](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/matiaschn/32/14135_2.png) [@matiaschn](https://forum.djangoproject.com/u/matiaschn)\
**Post date:** [January 18, 2025, 8:39pm UTC](https://forum.djangoproject.com/t/feature-proposal-simple-block-tag/32229/9 "2025-01-18T20:39:24Z")

</div>

I’m late too, but maybe we could add an `inclusion_block_tag`. I’m not an expert but this is what I have (based on the simple\_block\_tag):

```python
from functools import wraps
from inspect import getfullargspec, unwrap

from django.template import Library
from django.template.exceptions import TemplateSyntaxError
from django.template.library import InclusionNode, parse_bits

class ExtendedLibrary(Library):
    def inclusion_block_tag(self, filename, func=None, takes_context=None, name=None):
        def dec(func):
            (
                params,
                varargs,
                varkw,
                defaults,
                kwonly,
                kwonly_defaults,
                _,
            ) = getfullargspec(unwrap(func))
            function_name = name or func. __name__

            @wraps(func)
            def compile_func(parser, token):
                tag_params = params.copy()

                if takes_context:
                    if len(tag_params) >= 2 and tag_params[1] == "content":
                        del tag_params[1]
                    else:
                        raise TemplateSyntaxError(
                            f"{function_name!r} is decorated with takes_context=True so"
                            " it must have a first argument of 'context' and a second "
                            "argument of 'content'"
                        )
                elif tag_params and tag_params[0] == "content":
                    del tag_params[0]
                else:
                    raise TemplateSyntaxError(f"'{function_name}' must have a first argument of 'content'")

                bits = token.split_contents()[1:]

                nodelist = parser.parse((f"end{function_name}", f"end_{function_name}"))
                parser.delete_first_token()

                args, kwargs = parse_bits(
                    parser,
                    bits,
                    tag_params,
                    varargs,
                    varkw,
                    defaults,
                    kwonly,
                    kwonly_defaults,
                    takes_context,
                    function_name,
                )
                return InclusionBlockNode(filename, nodelist, func, takes_context, args, kwargs)

            self.tag(function_name, compile_func)
            return func

        return dec

class InclusionBlockNode(InclusionNode):
    def __init__ (self, filename, nodelist, *args, **kwargs):
        super(). __init__ (filename=filename, *args, **kwargs)
        self.nodelist = nodelist

    def get_resolved_arguments(self, context):
        resolved_args, resolved_kwargs = super().get_resolved_arguments(context)

        # Restore the "content" argument.
        # It will move depending on whether takes_context was passed.
        resolved_args.insert(1 if self.takes_context else 0, self.nodelist.render(context))

        return resolved_args, resolved_kwargs

```

Edit: formatting

---

<div class="post-metadata">

**Author:** ![theorangeone](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/theorangeone/32/21264_2.png) [@theorangeone](https://forum.djangoproject.com/u/theorangeone)\
**Post date:** [January 23, 2025, 5:30pm UTC](https://forum.djangoproject.com/t/feature-proposal-simple-block-tag/32229/10 "2025-01-23T17:30:39Z")

</div>

I’d love an `inclusion_block_tag` - I agree it’s the next logical step. It was talked about in a few different places, but intentionally out of scope for the original PR.

I also wrote a patch on my PR which works in a very similar way: [Fixed #35535 -- Add simple block tag by RealOrangeOne · Pull Request #18343 · django/django · GitHub](https://github.com/django/django/pull/18343#discussion_r1697263460). Wouldn’t take much to combine the 2 into an actual PR.

To help get things moving, I’ve opened a Trac ticket for `inclusion_block_tag` support: [#36130 (Add inclusion support for simple block tags) – Django](https://code.djangoproject.com/ticket/36130)
