Is there way to override changed data in django forms?

hi folks.

I think I need some help thinking through how to implement some fairly gnarly form logic, and before I spend more time banging my head against a wall, I figured asking here might help.

Background

I’m working on an open source project, where we’re building a multi step form wizard that it takes some data from a series of other models, and allows users to run through a series of steps to confirm that data is either correct, or needs to be updated, before writing the updated version back to the original model.

We’ve been using the initial_data method in django forms to prepopulate forms and formsets with data to allow a user to review it before either making the changes or accepting them as they are.

To make this concrete, you can see some code at the link below, in the class ProviderRequestWizardView, and in particular, the code to generate our initial_data using get_initial_dict() on that object.

And later on using the done

One thing we’re finding though is that when we use django forms, only the data that has changed is being saved as we run through the wizard, when we have initial data.

This wizard works fine for when we use it to allow a user to submit entirely new information, (i.e. to run through a form wizard with no initial data being added), but when we’re workign with existing data, this is bad because at the end of the wizard, when we would save the information back to the original models, only the data that has been changed is persisted back to the models.

In our case, we want all the data to be saved though, no matter what - is there a simple way to override this behaviour so we do this?

Adding a bit more here. I initially thought this was a problem related to the has_changed behaviour with django forms.

Use the has_changed() method on your Form when you need to check if the form data has been changed from the initial data.

Source: Django

This made me think I could override that.

However, I’m now thinking it’s more related to our use of formsets, because of these two paras in the docs - first this:

Validation with a formset is almost identical to a regular Form. There is an is_valid method on the formset to provide a convenient way to validate all forms in the formset:

… and later this:

The formset is smart enough to ignore extra forms that were not changed

Source: Django by djangoproject

I think in our case, we don’t want to ignore forms that have initial data that were not changed - we want to save them too, because if the data has not been changed, then it means we do want it still.

Is there an straight forward day to override is_valid to not ignore these forms, so that when saving a formset, we persist all the data in the form, without filtering out any unchanged forms?

Thanks

Ok, this looks like the method that might be filtering out the unchanged forms:

If looks like save_existing_objects and save_new_objects both drop a form if form.has_changed() is false:

This makes me think we’ll need override save_existing_objects, to avoid dropping unchanged forms for this to work.

I’m currently not sure what the full implications of this might be - we’d presumably save more things to the database rather than just the bits that have changed, so that might trigger more calls to save().

In our case, I think that’s manageable.

<conjecture>
I believe one of the biggest implications would involve any “extra” forms that may have been generated and rendered in the formset. If you generate an extra form and it’s submitted without data, you probably don’t want that attempted to be validated or saved.

If you are 100% certain that you can guarantee that no “extra” forms will be generated in the formset and that the front-end will never provide the facility to add forms dynamically, you’re probably ok with this.
</conjecture>

As a side note, my initial reaction is that I’m not sure I understand why not saving those unchanged forms is a problem. If you have the data when the form is being rendered, then you still should have that data available when the form is posted.

Hey @mrchrisadams

So just checking, you’ve looked at the form tools package right?

https://django-formtools.readthedocs.io

This provides a form wizard object that has likely covered most of the issues your facing here, at least in a manner you might draw from, even if it’s not perfect for your case.

The links you posted seem on the right path (but it’s difficult to say exactly without looking at depth in your code). You should be able to store (in the session?) a record of which forms were from a previous step (and should not be treated as unchanged).

One thought that comes to mind (but literally just :thought_balloon: so … :stuck_out_tongue_winking_eye:) is to merge previously submitted data into the data you pass to the formset, so from it’s POV it all got submitted in one go. I have no idea how easy or not that might be without trying, but it might be only a ChainMap (although getlist()…) :thinking:

1 Like

hi @carltongibson - yes we’re using the form-tools package extensively - it’s been fairly nice to work with, and we expect to work with it more in the future.

You can see the specific implementation below, where we’re extending the SessionWizardView

I hadn’t thought about making a copy of the object like that to compare against before - if i have the ‘pristine’ versions of the data as an object before someone starts working through a multi-stage form wizard, then I wouldn’t need to reach into the formset validation logic like I described.

I think I’d just need to look at the changed forms in each formset of the wizard, and then swap them into the ‘pristine’ version of the object, replacing the now out-of-date versions.

That seems less hairy then messing with core django formset logic, and lets me keep the merge logic in one place.

Thanks for the pointers :+1: