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

This commit is contained in:
Alexander Sosnovskiy 2015-11-13 10:56:10 +03:00 committed by Tim Graham
parent 0e7d59df3e
commit b61eab18f7
5 changed files with 73 additions and 28 deletions

View File

@ -626,6 +626,14 @@ class Field(RegisterLookupMixin):
except KeyError: except KeyError:
return None 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): def db_parameters(self, connection):
""" """
Extension of db_type(), providing a range of different return Extension of db_type(), providing a range of different return
@ -960,6 +968,9 @@ class AutoField(Field):
params={'value': value}, params={'value': value},
) )
def rel_db_type(self, connection):
return IntegerField().db_type(connection=connection)
def validate(self, value, model_instance): def validate(self, value, model_instance):
pass pass
@ -2072,7 +2083,24 @@ class NullBooleanField(Field):
return super(NullBooleanField, self).formfield(**defaults) 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") description = _("Positive integer")
def get_internal_type(self): def get_internal_type(self):
@ -2084,7 +2112,7 @@ class PositiveIntegerField(IntegerField):
return super(PositiveIntegerField, self).formfield(**defaults) return super(PositiveIntegerField, self).formfield(**defaults)
class PositiveSmallIntegerField(IntegerField): class PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField):
description = _("Positive small integer") description = _("Positive small integer")
def get_internal_type(self): def get_internal_type(self):

View File

@ -18,10 +18,7 @@ from django.utils.functional import cached_property, curry
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.version import get_docs_version from django.utils.version import get_docs_version
from . import ( from . import Field
AutoField, Field, IntegerField, PositiveIntegerField,
PositiveSmallIntegerField,
)
from .related_descriptors import ( from .related_descriptors import (
ForwardManyToOneDescriptor, ManyToManyDescriptor, ForwardManyToOneDescriptor, ManyToManyDescriptor,
ReverseManyToOneDescriptor, ReverseOneToOneDescriptor, ReverseManyToOneDescriptor, ReverseOneToOneDescriptor,
@ -935,19 +932,7 @@ class ForeignKey(ForeignObject):
return super(ForeignKey, self).formfield(**defaults) return super(ForeignKey, self).formfield(**defaults)
def db_type(self, connection): def db_type(self, connection):
# The database column type of a ForeignKey is the column type return self.target_field.rel_db_type(connection=connection)
# 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)
def db_parameters(self, connection): def db_parameters(self, connection):
return {"type": self.db_type(connection), "check": []} return {"type": self.db_type(connection), "check": []}

View File

@ -374,14 +374,14 @@ For example::
else: else:
return 'timestamp' return 'timestamp'
The :meth:`~Field.db_type` method is called by Django when the framework The :meth:`~Field.db_type` and :meth:`~Field.rel_db_type` methods are called by
constructs the ``CREATE TABLE`` statements for your application -- that is, Django when the framework constructs the ``CREATE TABLE`` statements for your
when you first create your tables. It is also called when constructing a application -- that is, when you first create your tables. The methods are also
``WHERE`` clause that includes the model field -- that is, when you retrieve data called when constructing a ``WHERE`` clause that includes the model field --
using QuerySet methods like ``get()``, ``filter()``, and ``exclude()`` and have that is, when you retrieve data using QuerySet methods like ``get()``,
the model field as an argument. It's not called at any other time, so it can afford to ``filter()``, and ``exclude()`` and have the model field as an argument. They
execute slightly complex code, such as the ``connection.settings_dict`` check in are not called at any other time, so it can afford to execute slightly complex
the above example. code, such as the ``connection.settings_dict`` check in the above example.
Some database column types accept parameters, such as ``CHAR(25)``, where the Some database column types accept parameters, such as ``CHAR(25)``, where the
parameter ``25`` represents the maximum column length. In cases like these, 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 table in some other way, of course, but this gives you a way to tell Django to
get out of the way. 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:
Converting values to Python objects Converting values to Python objects

View File

@ -1701,7 +1701,8 @@ Field API reference
where the arguments are interpolated from the field's ``__dict__``. 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() .. method:: get_internal_type()
@ -1717,6 +1718,16 @@ Field API reference
See :ref:`custom-database-types` for usage in custom fields. 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 There are three main situations where Django needs to interact with the
database backend and fields: database backend and fields:

View File

@ -202,6 +202,10 @@ Models
accessible as a descriptor on the proxied model class and may be referenced in accessible as a descriptor on the proxied model class and may be referenced in
queryset filtering. 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 * 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 :class:`~django.db.models.Func`. This attribute can be used to set the number
of arguments the function accepts. of arguments the function accepts.