Why does known valid Django model instance fail is_valid() after retrieval from database?
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.
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()
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?
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!
精彩评论