Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@

**Compose templates easily**

Django Compose tags provide the tags to ease templates composition,
Django Compose tags provides the tags to ease templates composition,
with an api close to the `include` template tag.

The api is thought to ease the implementation and usage of design systems in django.
The api is thought to simplify the implementation and usage of design systems in django.

---

Expand All @@ -27,8 +27,8 @@ Write your template as you would for the `include` tag:

## The `compose` tag

The `compose` template tag behave similarly to [django's `include`][django-include-doc].
The main difference is that the content between `{% compose %}` and `{% endcompose %}` is regular django template that's accessible withing the composed template as the `{{ children }}` variable.
The `compose` template tag behaves similarly to [django's `include`][django-include-doc].
The main difference is that the content between `{% compose %}` and `{% endcompose %}` is regular django template that's accessible within the composed template as the `{{ children }}` variable.

```jinja
{% load compose %}
Expand All @@ -39,7 +39,7 @@ The main difference is that the content between `{% compose %}` and `{% endcompo
{% endcompose %}
```

By default the composed template doesn't have access to the context, if you need access to the context set the takes_context option `{% compose "card.html" takes_context %}`. `takes_context` is the opposite of the `only`
By default, the composed template doesn't have access to the context, if you need access to the context set the takes_context option `{% compose "card.html" takes_context %}`. `takes_context` is the opposite of the `only`

## The `define` tag

Expand All @@ -60,7 +60,7 @@ Card body with {{ context_variable }}

`composition_tag` is to `compose` what [`inclustion_tag`][django-inclusiontag-doc] is to the `include` tag.

Define you tag function and decorate it with `composition_tag`, the decorator takes care of passing the children as the first argument of your custom tag.
Define your tag function and decorate it with `composition_tag`, the decorator takes care of passing the children as the first argument of your custom tag.

```python
# mydesignsystem/templatetags/mydesignsystem.py
Expand Down Expand Up @@ -105,10 +105,10 @@ def mytag(children, **kwargs):
...
```

When you don't need to do any python processing, there is a default implementation that forward all parameters as is.
When you don't need to do any python processing, there is a default implementation that forwards all parameters as is.
When you rely on that default implementation the default tag name is derived from the template name.

Which mean `register.tag(composition_tag("button.html"))` is equivalent to
Which means `register.tag(composition_tag("button.html"))` is equivalent to
```python
@register.tag
@composition_tag('button.html')
Expand Down
109 changes: 0 additions & 109 deletions compose_tags/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,112 +87,3 @@ def __init__(self, target_var, nodelist):
def render(self, context):
context[self.target_var] = self.nodelist.render(context)
return ""


# class DefineForNode(Node):
# # copypasta of django.template.defaulttags.ForNode with small modifications for target_var
# child_nodelists = ("nodelist_loop", "nodelist_empty")
#
# def __init__(
# self,
# target_var,
# loopvars,
# sequence,
# is_reversed,
# nodelist_loop,
# nodelist_empty=None,
# ):
# self.target_var = target_var # divergence from ForNode
# self.loopvars, self.sequence = loopvars, sequence
# self.is_reversed = is_reversed
# self.nodelist_loop = nodelist_loop
# if nodelist_empty is None:
# self.nodelist_empty = NodeList()
# else:
# self.nodelist_empty = nodelist_empty
#
# def __repr__(self):
# reversed_text = " reversed" if self.is_reversed else ""
# return "<%s: %s, for %s in %s, tail_len: %d%s>" % (
# self.__class__.__name__,
# self.target_var, # divergence from ForNode
# ", ".join(self.loopvars),
# self.sequence,
# len(self.nodelist_loop),
# reversed_text,
# )
#
# def render(self, context):
# list_value = [] # divergence from ForNode
#
# if "forloop" in context:
# parentloop = context["forloop"]
# else:
# parentloop = {}
#
# with context.push():
# values = self.sequence.resolve(context, ignore_failures=True)
# if values is None:
# values = []
# if not hasattr(values, "__len__"):
# values = list(values)
# len_values = len(values)
# if len_values < 1:
# list_value = [
# self.nodelist_empty.render(context)
# ] # divergence from ForNode
# else: # divergence from ForNode
# if self.is_reversed:
# values = reversed(values)
# num_loopvars = len(self.loopvars)
# unpack = num_loopvars > 1
# # Create a forloop value in the context. We'll update counters on each
# # iteration just below.
# loop_dict = context["forloop"] = {"parentloop": parentloop}
# for i, item in enumerate(values):
# nodelist = [] # divergence from ForNode
# # Shortcuts for current loop iteration number.
# loop_dict["counter0"] = i
# loop_dict["counter"] = i + 1
# # Reverse counter iteration numbers.
# loop_dict["revcounter"] = len_values - i
# loop_dict["revcounter0"] = len_values - i - 1
# # Boolean values designating first and last times through loop.
# loop_dict["first"] = i == 0
# loop_dict["last"] = i == len_values - 1
#
# pop_context = False
# if unpack:
# # If there are multiple loop variables, unpack the item into
# # them.
# try:
# len_item = len(item)
# except TypeError: # not an iterable
# len_item = 1
# # Check loop variable count before unpacking
# if num_loopvars != len_item:
# raise ValueError(
# "Need {} values to unpack in for loop; got {}. ".format(
# num_loopvars, len_item
# ),
# )
# unpacked_vars = dict(zip(self.loopvars, item))
# pop_context = True
# context.update(unpacked_vars)
# else:
# context[self.loopvars[0]] = item
#
# for node in self.nodelist_loop:
# nodelist.append(node.render_annotated(context))
#
# if pop_context:
# # Pop the loop variables pushed on to the context to avoid
# # the context ending up in an inconsistent state when other
# # tags (e.g., include and with) push data to context.
# context.pop()
# list_value.append(
# mark_safe("".join(nodelist))
# ) # divergence from ForNode
# context[self.target_var] = list_value # divergence from ForNode
# return "" # divergence from ForNode
#
6 changes: 3 additions & 3 deletions compose_tags/tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def parse_bits_with_children(
Do the same as parse_bits, except that context is the second argument, not the first.
The first one being children.
"""
if not params or not params[0] == "children":
if not params or params[0] != "children":
raise TemplateSyntaxError(
"'%s' must have a first argument of 'children'" % name
)
Expand Down Expand Up @@ -84,8 +84,8 @@ def dec(func_or_parser, token=None):
) = getfullargspec(unwrap(func))

@functools.wraps(func)
def compile_func(parser, token):
name, *bits = token.split_contents()
def compile_func(parser, compose_token):
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ça evite un warning de shadows name from outer scope, c'est pas grave dans ce cas la d'autant plus que tout est testé mais peut etre que pour une lib on veut éviter tous les soucis potentiels ?

Copy link
Member

Choose a reason for hiding this comment

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

C'est préfèrable de garder token ici, et de renommer l'autre maybe_token

name, *bits = compose_token.split_contents()
args, kwargs = parse_bits_with_children(
parser,
bits,
Expand Down
57 changes: 2 additions & 55 deletions compose_tags/templatetags/compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def do_compose(parser, token):
raise TemplateSyntaxError(
"%r must not take children as a keyword argument." % bits[0]
)
nodelist = parser.parse((f"endcompose",))
nodelist = parser.parse(('endcompose', ))
parser.next_token()

return ComposeNode(
Expand All @@ -44,59 +44,6 @@ def do_define(parser, token):
)
target_var = bits[1]

nodelist = parser.parse((f"enddefine",))
nodelist = parser.parse(('enddefine', ))
parser.next_token()
return DefineNode(target_var, nodelist)


# @register.tag("definelist")
# def do_define_list(parser, token):
# bits = token.split_contents()
# if len(bits) < 2:
# raise TemplateSyntaxError(
# "%r tag takes at least one argument: the name of the template variable that should store the result. Eg: {% definelist myvar for x in list %}item is {x}{% enddefinelist %}"
# % bits[0]
# )
# target_var = bits[1]
#
# if len(bits) == 2 or bits[2] != "for":
# raise TemplateSyntaxError(
# "definelist statements should be formated as {% definelist myvar for x in list %}"
# )
#
# # Most of this implementation come from django.template.defaulttags.do_for
# for_bits = bits[2:]
# is_reversed = for_bits[-1] == "reversed"
# in_index = -3 if is_reversed else -2
# if for_bits[in_index] != "in":
# raise TemplateSyntaxError(
# "'define myvar for' statements should use the format"
# " 'define myvar for x in y': %s" % token.contents
# )
#
# invalid_chars = frozenset((" ", '"', "'", FILTER_SEPARATOR))
# loopvars = re.split(r" *, *", " ".join(for_bits[1:in_index]))
# for var in loopvars:
# if not var or not invalid_chars.isdisjoint(var):
# raise TemplateSyntaxError(
# "'define myvar for' received an invalid argument:"
# " %s" % token.contents
# )
#
# sequence = parser.compile_filter(for_bits[in_index + 1])
# nodelist_loop = parser.parse(
# (
# "empty",
# "enddefinelist",
# )
# )
# token = parser.next_token()
# if token.contents == "empty":
# nodelist_empty = parser.parse(("enddefinelist",))
# parser.delete_first_token()
# else:
# nodelist_empty = None
# return DefineForNode(
# target_var, loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty
# )
#