diff --git a/django/db/models/base.py b/django/db/models/base.py index 7c7bd2d7ee..97d1eecbc8 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -727,7 +727,7 @@ class Model(metaclass=ModelBase): update_fields = frozenset(update_fields) field_names = set() - for field in self._meta.fields: + for field in self._meta.concrete_fields: if not field.primary_key: field_names.add(field.name) @@ -737,9 +737,11 @@ class Model(metaclass=ModelBase): non_model_fields = update_fields.difference(field_names) if non_model_fields: - raise ValueError("The following fields do not exist in this " - "model or are m2m fields: %s" - % ', '.join(non_model_fields)) + raise ValueError( + 'The following fields do not exist in this model, are m2m ' + 'fields, or are non-concrete fields: %s' + % ', '.join(non_model_fields) + ) # If saving to the same database, and this model is deferred, then # automatically do an "update_fields" save on the loaded fields. diff --git a/tests/update_only_fields/models.py b/tests/update_only_fields/models.py index b7100398cd..3bba684d21 100644 --- a/tests/update_only_fields/models.py +++ b/tests/update_only_fields/models.py @@ -22,9 +22,19 @@ class Employee(Person): accounts = models.ManyToManyField('Account', related_name='employees', blank=True) +class NonConcreteField(models.IntegerField): + def db_type(self, connection): + return None + + def get_attname_column(self): + attname, _ = super().get_attname_column() + return attname, None + + class Profile(models.Model): name = models.CharField(max_length=200) salary = models.FloatField(default=1000.0) + non_concrete = NonConcreteField() class ProxyEmployee(Employee): diff --git a/tests/update_only_fields/tests.py b/tests/update_only_fields/tests.py index 58ae94b7cc..65a260338c 100644 --- a/tests/update_only_fields/tests.py +++ b/tests/update_only_fields/tests.py @@ -5,7 +5,10 @@ from .models import Account, Employee, Person, Profile, ProxyEmployee class UpdateOnlyFieldsTests(TestCase): - msg = 'The following fields do not exist in this model or are m2m fields: %s' + msg = ( + 'The following fields do not exist in this model, are m2m fields, or ' + 'are non-concrete fields: %s' + ) def test_update_fields_basic(self): s = Person.objects.create(name='Sara', gender='F') @@ -254,3 +257,8 @@ class UpdateOnlyFieldsTests(TestCase): self.assertEqual(Person.objects.count(), 1) with self.assertNumQueries(2): s.save(update_fields=['name', 'employee_num']) + + def test_update_non_concrete_field(self): + profile_boss = Profile.objects.create(name='Boss', salary=3000) + with self.assertRaisesMessage(ValueError, self.msg % 'non_concrete'): + profile_boss.save(update_fields=['non_concrete'])