-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathfields.py
More file actions
184 lines (149 loc) · 5.61 KB
/
fields.py
File metadata and controls
184 lines (149 loc) · 5.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
from django_viewcomponent.component_registry import registry as component_registry
class FieldValue:
def __init__(
self,
nodelist,
field_context,
target_var,
polymorphic_type,
polymorphic_types,
dict_data: dict,
component: None,
parent_component=None,
):
self._nodelist = nodelist
self._field_context = field_context
self._target_var = target_var
self._polymorphic_type = polymorphic_type
self._polymorphic_types = polymorphic_types
self._dict_data = dict_data
self._component = component
self._parent_component = parent_component
def __str__(self):
return self.render()
def render(self):
if self._polymorphic_types:
component_expression = self._polymorphic_types[self._polymorphic_type]
return self._render(component_expression)
else:
component_expression = self._component
return self._render(component_expression)
def _render(self, target):
from django_viewcomponent.component import Component
if isinstance(target, str):
return self._render_for_component_cls(
component_registry.get(target),
)
elif not isinstance(target, type) and callable(target):
# target is function
callable_component = target
content = self._nodelist.render(self._field_context)
result = callable_component(
self=self._parent_component,
content=content,
**self._dict_data,
)
if isinstance(result, str):
return result
elif isinstance(result, Component):
# render component instance
return self._render_for_component_instance(result)
else:
raise ValueError(
f"Callable slot component must return str or Component instance. Got {result}",
)
elif isinstance(target, type) and issubclass(
target,
Component,
):
# target is Component class
return self._render_for_component_cls(target)
elif target is None:
return self._nodelist.render(self._field_context)
else:
raise ValueError(f"Invalid component variable {target}")
def _render_for_component_cls(self, component_cls):
component = component_cls(
**self._dict_data,
)
return self._render_for_component_instance(component)
def _render_for_component_instance(self, component):
"""
The logic should be the same as in the ComponentNode.render method
"""
component.component_target_var = self._target_var
component.component_context = self._field_context
# https://docs.djangoproject.com/en/5.1/ref/templates/api/#django.template.Context.push
with component.component_context.push():
# developer can add extra context data in this method
updated_context = component.get_context_data()
# create slot fields
component.create_slot_fields()
# render content first
component.content = self._nodelist.render(updated_context)
component.check_slot_fields()
return component.render(updated_context)
class BaseSlotField:
parent_component = None
def __init__(self, required=False, component=None, types=None, **kwargs):
self._value = None
self._filled = False
self._required = required
self._component = component
self._types = types
@classmethod
def initialize_fields(cls):
cls.header = RendersOneField()
cls.main = RendersOneField()
cls.footer = RendersOneField()
@property
def value(self):
return self._value
@property
def filled(self):
return self._filled
@property
def required(self):
return self._required
@property
def types(self):
return self._types
def handle_call(self, nodelist, context, target_var, polymorphic_type, **kwargs):
raise NotImplementedError("You must implement the `handle_call` method.")
class RendersOneField(BaseSlotField):
def handle_call(self, nodelist, context, target_var, polymorphic_type, **kwargs):
value_instance = FieldValue(
nodelist=nodelist,
field_context=context,
target_var=target_var,
polymorphic_type=polymorphic_type,
polymorphic_types=self.types,
dict_data={**kwargs},
component=self._component,
parent_component=self.parent_component,
)
self._value = value_instance.render()
self._filled = True
class FieldValueListWrapper:
def __init__(self):
self.data = []
def append(self, value):
self.data.append(value)
def __iter__(self):
yield from self.data
class RendersManyField(BaseSlotField):
def handle_call(self, nodelist, context, target_var, polymorphic_type, **kwargs):
value_instance = FieldValue(
nodelist=nodelist,
field_context=context,
target_var=target_var,
polymorphic_type=polymorphic_type,
polymorphic_types=self.types,
dict_data={**kwargs},
component=self._component,
parent_component=self.parent_component,
)
if self._value is None:
self._value = FieldValueListWrapper()
self._value.append(value_instance.render())
self._filled = True