Skip to content

Commit

Permalink
Feature/form tag (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-yin authored Sep 15, 2024
1 parent 9444569 commit b669d7f
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 80 deletions.
128 changes: 67 additions & 61 deletions src/django_formify/tailwind/formify_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,15 @@ def prepare_css_container(self):

def get_context_data(self, context_data) -> Context:
if isinstance(context_data, Context):
new_context = Context(context_data.flatten())
context = context_data
else:
new_context = Context(context_data)
context = Context(context_data)

new_context["formify_helper"] = self
new_context["form"] = self.form
new_context["formset"] = self.formset
return new_context
context["formify_helper"] = self
context["form"] = self.form
context["formset"] = self.formset

return context

def smart_render(self, template, context):
# if template is django.template.base.Template, make sure context is a Context object
Expand All @@ -126,6 +127,7 @@ def smart_render(self, template, context):
else:
# make sure the context is dict
if isinstance(context, Context):
# convert to dict
context_for_render = context.flatten()
else:
context_for_render = context
Expand All @@ -139,97 +141,100 @@ def build_default_layout(self):
# Rendering Methods
################################################################################

def render_formset(self, context, create_new_context=False):
def render_form_tag(self, context, content, **kwargs):
with context.push():
update_context = self.get_context_data(context)
update_context["form_content"] = content
attrs = {
"class": kwargs.pop("css_class", ""),
"method": kwargs.pop("method", "POST").upper(),
}
action = kwargs.pop("action", "")
if action:
attrs["action"] = action
# add extra attributes
for key, value in kwargs.items():
attrs[key] = value
update_context["attrs"] = attrs
template = get_template("formify/tailwind/form_tag.html")
return self.smart_render(template, update_context)

def render_formset(self, context):
"""
uni_formset.html
"""
if create_new_context:
context = self.get_context_data(context)

# render formset management form fields
management_form = self.formset.management_form
management_form_helper = init_formify_helper_for_form(management_form)
management_form_html = management_form_helper.render_form(
management_form_helper.get_context_data(context)
)
with context.push():
update_context = management_form_helper.get_context_data(context)
management_form_html = management_form_helper.render_form(update_context)

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

forms_html = ""
for form in self.formset:
form_helper = init_formify_helper_for_form(form)
forms_html += form_helper.render_form(form_helper.get_context_data(context))
with context.push():
update_context = form_helper.get_context_data(context)
forms_html += form_helper.render_form(update_context)

return SafeString(management_form_html + formset_errors + forms_html)

def render_form(self, context, create_new_context=False):
def render_form(self, context):
"""
uni_form.html
"""
if create_new_context:
context = self.get_context_data(context)

return SafeString(
self.render_form_errors(context) + self.render_form_fields(context)
)

def render_field(self, field, context, create_new_context=False, **kwargs):
def render_field(self, context, field, **kwargs):
"""
This method is to render specific field
"""
helper: FormifyHelper = self

if create_new_context:
# create a new instance of FormifyHelper
field_helper = copy.copy(self)
field_formify_helper = copy.copy(self)

# assign extra kwargs to field_helper
for key, value in kwargs.items():
setattr(field_helper, key, value)
# assign extra kwargs to formify_helper if needed
for key, value in kwargs.items():
setattr(field_formify_helper, key, value)

context = field_helper.get_context_data(context)

helper = field_helper
else:
pass
with context.push():
context["field"] = field

context["field"] = field

if field.is_hidden:
return SafeString(field.as_widget())
else:
dispatch_method_callable = helper.field_dispatch(field)
return SafeString(dispatch_method_callable(context))
if field.is_hidden:
return SafeString(field.as_widget())
else:
dispatch_method_callable = field_formify_helper.field_dispatch(field)
update_context = field_formify_helper.get_context_data(context)
return SafeString(dispatch_method_callable(update_context))

def render_submit(self, context, create_new_context=True, **kwargs):
def render_submit(self, context, **kwargs):
"""
It would be called from the render_submit tag
Here we use Submit component to render the submit button, you can also override this method and
use Django's get_template and render methods to render the submit button
"""
if create_new_context:
context = self.get_context_data(context)

css_class = kwargs.pop("css_class", None)
text = kwargs.pop("text", None)
submit_component = Submit(text=text, css_class=css_class, **kwargs)
return submit_component.render_from_parent_context(context)

def render_formset_errors(self, context, create_new_context=False):
if create_new_context:
context = self.get_context_data(context)

error_template = get_template("formify/tailwind/errors_formset.html")
return self.smart_render(error_template, context)

def render_form_errors(self, context, create_new_context=False):
if create_new_context:
context = self.get_context_data(context)

error_template = get_template("formify/tailwind/errors.html")
return self.smart_render(error_template, context)
with context.push():
update_context = self.get_context_data(context)
return submit_component.render_from_parent_context(update_context)

def render_formset_errors(self, context):
template = get_template("formify/tailwind/errors_formset.html")
with context.push():
update_context = self.get_context_data(context)
return self.smart_render(template, update_context)

def render_form_errors(self, context):
template = get_template("formify/tailwind/errors.html")
with context.push():
update_context = self.get_context_data(context)
return self.smart_render(template, update_context)

################################################################################

Expand All @@ -251,9 +256,10 @@ def field_dispatch(self, field):
def render_form_fields(self, context):
if not self.layout:
self.layout = self.build_default_layout()

# render_from_parent_context is a method from the viewcomponent class
return self.layout.render_from_parent_context(context)
with context.push():
update_context = self.get_context_data(context)
# render_from_parent_context is a method from the Component class
return self.layout.render_from_parent_context(update_context)

def render_as_tailwind_field(self, context):
"""
Expand Down
5 changes: 5 additions & 0 deletions src/django_formify/templates/formify/tailwind/form_tag.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{% load formify %}

<form {{ attrs|flatatt }}>
{{ form_content|safe }}
</form>
112 changes: 96 additions & 16 deletions src/django_formify/templatetags/formify.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from django import template
from django.forms.formsets import BaseFormSet
from django.template.base import Node, NodeList
from django.template.context import Context
from django.template.exceptions import TemplateSyntaxError
from django.template.library import parse_bits
from django.utils.safestring import mark_safe

from django_formify.utils import flatatt as utils_flatatt
Expand All @@ -18,16 +21,12 @@ def render_form(context, form_or_formset):
# formset
formset = form_or_formset
formify_helper = init_formify_helper_for_formset(formset)
return formify_helper.render_formset(
Context(context.flatten()), create_new_context=True
)
return formify_helper.render_formset(context)
else:
# form
form = form_or_formset
formify_helper = init_formify_helper_for_form(form)
return formify_helper.render_form(
Context(context.flatten()), create_new_context=True
)
return formify_helper.render_form(context)


@register.simple_tag(takes_context=True)
Expand All @@ -36,41 +35,112 @@ def render_form_errors(context, form_or_formset):
# formset
formset = form_or_formset
formify_helper = init_formify_helper_for_formset(formset)
return formify_helper.render_formset_errors(
Context(context.flatten()), create_new_context=True
)
return formify_helper.render_formset_errors(context)
else:
# form
form = form_or_formset
formify_helper = init_formify_helper_for_form(form)
return formify_helper.render_form_errors(
Context(context.flatten()), create_new_context=True
)
return formify_helper.render_form_errors(context)


@register.simple_tag(takes_context=True)
def render_field(context, field, **kwargs):
form = field.form
formify_helper = init_formify_helper_for_form(form)
return formify_helper.render_field(
context=context,
field=field,
context=Context(context.flatten()),
create_new_context=True,
**kwargs
**kwargs,
)


@register.simple_tag(takes_context=True)
def render_submit(context, form=None, **kwargs):
formify_helper = init_formify_helper_for_form(form)
return formify_helper.render_submit(Context(context.flatten()), **kwargs)
return formify_helper.render_submit(context, **kwargs)


@register.filter
def flatatt(attrs):
return mark_safe(utils_flatatt(attrs))


class FormTagNode(Node):
def __init__(
self,
context_args,
context_kwargs,
nodelist: NodeList,
):
self.context_args = context_args or []
self.context_kwargs = context_kwargs or {}
self.nodelist = nodelist

def __repr__(self):
return "<FormTagNode Contents: %r>" % (
getattr(
self, "nodelist", None
), # 'nodelist' attribute only assigned later.
)

def render(self, context: Context):
resolved_component_args = [
safe_resolve(arg, context) for arg in self.context_args
]
resolved_component_kwargs = {
key: safe_resolve(kwarg, context)
for key, kwarg in self.context_kwargs.items()
}
form = resolved_component_args[0]
formify_helper = init_formify_helper_for_form(form)
content = self.nodelist.render(context)
return formify_helper.render_form_tag(
context=context, content=content, **resolved_component_kwargs
)


@register.tag(name="form_tag")
def do_form_tag(parser, token):
bits = token.split_contents()
tag_name = "form_tag"
tag_args, tag_kwargs = parse_bits(
parser=parser,
bits=bits,
params=[],
takes_context=False,
name=tag_name,
varargs=True,
varkw=[],
defaults=None,
kwonly=[],
kwonly_defaults=None,
)

if tag_name != tag_args[0].token:
raise RuntimeError(
f"Internal error: Expected tag_name to be {tag_name}, but it was {tag_args[0].token}"
)

if len(tag_args) != 2:
raise TemplateSyntaxError(
f"'{tag_name}' tag should have form as the first argument, other arguments should be keyword arguments."
)

context_args = tag_args[1:]
context_kwargs = tag_kwargs

nodelist: NodeList = parser.parse(parse_until=["endform_tag"])
parser.delete_first_token()

component_node = FormTagNode(
context_args=context_args,
context_kwargs=context_kwargs,
nodelist=nodelist,
)

return component_node


@register.filter
def build_attrs(field):
"""
Expand Down Expand Up @@ -135,3 +205,13 @@ def optgroups(field):
attrs = field.build_widget_attrs(attrs)
values = field.field.widget.format_value(field.value())
return field.field.widget.optgroups(field.html_name, values, attrs)


def safe_resolve(context_item, context):
"""Resolve FilterExpressions and Variables in context if possible. Return other items unchanged."""

return (
context_item.resolve(context)
if hasattr(context_item, "resolve")
else context_item
)
1 change: 1 addition & 0 deletions tests/templates/test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello
Loading

0 comments on commit b669d7f

Please sign in to comment.