Ticket #919: random_tag_weighted.patch

File random_tag_weighted.patch, 4.1 KB (added by Tom Tobin <korpios@…>, 19 years ago)

Implements random template tag, with optional weighting.

  • core/template/defaulttags.py

     
    178178                return self.nodelist_true.render(context)
    179179        return self.nodelist_false.render(context)
    180180
     181class RandomNode(Node):
     182    def __init__(self, nodelist_options, weights=None):
     183        self.nodelist_options = nodelist_options
     184        self.weights = weights
     185        if weights:
     186            self.weight_total = 0
     187            self.weight_items = {}
     188            for i, option in enumerate(nodelist_options):
     189                self.weight_total += weights[i]
     190                self.weight_items[self.weight_total] = option
     191            self.weight_boundaries = self.weight_items.keys()
     192            self.weight_boundaries.sort()
     193
     194    def render(self, context):
     195        if self.weights:
     196            from bisect import bisect_left
     197            from random import randint
     198            return self.weight_items[
     199                        self.weight_boundaries[
     200                            bisect_left(
     201                                self.weight_boundaries,
     202                                randint(0, self.weight_total)
     203                                )
     204                            ]
     205                        ].render(context)
     206        else:
     207            from random import choice
     208            return choice(self.nodelist_options).render(context)
     209
    181210class RegroupNode(Node):
    182211    def __init__(self, target_var, expression, var_name):
    183212        self.target_var, self.expression = target_var, expression
     
    588617    parser.delete_first_token()
    589618    return IfChangedNode(nodelist)
    590619
     620def do_random(parser, token):
     621    """
     622    Output the contents of a random block, with optional weighting.
     623
     624    The `random` block tag must contain one or more `or` tags, which separate
     625    possible choices; a choice in this context is everything between a
     626    `random` and `or` tag, between two `or` tags, or between an `or` and an
     627    `endrandom` tag.
     628
     629    Sample usage::
     630
     631        {% random %}
     632        You will see me half the time.
     633        {% or %}
     634        You will see me the other half.
     635        {% endrandom %}
     636
     637    To specify optional weights for the items, include comma-separated integers
     638    in the `random` tag, one for each item in order, like so::
     639
     640        {% random 4,1 %}
     641        You will see me four times more often than the other option.
     642        {% or %}
     643        You will see me only one-fifth of the time.
     644        {% endrandom %}
     645
     646    The chance of a weighted item being shown is its weight value divided by
     647    the total of all weight values.
     648    """
     649    args = token.contents.split()
     650    if len(args) == 1:
     651        # {% random %}
     652        weights = None
     653    elif len(args) == 2 and ',' in args[1]:
     654        # {% random 4,2,1 %}
     655        try:
     656            weights = [int(w) for w in args[1].split(',') if w]
     657        except ValueError:
     658            raise TemplateSyntaxError("Weight arguments for 'random' must be integers")
     659    else:
     660        raise TemplateSyntaxError("Invalid arguments to 'random': %s" % args)
     661    options = NodeList()
     662    while True:
     663        option = parser.parse(('or', 'endrandom'))
     664        token = parser.next_token()
     665        options.append(option)
     666        if token.contents == 'or':
     667            continue
     668        parser.delete_first_token()
     669        break
     670    if len(options) < 2:
     671        raise TemplateSyntaxError, "'random' tag must have at least two possibilities"
     672    elif weights and len(options) != len(weights):
     673        raise TemplateSyntaxError, "The number of specified weights (%s)" \
     674                " must equal the number of possible options (%s)" % (len(options), len(weights))
     675    return RandomNode(options, weights=weights)
     676
    591677def do_ssi(parser, token):
    592678    """
    593679    Output the contents of a given file into the page.
     
    770856register_tag('if', do_if)
    771857register_tag('ifchanged', do_ifchanged)
    772858register_tag('regroup', do_regroup)
     859register_tag('random', do_random)
    773860register_tag('ssi', do_ssi)
    774861register_tag('load', do_load)
    775862register_tag('now', do_now)
Back to Top