﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
25349	ModelForm regression when setting None into a ForeignKey	John Paulett	Tim Graham <timograham@…>	"It appears that when using a ModelForm to ""unset"" a non-required foreign key field, Django 1.8 no longer will set the foreign key to None when saving the ModelForm, and the prior value for the ForeignKey remains. 

In practice, imagine a form where users can pick a category to assign to the instance, but can un-assign its category by picking the empty_label value in the <select>.

The behaviour worked previously in Django, including 1.6 and 1.7.9, but appears when testing against Django 1.8.5.  I have also tested the stable/1.8.x and master branches on github, which still show the issue.  I have reviewed the Release Notes and ModelForms documentation and can not see anything that would indicate this behaviour has changed. 

models.py:
{{{
from django.db import models

class Child(models.Model):
    name = models.TextField()

    def __unicode__(self):
        return self.name

class Parent(models.Model):
    name = models.TextField()
    child = models.ForeignKey(Child, null=True)

    def __unicode__(self):
        return self.name
}}}

forms.py:
{{{
from django import forms

from .models import Parent, Child

class ParentForm(forms.ModelForm):
    # field definition only set so `required = False`.  Have also tried
    # a custom ParentForm.__init__ with `self.fields['child'].required = False`
    child = forms.ModelChoiceField(
        queryset=Child.objects.all(),
        required=False
    )

    class Meta:
        model = Parent
        fields = ('name', 'child')

    def __init__(self, *args, **kwargs):
        super(ParentForm, self).__init__(*args, **kwargs)
        # self.fields['child'].required = False
}}}

tests.py:
{{{
from django.test import TestCase

from .forms import ParentForm
from .models import Parent, Child

class ParentFormTest(TestCase):
    def test_unset(self):
        p = Parent.objects.create(
            name='p',
            child=Child.objects.create(name='c')
        )

        form = ParentForm(
            data={'name': 'new name', 'child': None},
            instance=p
        )
        self.assertTrue(form.is_valid())

        form.save()

        obj = Parent.objects.get()
        self.assertEqual(obj.name, 'new name')
        self.assertIsNone(obj.child)  # ASSERTION FAILS

    def test_set_alternative(self):
        p = Parent.objects.create(
            name='p',
            child=Child.objects.create(name='c')
        )

        form = ParentForm(
            data={'name': 'new name',
                  'child': Child.objects.create(name='alt').id},
            instance=p
        )
        self.assertTrue(form.is_valid())

        form.save()

        obj = Parent.objects.get()
        self.assertEqual(obj.name, 'new name')
        self.assertEqual(obj.child.name, 'alt')  # ASSERTION WORKS
}}}

Result when running against Django 1.8.5: 
{{{
Creating test database for alias 'default'...
.F
======================================================================
FAIL: test_unset (demo.tests.ParentFormTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File ""/home/user/modelform-issue/demo/tests.py"", line 24, in test_unset
    self.assertIsNone(obj.child)  # ASSERTION FAILS
AssertionError: <Child: c> is not None

----------------------------------------------------------------------
Ran 2 tests in 0.007s

FAILED (failures=1)
Destroying test database for alias 'default'...
}}}

I have tried tracing through the ModelForm.save code with pdb.  The form.cleaned_data appears correct (`{'name': u'new name', 'child': None}`.   At the point that `save_instance` is called, it appears that the `instance.child`."	Bug	closed	Forms	1.8	Normal	fixed			Accepted	1	0	0	1	0	0
