Fixed #27118 -- Made QuerySet.get_or_create()/update_or_create() error for a non-field in their arguments.

This commit is contained in:
François Freitag 2016-09-15 20:42:31 -07:00 committed by Tim Graham
parent b3330f52a8
commit a5e13a0b92
3 changed files with 47 additions and 1 deletions

View File

@ -527,6 +527,18 @@ class QuerySet(object):
lookup[f.name] = lookup.pop(f.attname) lookup[f.name] = lookup.pop(f.attname)
params = {k: v for k, v in kwargs.items() if LOOKUP_SEP not in k} params = {k: v for k, v in kwargs.items() if LOOKUP_SEP not in k}
params.update(defaults) 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 return lookup, params
def _earliest_or_latest(self, field_name=None, direction="-"): def _earliest_or_latest(self, field_name=None, direction="-"):

View File

@ -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 and :func:`~django.views.i18n.set_language` protect users from being redirected
to non-HTTPS ``next`` URLs when the app is running over HTTPS. 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 Miscellaneous
------------- -------------

View File

@ -5,9 +5,10 @@ import traceback
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from threading import Thread from threading import Thread
from django.core.exceptions import FieldError
from django.db import DatabaseError, IntegrityError, connection from django.db import DatabaseError, IntegrityError, connection
from django.test import ( from django.test import (
TestCase, TransactionTestCase, ignore_warnings, skipUnlessDBFeature, SimpleTestCase, TestCase, TransactionTestCase, ignore_warnings, skipUnlessDBFeature,
) )
from django.utils.encoding import DjangoUnicodeDecodeError from django.utils.encoding import DjangoUnicodeDecodeError
@ -484,3 +485,27 @@ class UpdateOrCreateTransactionTests(TransactionTestCase):
updated_person = Person.objects.get(first_name='John') updated_person = Person.objects.get(first_name='John')
self.assertGreater(after_update - before_start, timedelta(seconds=0.5)) self.assertGreater(after_update - before_start, timedelta(seconds=0.5))
self.assertEqual(updated_person.last_name, 'NotLennon') 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'})