Let’s begin with what I was trying to achieve.
I’ve got a model containing a boolean field and a foreign key:
class MyModel(models.Model):
name = models.CharField(max_length=200)
class MyMark(models.Model):
chkb = models.BooleanField(default=True)
my_model = models.ForeignKey(MyModel, null=True, blank=True, on_delete=models.CASCADE)
I’m displaying a CreateView/UpdateView
for MyMark
model. What I wanted to do is to set my_model
value to None
if the chkb
value is False
, even if my_model
value is selected by the user in the form.
Originally I tried to do this as follows:
class MyMarkCreate(CreateView):
model = MyMark
fields = "__all__"
...
def form_valid(self, form):
if not form.cleaned_data["chkb"]:
form.cleaned_data.pop("my_model")
return super().form_valid(form)
but that didn’t work as I expected it to - created/updated MyMark
object still had non-empty my_model
value. I expected the object to be created from form.cleaned_data
in super().form_valid(form)
, but apparently that’s not what’s happening.
I poked around Django code a bit and found that the model instance is created in django.forms.models.construct_instance()
, which is called from BaseModelForm._post_clean()
, building instance from cleaned_data
- so before I alter cleaned_data
in my form_valid
.
This StackOverflow answer suggests calling form.save(commit=False)
manually and setting field’s value as needed:
class MyMarkUpdate(UpdateView):
...
def form_valid(self, form):
if not form.cleaned_data["chkb"]:
my_mark = form.save(commit=False)
my_mark.my_model = None
my_mark.save()
print(form.cleaned_data)
return super().form_valid(form)
That works, but then I don’t understand why my manually set value is not automatically overwritten again in super().form_valid(form)
. It should call form.save()
again after all - or is it not what’s happening? From the print
I see that form.cleaned_data
still contains a value for my_model
field.
So, if anyone could help explain what’s happening here, I’d be grateful.
And another question - is there a better way to achieve my original goal? Maybe writing a custom clean()
method, dropping my_model
from cleaned_data
would be better?