views:

100

answers:

3

We have a Django Model, ToolDataset, and a ModelForm, ToolForm. In the Model each instance, or database row, is referred to as a dataset, and the primary key is called the dataset_id. First time through, the user fills out an unbound form and submits it. Conceptually, this is the view code that is used to validate, save and analyze the dataset:

if (request.method == 'POST') and (not dataset_id):
    form = ToolForm(request.POST)
    if form.is_valid():
        new_dataset = form.save()
        dataset_id = new_dataset.pk
        results = analyze(form.cleaned_data)
    else:
        <validation error code>

I think so far this is very normal. Note that form data aren't saved and no dataset_id is assigned unless the data are valid.

Now some time passes, and the user wants to return to this particular old dataset, perhaps to change the data and re-analyze it. So, by whatever means, a URL is composed that looks like www.finesite.com/Tool/X/, where X is the dataset_id corresponding to the particular row of data the user wants to work with. Through the URLconf, a different branch of the view code is invoked, that we thought ought to look like this:

if (request.method != 'POST') and (dataset_id):
    oldset = get_object_or_404(ToolDataset, pk=dataset_id)
    form = ToolForm(instance=oldset)
    if form.is_valid():
        results = analyze(form.cleaned_data)
    else:
        <validation error code that we expected would never run>

Well, as it turns out, this dataset, that was valid when we stored it, doesn't validate now, which is a surprise. We used the manage.py shell to inspect the form a bit. Here's some of what we found:

>>> form.is_valid()
False
>>> form.errors
{}
>>> form.non_field_errors()
[]
>>> form.is_bound
False

Running form.as_p() yields what seems to be a complete form.

A very capable associate found an undocumented API function known as model_to_dict() in django/forms/models.py. He suggested substituting this,

form = BXEEP_L_Form(model_to_dict(oldset), instance=oldset),

for this,

form = BXEEP_L_Form(instance=oldset).

It works now - the form is valid and bound, according to the shell - but I am full of questions. Why does this work? Why is this necessary? Is there a more standard way of doing this? It seems odd to have to use an undocumented internal function for a use case that seems so commonplace and uncomplicated.

+1  A: 

I'm probably misunderstanding something but it looks to me that your analyze function should not take form.cleaned_data as input but rather dataset_id.

In case the example is not complete — why are you creating a form out of a dataset in order to analyze it?

lemonad
If I used dataset_id as the argument to my analyze function, that would be a much smaller argument to pass, but then the analyze function would need to make a database query, and I think the net result of that would be to slow things down a bit. The view, which really controls the overall process, used get_object_or_404 to fetch this data in part so it could be rendered to the page. Since this data is now in hand, why query the database twice. Why not just pass it to analyze()?
Thomas B. Higgins
A dataset contains parameters for an engineering design problem. When a user returns to a dataset after a lapse of time it is probably because he either wants to optimize the design (tweak the parameters) or just review the old results. In either case, the analysis must be performed again so he can see the current status of the design through a series of calculated performance measures, known as unity checks, that are not saved, and hence are not available for display until re-calculated by the analyze function.
Thomas B. Higgins
I'm seeing the meaning of your first point now. The best idea is to make the argument of the analyze function a model instance instead of form.cleaned_data. Then the analyze function can go forward without is_valid() having to run again. is_valid() is for validating user input, so its use here is unnecessary, as this was accomplished earlier. There are no drawbacks to this change except a little re-writing. See below.
Thomas B. Higgins
Sounds great — glad I could be of help!
lemonad
+1  A: 

form.is_valid() verifies the form.data dict, which is sent via the constructor for Form(data=request.POST)

ModelForm.instance associates the data with a particular table row, so that a save necessarily performs an update and not an insert. This is also passed via the constructor.

Both these are however, independent of each other. If you want to create a form with old instance's data you should do the following:

ToolForm(data=oldinstance.__data__, instance=oldinstance)

However, you perhaps don't really want to bind the data right away.

ToolForm(instance=oldinstance)

populates the right values from the instance, when rendered in the html and update the record, only if ToolForm instance is_changed()

Lakshman Prasad
Thomas B. Higgins
A: 

We changed the argument for the analyze function to be a model instance instead of form.cleaned_data. This decouples the analysis from form validation and is just much more sensible. Conceptually, the second part of the code given above now looks like this now:

if (request.method != 'POST') and (dataset_id):
        oldset = get_object_or_404(ToolDataset, pk=dataset_id)
        form = ToolForm(instance=oldset)
        results = analyze(oldset)

Of course, the unpacking of the data at the head of the analyze function had to be re-written somewhat.

This all seems obvious now. Thank you, everyone, for your interest!

Thomas B. Higgins