diff --git a/CHANGES b/CHANGES index b12dd4f50..2441849b0 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,10 @@ Version 2.10 - Added a `trimmed` modifier to `{% trans %}` to strip linebreaks and surrounding whitespace. Also added a new policy to enable this for all `trans` blocks. +- The ``random`` filter is no longer incorrectly constant folded and will + produce a new random choice each time the template is rendered. (`#478`_) + +.. _#478: https://github.com/pallets/jinja/pull/478 Version 2.9.6 ------------- diff --git a/jinja2/filters.py b/jinja2/filters.py index 38ac8934b..81040adac 100644 --- a/jinja2/filters.py +++ b/jinja2/filters.py @@ -10,8 +10,8 @@ """ import re import math +import random -from random import choice from itertools import groupby from collections import namedtuple from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \ @@ -359,13 +359,13 @@ def do_last(environment, seq): return environment.undefined('No last item, sequence was empty.') -@environmentfilter -def do_random(environment, seq): +@contextfilter +def do_random(context, seq): """Return a random item from the sequence.""" try: - return choice(seq) + return random.choice(seq) except IndexError: - return environment.undefined('No random item, sequence was empty.') + return context.environment.undefined('No random item, sequence was empty.') def do_filesizeformat(value, binary=False): diff --git a/tests/test_filters.py b/tests/test_filters.py index 318a347c4..54aa16b81 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -8,6 +8,7 @@ :copyright: (c) 2017 by the Jinja Team. :license: BSD, see LICENSE for more details. """ +import random import pytest from jinja2 import Markup, Environment from jinja2._compat import text_type, implements_to_string @@ -182,11 +183,21 @@ def test_pprint(self, env): data = list(range(1000)) assert tmpl.render(data=data) == pformat(data) - def test_random(self, env): - tmpl = env.from_string('''{{ seq|random }}''') - seq = list(range(100)) - for _ in range(10): - assert int(tmpl.render(seq=seq)) in seq + def test_random(self, env, request): + # restore the random state when the test ends + state = random.getstate() + request.addfinalizer(lambda: random.setstate(state)) + # generate the random values from a known seed + random.seed('jinja') + expected = [random.choice('1234567890') for _ in range(10)] + + # check that the random sequence is generated again by a template + # ensures that filter result is not constant folded + random.seed('jinja') + t = env.from_string('{{ "1234567890"|random }}') + + for value in expected: + assert t.render() == value def test_reverse(self, env): tmpl = env.from_string('{{ "foobar"|reverse|join }}|'