mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #15849 -- Made IfChanged node thread safe.
Previously, the ifchanged node stored state on `self._last_seen`, thereby giving undesired results when the node is reused by another thread at the same time (e.g. globally caching a Template object). Thanks to akaihola for the report and Diederik van der Boor and Bas Peschier for the patch.
This commit is contained in:
		
				
					committed by
					
						 Florian Apolloner
						Florian Apolloner
					
				
			
			
				
	
			
			
			
						parent
						
							a5733fcd7b
						
					
				
				
					commit
					8503120c10
				
			| @@ -215,32 +215,44 @@ class IfChangedNode(Node): | ||||
|  | ||||
|     def __init__(self, nodelist_true, nodelist_false, *varlist): | ||||
|         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false | ||||
|         self._last_seen = None | ||||
|         self._varlist = varlist | ||||
|         self._id = str(id(self)) | ||||
|  | ||||
|     def render(self, context): | ||||
|         if 'forloop' in context and self._id not in context['forloop']: | ||||
|             self._last_seen = None | ||||
|             context['forloop'][self._id] = 1 | ||||
|         # Init state storage | ||||
|         state_frame = self._get_context_stack_frame(context) | ||||
|         if self not in state_frame: | ||||
|             state_frame[self] = None | ||||
|  | ||||
|         try: | ||||
|             if self._varlist: | ||||
|                 # Consider multiple parameters.  This automatically behaves | ||||
|                 # like an OR evaluation of the multiple variables. | ||||
|                 compare_to = [var.resolve(context, True) for var in self._varlist] | ||||
|             else: | ||||
|                 # The "{% ifchanged %}" syntax (without any variables) compares the rendered output. | ||||
|                 compare_to = self.nodelist_true.render(context) | ||||
|         except VariableDoesNotExist: | ||||
|             compare_to = None | ||||
|  | ||||
|         if compare_to != self._last_seen: | ||||
|             self._last_seen = compare_to | ||||
|             content = self.nodelist_true.render(context) | ||||
|             return content | ||||
|         if compare_to != state_frame[self]: | ||||
|             state_frame[self] = compare_to | ||||
|             return self.nodelist_true.render(context) | ||||
|         elif self.nodelist_false: | ||||
|             return self.nodelist_false.render(context) | ||||
|         return '' | ||||
|  | ||||
|     def _get_context_stack_frame(self, context): | ||||
|         # The Context object behaves like a stack where each template tag can create a new scope. | ||||
|         # Find the place where to store the state to detect changes. | ||||
|         if 'forloop' in context: | ||||
|             # Ifchanged is bound to the local for loop. | ||||
|             # When there is a loop-in-loop, the state is bound to the inner loop, | ||||
|             # so it resets when the outer loop continues. | ||||
|             return context['forloop'] | ||||
|         else: | ||||
|             # Using ifchanged outside loops. Effectively this is a no-op because the state is associated with 'self'. | ||||
|             return context.render_context | ||||
|  | ||||
| class IfEqualNode(Node): | ||||
|     child_nodelists = ('nodelist_true', 'nodelist_false') | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user