I'm upgrading an old Django 1.1 project to a clean Django 1.5 installation. The Django upgrade itself has gone very well; {% url %}
changes, settings, some class-based generic views, some core patches replaced with a custom User model, others done with nicer middleware, and so on, hooray.
What I didn't expect to stop working, but has, is a model creation form that lets someone add a related object at the same time. In the code, this is done using a MultiValueField subclass:
class AutoCompleteMultiValueField(forms.MultiValueField):
def __init__(self, model, column, *args, **kwargs):
self.model = model
self.column = column
super(AutoCompleteMultiValueField, self).__init__(*args, **kwargs)
def compress(self, data_list):
if not data_list:
return None
if data_list[0] and not data_list[1]:
data_list[1] = self.model(**{self.column: data_list[0]})
return data_list[1]
Which is used in the following form:
class ProductionForm(forms.ModelForm):
...
play = AutoCompleteMultiValueField(
Play, 'title',
required = False, # It is required, but will be spotted in the clean function
fields = (StripCharField(), forms.ModelChoiceField(Play.objects.all())),
widget = ForeignKeySearchInput(Production.play.field.rel, ('title',))
)
...
def clean_play(self):
if not self.cleaned_data['play']:
raise forms.ValidationError('You must specify a play.')
...
def save(self, **kwargs):
if not self.cleaned_data['play'].id:
self.cleaned_data['play'].save()
return super(ProductionForm, self).save(**kwargs)
Where ForeignKeySearchInput is just a widget that displays the two fields as a text input and a hidden input, attached to some auto-complete JavaScript. So you can type in a play title and pick from a drop-down that appears (and then the StripCharField gets the text of the play title, and the ModelChoiceField is its ID), but it still lets you type a title that doesn't match anything already in the database (then the text input is your new play title, and the ModelChoiceField is empty).
Currently, running with Django 1.1, this works fine - the form validates, play is either an existing Play object or a new Play object created by the AutoCompleteMultiValueField, and the form's save() function saves the Play before saving the new Production.
Django 1.5, on the other hand, before the form save happens, calls construct_instance() in the ModelForm's _post_clean() and then tries to validate the model. As the model, correctly, says that a Production must have a Play, this raises a "play cannot be null" error. And I can't work out the correct way to get this working again. Any help would be appreciated, do let me know if more details would be helpful.
I guess I can stop it being a ModelForm and just a Form and do the save()ing in the right order manually. But it feels like something I should be able to do, especially as I could before.