Binding model data to a formset without POST

150 views
Skip to first unread message

Rob Groves

unread,
Jan 20, 2015, 8:06:06 AM1/20/15
to django...@googlegroups.com
Hi All,
I am new to Django and am enjoying it very much.  Excellent documentation, tutorials, and general environment to work in.  Good job Django developers!

I have a question on binding data to a ModelFormset.  I have looked extensively and not seen this particular issue covered before.

I have a ModelFormset that I send to a template for custom rendering so I can present the fields in a controlled tabular format.  The ultimate goal is to use CSS to create a red bounding box around any fields with validation errors and to set the "title" attribute of those input fields so that the user can see what errors were generated  by hovering over the red bounded field.  This presents a nice clean interface when there are errors.  It all works great, with one annoyance if the model data starts out with values that will generate validation errors (i.e. missing required data). 

I initially call the view with a GET, which creates the forms and populates the form fields.  Since the data is not bound to forms at this point, I can't validate and set the errors.  In order to validate the fields and set the table error borders, I have to submit the form using POST to bind the data, validate the form and generate the errors which ultimately set an error_class for styling the table.  This means that I have to "save" the from from my form page to get the table style I want.

What I really want is to instantiate a ModelFormset, setting the instance, but then to bind the data present in the instance and set errors by sending is_valid() to the formset before rendering (i.e. on initial page rendering with the GET, so no POST data).  It seemed to me that there should simply be a bind_instance() function for all ModelFormsets that contain an instance.  I looked and didn't find that.  Eventually, I just decided to bind the data myself manually using the following helper function:

# helper functions
def bind_formset(formset):
    bindData
={}
   
# add management form data
    bindData
[formset.get_default_prefix()+"-TOTAL_FORMS"]=str(formset.management_form['TOTAL_FORMS'].value())
    bindData
[formset.get_default_prefix()+"-INITIAL_FORMS"]=str(formset.management_form['INITIAL_FORMS'].value())
    bindData
[formset.get_default_prefix()+"-MIN_NUM_FORMS"]=str(formset.management_form['MIN_NUM_FORMS'].value())
    bindData
[formset.get_default_prefix()+"-MAX_NUM_FORMS"]=str(formset.management_form['MAX_NUM_FORMS'].value())
   
for form in formset:
       
if form.instance:
           
for fieldName,fieldValue in form.fields.iteritems():
               
try:
                    bindData
[form.add_prefix(fieldName)]=getattr(form.instance,fieldName)
               
except:
                   
# this is an added field, not derived from the model
                   
pass
    newFormset
=formset.__class__(bindData,instance=formset.instance,
                                  queryset
=formset.queryset, error_class=formset.error_class)
   
return newFormset

This works!  My question... Is this a reasonable approach?  Or did I just miss the obvious way of doing this?

Thanks for any help!

Collin Anderson

unread,
Jan 22, 2015, 8:36:08 PM1/22/15
to django...@googlegroups.com
Hi,

Interesting. I've never heard of someone wanting to show validation errors on the initial data when the page first loads.

I have however wanted to manually bind data to a form before. Last time I checked it's not super trivial, because you could for instance have a SplitDateTimeWidget which actually expects _two_ keys in the bound data.

If you're just doing basic fields, that should work. If you're doing anything more complicated, it might make sense to run the validation more by hand. If you could call field.clean() on the less raw data you could skip widgets and prefixes all together.

Collin

Rob Groves

unread,
Jan 23, 2015, 7:24:14 AM1/23/15
to django...@googlegroups.com
Thanks for the reply Collin, and thanks for the warning about this being more complicated than it seemed at first.  I later discovered that I needed to set the hidden fields that contain the foreign and instance keys as well.  For completeness, the below code is working for me given the fact that I only have simple fields.

# helper functions
def bind_formset(formset):
   
"""
    Bind initial data to a formset
    """

   
if formset.is_bound:
       
# do nothing if the formset is already bound
       
return formset
   
    bindData
={}
   
# the formset.get_default_prefix() and form.add_prefix() methods add in the
   
# dict keys that uniquely identify the various form fields with the individual
   
# instance data
   
   
# add formset management form data

    bindData
[formset.get_default_prefix()+"-TOTAL_FORMS"]=str(formset.management_form['TOTAL_FORMS'].value())
    bindData
[formset.get_default_prefix()+"-INITIAL_FORMS"]=str(formset.management_form['INITIAL_FORMS'].value())
    bindData
[formset.get_default_prefix()+"-MIN_NUM_FORMS"]=str(formset.management_form['MIN_NUM_FORMS'].value())
    bindData
[formset.get_default_prefix()+"-MAX_NUM_FORMS"]=str(formset.management_form['MAX_NUM_FORMS'].value())
   
for form in formset:
       
if form.instance:

           
# field data, get these values from the instance

           
for fieldName,fieldValue in form.fields.iteritems():
               
try:
                    bindData
[form.add_prefix(fieldName)]=getattr(form.instance,
                                                                 fieldName
)

               
except AttributeError:
                   
# this is an added field (i.e. DELETE), not derived from the
                   
# model, do nothing with it, since we are only binding instance
                   
# data to the form
                   
pass
           
# hidden field data, get these from the field initial values set
           
# when the form was created
           
for field in form.hidden_fields():
                bindData
[form.add_prefix(field.name)]=field.field.initial
   
# create a new bound formset by passing in the bindData dict, this looks
   
# to the formset constructor like a request.POST dict
    newFormset
=formset.__class__(bindData,instance=formset.instance,
                                 error_class
=formset.error_class)
   
return newFormset
Reply all
Reply to author
Forward
0 new messages