Fixed #28222 - Allow non-field model property attributes in defaults passed update_or_create()/get_or_create()
diff --git a/django/db/models/options.py b/django/db/models/options.py
index cd50ff0aaae4e..557455c128b9f 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -828,10 +828,7 @@ def has_auto_field(self, value):
 
     @cached_property
     def _property_names(self):
-        """
-        Return a set of the names of the properties defined on the model.
-        Internal helper for model initialization.
-        """
+        """Return a set of the names of the properties defined on the model."""
         return frozenset({
             attr for attr in
             dir(self.model) if isinstance(getattr(self.model, attr), property)
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 9fd116c4a63d1..10952195db58b 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -504,12 +504,14 @@ def _extract_model_params(self, defaults, **kwargs):
                 lookup[f.name] = lookup.pop(f.attname)
         params = {k: v for k, v in kwargs.items() if LOOKUP_SEP not in k}
         params.update(defaults)
+        property_names = self.model._meta._property_names
         invalid_params = []
         for param in params:
             try:
                 self.model._meta.get_field(param)
             except exceptions.FieldDoesNotExist:
-                if param != 'pk':  # It's okay to use a model's pk property.
+                # It's okay to use a model's property if it has a setter.
+                if not (param in property_names and getattr(self.model, param).fset):
                     invalid_params.append(param)
         if invalid_params:
             raise exceptions.FieldError(
diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt
index e1cd939fa0a9d..ef235fa9ed576 100644
--- a/docs/releases/1.11.2.txt
+++ b/docs/releases/1.11.2.txt
@@ -32,3 +32,7 @@ Bugfixes
 
 * Allowed ``DjangoJSONEncoder`` to serialize
   ``django.utils.deprecation.CallableBool`` (:ticket:`28230`).
+
+* Relaxed the validation added in Django 1.11 of the fields in the ``defaults``
+  argument of ``QuerySet.get_or_create()`` and ``update_or_create()`` to
+  reallow settable model properties (:ticket:`28222`).
diff --git a/tests/get_or_create/models.py b/tests/get_or_create/models.py
index 865798ae98870..4a33a809bb279 100644
--- a/tests/get_or_create/models.py
+++ b/tests/get_or_create/models.py
@@ -32,6 +32,18 @@ class Thing(models.Model):
     name = models.CharField(max_length=255)
     tags = models.ManyToManyField(Tag)
 
+    @property
+    def capitalized_name_property(self):
+        return self.name
+
+    @capitalized_name_property.setter
+    def capitalized_name_property(self, val):
+        self.name = val.capitalize()
+
+    @property
+    def name_in_all_caps(self):
+        return self.name.upper()
+
 
 class Publisher(models.Model):
     name = models.CharField(max_length=100)
diff --git a/tests/get_or_create/tests.py b/tests/get_or_create/tests.py
index 2ac5a862b042e..60e1ef23f26e3 100644
--- a/tests/get_or_create/tests.py
+++ b/tests/get_or_create/tests.py
@@ -73,6 +73,11 @@ def test_get_or_create_with_pk_property(self):
         """
         Thing.objects.get_or_create(pk=1)
 
+    def test_get_or_create_with_model_property_defaults(self):
+        """Using a property with a setter implemented is allowed."""
+        t, _ = Thing.objects.get_or_create(defaults={'capitalized_name_property': 'annie'}, pk=1)
+        self.assertEqual(t.name, 'Annie')
+
     def test_get_or_create_on_related_manager(self):
         p = Publisher.objects.create(name="Acme Publishing")
         # Create a book through the publisher.
@@ -328,6 +333,11 @@ def test_with_pk_property(self):
         """
         Thing.objects.update_or_create(pk=1)
 
+    def test_update_or_create_with_model_property_defaults(self):
+        """Using a property with a setter implemented is allowed."""
+        t, _ = Thing.objects.get_or_create(defaults={'capitalized_name_property': 'annie'}, pk=1)
+        self.assertEqual(t.name, 'Annie')
+
     def test_error_contains_full_traceback(self):
         """
         update_or_create should raise IntegrityErrors with the full traceback.
@@ -514,3 +524,11 @@ def test_update_or_create_with_invalid_kwargs(self):
     def test_multiple_invalid_fields(self):
         with self.assertRaisesMessage(FieldError, "Invalid field name(s) for model Thing: 'invalid', 'nonexistent'"):
             Thing.objects.update_or_create(name='a', nonexistent='b', defaults={'invalid': 'c'})
+
+    def test_property_attribute_without_setter_defaults(self):
+        with self.assertRaisesMessage(FieldError, "Invalid field name(s) for model Thing: 'name_in_all_caps'"):
+            Thing.objects.update_or_create(name='a', defaults={'name_in_all_caps': 'FRANK'})
+
+    def test_property_attribute_without_setter_kwargs(self):
+        with self.assertRaisesMessage(FieldError, "Invalid field name(s) for model Thing: 'name_in_all_caps'"):
+            Thing.objects.update_or_create(name_in_all_caps='FRANK', defaults={'name': 'Frank'})

Allow the `defaults` argument to `update_or_create()`/`get_or_create()` to contain property attributes. The added tests further demonstrate the issue. 

For context, in 1.11 the `defaults` argument passed to `update_or_create()` and `get_or_create()` is validated and non-fields are reported as `FieldErrors` (introduced by #7255). There is an exception made for `pk` (added in #7338) but I believe it should apply to all `_meta._property_names` for the creation behavior to be consistent with `Model.create` and better backwards compatibility.

One thing to note @francoisfreitag, if `self.model._meta._property_names` always has `pk` then the param test can be made simpler by not checking for `pk` explicitly. 

https://code.djangoproject.com/ticket/28222

