mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #35735 -- Enabled template access to methods and properties of classes with __class_get_item__.
This commit is contained in:
		
				
					committed by
					
						
						Sarah Boyce
					
				
			
			
				
	
			
			
			
						parent
						
							8b9a2bf34e
						
					
				
				
					commit
					d2c97981fb
				
			@@ -880,6 +880,10 @@ class Variable:
 | 
			
		||||
        try:  # catch-all for silent variable failures
 | 
			
		||||
            for bit in self.lookups:
 | 
			
		||||
                try:  # dictionary lookup
 | 
			
		||||
                    # Only allow if the metaclass implements __getitem__. See
 | 
			
		||||
                    # https://docs.python.org/3/reference/datamodel.html#classgetitem-versus-getitem
 | 
			
		||||
                    if not hasattr(type(current), "__getitem__"):
 | 
			
		||||
                        raise TypeError
 | 
			
		||||
                    current = current[bit]
 | 
			
		||||
                    # ValueError/IndexError are for numpy.array lookup on
 | 
			
		||||
                    # numpy < 1.9 and 1.9+ respectively
 | 
			
		||||
 
 | 
			
		||||
@@ -346,6 +346,52 @@ class BasicSyntaxTests(SimpleTestCase):
 | 
			
		||||
        output = self.engine.render_to_string("tpl-weird-percent")
 | 
			
		||||
        self.assertEqual(output, "% %s")
 | 
			
		||||
 | 
			
		||||
    @setup(
 | 
			
		||||
        {"template": "{{ class_var.class_property }} | {{ class_var.class_method }}"}
 | 
			
		||||
    )
 | 
			
		||||
    def test_subscriptable_class(self):
 | 
			
		||||
        class MyClass(list):
 | 
			
		||||
            # As of Python 3.9 list defines __class_getitem__ which makes it
 | 
			
		||||
            # subscriptable.
 | 
			
		||||
            class_property = "Example property"
 | 
			
		||||
            do_not_call_in_templates = True
 | 
			
		||||
 | 
			
		||||
            @classmethod
 | 
			
		||||
            def class_method(cls):
 | 
			
		||||
                return "Example method"
 | 
			
		||||
 | 
			
		||||
        for case in (MyClass, lambda: MyClass):
 | 
			
		||||
            with self.subTest(case=case):
 | 
			
		||||
                output = self.engine.render_to_string("template", {"class_var": case})
 | 
			
		||||
                self.assertEqual(output, "Example property | Example method")
 | 
			
		||||
 | 
			
		||||
    @setup({"template": "{{ meals.lunch }}"})
 | 
			
		||||
    def test_access_class_property_if_getitem_is_defined_in_metaclass(self):
 | 
			
		||||
        """
 | 
			
		||||
        If the metaclass defines __getitem__, the template system should use
 | 
			
		||||
        it to resolve the dot notation.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        class MealMeta(type):
 | 
			
		||||
            def __getitem__(cls, name):
 | 
			
		||||
                return getattr(cls, name) + " is yummy."
 | 
			
		||||
 | 
			
		||||
        class Meals(metaclass=MealMeta):
 | 
			
		||||
            lunch = "soup"
 | 
			
		||||
            do_not_call_in_templates = True
 | 
			
		||||
 | 
			
		||||
            # Make class type subscriptable.
 | 
			
		||||
            def __class_getitem__(cls, key):
 | 
			
		||||
                from types import GenericAlias
 | 
			
		||||
 | 
			
		||||
                return GenericAlias(cls, key)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(Meals.lunch, "soup")
 | 
			
		||||
        self.assertEqual(Meals["lunch"], "soup is yummy.")
 | 
			
		||||
 | 
			
		||||
        output = self.engine.render_to_string("template", {"meals": Meals})
 | 
			
		||||
        self.assertEqual(output, "soup is yummy.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BlockContextTests(SimpleTestCase):
 | 
			
		||||
    def test_repr(self):
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user