summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/db/models/query.py12
-rw-r--r--docs/releases/1.11.txt9
-rw-r--r--tests/get_or_create/tests.py27
3 files changed, 47 insertions, 1 deletions
diff --git a/django/db/models/query.py b/django/db/models/query.py
index fddcbb5bbe..dac6b1c1c8 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -527,6 +527,18 @@ class QuerySet(object):
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)
+ invalid_params = []
+ for param in params:
+ try:
+ self.model._meta.get_field(param)
+ except exceptions.FieldDoesNotExist:
+ invalid_params.append(param)
+ if invalid_params:
+ raise exceptions.FieldError(
+ "Invalid field name(s) for model %s: '%s'." % (
+ self.model._meta.object_name,
+ "', '".join(sorted(invalid_params)),
+ ))
return lookup, params
def _earliest_or_latest(self, field_name=None, direction="-"):
diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt
index 73240c76ac..ded12408b9 100644
--- a/docs/releases/1.11.txt
+++ b/docs/releases/1.11.txt
@@ -446,6 +446,15 @@ Protection against insecure redirects in :mod:`django.contrib.auth` and ``i18n``
and :func:`~django.views.i18n.set_language` protect users from being redirected
to non-HTTPS ``next`` URLs when the app is running over HTTPS.
+``QuerySet.get_or_create()`` and ``update_or_create()`` validate arguments
+--------------------------------------------------------------------------
+
+To prevent typos from passing silently,
+:meth:`~django.db.models.query.QuerySet.get_or_create` and
+:meth:`~django.db.models.query.QuerySet.update_or_create` check that their
+arguments are model fields. This should be backwards-incompatible only in the
+fact that it might expose a bug in your project.
+
Miscellaneous
-------------
diff --git a/tests/get_or_create/tests.py b/tests/get_or_create/tests.py
index 108c8e3b4f..c569d938b0 100644
--- a/tests/get_or_create/tests.py
+++ b/tests/get_or_create/tests.py
@@ -5,9 +5,10 @@ import traceback
from datetime import date, datetime, timedelta
from threading import Thread
+from django.core.exceptions import FieldError
from django.db import DatabaseError, IntegrityError, connection
from django.test import (
- TestCase, TransactionTestCase, ignore_warnings, skipUnlessDBFeature,
+ SimpleTestCase, TestCase, TransactionTestCase, ignore_warnings, skipUnlessDBFeature,
)
from django.utils.encoding import DjangoUnicodeDecodeError
@@ -484,3 +485,27 @@ class UpdateOrCreateTransactionTests(TransactionTestCase):
updated_person = Person.objects.get(first_name='John')
self.assertGreater(after_update - before_start, timedelta(seconds=0.5))
self.assertEqual(updated_person.last_name, 'NotLennon')
+
+
+class InvalidCreateArgumentsTests(SimpleTestCase):
+ msg = "Invalid field name(s) for model Thing: 'nonexistent'."
+
+ def test_get_or_create_with_invalid_defaults(self):
+ with self.assertRaisesMessage(FieldError, self.msg):
+ Thing.objects.get_or_create(name='a', defaults={'nonexistent': 'b'})
+
+ def test_get_or_create_with_invalid_kwargs(self):
+ with self.assertRaisesMessage(FieldError, self.msg):
+ Thing.objects.get_or_create(name='a', nonexistent='b')
+
+ def test_update_or_create_with_invalid_defaults(self):
+ with self.assertRaisesMessage(FieldError, self.msg):
+ Thing.objects.update_or_create(name='a', defaults={'nonexistent': 'b'})
+
+ def test_update_or_create_with_invalid_kwargs(self):
+ with self.assertRaisesMessage(FieldError, self.msg):
+ Thing.objects.update_or_create(name='a', nonexistent='b')
+
+ 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'})