Django: ValueError: cannot assign anyone to ForeignField when trying to create in a pure error

I have a lot of problems when I figure out how to automatically create an instance of a model for a ForeignKey field when submitting a form. Here is a simple toy website that illustrates the problem:

I have two models: Model1 and Model2. Model2 contains ForeignKey for Model1. I want the user to be able to create an instance of Model2, either by specifically selecting an instance of Model1 to store in ForeignKey, or by leaving this value empty and allowing the instance of Model1 to be automatically generated.

Here it seems to me what this code should look like. My models.py code is very simple:

# models.py from django.db import models from django.core.validators import MinValueValidator class Model1(models.Model): # Note this field cannot be negative my_field1 = models.IntegerField(validators=[MinValueValidator(0)]) class Model2(models.Model): # blank = True will make key_to_model1 not required on the form, # but since null = False, I will still require the ForeignKey # to be set in the database. related_model1 = models.ForeignKey(Model1, blank=True) # Note this field cannot be negative my_field2 = models.IntegerField(validators=[MinValueValidator(0)]) 

forms.py is a bit involved, but what happens is pretty simple. If Model2Form does not receive an instance of Model1, it tries to automatically create it in a clean method, validates it, and if it is valid, it saves it. If this is not valid, an exception is thrown.

 #forms.py from django import forms from django.forms.models import model_to_dict from .models import Model1, Model2 # A ModelForm used for validation purposes only. class Model1Form(forms.ModelForm): class Meta: model = Model1 class Model2Form(forms.ModelForm): class Meta: model = Model2 def clean(self): cleaned_data = super(Model2Form, self).clean() if not cleaned_data.get('related_model1', None): # Don't instantiate field2 if it doesn't exist. val = cleaned_data.get('my_field2', None) if not val: raise forms.ValidationError("My field must exist") # Generate a new instance of Model1 based on Model2 data new_model1 = Model1(my_field1=val) # validate the Model1 instance with a form form validation_form_data = model_to_dict(new_model1) validation_form = Model1Form(validation_form_data) if not validation_form.is_valid(): raise forms.ValidationError("Could not create a proper instance of Model1.") # set the model1 instance to the related model and save it to the database. new_model1.save() cleaned_data['related_model1'] = new_model1 return cleaned_data 

However, this approach does not work. If I enter valid data in my form, it works fine. But, if I don't enter anything for ForeignKey and put a negative value for an integer, I get a ValueError.

Traceback: File "/Library/Python/2.7/site-packages/django/core/handlers/base.py" in get_response 111. response = callback (request, * callback_args, ** callback_kwargs) File "/Library/Python/2.7 /site-packages/django/views/generic/base.py "in View 48. return self.dispatch (request, * args, ** kwargs) File" /Library/Python/2.7/site-packages/django/views/ generic / base.py "to send 69. Return handler (request, * args, ** kwargs) File" /Library/Python/2.7/site-packages/django/views/generic/edit.py "after after 172. return super (BaseCreateView, self) .post (request, * args, ** kwargs) File "/Library/Python/2.7/site-packages/django/views/generic/edit.py" after 137. if form.is_valid ( ): The file /Library/Python/2.7/site-packages/django/forms/forms.py in is_valid 124. return self.is_bound, not bool (self.errors) The file /Library/Python/2.7/site -packages / django / forms / forms.py "in _get_e rrors 115. self.full_clean () File "/Library/Python/2.7/site-packages/django/forms/forms.py" in full_clean 272. self._post_clean () File "/Library/Python/2.7/site-packages /django/forms/models.py "in _post_clean 309. self.instance = construct_instance (self, self.instance, opts.fields, opts.exclude) file" /Library/Python/2.7/site-packages/django/forms/ models.py "in construct_instance 51. f.save_form_data (instance, cleaned_data [f.name]) File" /Library/Python/2.7/site-packages/django/db/models/fields/ init .py "in save_form_data 454. setattr (instance, self.name, data) The file "/Library/Python/2.7/site-packages/django/db/models/fields/related.py" in install 362. (instance._meta.object_name, self.field. name))

