Ticket #5908: 5908_resetcycle.2.diff

File 5908_resetcycle.2.diff, 7.0 KB (added by akaihola, 6 years ago)

Patch: add the {% resetcycle %} tag, with tests and documentation

  • django/template/defaulttags.py

    diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
    index c08ecc4..ddf1783 100644
    a b class CommentNode(Node): 
    3939
    4040class CycleNode(Node):
    4141    def __init__(self, cyclevars, variable_name=None):
    42         self.cycle_iter = itertools_cycle(cyclevars)
     42        self.cyclevars = cyclevars
     43        self.reset()
    4344        self.variable_name = variable_name
    4445
     46    def reset(self):
     47        self.cycle_iter = itertools_cycle(self.cyclevars)
     48
    4549    def render(self, context):
    4650        value = self.cycle_iter.next().resolve(context)
    4751        if self.variable_name:
    4852            context[self.variable_name] = value
    4953        return value
    5054
     55class ResetCycleNode(Node):
     56    def __init__(self, node):
     57        self.node = node
     58
     59    def render(self, context):
     60        self.node.reset()
     61        return ''
     62
    5163class DebugNode(Node):
    5264    def render(self, context):
    5365        from pprint import pformat
    def cycle(parser, token): 
    481493    # Ugly hack warning: This stuffs the named template dict into parser so
    482494    # that names are only unique within each template (as opposed to using
    483495    # a global variable, which would make cycle names have to be unique across
    484     # *all* templates.
     496    # *all* templates). Also keeps last unnamed node in the parser.
    485497
    486498    args = token.split_contents()
    487499
    def cycle(parser, token): 
    512524    else:
    513525        values = [parser.compile_filter(arg) for arg in args[1:]]
    514526        node = CycleNode(values)
     527        parser._lastUnnamedCycleNode = node
    515528    return node
    516529cycle = register.tag(cycle)
    517530
     531def resetcycle(parser, token):
     532    """
     533    Resets a cycle tag.
     534
     535    If an argument is given, resets the last rendered cycle tag whose name
     536    matches the argument, else resets last rendered unnamed cycle tag.
     537    """
     538    args = token.split_contents()
     539
     540    if len(args) > 2:
     541        raise TemplateSyntaxError("%r tag accepts at most one argument."
     542                                  % args[0])
     543    if len(args) == 2:
     544        name = args[1]
     545        try:
     546            return ResetCycleNode(parser._namedCycleNodes[name])
     547        except AttributeError:
     548            raise TemplateSyntaxError("No named cycles in template. "
     549                                      "%r is not defined" % name)
     550        except KeyError:
     551            raise TemplateSyntaxError("Named cycle %r does not exist" % name)
     552    try:
     553        return ResetCycleNode(parser._lastUnnamedCycleNode)
     554    except AttributeError:
     555        raise TemplateSyntaxError("No unnamed cycles in template.")
     556resetcycle = register.tag(resetcycle)
     557
    518558def debug(parser, token):
    519559    """
    520560    Outputs a whole load of debugging information, including the current
  • docs/ref/templates/builtins.txt

    diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
    index 17e6e8b..0193c8d 100644
    a b You can use any number of values in a ``{% cycle %}`` tag, separated by spaces. 
    101101Values enclosed in single (``'``) or double quotes (``"``) are treated as
    102102string literals, while values without quotes are treated as template variables.
    103103
     104You can use the `resetcycle`_ tag to reset a ``{% cycle %}`` tag to restart from
     105its first value when it's next encountered.
     106
    104107For backwards compatibility, the ``{% cycle %}`` tag supports the much inferior
    105108old syntax from previous Django versions. You shouldn't use this in any new
    106109projects, but for the sake of the people who are still using it, here's what it
    filter, if your data is in a list of dictionaries:: 
    622625
    623626    {% regroup people|dictsort:"gender" by gender as gender_list %}
    624627
     628.. templatetag:: resetcycle
     629
     630resetcycle
     631~~~~~~~~~~
     632
     633Resets a previous `cycle`_ so that it restarts from its first item when it's
     634next encountered.  Without arguments, ``{% resetcycle %}`` will reset the last
     635unnamed ``{% cycle %}`` defined in the template.
     636
     637Example usage::
     638
     639    {% for coach in coach_list %}
     640        <h1>{{ coach.name }}</h1>
     641        {% for athlete in coach.athlete_set.all %}
     642            <p class="{% cycle 'odd' 'even' %}">{{ athlete.name }}</p>
     643        {% endfor %}
     644        {% resetcycle %}
     645    {% endfor %}
     646
     647The athlete list for every coach starts with ``class="odd"``.  Without the
     648``{% resetcycle %}`` tag, the first athlete of a coach might be rendered with
     649``class="even"`` if the last athlete of the previous coach had ``class="odd"``.
     650
     651You can also reset named cycle tags::
     652
     653    {% for item in list %}
     654        <p class="{% cycle 'odd' 'evn' as stripe %} {% cycle 'majr' 'minr' 'minr' 'minr' 'minr' as tick %}">
     655            {{ item.data }}
     656        </p>
     657        {% ifchanged item.category %}
     658            <h1>{{ item.category }}</h1>
     659            {% if not forloop.first %}{% resetcycle tick %}{% endif %}
     660        {% endifchanged %}
     661    {% endfor %}
     662
     663In this example we have both the alternating odd/even rows and a "major" row
     664every fifth row.  Only the five-row cycle is reset when a category changes.
     665
    625666.. templatetag:: spaceless
    626667
    627668spaceless
  • tests/regressiontests/templates/tests.py

    diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
    index f0eee52..a4f1a26 100644
    a b class Templates(unittest.TestCase): 
    434434            'cycle15': ("{% for i in test %}{% cycle aye bee %}{{ i }},{% endfor %}", {'test': range(5), 'aye': 'a', 'bee': 'b'}, 'a0,b1,a2,b3,a4,'),
    435435            'cycle16': ("{% cycle one|lower two as foo %}{% cycle foo %}", {'one': 'A','two': '2'}, 'a2'),
    436436
     437            ### RESETCYCLE TAG ########################################################
     438            'resetcycle01': ("{% for i in test %}{% cycle a,b %}{% resetcycle %}{% endfor %}", {'test': range(5)}, 'aaaaa'),
     439            'resetcycle02': ("{% cycle a,b,c as abc %}{% for i in test %}{% cycle abc %}{% cycle '-' '+' %}{% resetcycle %}{% endfor %}", {'test': range(5)}, 'ab-c-a-b-c-'),
     440            'resetcycle03': ("{% cycle a,b,c as abc %}{% for i in test %}{% resetcycle abc %}{% cycle abc %}{% cycle '-' '+' %}{% endfor %}", {'test': range(5)}, 'aa-a+a-a+a-'),
     441            'resetcycle04': ("{% for i in outer %}{% for j in inner %}{% cycle a,b %}{% endfor %}{% resetcycle %}{% endfor %}", {'outer': range(2), 'inner': range(3)}, 'abaaba'),
     442            'resetcycle05': ("{% for i in outer %}{% cycle a,b %}{% for j in inner %}{% cycle X,Y %}{% endfor %}{% resetcycle %}{% endfor %}", {'outer': range(2), 'inner': range(3)}, 'aXYXbXYX'),
     443            'resetcycle06': ("{% for i in test %}{% cycle X,Y,Z as XYZ %}{% cycle a,b,c as abc %}{% ifequal i 1 %}{% resetcycle abc %}{% endifequal %}{% endfor %}", {'test': range(5)}, 'XaYbZaXbYc'),
     444            'resetcycle07': ("{% for i in test %}{% cycle X,Y,Z as XYZ %}{% cycle a,b,c as abc %}{% ifequal i 1 %}{% resetcycle XYZ %}{% endifequal %}{% endfor %}", {'test': range(5)}, 'XaYbXcYaZb'),
     445
    437446            ### EXCEPTIONS ############################################################
    438447
    439448            # Raise exception for invalid template name
Back to Top