Refactored Component class

# Conflicts:
#	README.md
#	pyproject.toml
This commit is contained in:
Emil Stenström 2021-09-10 11:27:28 +02:00
parent 55f46d6069
commit 5b9188cc9c
8 changed files with 91 additions and 111 deletions

View file

@ -189,14 +189,8 @@ from django_components import component
@component.register("calendar") @component.register("calendar")
class Calendar(component.Component): class Calendar(component.Component):
def context(self, date):
return {
"date": date,
}
# Note that Django will look for templates inside `[your app]/components` dir # Note that Django will look for templates inside `[your app]/components` dir
def template(self, context): template_name = "calendar/calendar.html"
return "calendar/calendar.html"
class Media: class Media:
css = '[your app]/components/calendar/calendar.css' css = '[your app]/components/calendar/calendar.css'

View file

@ -10,26 +10,26 @@ from tests.testutils import Django30CompatibleSimpleTestCase as SimpleTestCase,
class SlottedComponent(component.Component): class SlottedComponent(component.Component):
def template(self, context): template_name = "slotted_template.html"
return "slotted_template.html"
class SimpleComponent(component.Component): class SimpleComponent(component.Component):
def context(self, variable, variable2="default"): template_name = "simple_template.html"
def get_context(self, variable, variable2="default"):
return { return {
"variable": variable, "variable": variable,
"variable2": variable2, "variable2": variable2,
} }
def template(self, context):
return "simple_template.html"
class Media: class Media:
css = {"all": ["style.css"]} css = {"all": ["style.css"]}
js = ["script.js"] js = ["script.js"]
class BreadcrumbComponent(component.Component): class BreadcrumbComponent(component.Component):
template_name = "mdn_component_template.html"
LINKS = [ LINKS = [
('https://developer.mozilla.org/en-US/docs/Learn', ('https://developer.mozilla.org/en-US/docs/Learn',
'Learn web development'), 'Learn web development'),
@ -41,16 +41,13 @@ class BreadcrumbComponent(component.Component):
'Document and website structure') 'Document and website structure')
] ]
def context(self, items): def get_context(self, items):
if items > 4: if items > 4:
items = 4 items = 4
elif items < 0: elif items < 0:
items = 0 items = 0
return {'links': self.LINKS[:items - 1]} return {'links': self.LINKS[:items - 1]}
def template(self, context):
return "mdn_component_template.html"
class Media: class Media:
css = {"all": ["test.css"]} css = {"all": ["test.css"]}
js = ["test.js"] js = ["test.js"]

View file

@ -2,6 +2,7 @@ import warnings
from functools import lru_cache from functools import lru_cache
from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.forms.widgets import MediaDefiningClass from django.forms.widgets import MediaDefiningClass
from django.template.base import Node, TokenType from django.template.base import Node, TokenType
from django.template.loader import get_template from django.template.loader import get_template
@ -41,17 +42,21 @@ class SimplifiedInterfaceMediaDefiningClass(MediaDefiningClass):
class Component(metaclass=SimplifiedInterfaceMediaDefiningClass): class Component(metaclass=SimplifiedInterfaceMediaDefiningClass):
template_name = None
def __init__(self, component_name): def __init__(self, component_name):
self._component_name = component_name self._component_name = component_name
self.instance_template = None self.instance_template = None
self.slots = {} self.slots = {}
def context(self): def get_context(self, *args, **kwargs):
return {} return kwargs
def template(self, context): def get_template_name(self, context=None):
raise NotImplementedError("Missing template() method on component") if not self.template_name:
raise ImproperlyConfigured(f'Template name is not set for Component {self.__class__.__name__}')
return self.template_name
def render_dependencies(self): def render_dependencies(self):
"""Helper function to access media.render()""" """Helper function to access media.render()"""
@ -112,7 +117,21 @@ class Component(metaclass=SimplifiedInterfaceMediaDefiningClass):
return component_template return component_template
def render(self, context): def render(self, context):
template_name = self.template(context) if hasattr(self, 'context'):
warnings.warn(
f'{self.__class__.__name__}: `context` method is deprecated, use `get_context` instead',
DeprecationWarning
)
if hasattr(self, 'template'):
warnings.warn(
f'{self.__class__.__name__}: `template` method is deprecated, set `template_name` or override `get_template_name` instead',
DeprecationWarning
)
template_name = self.template(context)
else:
template_name = self.get_template_name(context)
instance_template = self.get_processed_template(template_name) instance_template = self.get_processed_template(template_name)
with context.update({ACTIVE_SLOT_CONTEXT_KEY: self.slots}): with context.update({ACTIVE_SLOT_CONTEXT_KEY: self.slots}):
return instance_template.render(context) return instance_template.render(context)

