Opened 9 years ago

Last modified 4 years ago

#5865 new New feature

cycle template tag should accept a single argument

Reported by: Gary Wilson Owned by: munhitsu
Component: Template system Version: master
Severity: Normal Keywords:
Cc: mateusz@… Triage Stage: Accepted
Has patch: yes Needs documentation: yes
Needs tests: yes Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description

When passed a single argument, the cycle tag should treat it as an iterable to cycle through. For example, with the context:

{'colors': ['red', 'blue', 'green']}

you should be able to have a template like:

{% for row in mydata %}
<tr class="{% cycle colors %}">...</tr>
{% endfor %}

that would output:

<tr class="red">...</tr>
<tr class="blue">...</tr>
<tr class="green">...</tr>
<tr class="red">...</tr>
...

Attachments (2)

cycle.diff (6.7 KB) - added by munhitsu 9 years ago.
v2
cycle-doc.diff (1.2 KB) - added by munhitsu 9 years ago.

Download all attachments as: .zip

Change History (18)

comment:1 Changed 9 years ago by Simon G <dev@…>

Triage Stage: UnreviewedAccepted

This sounds like a sensible improvement

comment:2 Changed 9 years ago by munhitsu

Owner: changed from nobody to munhitsu

comment:3 Changed 9 years ago by Malcolm Tredinnick

I'm a little concerned that this is ambiguous when you're reading it: you can't easily tell if it's referring to a previously named "cycle" tag or creating a new one using a variable as an iterator.

Is there a better way to write this that won't cause confusion when reading templates?

comment:4 Changed 9 years ago by munhitsu

So far I've managed to perform the request but maintaining compatibility with older approach makes my code not so... clean.

What do you think about a new keyword i.e.: cycletable?

Maybe its a time to refer to named cycle tag by variable reference? To be honest referring by name is not to clear for me.

comment:5 in reply to:  3 Changed 9 years ago by Gary Wilson

Replying to mtredinnick:

I'm a little concerned that this is ambiguous when you're reading it: you can't easily tell if it's referring to a previously named "cycle" tag or creating a new one using a variable as an iterator.

Is it really that different from using the "as varname" syntax of other tags or the {% with %} tag though? The way I see it, when you encounter any variable name in the templates, you have to check where it came from. Was it passed in the context? Was it defined as the target of "as varname" syntax of another variable? Was it defined with a with tag? Is it a loop variable? etc. This would be no different.

comment:6 Changed 9 years ago by munhitsu

Cc: mateusz@… added

Guys take a look at following unit test suite. Does it reflect your expectations?

{
'cycle20': ("{% cycle colors %}", {'colors': 'red'}, template.TemplateSyntaxError),
'cycle21': ("{% cycle colors %}{% cycle colors %}", {'colors': ['red', 'blue', 'green']}, 'redblue'),
'cycle22': ("{% cycle colors %}{% cycle colors %}{% cycle colors %}", {'colors': ['red', 'blue', 'green']}, 'redbluegreen'),
'cycle23': ("{% cycle colors %}{% cycle colors %}{% cycle colors %}{% cycle colors %}", {'colors': ['red', 'blue', 'green']}, 'redbluegreenred'),
'cycle24': ("{% cycle colors as color %}{% cycle color %}", {'colors': ['red', 'blue', 'green']}, 'redblue'),
'cycle25': ("{% cycle colors as color %}{% cycle color %}{% cycle color %}", {'colors': ['red', 'blue', 'green']}, 'redbluegreen'),
'cycle26': ("{% cycle colors as color %}{% cycle color %}{% cycle color %}{% cycle color %}", {'colors': ['red', 'blue', 'green']}, 'redbluegreenred'),
'cycle27': ("{% for i in test %}{% cycle colors %}{{ i }},{% endfor %}", {'test': range(5),'colors': ['a', 'b']}, 'a0,b1,a2,b3,a4,'),
}

comment:7 Changed 9 years ago by munhitsu

Has patch: set
Resolution: fixed
Status: newclosed

