| 182 | class RandomNode(Node): |
| 183 | def __init__(self, nodelist_options, weights=None): |
| 184 | self.nodelist_options = nodelist_options |
| 185 | self.weights = weights |
| 186 | if weights: |
| 187 | self.weight_total = 0 |
| 188 | self.weight_items = {} |
| 189 | for i, option in enumerate(nodelist_options): |
| 190 | self.weight_total += weights[i] |
| 191 | self.weight_items[self.weight_total] = option |
| 192 | self.weight_boundaries = self.weight_items.keys() |
| 193 | self.weight_boundaries.sort() |
| 194 | |
| 195 | def render(self, context): |
| 196 | if self.weights: |
| 197 | from bisect import bisect_right |
| 198 | from random import randint |
| 199 | return self.weight_items[ |
| 200 | self.weight_boundaries[ |
| 201 | bisect_right( |
| 202 | self.weight_boundaries, |
| 203 | randint(0, self.weight_total - 1) |
| 204 | ) |
| 205 | ] |
| 206 | ].render(context) |
| 207 | else: |
| 208 | from random import choice |
| 209 | return choice(self.nodelist_options).render(context) |
| 210 | |
| 630 | def random(parser, token): |
| 631 | """ |
| 632 | Output the contents of a random block, with optional weighting. |
| 633 | |
| 634 | The `random` block tag must contain one or more `or` tags, which separate |
| 635 | possible choices; a choice in this context is everything between a |
| 636 | `random` and `or` tag, between two `or` tags, or between an `or` and an |
| 637 | `endrandom` tag. |
| 638 | |
| 639 | Sample usage:: |
| 640 | |
| 641 | {% random %} |
| 642 | You will see me half the time. |
| 643 | {% or %} |
| 644 | You will see me the other half. |
| 645 | {% endrandom %} |
| 646 | |
| 647 | To specify optional weights for the items, include comma-separated integers |
| 648 | in the `random` tag, one for each item in order, like so:: |
| 649 | |
| 650 | {% random 4,1 %} |
| 651 | You will see me four times more often than the other option. |
| 652 | {% or %} |
| 653 | You will see me only one-fifth of the time. |
| 654 | {% endrandom %} |
| 655 | |
| 656 | The chance of a weighted item being shown is its weight value divided by |
| 657 | the total of all weight values. |
| 658 | """ |
| 659 | args = token.contents.split() |
| 660 | if len(args) == 1: |
| 661 | # {% random %} |
| 662 | weights = None |
| 663 | elif len(args) == 2 and ',' in args[1]: |
| 664 | # {% random 4,2,1 %} |
| 665 | try: |
| 666 | weights = [int(w) for w in args[1].split(',') if w] |
| 667 | except ValueError: |
| 668 | raise TemplateSyntaxError("Weight arguments for 'random' must be integers") |
| 669 | for w in weights: |
| 670 | if w < 1: |
| 671 | raise TemplateSyntaxError("Weight arguments must be positive integers") |
| 672 | else: |
| 673 | raise TemplateSyntaxError("Invalid arguments to 'random': %s" % args) |
| 674 | options = NodeList() |
| 675 | while True: |
| 676 | option = parser.parse(('or', 'endrandom')) |
| 677 | token = parser.next_token() |
| 678 | options.append(option) |
| 679 | if token.contents == 'or': |
| 680 | continue |
| 681 | parser.delete_first_token() |
| 682 | break |
| 683 | if len(options) < 2: |
| 684 | raise TemplateSyntaxError, "'random' tag must have at least two possibilities" |
| 685 | elif weights and len(options) != len(weights): |
| 686 | raise TemplateSyntaxError, "The number of specified weights (%s)" \ |
| 687 | " must equal the number of possible options (%s)" % (len(options), len(weights)) |
| 688 | return RandomNode(options, weights=weights) |
| 689 | do_random = register.tag("random", random) |
| 690 | |
| 691 | #@register.tag |