django-formset icon indicating copy to clipboard operation
django-formset copied to clipboard

Render form will always render default template

Open codematsing opened this issue 1 year ago • 4 comments

Issue

I found that using the templatetag render_form will ignore the form renderer I declared in forms.py when I implement my project as follows:

from formset.renderers.bootstrap import FormRenderer as BootstrapFormRenderer
class Form(ModelForm):
    default_renderer = BootstrapFormRenderer(
        field_css_classes='row mb-3',
        label_css_classes='col-sm-3',
        control_css_classes='col-sm-9',
    )
    class Meta:
         model = ...
         fields = "__all__"

and template:

<django-formset endpoint="{{ request.path }}" csrf-token="{{ csrf_token }}">
  {% render_form form %}
  <div class="offset-sm-3">
    <button type="button" click="submit -> proceed" class="btn btn-primary">Submit</button>
    <button type="button" click="reset" class="ms-2 btn btn-warning">Reset to initial</button>
  </div>
</django-formset>

Clarification

I'm not sure if this is correct since I'm still checking this out:

https://github.com/jrief/django-formset/blob/e12c947a519ce84510b15df07030ac2c77df2a39/formset/templatetags/formsetify.py#L49C1-L52C66

def render_form(context, form, *args, **kwargs):
    get_token(context['request'])  # ensures that the CSRF-Cookie is set
    form = _formsetify(form, *args, **kwargs)
    # shouldnt this:
    return form.render(template_name='formset/default/form.html')
    # be this:
     return form.render()

It's causing rendering issues on my project since my ModelForm.default_renderer is bootstrap-based renderer, while {% render_form form %} forces to use formset/default/form.html instead of formset/bootstrap/form.html

codematsing avatar May 15 '24 13:05 codematsing

I would have to step through with the debugger, but usually you would use the Bootstrap Renderer class to render your forms. And here https://github.com/jrief/django-formset/blob/e12c947a519ce84510b15df07030ac2c77df2a39/formset/renderers/bootstrap.py#L17C39-L17C66 the default template is mapped to one specific to Bootstrap.

jrief avatar May 15 '24 13:05 jrief

I agree to the reference. It is what I use, but when I check the rendered html, each field group follows formset/default/field_group.html instead of formset/bootstrap/field.group.html

... which in turn, fails to create the inline formatting of labels and inputs as referenced here

This is due to the implementation of formset/default/field_group.html having no separate div classes for label and input, which formset/bootstrap/field_group.html wraps widget field inside a div element

So I'm not sure if the templatetags.formsetify.render_form should be adjusted, or, default/field_group.html needs a div class similar to bootstrap/field_group.html

codematsing avatar May 15 '24 13:05 codematsing

Can you please create a small example. Take the examples in testapp as blueprint.

jrief avatar May 15 '24 15:05 jrief

Investigatory diff:

  • reference diff: https://github.com/jrief/django-formset/commit/f1391294e459b45d6a98b37ca168dee192cee24b
  • access http://localhost:8000/bootstrap/custom-bootstrap-renderer
  • expectation: label and input should be inline
  • findings: If you check dev-tools, you will see that the html being renderered is default/field_group.html

image

<!-- snippet -->
<div role="group" class="dj-required row mb-3">
   <label for="id_name" class="col-sm-3">Team name:</label><input type="text" name="name" maxlength="50" required="" form="id_customrendererteamform" id="id_name" class="form-control">
   <div role="alert" class="dj-field-errors">
      <meta name="error-messages" value_missing="This field is required." too_long="Ensure this value has at most 50 characters." bad_input="Null characters are not allowed.">
      <ul class="dj-errorlist">
         <li class="dj-placeholder"></li>
      </ul>
   </div>
   <div class="dj-help-text">The name of the team</div>
</div>

Overridden diff:

  • reference diff: https://github.com/jrief/django-formset/commit/84b9e1cf07a72c959a10fbae9edbb052c5fe971c
  • access http://localhost:8000/bootstrap/correct-custom-bootstrap-renderer
  • findings: If you check dev-tools, you will see that the html being renderered is bootstrap/field_group.html

image

<!-- snippet -->
<div role="group" class="row mb-3 dj-required">
   <label for="id_name" class="col-sm-3">
   Team name:</label>
   <div class="col-sm-9">
      <input type="text" name="name" maxlength="50" required="" form="id_customrendererteamform" id="id_name" class="form-control">
      <div role="alert" class="dj-field-errors">
         <meta name="error-messages" value_missing="This field is required." too_long="Ensure this value has at most 50 characters." bad_input="Null characters are not allowed.">
         <ul class="dj-errorlist">
            <li class="dj-placeholder"></li>
         </ul>
      </div>
      <div class="form-text text-muted">The name of the team</div>
   </div>
</div>

Some Notes:

  • I had to externally import bootstrap cdn since I had issues loading static files when trying to run webapp (It's kind of irrelevant to the investigation but provides us with needed visual cues)
  • Implementation assumes having a CreateView inheriting native django CreateView that:
    • takes in form_class with override of default_renderer=BootstrapFormRenderer
    • overrides template that uses {% render_form form %}
  • I also tried using {{form}} instead of {% render_form form %}. Doing so will partially render the form correctly except for field_css_clases but will raise 422 on submit, consequently, will to fail to create an instance. I'm not sure if it's an implementation issue or a limitation (which is why render_form is needed)
  • I find that https://github.com/jrief/django-formset/commit/84b9e1cf07a72c959a10fbae9edbb052c5fe971c works for my usecase but I'm not sure if it's all encompassing solution or would affect implementation

Hopefully implementation is readable enough!

codematsing avatar May 16 '24 04:05 codematsing

On branch https://github.com/jrief/django-formset/tree/releases/1.5 I have solved this. I used your sample code and applied them to an existing model in testapp.

Please try the examples at http://localhost:8000/bootstrap/person-bootstrap-params and http://localhost:8000/bootstrap/person-bootstrap-renderer

The first uses {% render_form form "boostrap" field_classes="row mb-3" label_classes="col-sm-3" control_classes="col-sm-9" %}

while the second uses a form with

class PersonFormBootstrapRenderer(FormMixin, PersonForm):
    default_renderer = BootstrapFormRenderer(
        field_css_classes='row mb-3',
        label_css_classes='col-sm-3',
        control_css_classes='col-sm-9',
    )

I haven't tested this with Bootstrap loaded from a CDN though.

Thanks for reporting.

jrief avatar Jun 22 '24 16:06 jrief