View file

@ -149,7 +149,7 @@ class ComponentNode(Node):
# context method to get values to insert into the context # context method to get values to insert into the context
resolved_context_args = [safe_resolve(arg, context) for arg in self.context_args] resolved_context_args = [safe_resolve(arg, context) for arg in self.context_args]
resolved_context_kwargs = {key: safe_resolve(kwarg, context) for key, kwarg in self.context_kwargs.items()} resolved_context_kwargs = {key: safe_resolve(kwarg, context) for key, kwarg in self.context_kwargs.items()}
component_context = self.component.context(*resolved_context_args, **resolved_context_kwargs) component_context = self.component.get_context(*resolved_context_args, **resolved_context_kwargs)
# Create a fresh context if requested # Create a fresh context if requested
if self.isolated_context: if self.isolated_context:

View file

@ -1,6 +1,7 @@
from textwrap import dedent from textwrap import dedent
from django.template import Context, Template from django.template import Context, Template
from django.core.exceptions import ImproperlyConfigured
from .django_test_setup import * # NOQA from .django_test_setup import * # NOQA
@ -13,25 +14,24 @@ class ComponentTest(SimpleTestCase):
class EmptyComponent(component.Component): class EmptyComponent(component.Component):
pass pass
with self.assertRaises(NotImplementedError): with self.assertRaises(ImproperlyConfigured):
EmptyComponent("empty_component").template({}) EmptyComponent("empty_component").get_template_name()
def test_simple_component(self): def test_simple_component(self):
class SimpleComponent(component.Component): class SimpleComponent(component.Component):
def context(self, variable=None): template_name = "simple_template.html"
def get_context(self, variable=None):
return { return {
"variable": variable, "variable": variable,
} }
def template(self, context):
return "simple_template.html"
class Media: class Media:
css = "style.css" css = "style.css"
js = "script.js" js = "script.js"
comp = SimpleComponent("simple_component") comp = SimpleComponent("simple_component")
context = Context(comp.context(variable="test")) context = Context(comp.get_context(variable="test"))
self.assertHTMLEqual(comp.render_dependencies(), dedent(""" self.assertHTMLEqual(comp.render_dependencies(), dedent("""
<link href="style.css" type="text/css" media="all" rel="stylesheet"> <link href="style.css" type="text/css" media="all" rel="stylesheet">
@ -59,17 +59,16 @@ class ComponentTest(SimpleTestCase):
def test_component_with_filtered_template(self): def test_component_with_filtered_template(self):
class FilteredComponent(component.Component): class FilteredComponent(component.Component):
def context(self, var1=None, var2=None): template_name = "filtered_template.html"
def get_context(self, var1=None, var2=None):
return { return {
"var1": var1, "var1": var1,
"var2": var2, "var2": var2,
} }
def template(self, context):
return "filtered_template.html"
comp = FilteredComponent("filtered_component") comp = FilteredComponent("filtered_component")
context = Context(comp.context(var1="test1", var2="test2")) context = Context(comp.get_context(var1="test1", var2="test2"))
self.assertHTMLEqual(comp.render(context), dedent(""" self.assertHTMLEqual(comp.render(context), dedent("""
Var1: <strong>test1</strong> Var1: <strong>test1</strong>
@ -78,21 +77,21 @@ class ComponentTest(SimpleTestCase):
def test_component_with_dynamic_template(self): def test_component_with_dynamic_template(self):
class SvgComponent(component.Component): class SvgComponent(component.Component):
def context(self, name, css_class="", title="", **attrs): def get_context(self, name, css_class="", title="", **attrs):
return {"name": name, "css_class": css_class, "title": title, **attrs} return {"name": name, "css_class": css_class, "title": title, **attrs}
def template(self, context): def get_template_name(self, context):
return f"svg_{context['name']}.svg" return f"svg_{context['name']}.svg"
comp = SvgComponent("svg_component") comp = SvgComponent("svg_component")
self.assertHTMLEqual( self.assertHTMLEqual(
comp.render(Context(comp.context(name="dynamic1"))), comp.render(Context(comp.get_context(name="dynamic1"))),
dedent("""\ dedent("""\
<svg>Dynamic1</svg> <svg>Dynamic1</svg>
""") """)
) )
self.assertHTMLEqual( self.assertHTMLEqual(
comp.render(Context(comp.context(name="dynamic2"))), comp.render(Context(comp.get_context(name="dynamic2"))),
dedent("""\ dedent("""\
<svg>Dynamic2</svg> <svg>Dynamic2</svg>
""") """)
@ -171,8 +170,7 @@ class ComponentMediaTests(SimpleTestCase):
class ComponentIsolationTests(SimpleTestCase): class ComponentIsolationTests(SimpleTestCase):
def setUp(self): def setUp(self):
class SlottedComponent(component.Component): class SlottedComponent(component.Component):
def template(self, context): template_name = "slotted_template.html"
return "slotted_template.html"
component.registry.register('test', SlottedComponent) component.registry.register('test', SlottedComponent)
@ -221,13 +219,11 @@ class RecursiveSlotNameTest(SimpleTestCase):
def setUp(self): def setUp(self):
@component.register('outer') @component.register('outer')
class OuterComponent(component.Component): class OuterComponent(component.Component):
def template(self, context): template_name = "slotted_template.html"
return "slotted_template.html"
@component.register('inner') @component.register('inner')
class InnerComponent(component.Component): class InnerComponent(component.Component):
def template(self, context): template_name = "slotted_template.html"
return "slotted_template.html"
def test_no_infinite_recursion_when_slot_name_is_reused(self): def test_no_infinite_recursion_when_slot_name_is_reused(self):
template = Template( template = Template(

View file

@ -8,11 +8,10 @@ from .testutils import Django30CompatibleSimpleTestCase as SimpleTestCase
class SimpleComponent(component.Component): class SimpleComponent(component.Component):
def context(self, variable=None): template_name = "simple_template.html"
return {"variable": variable} if variable is not None else {}
def template(self, context): def get_context(self, variable=None):
return "simple_template.html" return {"variable": variable} if variable is not None else {}
@staticmethod @staticmethod
def expected_output(variable_value): def expected_output(variable_value):
@ -20,27 +19,25 @@ class SimpleComponent(component.Component):
class ParentComponent(component.Component): class ParentComponent(component.Component):
def context(self): template_name = "parent_template.html"
def get_context(self):
return { return {
"shadowing_variable": 'NOT SHADOWED' "shadowing_variable": 'NOT SHADOWED'
} }
def template(self, context):
return "parent_template.html"
class ParentComponentWithArgs(component.Component): class ParentComponentWithArgs(component.Component):
def context(self, parent_value): template_name = "parent_with_args_template.html"
return {
"inner_parent_value": parent_value
}
def template(self, context): def get_context(self, parent_value):
return "parent_with_args_template.html" return {"inner_parent_value": parent_value}
class VariableDisplay(component.Component): class VariableDisplay(component.Component):
def context(self, shadowing_variable=None, new_variable=None): template_name = "variable_display.html"
def get_context(self, shadowing_variable=None, new_variable=None):
context = {} context = {}
if shadowing_variable is not None: if shadowing_variable is not None:
context['shadowing_variable'] = shadowing_variable context['shadowing_variable'] = shadowing_variable
@ -48,12 +45,11 @@ class VariableDisplay(component.Component):
context['unique_variable'] = new_variable context['unique_variable'] = new_variable
return context return context
def template(self, context):
return "variable_display.html"
class IncrementerComponent(component.Component): class IncrementerComponent(component.Component):
def context(self, value=0): template_name = "incrementer.html"
def get_context(self, value=0):
value = int(value) value = int(value)
if hasattr(self, 'call_count'): if hasattr(self, 'call_count'):
self.call_count += 1 self.call_count += 1
@ -64,16 +60,13 @@ class IncrementerComponent(component.Component):
"calls": self.call_count "calls": self.call_count
} }
def template(self, context):
return "incrementer.html"
class OuterContextComponent(component.Component): class OuterContextComponent(component.Component):
def context(self): template_name = "simple_template.html"
return self.outer_context
def template(self, context): def get_context(self):
return "simple_template.html" return self.outer_context
component.registry.register(name='parent_component', component=ParentComponent) component.registry.register(name='parent_component', component=ParentComponent)

View file

@ -9,11 +9,7 @@ from .testutils import create_and_process_template_response, Django30CompatibleS
class SimpleComponentAlternate(component.Component): class SimpleComponentAlternate(component.Component):
def context(self, variable): template_name = "simple_template.html"
return {}
def template(self, context):
return "simple_template.html"
class Media: class Media:
css = "style2.css" css = "style2.css"
@ -21,23 +17,21 @@ class SimpleComponentAlternate(component.Component):
class SimpleComponentWithSharedDependency(component.Component): class SimpleComponentWithSharedDependency(component.Component):
template_name = "simple_template.html"
def context(self, variable, variable2="default"): def context(self, variable, variable2="default"):
return { return {
"variable": variable, "variable": variable,
"variable2": variable2, "variable2": variable2,
} }
def template(self, context):
return "simple_template.html"
class Media: class Media:
css = ["style.css", "style2.css"] css = ["style.css", "style2.css"]
js = ["script.js", "script2.js"] js = ["script.js", "script2.js"]
class MultistyleComponent(component.Component): class MultistyleComponent(component.Component):
def template(self, context): template_name = "simple_template.html"
return "simple_template.html"
class Media: class Media:
css = ["style.css", "style2.css"] css = ["style.css", "style2.css"]

View file

@ -8,59 +8,51 @@ from .testutils import Django30CompatibleSimpleTestCase as SimpleTestCase
class SimpleComponent(component.Component): class SimpleComponent(component.Component):
def context(self, variable, variable2="default"): template_name = "simple_template.html"
def get_context(self, variable, variable2="default"):
return { return {
"variable": variable, "variable": variable,
"variable2": variable2, "variable2": variable2,
} }
def template(self, context):
return "simple_template.html"
class Media: class Media:
css = "style.css" css = "style.css"
js = "script.js" js = "script.js"
class IffedComponent(SimpleComponent): class IffedComponent(SimpleComponent):
def template(self, context): template_name = "iffed_template.html"
return "iffed_template.html"
class SlottedComponent(component.Component): class SlottedComponent(component.Component):
def template(self, context): template_name = "slotted_template.html"
return "slotted_template.html"
class BrokenComponent(component.Component): class BrokenComponent(component.Component):
def template(self, context): template_name = "template_with_illegal_slot.html"
return "template_with_illegal_slot.html"
class SlottedComponentWithMissingVariable(component.Component): class SlottedComponentWithMissingVariable(component.Component):
def template(self, context): template_name = "slotted_template_with_missing_variable.html"
return "slotted_template_with_missing_variable.html"
class SlottedComponentNoSlots(component.Component): class SlottedComponentNoSlots(component.Component):
def template(self, context): template_name = "slotted_template_no_slots.html"
return "slotted_template_no_slots.html"
class SlottedComponentWithContext(component.Component): class SlottedComponentWithContext(component.Component):
def context(self, variable): template_name = "slotted_template.html"
return {"variable": variable}
def template(self, context): def get_context(self, variable):
return "slotted_template.html" return {"variable": variable}
class ComponentWithProvidedAndDefaultParameters(component.Component): class ComponentWithProvidedAndDefaultParameters(component.Component):
def context(self, variable, default_param="default text"): template_name = "template_with_provided_and_default_parameters.html"
return {"variable": variable, 'default_param': default_param}
def template(self, context): def get_context(self, variable, default_param="default text"):
return "template_with_provided_and_default_parameters.html" return {"variable": variable, 'default_param': default_param}
class ComponentTemplateTagTest(SimpleTestCase): class ComponentTemplateTagTest(SimpleTestCase):
@ -364,11 +356,7 @@ class TemplateInstrumentationTest(SimpleTestCase):
class NestedSlotTests(SimpleTestCase): class NestedSlotTests(SimpleTestCase):
class NestedComponent(component.Component): class NestedComponent(component.Component):
def context(self): template_name = "nested_slot_template.html"
return {}
def template(self, context):
return "nested_slot_template.html"
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
@ -427,11 +415,10 @@ class NestedSlotTests(SimpleTestCase):
class ConditionalSlotTests(SimpleTestCase): class ConditionalSlotTests(SimpleTestCase):
class ConditionalComponent(component.Component): class ConditionalComponent(component.Component):
def context(self, branch=None): template_name = "conditional_template.html"
return {'branch': branch}
def template(self, context): def get_context(self, branch=None):
return "conditional_template.html" return {'branch': branch}
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):