Ticket #5908: 5908-resetcycle.4.diff

File 5908-resetcycle.4.diff, 7.7 KB (added by b.schube@…, 11 years ago)

Updated patch to current trunk

  • django/template/defaulttags.py

    diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
    index 04e7a37..b84ed4e 100644
    a b class CycleNode(Node):  
    6565        self.variable_name = variable_name
    6666        self.silent = silent
    6767        self.escape = escape        # only while the "future" version exists
     68       
     69    def reset(self, context):
     70        context.render_context[self] = itertools_cycle(self.cyclevars)
    6871
    6972    def render(self, context):
    7073        if self not in context.render_context:
    class CycleNode(Node):  
    8083            value = mark_safe(value)
    8184        return render_value_in_context(value, context)
    8285
     86class ResetCycleNode(Node):
     87    def __init__(self, node):
     88        self.node = node
     89    def render(self, context):
     90        self.node.reset(context)
     91        return ''
     92
    8393class DebugNode(Node):
    8494    def render(self, context):
    8595        from pprint import pformat
    def cycle(parser, token, escape=False):  
    578588    # Ugly hack warning: This stuffs the named template dict into parser so
    579589    # that names are only unique within each template (as opposed to using
    580590    # a global variable, which would make cycle names have to be unique across
    581     # *all* templates.
     591    # *all* templates. Also keeps last unnamed node in the parser.
    582592
    583593    args = token.split_contents()
    584594
    def cycle(parser, token, escape=False):  
    623633    else:
    624634        values = [parser.compile_filter(arg) for arg in args[1:]]
    625635        node = CycleNode(values, escape=escape)
     636        parser._lastUnnamedCycleNode = node
    626637    return node
     638   
     639@register.tag
     640def resetcycle(parser, token):
     641    """
     642    Resets a cycle tag.
     643
     644    If an argument is given, resets the last rendered cycle tag whose name
     645    matches the argument, else resets last rendered unnamed cycle tag.
     646
     647    """
     648    args = token.split_contents()
     649
     650    if len(args) > 2:
     651        raise TemplateSyntaxError("%r tag accepts at most one argument."
     652                                  % args[0])
     653    if len(args) == 2:
     654        name = args[1]
     655        try:
     656            return ResetCycleNode(parser._namedCycleNodes[name])
     657        except AttributeError:
     658            raise TemplateSyntaxError("No named cycles in template. "
     659                                      "%r is not defined" % name)
     660        except KeyError:
     661            raise TemplateSyntaxError("Named cycle %r does not exist" % name)
     662    try:
     663        return ResetCycleNode(parser._lastUnnamedCycleNode)
     664    except AttributeError:
     665        raise TemplateSyntaxError("No unnamed cycles in template.")
    627666
    628667@register.tag
    629668def csrf_token(parser, token):
  • docs/ref/templates/builtins.txt

    diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
    index 287fd4f..86f5d0f 100644
    a b string literals, while values without quotes are treated as template variables.  
    149149
    150150Note that currently the variables included in the cycle will not be escaped.
    151151Any HTML or Javascript code contained in the printed variable will be rendered
    152 as-is, which could potentially lead to security issues.
     152as-is, which could potentially lead to security issues.86
     153
     154You can use the `resetcycle`_ tag to reset a ``{% cycle %}`` tag to restart
     155from its first value when it's next encountered.
    153156
    154157For backwards compatibility, the ``{% cycle %}`` tag supports the much inferior
    155158old syntax from previous Django versions. You shouldn't use this in any new
    attribute, allowing you to group on the display string rather than the  
    893896``{{ country.grouper }}`` will now display the value fields from the
    894897``choices`` set rather than the keys.
    895898
     899.. templatetag:: resetcycle
     900
     901resetcycle
     902^^^^^^^^^^
     903
     904Resets a previous `cycle`_ so that it restarts from its first item at its next
     905invocation.  Without arguments, ``{% resetcycle %}`` will reset the last
     906unnamed ``{% cycle %}`` defined in the template.
     907
     908Example usage::
     909
     910    {% for coach in coach_list %}
     911        <h1>{{ coach.name }}</h1>
     912        {% for athlete in coach.athlete_set.all %}
     913            <p class="{% cycle 'odd' 'even' %}">{{ athlete.name }}</p>
     914        {% endfor %}
     915        {% resetcycle %}
     916    {% endfor %}
     917
     918The athlete list for every coach starts with ``class="odd"``.  Without the
     919``{% resetcycle %}`` tag, the first athlete of a coach might be rendered with
     920``class="even"`` if the last athlete of the previous coach had ``class="odd"``.
     921
     922You can also reset named cycle tags::
     923
     924    {% for item in list %}
     925        <p class="{% cycle 'odd' 'evn' as stripe %} {% cycle 'majr' 'minr' 'minr' 'minr' 'minr' as tick %}">
     926            {{ item.data }}
     927        </p>
     928        {% ifchanged item.category %}
     929            <h1>{{ item.category }}</h1>
     930            {% if not forloop.first %}{% resetcycle tick %}{% endif %}
     931        {% endifchanged %}
     932    {% endfor %}
     933
     934In this example we have both the alternating odd/even rows and a "major" row
     935every fifth row.  Only the five-row cycle is reset when a category changes.
     936
    896937.. templatetag:: spaceless
    897938
    898939spaceless
  • tests/template_tests/tests.py

    diff --git a/tests/template_tests/tests.py b/tests/template_tests/tests.py
    index 2aeaee9..c2b3a76 100644
    a b class Templates(TestCase):  
    818818            'cycle26': ('{% load cycle from future %}{% cycle a b as ab %}{% cycle ab %}', {'a': '<', 'b': '>'}, '&lt;&gt;'),
    819819            'cycle27': ('{% load cycle from future %}{% autoescape off %}{% cycle a b as ab %}{% cycle ab %}{% endautoescape %}', {'a': '<', 'b': '>'}, '<>'),
    820820            'cycle28': ('{% load cycle from future %}{% cycle a|safe b as ab %}{% cycle ab %}', {'a': '<', 'b': '>'}, '<&gt;'),
     821           
     822            ### RESETCYCLE TAG ########################################################
     823            'resetcycle01': ("{% resetcycle %}", {}, template.TemplateSyntaxError),
     824            'resetcycle02': ("{% resetcycle undefinedcycle %}", {}, template.TemplateSyntaxError),
     825            'resetcycle03': ("{% cycle 'a' 'b' 'c' as abc %}{% resetcycle %}", {}, template.TemplateSyntaxError),
     826            'resetcycle04': ("{% cycle 'a' 'b' 'c' %}{% resetcycle undefinedcycle %}", {}, template.TemplateSyntaxError),
     827            'resetcycle05': ("{% cycle 'a' 'b' 'c' as abc %}{% resetcycle undefinedcycle %}", {}, template.TemplateSyntaxError),
     828            'resetcycle06': ("{% for i in test %}{% cycle 'a' 'b' %}{% resetcycle %}{% endfor %}", {'test': range(5)}, 'aaaaa'),
     829            'resetcycle07': ("{% cycle 'a' 'b' 'c' as abc %}{% for i in test %}{% cycle abc %}{% cycle '-' '+' %}{% resetcycle %}{% endfor %}", {'test': range(5)}, 'ab-c-a-b-c-'),
     830            'resetcycle08': ("{% cycle 'a' 'b' 'c' as abc %}{% for i in test %}{% resetcycle abc %}{% cycle abc %}{% cycle '-' '+' %}{% endfor %}", {'test': range(5)}, 'aa-a+a-a+a-'),
     831            'resetcycle09': ("{% for i in outer %}{% for j in inner %}{% cycle 'a' 'b' %}{% endfor %}{% resetcycle %}{% endfor %}", {'outer': range(2), 'inner': range(3)}, 'abaaba'),
     832            'resetcycle10': ("{% for i in outer %}{% cycle 'a' 'b' %}{% for j in inner %}{% cycle 'X' 'Y' %}{% endfor %}{% resetcycle %}{% endfor %}", {'outer': range(2), 'inner': range(3)}, 'aXYXbXYX'),
     833            'resetcycle11': ("{% 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'),
     834            'resetcycle12': ("{% 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'),
    821835
    822836            ### EXCEPTIONS ############################################################
    823837
Back to Top