Bläddra i källkod

Fixed #13774 -- Added models.Field.rel_db_type().

Alexander Sosnovskiy 9 år sedan
förälder
incheckning
b61eab18f7

+ 30 - 2
django/db/models/fields/__init__.py

@@ -626,6 +626,14 @@ class Field(RegisterLookupMixin):
         except KeyError:
             return None
 
+    def rel_db_type(self, connection):
+        """
+        Return the data type that a related field pointing to this field should
+        use. For example, this method is called by ForeignKey and OneToOneField
+        to determine its data type.
+        """
+        return self.db_type(connection)
+
     def db_parameters(self, connection):
         """
         Extension of db_type(), providing a range of different return
@@ -960,6 +968,9 @@ class AutoField(Field):
                 params={'value': value},
             )
 
+    def rel_db_type(self, connection):
+        return IntegerField().db_type(connection=connection)
+
     def validate(self, value, model_instance):
         pass
 
@@ -2072,7 +2083,24 @@ class NullBooleanField(Field):
         return super(NullBooleanField, self).formfield(**defaults)
 
 
-class PositiveIntegerField(IntegerField):
+class PositiveIntegerRelDbTypeMixin(object):
+
+    def rel_db_type(self, connection):
+        """
+        Return the data type that a related field pointing to this field should
+        use. In most cases, a foreign key pointing to a positive integer
+        primary key will have an integer column data type but some databases
+        (e.g. MySQL) have an unsigned integer type. In that case
+        (related_fields_match_type=True), the primary key should return its
+        db_type.
+        """
+        if connection.features.related_fields_match_type:
+            return self.db_type(connection)
+        else:
+            return IntegerField().db_type(connection=connection)
+
+
+class PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField):
     description = _("Positive integer")
 
     def get_internal_type(self):
@@ -2084,7 +2112,7 @@ class PositiveIntegerField(IntegerField):
         return super(PositiveIntegerField, self).formfield(**defaults)
 
 
-class PositiveSmallIntegerField(IntegerField):
+class PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField):
     description = _("Positive small integer")
 
     def get_internal_type(self):

+ 2 - 17
django/db/models/fields/related.py

@@ -18,10 +18,7 @@ from django.utils.functional import cached_property, curry
 from django.utils.translation import ugettext_lazy as _
 from django.utils.version import get_docs_version
 
-from . import (
-    AutoField, Field, IntegerField, PositiveIntegerField,
-    PositiveSmallIntegerField,
-)
+from . import Field
 from .related_descriptors import (
     ForwardManyToOneDescriptor, ManyToManyDescriptor,
     ReverseManyToOneDescriptor, ReverseOneToOneDescriptor,
@@ -935,19 +932,7 @@ class ForeignKey(ForeignObject):
         return super(ForeignKey, self).formfield(**defaults)
 
     def db_type(self, connection):
-        # The database column type of a ForeignKey is the column type
-        # of the field to which it points. An exception is if the ForeignKey
-        # points to an AutoField/PositiveIntegerField/PositiveSmallIntegerField,
-        # in which case the column type is simply that of an IntegerField.
-        # If the database needs similar types for key fields however, the only
-        # thing we can do is making AutoField an IntegerField.
-        rel_field = self.target_field
-        if (isinstance(rel_field, AutoField) or
-                (not connection.features.related_fields_match_type and
-                isinstance(rel_field, (PositiveIntegerField,
-                                       PositiveSmallIntegerField)))):
-            return IntegerField().db_type(connection=connection)
-        return rel_field.db_type(connection=connection)
+        return self.target_field.rel_db_type(connection=connection)
 
     def db_parameters(self, connection):
         return {"type": self.db_type(connection), "check": []}

+ 25 - 8
docs/howto/custom-model-fields.txt

@@ -374,14 +374,14 @@ For example::
             else:
                 return 'timestamp'
 
-The :meth:`~Field.db_type` method is called by Django when the framework
-constructs the ``CREATE TABLE`` statements for your application -- that is,
-when you first create your tables. It is also called when constructing a
-``WHERE`` clause that includes the model field -- that is, when you retrieve data
-using QuerySet methods like ``get()``, ``filter()``, and ``exclude()`` and have
-the model field as an argument. It's not called at any other time, so it can afford to
-execute slightly complex code, such as the ``connection.settings_dict`` check in
-the above example.
+The :meth:`~Field.db_type` and :meth:`~Field.rel_db_type` methods are called by
+Django when the framework constructs the ``CREATE TABLE`` statements for your
+application -- that is, when you first create your tables. The methods are also
+called when constructing a ``WHERE`` clause that includes the model field --
+that is, when you retrieve data using QuerySet methods like ``get()``,
+``filter()``, and ``exclude()`` and have the model field as an argument. They
+are not called at any other time, so it can afford to execute slightly complex
+code, such as the ``connection.settings_dict`` check in the above example.
 
 Some database column types accept parameters, such as ``CHAR(25)``, where the
 parameter ``25`` represents the maximum column length. In cases like these,
@@ -423,6 +423,23 @@ over this field. You are then responsible for creating the column in the right
 table in some other way, of course, but this gives you a way to tell Django to
 get out of the way.
 
+The :meth:`~Field.rel_db_type` method is called by fields such as ``ForeignKey``
+and ``OneToOneField`` that point to another field to determine their database
+column data types. For example, if you have an ``UnsignedAutoField``, you also
+need the foreign keys that point to that field to use the same data type::
+
+    # MySQL unsigned integer (range 0 to 4294967295).
+    class UnsignedAutoField(models.AutoField):
+        def db_type(self, connection):
+            return 'integer UNSIGNED AUTO_INCREMENT'
+
+        def rel_db_type(self, connection):
+            return 'integer UNSIGNED'
+
+.. versionadded:: 1.10
+
+    The :meth:`~Field.rel_db_type` method was added.
+
 .. _converting-values-to-python-objects:
 
 Converting values to Python objects

+ 12 - 1
docs/ref/models/fields.txt

@@ -1701,7 +1701,8 @@ Field API reference
 
         where the arguments are interpolated from the field's ``__dict__``.
 
-    To map a ``Field`` to a database-specific type, Django exposes two methods:
+    To map a ``Field`` to a database-specific type, Django exposes several
+    methods:
 
     .. method:: get_internal_type()
 
@@ -1717,6 +1718,16 @@ Field API reference
 
         See :ref:`custom-database-types` for usage in custom fields.
 
+    .. method:: rel_db_type(connection)
+
+        .. versionadded:: 1.10
+
+        Returns the database column data type for fields such as ``ForeignKey``
+        and ``OneToOneField`` that point to the :class:`Field`, taking
+        into account the ``connection``.
+
+        See :ref:`custom-database-types` for usage in custom fields.
+
     There are three main situations where Django needs to interact with the
     database backend and fields:
 

+ 4 - 0
docs/releases/1.10.txt

@@ -202,6 +202,10 @@ Models
   accessible as a descriptor on the proxied model class and may be referenced in
   queryset filtering.
 
+* The new :meth:`Field.rel_db_type() <django.db.models.Field.rel_db_type>`
+  method returns the database column data type for fields such as ``ForeignKey``
+  and ``OneToOneField`` that point to another field.
+
 * The :attr:`~django.db.models.Func.arity` class attribute is added to
   :class:`~django.db.models.Func`. This attribute can be used to set the number
   of arguments the function accepts.