diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 6f1f4b9618..0d664d452a 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -770,6 +770,14 @@ class BaseDatabaseOperations(object): # need not necessarily be implemented using "LIKE" in the backend. prep_for_iexact_query = prep_for_like_query + def validate_autopk_value(self, value): + """ + Certain backends do not accept some values for "serial" fields + (for example zero in MySQL). This method will raise a ValueError + if the value is invalid, otherwise returns validated value. + """ + return value + def value_to_db_date(self, value): """ Transform a date value to an object compatible with what is expected diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index bacd998fa5..6397a0972c 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -276,6 +276,13 @@ class DatabaseOperations(BaseDatabaseOperations): else: return [] + def validate_autopk_value(self, value): + # MySQLism: zero in AUTO_INCREMENT field does not work. Refs #17653. + if value == 0: + raise ValueError('The database backend does not accept 0 as a ' + 'value for AutoField.') + return value + def value_to_db_datetime(self, value): if value is None: return None diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index e240d39cc4..dc4495390a 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -531,6 +531,12 @@ class AutoField(Field): def validate(self, value, model_instance): pass + def get_db_prep_value(self, value, connection, prepared=False): + if not prepared: + value = self.get_prep_value(value) + value = connection.ops.validate_autopk_value(value) + return value + def get_prep_value(self, value): if value is None: return None diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index 9b28787c15..3e675cc1ea 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -13,7 +13,8 @@ from django.db import (backend, connection, connections, DEFAULT_DB_ALIAS, from django.db.backends.signals import connection_created from django.db.backends.postgresql_psycopg2 import version as pg_version from django.db.utils import ConnectionHandler, DatabaseError, load_backend -from django.test import TestCase, skipUnlessDBFeature, TransactionTestCase +from django.test import (TestCase, skipUnlessDBFeature, skipIfDBFeature, + TransactionTestCase) from django.test.utils import override_settings from django.utils import unittest @@ -642,3 +643,15 @@ class BackendLoadingTests(TestCase): self.assertRaisesRegexp(ImproperlyConfigured, "Try using django.db.backends.sqlite3 instead", load_backend, 'sqlite3') + + +class MySQLPKZeroTests(TestCase): + """ + Zero as id for AutoField should raise exception in MySQL, because MySQL + does not allow zero for automatic primary key. + """ + + @skipIfDBFeature('allows_primary_key_0') + def test_zero_as_autoval(self): + with self.assertRaises(ValueError): + models.Square.objects.create(id=0, root=0, square=1) diff --git a/tests/regressiontests/bulk_create/tests.py b/tests/regressiontests/bulk_create/tests.py index 284ea9fdd9..0b55f637a4 100644 --- a/tests/regressiontests/bulk_create/tests.py +++ b/tests/regressiontests/bulk_create/tests.py @@ -2,7 +2,7 @@ from __future__ import absolute_import from operator import attrgetter -from django.test import TestCase, skipUnlessDBFeature +from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature from .models import Country, Restaurant, Pizzeria, State @@ -56,4 +56,16 @@ class BulkCreateTests(TestCase): ]) self.assertQuerysetEqual(State.objects.order_by("two_letter_code"), [ "CA", "IL", "ME", "NY", - ], attrgetter("two_letter_code")) \ No newline at end of file + ], attrgetter("two_letter_code")) + + @skipIfDBFeature('allows_primary_key_0') + def test_zero_as_autoval(self): + """ + Zero as id for AutoField should raise exception in MySQL, because MySQL + does not allow zero for automatic primary key. + """ + + valid_country = Country(name='Germany', iso_two_letter='DE') + invalid_country = Country(id=0, name='Poland', iso_two_letter='PL') + with self.assertRaises(ValueError): + Country.objects.bulk_create([valid_country, invalid_country])