Skip to content

Commit b669d7f

Browse files
authored
Feature/form tag (#6)
1 parent 9444569 commit b669d7f

File tree

5 files changed

+221
-80
lines changed

5 files changed

+221
-80
lines changed

src/django_formify/tailwind/formify_helper.py

+67-61
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,15 @@ def prepare_css_container(self):
104104

105105
def get_context_data(self, context_data) -> Context:
106106
if isinstance(context_data, Context):
107-
new_context = Context(context_data.flatten())
107+
context = context_data
108108
else:
109-
new_context = Context(context_data)
109+
context = Context(context_data)
110110

111-
new_context["formify_helper"] = self
112-
new_context["form"] = self.form
113-
new_context["formset"] = self.formset
114-
return new_context
111+
context["formify_helper"] = self
112+
context["form"] = self.form
113+
context["formset"] = self.formset
114+
115+
return context
115116

116117
def smart_render(self, template, context):
117118
# if template is django.template.base.Template, make sure context is a Context object
@@ -126,6 +127,7 @@ def smart_render(self, template, context):
126127
else:
127128
# make sure the context is dict
128129
if isinstance(context, Context):
130+
# convert to dict
129131
context_for_render = context.flatten()
130132
else:
131133
context_for_render = context
@@ -139,97 +141,100 @@ def build_default_layout(self):
139141
# Rendering Methods
140142
################################################################################
141143

142-
def render_formset(self, context, create_new_context=False):
144+
def render_form_tag(self, context, content, **kwargs):
145+
with context.push():
146+
update_context = self.get_context_data(context)
147+
update_context["form_content"] = content
148+
attrs = {
149+
"class": kwargs.pop("css_class", ""),
150+
"method": kwargs.pop("method", "POST").upper(),
151+
}
152+
action = kwargs.pop("action", "")
153+
if action:
154+
attrs["action"] = action
155+
# add extra attributes
156+
for key, value in kwargs.items():
157+
attrs[key] = value
158+
update_context["attrs"] = attrs
159+
template = get_template("formify/tailwind/form_tag.html")
160+
return self.smart_render(template, update_context)
161+
162+
def render_formset(self, context):
143163
"""
144164
uni_formset.html
145165
"""
146-
if create_new_context:
147-
context = self.get_context_data(context)
148-
149166
# render formset management form fields
150167
management_form = self.formset.management_form
151168
management_form_helper = init_formify_helper_for_form(management_form)
152-
management_form_html = management_form_helper.render_form(
153-
management_form_helper.get_context_data(context)
154-
)
169+
with context.push():
170+
update_context = management_form_helper.get_context_data(context)
171+
management_form_html = management_form_helper.render_form(update_context)
155172

156173
# render formset errors
157174
formset_errors = self.render_formset_errors(context)
158175

159176
forms_html = ""
160177
for form in self.formset:
161178
form_helper = init_formify_helper_for_form(form)
162-
forms_html += form_helper.render_form(form_helper.get_context_data(context))
179+
with context.push():
180+
update_context = form_helper.get_context_data(context)
181+
forms_html += form_helper.render_form(update_context)
163182

164183
return SafeString(management_form_html + formset_errors + forms_html)
165184

166-
def render_form(self, context, create_new_context=False):
185+
def render_form(self, context):
167186
"""
168187
uni_form.html
169188
"""
170-
if create_new_context:
171-
context = self.get_context_data(context)
172-
173189
return SafeString(
174190
self.render_form_errors(context) + self.render_form_fields(context)
175191
)
176192

177-
def render_field(self, field, context, create_new_context=False, **kwargs):
193+
def render_field(self, context, field, **kwargs):
178194
"""
179195
This method is to render specific field
180196
"""
181-
helper: FormifyHelper = self
182-
183-
if create_new_context:
184-
# create a new instance of FormifyHelper
185-
field_helper = copy.copy(self)
197+
field_formify_helper = copy.copy(self)
186198

187-
# assign extra kwargs to field_helper
188-
for key, value in kwargs.items():
189-
setattr(field_helper, key, value)
199+
# assign extra kwargs to formify_helper if needed
200+
for key, value in kwargs.items():
201+
setattr(field_formify_helper, key, value)
190202

191-
context = field_helper.get_context_data(context)
192-
193-
helper = field_helper
194-
else:
195-
pass
203+
with context.push():
204+
context["field"] = field
196205

197-
context["field"] = field
198-
199-
if field.is_hidden:
200-
return SafeString(field.as_widget())
201-
else:
202-
dispatch_method_callable = helper.field_dispatch(field)
203-
return SafeString(dispatch_method_callable(context))
206+
if field.is_hidden:
207+
return SafeString(field.as_widget())
208+
else:
209+
dispatch_method_callable = field_formify_helper.field_dispatch(field)
210+
update_context = field_formify_helper.get_context_data(context)
211+
return SafeString(dispatch_method_callable(update_context))
204212

205-
def render_submit(self, context, create_new_context=True, **kwargs):
213+
def render_submit(self, context, **kwargs):
206214
"""
207215
It would be called from the render_submit tag
208216
209217
Here we use Submit component to render the submit button, you can also override this method and
210218
use Django's get_template and render methods to render the submit button
211219
"""
212-
if create_new_context:
213-
context = self.get_context_data(context)
214-
215220
css_class = kwargs.pop("css_class", None)
216221
text = kwargs.pop("text", None)
217222
submit_component = Submit(text=text, css_class=css_class, **kwargs)
218-
return submit_component.render_from_parent_context(context)
219-
220-
def render_formset_errors(self, context, create_new_context=False):
221-
if create_new_context:
222-
context = self.get_context_data(context)
223-
224-
error_template = get_template("formify/tailwind/errors_formset.html")
225-
return self.smart_render(error_template, context)
226-
227-
def render_form_errors(self, context, create_new_context=False):
228-
if create_new_context:
229-
context = self.get_context_data(context)
230-
231-
error_template = get_template("formify/tailwind/errors.html")
232-
return self.smart_render(error_template, context)
223+
with context.push():
224+
update_context = self.get_context_data(context)
225+
return submit_component.render_from_parent_context(update_context)
226+
227+
def render_formset_errors(self, context):
228+
template = get_template("formify/tailwind/errors_formset.html")
229+
with context.push():
230+
update_context = self.get_context_data(context)
231+
return self.smart_render(template, update_context)
232+
233+
def render_form_errors(self, context):
234+
template = get_template("formify/tailwind/errors.html")
235+
with context.push():
236+
update_context = self.get_context_data(context)
237+
return self.smart_render(template, update_context)
233238

234239
################################################################################
235240

@@ -251,9 +256,10 @@ def field_dispatch(self, field):
251256
def render_form_fields(self, context):
252257
if not self.layout:
253258
self.layout = self.build_default_layout()
254-
255-
# render_from_parent_context is a method from the viewcomponent class
256-
return self.layout.render_from_parent_context(context)
259+
with context.push():
260+
update_context = self.get_context_data(context)
261+
# render_from_parent_context is a method from the Component class
262+
return self.layout.render_from_parent_context(update_context)
257263

258264
def render_as_tailwind_field(self, context):
259265
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{% load formify %}
2+
3+
<form {{ attrs|flatatt }}>
4+
{{ form_content|safe }}
5+
</form>

src/django_formify/templatetags/formify.py

+96-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from django import template
22
from django.forms.formsets import BaseFormSet
3+
from django.template.base import Node, NodeList
34
from django.template.context import Context
5+
from django.template.exceptions import TemplateSyntaxError
6+
from django.template.library import parse_bits
47
from django.utils.safestring import mark_safe
58

69
from django_formify.utils import flatatt as utils_flatatt
@@ -18,16 +21,12 @@ def render_form(context, form_or_formset):
1821
# formset
1922
formset = form_or_formset
2023
formify_helper = init_formify_helper_for_formset(formset)
21-
return formify_helper.render_formset(
22-
Context(context.flatten()), create_new_context=True
23-
)
24+
return formify_helper.render_formset(context)
2425
else:
2526
# form
2627
form = form_or_formset
2728
formify_helper = init_formify_helper_for_form(form)
28-
return formify_helper.render_form(
29-
Context(context.flatten()), create_new_context=True
30-
)
29+
return formify_helper.render_form(context)
3130

3231

3332
@register.simple_tag(takes_context=True)
@@ -36,41 +35,112 @@ def render_form_errors(context, form_or_formset):
3635
# formset
3736
formset = form_or_formset
3837
formify_helper = init_formify_helper_for_formset(formset)
39-
return formify_helper.render_formset_errors(
40-
Context(context.flatten()), create_new_context=True
41-
)
38+
return formify_helper.render_formset_errors(context)
4239
else:
4340
# form
4441
form = form_or_formset
4542
formify_helper = init_formify_helper_for_form(form)
46-
return formify_helper.render_form_errors(
47-
Context(context.flatten()), create_new_context=True
48-
)
43+
return formify_helper.render_form_errors(context)
4944

5045

5146
@register.simple_tag(takes_context=True)
5247
def render_field(context, field, **kwargs):
5348
form = field.form
5449
formify_helper = init_formify_helper_for_form(form)
5550
return formify_helper.render_field(
51+
context=context,
5652
field=field,
57-
context=Context(context.flatten()),
58-
create_new_context=True,
59-
**kwargs
53+
**kwargs,
6054
)
6155

6256

6357
@register.simple_tag(takes_context=True)
6458
def render_submit(context, form=None, **kwargs):
6559
formify_helper = init_formify_helper_for_form(form)
66-
return formify_helper.render_submit(Context(context.flatten()), **kwargs)
60+
return formify_helper.render_submit(context, **kwargs)
6761

6862

6963
@register.filter
7064
def flatatt(attrs):
7165
return mark_safe(utils_flatatt(attrs))
7266

7367

68+
class FormTagNode(Node):
69+
def __init__(
70+
self,
71+
context_args,
72+
context_kwargs,
73+
nodelist: NodeList,
74+
):
75+
self.context_args = context_args or []
76+
self.context_kwargs = context_kwargs or {}
77+
self.nodelist = nodelist
78+
79+
def __repr__(self):
80+
return "<FormTagNode Contents: %r>" % (
81+
getattr(
82+
self, "nodelist", None
83+
), # 'nodelist' attribute only assigned later.
84+
)
85+
86+
def render(self, context: Context):
87+
resolved_component_args = [
88+
safe_resolve(arg, context) for arg in self.context_args
89+
]
90+
resolved_component_kwargs = {
91+
key: safe_resolve(kwarg, context)
92+
for key, kwarg in self.context_kwargs.items()
93+
}
94+
form = resolved_component_args[0]
95+
formify_helper = init_formify_helper_for_form(form)
96+
content = self.nodelist.render(context)
97+
return formify_helper.render_form_tag(
98+
context=context, content=content, **resolved_component_kwargs
99+
)
100+
101+
102+
@register.tag(name="form_tag")
103+
def do_form_tag(parser, token):
104+
bits = token.split_contents()
105+
tag_name = "form_tag"
106+
tag_args, tag_kwargs = parse_bits(
107+
parser=parser,
108+
bits=bits,
109+
params=[],
110+
takes_context=False,
111+
name=tag_name,
112+
varargs=True,
113+
varkw=[],
114+
defaults=None,
115+
kwonly=[],
116+
kwonly_defaults=None,
117+
)
118+
119+
if tag_name != tag_args[0].token:
120+
raise RuntimeError(
121+
f"Internal error: Expected tag_name to be {tag_name}, but it was {tag_args[0].token}"
122+
)
123+
124+
if len(tag_args) != 2:
125+
raise TemplateSyntaxError(
126+
f"'{tag_name}' tag should have form as the first argument, other arguments should be keyword arguments."
127+
)
128+
129+
context_args = tag_args[1:]
130+
context_kwargs = tag_kwargs
131+
132+
nodelist: NodeList = parser.parse(parse_until=["endform_tag"])
133+
parser.delete_first_token()
134+
135+
component_node = FormTagNode(
136+
context_args=context_args,
137+
context_kwargs=context_kwargs,
138+
nodelist=nodelist,
139+
)
140+
141+
return component_node
142+
143+
74144
@register.filter
75145
def build_attrs(field):
76146
"""
@@ -135,3 +205,13 @@ def optgroups(field):
135205
attrs = field.build_widget_attrs(attrs)
136206
values = field.field.widget.format_value(field.value())
137207
return field.field.widget.optgroups(field.html_name, values, attrs)
208+
209+
210+
def safe_resolve(context_item, context):
211+
"""Resolve FilterExpressions and Variables in context if possible. Return other items unchanged."""
212+
213+
return (
214+
context_item.resolve(context)
215+
if hasattr(context_item, "resolve")
216+
else context_item
217+
)

tests/templates/test.html

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello

0 commit comments

Comments
 (0)