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")
class Calendar(component.Component):
def context(self, date):
return {
"date": date,
}
# Note that Django will look for templates inside `[your app]/components` dir
def template(self, context):
return "calendar/calendar.html"
template_name = "calendar/calendar.html"
class Media:
css = '[your app]/components/calendar/calendar.css'

View file

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

View file

@ -2,6 +2,7 @@ import warnings
from functools import lru_cache
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.forms.widgets import MediaDefiningClass
from django.template.base import Node, TokenType
from django.template.loader import get_template
@ -41,17 +42,21 @@ class SimplifiedInterfaceMediaDefiningClass(MediaDefiningClass):
class Component(metaclass=SimplifiedInterfaceMediaDefiningClass):
template_name = None
def __init__(self, component_name):
self._component_name = component_name
self.instance_template = None
self.slots = {}
def context(self):
return {}
def get_context(self, *args, **kwargs):
return kwargs
def template(self, context):
raise NotImplementedError("Missing template() method on component")
def get_template_name(self, context=None):
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):
"""Helper function to access media.render()"""
@ -112,7 +117,21 @@ class Component(metaclass=SimplifiedInterfaceMediaDefiningClass):
return component_template
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)
with context.update({ACTIVE_SLOT_CONTEXT_KEY: self.slots}):
return instance_template.render(context)

View file

@ -149,7 +149,7 @@ class ComponentNode(Node):
# 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_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
if self.isolated_context:

View file

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

View file

@ -8,11 +8,10 @@ from .testutils import Django30CompatibleSimpleTestCase as SimpleTestCase
class SimpleComponent(component.Component):
def context(self, variable=None):
return {"variable": variable} if variable is not None else {}
template_name = "simple_template.html"
def template(self, context):
return "simple_template.html"
def get_context(self, variable=None):
return {"variable": variable} if variable is not None else {}
@staticmethod
def expected_output(variable_value):
@ -20,27 +19,25 @@ class SimpleComponent(component.Component):
class ParentComponent(component.Component):
def context(self):
template_name = "parent_template.html"
def get_context(self):
return {
"shadowing_variable": 'NOT SHADOWED'
}
def template(self, context):
return "parent_template.html"
class ParentComponentWithArgs(component.Component):
def context(self, parent_value):
return {
"inner_parent_value": parent_value
}
template_name = "parent_with_args_template.html"
def template(self, context):
return "parent_with_args_template.html"
def get_context(self, parent_value):
return {"inner_parent_value": parent_value}
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 = {}
if shadowing_variable is not None:
context['shadowing_variable'] = shadowing_variable
@ -48,12 +45,11 @@ class VariableDisplay(component.Component):
context['unique_variable'] = new_variable
return context
def template(self, context):
return "variable_display.html"
class IncrementerComponent(component.Component):
def context(self, value=0):
template_name = "incrementer.html"
def get_context(self, value=0):
value = int(value)
if hasattr(self, 'call_count'):
self.call_count += 1
@ -64,16 +60,13 @@ class IncrementerComponent(component.Component):
"calls": self.call_count
}
def template(self, context):
return "incrementer.html"
class OuterContextComponent(component.Component):
def context(self):
return self.outer_context
template_name = "simple_template.html"
def template(self, context):
return "simple_template.html"
def get_context(self):
return self.outer_context
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):
def context(self, variable):
return {}
def template(self, context):
return "simple_template.html"
template_name = "simple_template.html"
class Media:
css = "style2.css"
@ -21,23 +17,21 @@ class SimpleComponentAlternate(component.Component):
class SimpleComponentWithSharedDependency(component.Component):
template_name = "simple_template.html"
def context(self, variable, variable2="default"):
return {
"variable": variable,
"variable2": variable2,
}
def template(self, context):
return "simple_template.html"
class Media:
css = ["style.css", "style2.css"]
js = ["script.js", "script2.js"]
class MultistyleComponent(component.Component):
def template(self, context):
return "simple_template.html"
template_name = "simple_template.html"
class Media:
css = ["style.css", "style2.css"]

View file

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