Yes, I am defining _construct_form() as you do. I had just not
thought to set initial there - but that makes sense. For reference,
here is my current code, with it setting initial in _construct_form.
But you may want to read on below before bothering to look at it
because I have a theory about why cleaned_data is not getting set.
class TaskTileDetailCustomBaseFormSetForCreate(BaseFormSet):
def __init__(self, *args, **kwargs):
self.tiles = list(kwargs.pop("tiles"))
self.extra = len(self.tiles)
super(TaskTileDetailCustomBaseFormSetForCreate, self).__init__
(*args, **kwargs)
def _construct_form(self, i, **kwargs):
kwargs["tile"] = self.tiles[i]
kwargs["initial"] = {'tile':self.tiles[i].id}
return super(TaskTileDetailCustomBaseFormSetForCreate,
self)._construct_form(i, **kwargs)
def makeTaskTileDetailFormSetForCreate(tileList, data=None):
TaskTileDetailFormSet = formset_factory(form=TaskTileDetailForm,
formset=TaskTileDetailCustomBaseFormSetForCreate)
taskTileDetailFormSet = TaskTileDetailFormSet(data,
tiles=tileList)
return taskTileDetailFormSet
After debugging into the source some more, I'm finding that my the
problem is related to the fact that empty_permitted is getting set to
True when all of my forms are considered "extra. Specifically, in site-
packages/django/forms/forms.py, in the full_clean() function, I find
that when empty_permitted is set to True, this code executes, which
drops me out of the code without setting cleaned_data at all:
# If the form is permitted to be empty, and none of the form
data has
# changed from the initial data, short circuit any validation.
if self.empty_permitted and not self.has_changed():
return
It seems to me that if I pass in initial when I create the
TaskTileDetailFormSet, ie, like this:
initial = []
for tile in tileList:
initial.append({'tile': tile.id})
taskTileDetailFormSet = TaskTileDetailFormSet(data,
tiles=tileList, initial=initial)
then empty_permitted gets set to False ibecause my initial_form_count
is non-zero. IE, in site-packages/django/forms/formsets.py, in
_construct_form(), in thh code that looks like this:
# Allow extra forms to be empty.
if i >= self._initial_form_count:
defaults['empty_permitted'] = True
But if I pass initial in via my own _construct_form() function as you
suggested, then I have no initial data, so all of my forms are
"extra". IN this case self._initial_form_count is 0, and it seems
that the result is that cleaned_data doesn't get set correctly.
I am probably far from undrestanding this, but if what I said is
atually true, it seems like this is actually a bug? The bug being
that cleaned_data is not getting set correctly when the forms are
created as "extra" forms. Perhaps cleaned_data is not supposed to get
set in this case? The whole reason that I happened upon this is
beacuse I am trying to identify which form is which, so I was looking
at cleaned_data['tile']. I set that myself anyway, so I can just look
at data['tile']. However, it seems that none of my post data is
getting put in cleaned_data, so that still seems like a general
problem.
Margie
On Mar 3, 11:17 pm, Malcolm Tredinnick <[email protected]>
wrote:
> On Tue, 2009-03-03 at 22:42 -0800, Margie wrote:
> > Hi Malcolm - Sorry, another formset question. Maybe you'll know the
> > answer offhand, have been trying to figure this out for awhile now.
> > I'm trying to set the initial value for a form field inside the
> > constructor for the form. The high level goal is to send out the
> > 'tile' field as a hidden input so that I can get it back in the POST
> > so that I can figure out wich 'tile' each form in the forset
> > corresonds to.
>
> > For example, I'm trying to do this in my form constructor
> > (specifically see the ==> line)
>
> > class TaskTileDetailForm(forms.ModelForm):
> > class Meta:
> > model=TaskTileDetail
> > exclude=('task')
>
> If this is a cut-and-paste, this line is almost certainly a bug. The
> "exclude" parameter should be a tuple or a list, so ("task",) or
> ["task"] (note the trailing comma in the tuple case).
>
>
>
> > def __init__(self, tile, *args, **kwargs):
>
> This line is a probably going to be a problem at some point. You cannot
> just add new positional arguments into the front of the __init__ call
> like this. When Django calls the constructor, it could well be passing
> the "data" parameter as the first argument.
>
> Instead, pass it in as a keyword argument. To wit:
>
> def __init__(self, *args, **kwargs):
> tile = kwargs.pop("tile")
> super(TaskTileDetailForm, self).__init__(*args, **kwargs)
> ...
>
> That preserves the ordering of positional arguments.
>
> > super(TaskTileDetailForm, self).__init__(*args, **kwargs)
> > self.fields['tile'].widget = forms.HiddenInput()
> > ==> self.fields['tile'].initial = tile.id
>
> > If I do this, then later when processing my POST data there is no
> > 'tile' key in cleaned_data. IE, I get a KeyError when I do this:
> > if taskTileDetailForm.cleaned_data["tile"] in taskTiles:
>
> > If I intiaizize it with the initial arg when creating the formset, the
> > cleaned_data["tile"] key is set just fine, ie when I use initial as
> > shown at the ==> below:
>
> It seems like the problem is how is "tile" meant to be known to each
> form? When a formset is constructed, it just constructs and essentially
> normal form, passing in any "data" and "initial" parameters. If you need
> to do any extra setup on the form -- particularly dealing with passing
> in extra parameters -- then you have to do something similar to what I
> did in my blog post ([1], for those who don't know what we're talking
> about).
>
> So, either, pass in "initial" to the formset, or override the
> _construct_form() method, as in the blog post. That's precisely why I
> had to do it that way in my example: I needed to pass in some custom
> information to each form as it was constructed as part of the formset.
>
> The difference between your case and mine, is that your extra data
> corresponds directly to a form field (in mine, the question text was
> auxiliary data that wasn't a form field). So I would probably set it up
> using the "initial" parameter, if it was me. It would involve far less
> formset base class customisation.
>
> [1]http://www.pointy-stick.com/blog/2009/01/23/advanced-formset-usage-dj...
>
> > def makeTaskTileDetailFormSetForCreate(tileList, data=None):
> > TaskTileDetailFormSet = formset_factory
> > (form=TaskTileDetailForm, formset=CustomBaseFormSe)
>
> > initial = []
> > for tile in tileList:
> > initial.append({'tile': tile.id})
>
> Since this is a read-only attribute, you could probably just write
>
> initial = [{"tile": tile.id}] * len(tileList)
>
> That will make each element of initial a reference to the same
> dictionary, but that's not a problem since the initial data is only
> read, never changed.
>
> Regards,
> Malcolm
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"Django users" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/django-users?hl=en
-~----------~----~----~----~------~----~------~--~---