Loading django/forms/models.py +4 −1 Original line number Diff line number Diff line Loading @@ -85,6 +85,8 @@ def save_instance(form, instance, fields=None, fail_message='saved', for f in opts.many_to_many: if fields and f.name not in fields: continue if exclude and f.name in exclude: continue if f.name in cleaned_data: f.save_form_data(instance, cleaned_data[f.name]) if commit: Loading Loading @@ -405,7 +407,8 @@ class BaseModelForm(BaseForm): else: fail_message = 'changed' return save_instance(self, self.instance, self._meta.fields, fail_message, commit, construct=False) fail_message, commit, self._meta.exclude, construct=False) save.alters_data = True Loading tests/forms_tests/tests/tests.py +37 −1 Original line number Diff line number Diff line Loading @@ -5,7 +5,7 @@ import datetime from django.core.files.uploadedfile import SimpleUploadedFile from django.db import models from django.forms import Form, ModelForm, FileField, ModelChoiceField from django.forms import Form, ModelForm, FileField, ModelChoiceField, CharField from django.forms.models import ModelFormMetaclass from django.test import TestCase from django.utils import six Loading @@ -26,6 +26,14 @@ class OptionalMultiChoiceModelForm(ModelForm): fields = '__all__' class ChoiceFieldExclusionForm(ModelForm): multi_choice = CharField(max_length=50) class Meta: exclude = ['multi_choice'] model = ChoiceFieldModel class FileForm(Form): file1 = FileField() Loading Loading @@ -221,3 +229,31 @@ class RelatedModelFormTests(TestCase): model=A self.assertTrue(issubclass(ModelFormMetaclass(str('Form'), (ModelForm,), {'Meta': Meta}), ModelForm)) class ManyToManyExclusionTestCase(TestCase): def test_m2m_field_exclusion(self): # Issue 12337. save_instance should honor the passed-in exclude keyword. opt1 = ChoiceOptionModel.objects.create(id=1, name='default') opt2 = ChoiceOptionModel.objects.create(id=2, name='option 2') opt3 = ChoiceOptionModel.objects.create(id=3, name='option 3') initial = { 'choice': opt1, 'choice_int': opt1, } data = { 'choice': opt2.pk, 'choice_int': opt2.pk, 'multi_choice': 'string data!', 'multi_choice_int': [opt1.pk], } instance = ChoiceFieldModel.objects.create(**initial) instance.multi_choice = instance.multi_choice_int = [opt2, opt3] form = ChoiceFieldExclusionForm(data=data, instance=instance) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['multi_choice'], data['multi_choice']) form.save() self.assertEqual(form.instance.choice.pk, data['choice']) self.assertEqual(form.instance.choice_int.pk, data['choice_int']) self.assertEqual(list(form.instance.multi_choice.all()), [opt2, opt3]) self.assertEqual([obj.pk for obj in form.instance.multi_choice_int.all()], data['multi_choice_int']) Loading
django/forms/models.py +4 −1 Original line number Diff line number Diff line Loading @@ -85,6 +85,8 @@ def save_instance(form, instance, fields=None, fail_message='saved', for f in opts.many_to_many: if fields and f.name not in fields: continue if exclude and f.name in exclude: continue if f.name in cleaned_data: f.save_form_data(instance, cleaned_data[f.name]) if commit: Loading Loading @@ -405,7 +407,8 @@ class BaseModelForm(BaseForm): else: fail_message = 'changed' return save_instance(self, self.instance, self._meta.fields, fail_message, commit, construct=False) fail_message, commit, self._meta.exclude, construct=False) save.alters_data = True Loading
tests/forms_tests/tests/tests.py +37 −1 Original line number Diff line number Diff line Loading @@ -5,7 +5,7 @@ import datetime from django.core.files.uploadedfile import SimpleUploadedFile from django.db import models from django.forms import Form, ModelForm, FileField, ModelChoiceField from django.forms import Form, ModelForm, FileField, ModelChoiceField, CharField from django.forms.models import ModelFormMetaclass from django.test import TestCase from django.utils import six Loading @@ -26,6 +26,14 @@ class OptionalMultiChoiceModelForm(ModelForm): fields = '__all__' class ChoiceFieldExclusionForm(ModelForm): multi_choice = CharField(max_length=50) class Meta: exclude = ['multi_choice'] model = ChoiceFieldModel class FileForm(Form): file1 = FileField() Loading Loading @@ -221,3 +229,31 @@ class RelatedModelFormTests(TestCase): model=A self.assertTrue(issubclass(ModelFormMetaclass(str('Form'), (ModelForm,), {'Meta': Meta}), ModelForm)) class ManyToManyExclusionTestCase(TestCase): def test_m2m_field_exclusion(self): # Issue 12337. save_instance should honor the passed-in exclude keyword. opt1 = ChoiceOptionModel.objects.create(id=1, name='default') opt2 = ChoiceOptionModel.objects.create(id=2, name='option 2') opt3 = ChoiceOptionModel.objects.create(id=3, name='option 3') initial = { 'choice': opt1, 'choice_int': opt1, } data = { 'choice': opt2.pk, 'choice_int': opt2.pk, 'multi_choice': 'string data!', 'multi_choice_int': [opt1.pk], } instance = ChoiceFieldModel.objects.create(**initial) instance.multi_choice = instance.multi_choice_int = [opt2, opt3] form = ChoiceFieldExclusionForm(data=data, instance=instance) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['multi_choice'], data['multi_choice']) form.save() self.assertEqual(form.instance.choice.pk, data['choice']) self.assertEqual(form.instance.choice_int.pk, data['choice_int']) self.assertEqual(list(form.instance.multi_choice.all()), [opt2, opt3]) self.assertEqual([obj.pk for obj in form.instance.multi_choice_int.all()], data['multi_choice_int'])