Efrin
Efrin

Reputation: 2423

Django formset data does not get saved when initial values are provided

I have a formset which has some initial data provided - it's a data cloned from other model which contains 2 fields 'group' and 'requested'.

When initial data is provided the forms in formset do not get saved, they only get saved when I'll modify the form with the data a little bit.

When no initial data is provided forms do get saved.

Why adding intial data to a formset makes it impossible to save formsets data to the database?

This is my formset existing in get_context_data dictionary:

        initial = ProcedureActionGroup.objects.filter(procedure__id=self.kwargs.get('pk', None))
        initial_values = initial.values('group', 'requested')
        print initial_values
        initial_count = initial.count()

        ActionGroupFormset = inlineformset_factory(self.model, TaskGroup, extra=initial_count,
                                                   form=TaskActionGroupForm,
                                                   can_delete=False,
                                                   )
        data['formset'] = ActionGroupFormset(self.request.POST or None, initial=initial_values,
                                             **self.get_formset_kwargs())

This is my form_valid method where I save all data

def form_valid(self, form):
    context = self.get_context_data()
    forms = []
    forms.append(form.is_valid())
    if self.get_procedure_obj():
        formset = context['formset']
        forms.append(formset.is_valid())

    if all(forms):
        self.object = form.save(commit=False)
        form.save()
        if self.get_procedure_obj():
            formset = formset.save(commit=False)
            for obj in formset:
                obj.task = self.object
                obj.save()

        self.object.extract_users()

        return HttpResponseRedirect(self.object.get_absolute_url())

Model:

class TaskGroup(models.Model):
    task = models.ForeignKey(Task, null=True, blank=False)
    group = models.ForeignKey(ActionGroup, null=True, blank=True)
    requested = models.PositiveIntegerField(u'Requested', null=True, blank=True)

form template:

<form method="post" action="" class="span6 offset2 form form-horizontal">
    {% crispy form%}
    {{formset.management_form}}
    {% if formset %}
    <div>
        <fieldset>
            <table class="table table-striped">
                {% for form in formset%}
                <tr>
                    {% for field in form %}
                     <td> {{field}} </td>
                    {% endfor %}
                </tr>
                {% endfor %}
            </table>
        </fieldset>
    </div>
    {% endif %}
    <div class="form-actions">
        <button class="btn btn-primary btn-large" type="submit">
            Save
        </button>
    </div>
</form>

Upvotes: 6

Views: 2948

Answers (3)

FMCorz
FMCorz

Reputation: 2706

TLDR; Remove initial on POST.

I have myself faced this issue in Django admin with the formsets generated by using InlineModelAdmin. What I was trying to achieve was to prefill the inline forms using get_formset_kwars to reduce the need for data entry by the user.

However, it took me a while to discover why the prefilled data was never saved. It seemed random and buggy, but after a lot of Googling, I did find out that initial data is never saved if it has not changed. This is a feature not a bug although a confusing one.

Long story short, the simplest method for me was to remove the initial property when the request.method is POST. That way, Django cannot establish that the initial data has not changed, and will save the related models as intended.

Upvotes: 0

wobbily_col
wobbily_col

Reputation: 11891

I had a similar issue using django extra views.

I solved it bay modifying the form class that is passed to the inlineformset_factory. the ModelForm.has_changed method was returning false.

class TaskActionGroupForm(ModelForm):

    model = YourModel

    def has_changed(self):
        """
        Overriding this, as the initial data passed to the form does not get noticed, 
        and so does not get saved, unless it actually changes
        """
        changed_data = super(ModelForm, self).has_changed()
        return bool(self.initial or changed_data)

Upvotes: 7

Efrin
Efrin

Reputation: 2423

In exmaple above I used inlineformset_factory which treaded values passed to initial as already existing objects.

So when I looped over my formset with:

for form in formset:
   form.has_changed()

form.has_changed() returned False

I've repleaced inlineformset_factory to formset_factory which treats intial data as new data which get's saved correctly. Code example will be posted tomorrow.

Upvotes: 1

Related Questions