Seems to be working. Old regression is passing. New has been added.
I've added a small enhancement - a cycle pushed to context. Thanks to it cycle internals can be used inside template.

        context['cycle'] = {
            # cycle internals
            'collection': self.collection,
            'name':self.variable_name,
            'value':value,
        }

One note. Last part of syntax parsing has to be done in render method. We need context to say if {% cycle colors %} refers to table or is a mistake.

comment:8 Changed 9 years ago by munhitsu

Resolution: fixed
Status: closedreopened

comment:9 Changed 9 years ago by Gary Wilson

Needs documentation: set
Patch needs improvement: set

Your cycle20 test case should not raise a TemplateSyntaxError, but rather should output "r" since a string is iterable. A TemplateSyntaxError should only be raised if the syntax for the tag in not correct, i.e. {% cycle colors as %}. If the object passed is not iterable, the template tag should fail silently and return an empty string. Also, your patch should allow any iterable instead of just lists. I also don't see a reason that we should be exposing the internal state of the tag. Could you explain your reasoning for wanting to do that?

Changed 9 years ago by munhitsu

Attachment: cycle.diff added

v2

comment:10 Changed 9 years ago by munhitsu

Patch needs improvement: unset

Replying to gwilson:

Thanks for the comments.

  1. Functionality for test cycle20 updated. Do you have an idea how to prepare context with non iterable variable? I have no clue how to make unit test for it.
    'cycle20': ("{% cycle colors %}", {'colors': 'r'}, 'r')
    
  2. Regarding silent fail... Actual implementation will throw TemplateSyntaxError for test cycle01 as original one. But i.e if 'a' would be defined in context than it will fall back to Variable('a').resolve(context).
    'cycle01': ('{% cycle a %}', {}, template.TemplateSyntaxError)
    
  3. I've just added NameError exception catch as a backup to make sure we will fail silently
  4. Cycle patch now iterates over everything that is iterable - see test cycle22. It's hashed as Python does not guarantee iteration sequence on hash.
    'cycle22': ("{% cycle colors %}{% cycle colors %}", {'colors': {'r': 1, 'g': 2}}, 'rg')
    
  5. Regarding cycle internal state in tag. I must say that I was inspired by ForNode and cycle is a somehow close to it. Take a look at tests cycle28..29. That the only reasonable case that I see now to use cycle internals. It's your choice.
    'cycle28': ("{% for i in test %}{% cycle colors %}{{ cycle.value }}{{ i }},{% endfor %}", {'test': range(5),'colors': ['r', 'g', 'b']}, 'rr0,gg1,bb2,rr3,gg4,'),
    'cycle29': ("{% for i in test %}{% cycle colors %}{% cycle colors %}{{ i }},{% endfor %}", {'test': range(5),'colors': ['r', 'g', 'b']}, 'rg0,br1,gb2,rg3,br4,'),
    

Changed 9 years ago by munhitsu

Attachment: cycle-doc.diff added

comment:11 Changed 9 years ago by munhitsu

Cc: mateusz@… added; mateusz@… removed
Needs documentation: unset

comment:12 Changed 6 years ago by Gabriel Hurley

Severity: Normal
Type: New feature

comment:13 Changed 6 years ago by Julien Phalip

Easy pickings: unset
Triage Stage: AcceptedDesign decision needed

A design decision hasn't been reached yet.

comment:14 Changed 5 years ago by Alex Gaynor

Triage Stage: Design decision neededAccepted
UI/UX: unset

Discussion with Carl: this new syntax would be ambiguous with the current syntax, however this is a good feature, and the current API is kind of silly. This is accepted, provisional on creating a new, more sane and consistent syntax, to be placed in the future module.

comment:15 Changed 5 years ago by Preston Holmes

Needs documentation: set
Needs tests: set
Patch needs improvement: set

comment:16 Changed 4 years ago by Aymeric Augustin

Status: reopenednew
Note: See TracTickets for help on using tickets.
Back to Top