summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/contrib/contenttypes/generic.py1
-rw-r--r--django/forms/models.py15
-rw-r--r--docs/releases/1.6.1.txt2
-rw-r--r--tests/generic_relations_regress/models.py12
-rw-r--r--tests/generic_relations_regress/tests.py11
5 files changed, 35 insertions, 6 deletions
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
index 4880214200..469e64db33 100644
--- a/django/contrib/contenttypes/generic.py
+++ b/django/contrib/contenttypes/generic.py
@@ -39,6 +39,7 @@ class GenericForeignKey(six.with_metaclass(RenameGenericForeignKeyMethods)):
self.ct_field = ct_field
self.fk_field = fk_field
self.for_concrete_model = for_concrete_model
+ self.editable = False
def contribute_to_class(self, cls, name):
self.name = name
diff --git a/django/forms/models.py b/django/forms/models.py
index 6d9a567926..610a2a176a 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -83,7 +83,12 @@ def save_instance(form, instance, fields=None, fail_message='saved',
# Wrap up the saving of m2m data as a function.
def save_m2m():
cleaned_data = form.cleaned_data
- for f in opts.many_to_many:
+ # Note that for historical reasons we want to include also
+ # virtual_fields here. (GenericRelation was previously a fake
+ # m2m field).
+ for f in opts.many_to_many + opts.virtual_fields:
+ if not hasattr(f, 'save_form_data'):
+ continue
if fields and f.name not in fields:
continue
if exclude and f.name in exclude:
@@ -119,8 +124,8 @@ def model_to_dict(instance, fields=None, exclude=None):
from django.db.models.fields.related import ManyToManyField
opts = instance._meta
data = {}
- for f in opts.concrete_fields + opts.many_to_many:
- if not f.editable:
+ for f in opts.concrete_fields + opts.virtual_fields + opts.many_to_many:
+ if not getattr(f, 'editable', False):
continue
if fields and not f.name in fields:
continue
@@ -174,8 +179,8 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None,
field_list = []
ignored = []
opts = model._meta
- for f in sorted(opts.concrete_fields + opts.many_to_many):
- if not f.editable:
+ for f in sorted(opts.concrete_fields + opts.virtual_fields + opts.many_to_many):
+ if not getattr(f, 'editable', False):
continue
if fields is not None and not f.name in fields:
continue
diff --git a/docs/releases/1.6.1.txt b/docs/releases/1.6.1.txt
index baf53e5cd3..55115704bb 100644
--- a/docs/releases/1.6.1.txt
+++ b/docs/releases/1.6.1.txt
@@ -20,3 +20,5 @@ Bug fixes
* Fixed :class:`~django.contrib.auth.backends.ModelBackend` raising
``UnboundLocalError`` if :func:`~django.contrib.auth.get_user_model`
raised an error (#21439).
+* Fixed a regression that prevented editable ``GenericRelation`` subclasses
+ from working in ``ModelForms``.
diff --git a/tests/generic_relations_regress/models.py b/tests/generic_relations_regress/models.py
index 300ed9d54a..e9fb41dfe3 100644
--- a/tests/generic_relations_regress/models.py
+++ b/tests/generic_relations_regress/models.py
@@ -143,8 +143,18 @@ class Board(models.Model):
name = models.CharField(primary_key=True, max_length=15)
+class SpecialGenericRelation(generic.GenericRelation):
+ def __init__(self, *args, **kwargs):
+ super(SpecialGenericRelation, self).__init__(*args, **kwargs)
+ self.editable = True
+ self.save_form_data_calls = 0
+
+ def save_form_data(self, *args, **kwargs):
+ self.save_form_data_calls += 1
+
+
class HasLinks(models.Model):
- links = generic.GenericRelation(Link)
+ links = SpecialGenericRelation(Link)
class Meta:
abstract = True
diff --git a/tests/generic_relations_regress/tests.py b/tests/generic_relations_regress/tests.py
index 285bd43c47..6e6ada5872 100644
--- a/tests/generic_relations_regress/tests.py
+++ b/tests/generic_relations_regress/tests.py
@@ -1,6 +1,7 @@
from django.db.models import Q, Sum
from django.db.utils import IntegrityError
from django.test import TestCase, skipIfDBFeature
+from django.forms.models import modelform_factory
from .models import (
Address, Place, Restaurant, Link, CharLink, TextLink,
@@ -236,3 +237,13 @@ class GenericRelationTests(TestCase):
# Finally test that filtering works.
self.assertEqual(qs.filter(links__sum__isnull=True).count(), 1)
self.assertEqual(qs.filter(links__sum__isnull=False).count(), 0)
+
+ def test_editable_generic_rel(self):
+ GenericRelationForm = modelform_factory(HasLinkThing, fields='__all__')
+ form = GenericRelationForm()
+ self.assertIn('links', form.fields)
+ form = GenericRelationForm({'links': None})
+ self.assertTrue(form.is_valid())
+ form.save()
+ links = HasLinkThing._meta.get_field_by_name('links')[0].field
+ self.assertEqual(links.save_form_data_calls, 1)