django / django/new-features

Add a new `map` template tag that formats a list of items and returns them in a new list

Open
#178 9 comments 1 reaction 0 assignees View on GitHub
Templates Third Party Package
Dominant language
No language data
Stars
188
Forks
7
PR merge metrics
No merged PRs in 30d

Description

### Code of Conduct

- [x] I agree to follow Django's Code of Conduct

### Feature Description

In python, we can use the `map` function to perform an operation on each member of a list (or list-like object) and then return the results in a new list. For example, assuming a queryset of users:
```
format_func = lambda user: f'

{{ user.name }} from {{ user.city }} ({{ user.age }}
'
formatted_users_list = map(format_func, users)
```
It would be helpful to be able to do this directly from within a template, so that, given a `users` queryset, we could derive `formatted_users_list`, which could then be passed on to another template tag or fed into another template for further processing. I would propose the following format:
```
{% map user in users as formatted_users_list %}
{{ user.name }} from {{ user.city }} ({{ user.age }}

{% endmap %}
```
In both of the above cases, `formatted_users_list` now holds formatted HTML for each user record. This list could then be passed to another template as follows:
```
{% include 'some/fixed/template.html' only_a_list_no_strings=formatted_users_list %}
```

### Problem

If I have a list-like object, each of whose members need to be formatted in a particular way, and I want to display results directly on the page, a simple for loop will do the trick. If I want to capture the output as a string, a "simple_block_tag" will work. But if I want to format members and then pass results **as a list** to another template that requires a list rather than a string, then I need a template tag that operates on an iterable in template context and then injects the resulting list back into template context in a manner that is visible to subsequent tags / includes. At present, there is no straightforward way to do this.

### Request or proposal

proposal

### Additional Details

I've already built it. See below.

### Implementation Suggestions

The following code is tested and working.
```
class MapNode(template.Node):
def __init__(self, item_var, items_iterable, new_list_name, nodelist):
self.item_var = item_var
self.items_iterable = items_iterable
self.new_list_name = new_list_name
self.nodelist = nodelist

def render(self, context):
new_list = []
for item in self.items_iterable.resolve(context, ignore_failures=True):
context[self.item_var] = item
new_list.append(self.nodelist.render(context))
context[self.new_list_name] = new_list
return ''

@register.tag('map')
def do_map(parser, token):
"""
This tag is meant to emulate the python ``map`` function, accepting a list of X items, rendering them
one at a time using the template fragment within the map block, and saving the result into a template
variable, which can then be passed on to another template tag or template.

For example, suppose we have a list of users and want to wrap each item in the list in HTML. We might
do this in python as follows before passing it into template context::

wrapped_users = map(lambda user: f"

{user.name} from {user.city} ({user.age})
", users)

Or we could do it directly inside a Django template as follows::

{% map user in users as wrapped_users %}

{{ user.name }} from {{ user.city }} ({{ user.age }})

{% endmap %}

Either way, ``wrapped_users`` will now contain one HTML formatted item for each user in ``users``.

The syntax here is similar to that of a for loop, but unlike the for loop, which renders output in place,
an "as" clause is required here to indicate the name of the context variable in which the resulting list
should be stored. This variable will then be injected into context for later use.
"""
error = False
try:
_, item_var, in_kw, items_iterable, as_kw, new_list_name = token.split_contents()
if in_kw != 'in' or as_kw != 'as':
error = True
except:
error = True
if error:
raise TemplateSyntaxError("'map' tags take the form {%% map ITEM_VAR in ITEMS_ITERABLE"
" as NEW_LIST_NAME %%}; you entered {%% %s %%}" % token.contents)
nodelist = parser.parse(('endmap',))
parser.delete_first_token()
items_iterable = parser.compile_filter(items_iterable)
return MapNode(item_var, items_iterable, new_list_name, nodelist)
```

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by locating Django's template tag registration and Node rendering APIs, then compare the proposed MapNode and do_map entry points with existing block tags. No tests or target files are named; done would require an agreed design, implementation, and coverage for the proposed map syntax and resulting context list.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.