Ok - so one more day of working through this did the trick and I got
through all of my issues and am successfully using modelformset, both
when creating new objects and when editing existing ones.  I thought
I'd recap here in case anyone is searching for formset info, and also
to give any developers that are reading this some examples of new and
different usage issues.


First, in order to create a formset for a bunch of objects that don't
yet exist (ie, the form is the user's way of creating the objects) I
am setting initial inside _construct_form(), as Malcolm suggested:

    def _construct_form(self, i, **kwargs):
        kwargs["tile"] = self.tiles[i]
        kwargs["initial"] = {'tile':self.tiles[i].id}
        return super(TaskDetailCustomBaseFormSetForCreate,
self)._construct_form(i, **kwargs)

I think that this is the best way to create initial data with a
modelformset.  I was never successful using the initial argument when
constructng the formset from the formset factory class and I think
that would probably require a lot more hacking get it to work.  Once
you use the initial argument, ie like this:

    TaskTileDetailFormSet = modelformset_factory(TaskTileDetail,
 
form=TaskTileDetailForm,
 
formset=TaskTileDetailCustomBaseFormSetForCreate)


    taskTileDetailFormSet = TaskTileDetailFormSet(data=data,
                                                  tiles=tileList,
                                                  initial=
[{"tile":tile.id} for tile in tileList])

it seems that the modelformset code thinks you have 'n' existing
objects, and you get errors like this:

-> kwargs['instance'] = self.get_queryset()[i]
(Pdb) n
IndexError: 'list index out of range'

So for anyone using modelformsets - put your initial data into
construct_form().

One thing that I spent some time thinking about was whether I could
identify which form was which by indexing into my formset during the
POST.  I think Malcolm's suggestion was that I create the formset for
the GET, and then create the same formset for the POST (but with
postdict data this time).  I think the point was that since I am
creating the data myself during the POST, I should know what's what
and shouldn't be able to just index in at the right point.  After
thinking about this for some time, I think there is a problem with
this approach, at least for my app.


Let's say my app displays a form that presents the users with book
titles for a set of books, and the user is filling out the author and
submitting the form.  Now, suppose the app also allows a user to
change the books in the group (ie, via some other url).  I think this
would make it impossible to use the indexing method because the
formset that I create during POST might not be the same as the formset
I create during the GET.  IE, I'd run into this case if one user goes
to the form where they fill out authors and then sits on that form for
awhile, and meanwhile some other user goes and changes the books in
the group.  For error checking purposes, I think I need to send the
book titles as hidden inputs with the form, that when I proceses the
POST, if some of those titles are now gone, I can identify which
titles are still valid and also generate good error messags for the
ones that are not.


This example is sort of contrived, but in the system I'm designing, it
could happen and I'd like to be able to cover it gracefully.   The
code I created to check for this error is pretty ugly, in that it
reaches right into the django source private space.  This assumes that
fieldName is some "hidden" field that lets me identify each form.  The
code below compares the post data to the initial data, in a similar to
manner to the get_changed_data() function in the source.  Here it is:

def getFormContinuityError(form, fieldName):
    """Confirm that the 'initial' value for fieldName is the same as
the 'data' value for fieldName.
    This protects from case where the fieldNames have changed between
the get and post.
    """
    data_value = form.fields[fieldName].widget.value_from_datadict
(form.data, form.files, form.add_prefix(fieldName))
    initial_value = form.initial.get(fieldName, form.fields
[fieldName].initial)
    has_changed = form.fields[fieldName].widget._has_changed
(initial_value, data_value)
    return has_changed

def getFormSetContinuityError(formSet, fieldName):
    continuityError = 0
    for form in formSet.forms:
        if getFormContinuityError(form, fieldName):
            continuityError = 1
            break
    return continuityError

I will probably no doubt regret this code some day when I update to a
new version of django!

A second thing that I needed to do that is perhaps different from what
other people have needed was to save the formset forms even if they
are empty.  In my particular app, the user is creating tasks.  They
put in a description of the task and each task has 1 or more results,
one per "tile".  Associated with each tile is an optional set of
"special instructions", and a location for the tile owner to input
their result.  When creating the task, I create the formset to give
the user a way to identify which tiles are associated with the task.
When the user creates the task, they need to choose which tiles it is
for, but they may not have any special tile instructions and they
definitely don't have the result.  So I had a situation where I did
need to be able to save empty forms.  IE, once the user submits the
task, I want a tile object to exist for each tile they selected.  I
found that when my formset forms were empty, the existing framework
gave me no cleaned data and in fact would not let me save the form to
an object.  I got around this by simply creating a dummy field, which
I set to a value in the view.py POST code.  After setting that dummy
field in the postdict, I created the formset and having that dummy
field caused has_changed() to be true, which allowed the cleaned_data
to be created and allowed the form.save() to actually do a save.

Anyway, that's it.  It's been a long haul (took me about 3 days to
really grock what is going on) but it's cool now that it is working.
Malcolm, thanks for all your help.  I don't think I could have gotten
it working without your pointers and high level review.

Margie
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To post to this group, send email to django-users@googlegroups.com
To unsubscribe from this group, send email to 
django-users+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-users?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to