Exception Type: ValueError at / add / Exception Value: Cannot Assign None: "Model2.related_model1" does not allow null values.

So what happens is that Django catches my ValidationError and still creates an instance of Model2, although the validation fails.

I could fix this by overriding the _post_clean method so as not to instantiate Model2 if there are errors. But this decision is ugly. In particular, the behavior of _post_clean is very useful overall. In more complex projects, I need _post_clean to start for other reasons.

I could also let ForeignKey be null, but never set it to zero in practice. But, again, this seems like a bad idea.

I could even create the Model1 mannequin, which I use whenever the test on trying to create a new model 1 fails, but it also seems hacky.

In general, I can come up with a lot of hacks to fix this, but I have no idea how to fix this in a clean, pythonic way.

+7
source share
1 answer

I found a solution that, in my opinion, might be acceptable, based on the discussion of karthikr in the comments. I am definitely still open to alternatives.

The idea is to use the logic in the view to choose between two forms to test: one form is a standard model form, and one is a model without a ForeignKey field.

So my models.py are identical.

My forms.py has two Model2 forms ... one extremely simple and one without a ForeignKey field and with new logic to dynamically create a new instance of Model1 for ForeignKey. The new logical form of the pure form is just the pure logic that I used to install in Model2Form:

 #forms.py from django import forms from django.forms.models import model_to_dict from .models import Model1, Model2 # A ModelForm used for validation purposes only. class Model1Form(forms.ModelForm): class Meta: model = Model1 class Model2Form(forms.ModelForm): class Meta: model = Model2 # This inherits from Model2Form so that any additional logic that I put in Model2Form # will apply to it. class Model2FormPrime(Model2Form): class Meta: model = Model2 exclude = ('related_model1',) def clean(self): cleaned_data = super(Model2Form, self).clean() if cleaned_data.get('related_model1', None): raise Exception('Huh? This should not happen...') # Don't instantiate field2 if it doesn't exist. val = cleaned_data.get('my_field2', None) if not val: raise forms.ValidationError("My field must exist") # Generate a new instance of Model1 based on Model2 data new_model1 = Model1(my_field1=val) # validate the Model1 instance with a form form validation_form_data = model_to_dict(new_model1) validation_form = Model1Form(validation_form_data) if not validation_form.is_valid(): raise forms.ValidationError("Could not create a proper instance of Model1.") # set the Model1 instance to the related model and save it to the database. cleaned_data['related_model1'] = new_model1 return cleaned_data def save(self, commit=True): # Best to wait til save is called to save the instance of Model1 # so that instances aren't created when the Model2Form is invalid self.cleaned_data['related_model1'].save() # Need to handle saving this way because otherwise related_model1 is excluded # from the save due to Meta.excludes instance = super(Model2FormPrime, self).save(False) instance.related_model1 = self.cleaned_data['related_model1'] instance.save() return instance 

And then my presentation logic uses one of two forms for validation, depending on the mail data. If it uses Model2FormPrime and the validation fails, it moves the data and errors to the regular Model2Form to show the user:

 # Create your views here. from django.views.generic.edit import CreateView from django.http import HttpResponseRedirect from .forms import Model2Form, Model2FormPrime class Model2CreateView(CreateView): form_class = Model2Form template_name = 'form_template.html' success_url = '/add/' def post(self, request, *args, **kwargs): if request.POST.get('related_model', None): # Complete data can just be sent to the standard CreateView form return super(Model2CreateView, self).post(request, *args, **kwargs) else: # super does this, and I won't be calling super. self.object = None # use Model2FormPrime to validate the post data without the related model. validation_form = Model2FormPrime(request.POST) if validation_form.is_valid(): return self.form_valid(validation_form) else: # Create a normal instance of Model2Form to be displayed to the user # Insantiate it with post data and validation_form errors form = Model2Form(request.POST) form._errors = validation_form._errors return self.form_invalid(form) 

This solution works, and it is quite flexible. I can add logic to my models and to the Model2Form base without worrying too much about its violation or DRY violation.

This is a little ugly, though, since it requires me to use two forms to complete one thing, skip errors between forms. So, I am definitely open to alternative solutions if anyone can offer something.

+1
source

All Articles