iommirocks / iommi

Your first pick for a django power cord
http://iommi.rocks
BSD 3-Clause "New" or "Revised" License
784 stars 51 forks source link

Form.fields_template #408

Closed berycz closed 1 year ago

berycz commented 1 year ago

As we discussed with boxed on discord (2023-05-03)

Form should probably have a template for fields, for example when you need bs row/col wrappers and such. The html structure can get quite complicated and it's really no fun to maintain via Fragment(format_html(.... And groups are not nested, but even with them it still would be harder to work than with html template imo

I fixed it for me like this:

class Form(iommi.Form):
    fields_template: Union[str, Template] = EvaluatedRefinable()

    @with_defaults(fields_template=None)
    def __init__(self, **kwargs):
        super(Form, self).__init__(**kwargs)

    class Meta:
        member_class = Field
        page_class = Page
        action_class = Action

    # property for jinja2 compatibility
    @property
    def render_fields(self):
        assert self._is_bound, NOT_BOUND_MESSAGE

        if self.fields_template is None:
            return super(Form, self).render_fields

        context = self.iommi_evaluate_parameters().copy()
        context["groups"] = {}
        context["fields"] = {}
        for group, parts in groupby(values(self.parts), key=lambda x: getattr(x, 'group', MISSING)):
            if group is not MISSING:
                current_group = self.field_group(_name=f'iommi_field_group_{group}', group=group).bind(parent=self)
                parts_html_by_name = {part._name: part.__html__() for part in parts}
                context["groups"][group] = "\n".join(
                    [current_group.iommi_open_tag()] +
                    list(parts_html_by_name.values()) +
                    [current_group.iommi_close_tag()]
                )
            else:
                for part in parts:
                    context["fields"][part._name] = part

        # We need to preserve all other GET parameters,
        # so we can e.g. filter in two forms on the same page, and keep sorting after filtering
        own_field_paths = {f.iommi_path for f in values(self.fields)}
        context["hidden_fields"] = []
        for k, v in items(self.get_request().GET):
            if k not in own_field_paths and not k.startswith('-') and not k.startswith(DISPATCH_PREFIX):
                context["hidden_fields"].append(format_html('<input type="hidden" name="{}" value="{}" />', k, v))

        return render_template(request=self.get_request(), template=self.fields_template, context=context)