Commits

Anonymous committed b6db1c1

Fixed #17653 -- Changed MySQL backend to raise a ValueError if zero is used as an AutoField value.

Thanks to Sylvain Lebon for the report, krzysiumed for the patch and charettes and claudep for reviews.

Comments (0)

Files changed (5)

django/db/backends/__init__.py

     # 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

django/db/backends/mysql/base.py

         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

django/db/models/fields/__init__.py

     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

tests/regressiontests/backends/tests.py

 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
 
         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)

tests/regressiontests/bulk_create/tests.py

 
 from operator import attrgetter
 
-from django.test import TestCase, skipUnlessDBFeature
+from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
 
 from .models import Country, Restaurant, Pizzeria, State
 
             ])
         self.assertQuerysetEqual(State.objects.order_by("two_letter_code"), [
             "CA", "IL", "ME", "NY",
-        ], attrgetter("two_letter_code"))
+        ], 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])