model reference文档已经介绍了如何使用 Django 的标准字段类;例如 CharField
, DateField
,等等。 对于很多应用来说,这些类足够用了。 但是在某些情况下, 你所用的Django 不具备你需要的某些精巧功能,或是你想使用的字段与 Django 自带字段完全不同。
Django 内置的字段类型并不能覆盖所有可能遇到的数据库的列类型,仅仅是些普通的字段类型,例如VARCHAR
和INTEGER
。 对于更多不常用的列类型,比如地理定位数据和诸如PostgreSQL自定义类型的自定义字段,你可以定义你自己的Django Field
子类。
有两种实现方式:你可以编写一个复杂的 Python 对象,让它以某种方式将数据序列化,以适应某个数据库的列类型; 或是你创建一个Field
的子类,从而让你可以使用 model 中的对象。
创建自定义字段需要注意很多细节。 为了使这一章内容容易理解,自始至终我们都只使用这一个例子:包装一个 Python 对象来表示手中桥牌的详细信息。 不用担心,这个例子并不要求你会玩桥牌。 你只要知道 52 张牌被平均分配给四个玩家,按惯例,他们被称之为北, 东, 南 和 西。 我们的类看起来就象这个样子:
class Hand(object):
"""A hand of cards (bridge style)"""
def __init__(self, north, east, south, west):
# Input parameters are lists of cards ('Ah', '9s', etc.)
self.north = north
self.east = east
self.south = south
self.west = west
# ... (other possibly useful methods omitted) ...
这只是一个普通的 Python 类,并没有对 Django 做特别的设定。
在 model 中我们可以象下面这样使用 Hand (我们假设 model 中的 hand
属性是 Hand
类的一个实例):
example = MyModel.objects.get(pk=1)
print(example.hand.north)
new_hand = Hand(north, east, south, west)
example.hand = new_hand
example.save()
我们就象使用任何 Python 类一样,对 model 中的 hand
属性进行赋值和取值。 利用这一点让 Django 知道如何处理保存和载入一个对象。
为了在 model 中使用 Hand
类,我们不必为这个类做任何的改动。 这是非常有用的,它表示着你可以很容易地为已存在的类编写模型支持,而不必改动类的原代码。
注
您可能只想利用自定义数据库列类型,并在模型中处理数据作为标准Python类型;字符串或浮点数。 这种情况与我们的 Hand
例子非常相似,我们随着文档的展开对两者的差异进行比较。
可以简单的认为 model 字段提供了一种方法来接受普通的 Python 对象,比如布尔值,datetime
,或是象Hand
这样更复杂的对象,然后在操作数据库时,对对象进行格式转换以适应数据库。(还有序列化也是同样处理,但接下来我们会看到,一旦我们掌握了数据库这方面的转换,再对序列化做处理就游刃有余了)
模型中的字段必须以某种方式转化为现有的数据库字段类型。 不同的数据库提供了不同的有效的列类型的集合,但是规则仍然是相同的:这些是唯一工作类型。 任何你想存储在数据库中必须适应这些类型之一。
通常情况下,你可以写一个Django字段来匹配特定的数据库行的类型,或有一个相当直接的的方式将你的数据转化为一个字符串。
以我们的 Hand
为例,我们可以将卡片的数据以预先决定好的顺序,连接转化为一个104个字符的字符串– 也就是说, north 卡片排在第一, 然后是 east, south 和 west 。 所以Hand
对象可以在数据库中以 text或者character 的columns的形式储存
所有Django的字段(当我们在本文档中提到字段时,我们总是指模型字段,而不是指form fields)是django.db.models.Field
。 Django记录有关字段的大多数信息对于所有字段都是通用的 - 名称,帮助文本,唯一性等。 由Field
处理所有存储的信息。 我们将详细介绍Field
稍后可以做的详细信息;现在只要说一切都从Field
下降,然后自定义类行为的关键部分。
重要的是要意识到Django字段类不是你的model的属性。 模型属性包含普通的Python对象。 当创建模型类时,在模型中定义的字段类实际上存储在Meta
类中(此处的具体细节不重要)。 这是因为当你只是创建和修改属性时,字段类不是必需的。 相反,它们提供了在属性值和存储在数据库中或发送到serializer之间进行转换的机制。
创建自己的自定义字段时请记住这一点。 您编写的Django Field
子类提供了以各种方式在Python实例和数据库/序列化器值之间进行转换的机制(例如,在存储值和使用值之间存在差异)。 如果这听起来有点棘手,不要担心 - 这将变得更清楚在下面的例子。 只要记住,当你想要一个自定义字段时,你最终会创建两个类:
Hand
类。Field
子类。 这是知道如何在其永久存储形式和Python表单之间来回转换你的第一个类的类。在规划Field
子类时,请先考虑您的新字段与之最相似的现有Field
类。 你可以继承一个现有的Django字段并保存自己一些工作吗? 如果没有,你应该继承Field
类,所有类都从中继承。
初始化您的新字段是一个问题,从您的案例中分离出任何特定于常见参数的参数,并将后者传递到Field
的__init__()
在我们的示例中,我们将调用字段HandField
。 (最好调用Field
子类<Something>Field
,因此很容易被识别为Field
子类)。 它不像任何现有字段,因此我们将直接从Field
子类化:
from django.db import models
class HandField(models.Field):
description = "A hand of cards (bridge style)"
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 104
super(HandField, self).__init__(*args, **kwargs)
我们的HandField
接受大多数标准字段选项(见下面的列表),但是我们确保它有一个固定的长度,因为它只需要持有52个卡值加上它们的套装;总共104个字符
注
许多Django的模型字段接受他们不做任何事情的选项。 例如,您可以将editable
和auto_now
同时传递到django.db.models.DateField
,它将忽略editable
参数(auto_now
被设置意味着editable=False
)。 在这种情况下不会出现错误。
此行为简化了字段类,因为它们不需要检查不必要的选项。 它们只是将所有选项传递给父类,然后不再使用它们。 这取决于你是否希望字段对他们选择的选项更加严格,或者使用当前字段的更简单,更宽松的行为。
Field.__init__()
方法采用以下参数:
verbose_name
名称
primary_key(关键字)
max_length(最大长度)
unique(唯一性)
blank(空白)
null(空值)
db_index(数据库(创建)索引)
rel
:用于相关字段(如ForeignKey
)。 仅供高级使用。default(默认值)
editable(可编辑)
serialize
:如果False
,当模型传递给Django的serializers时,字段不会被序列化。 默认为True
。unique_for_date
unique_for_month
unique_for_year
choices(选择)
help_text
db_column
db_tablespace
:仅用于创建索引(如果后端支持tablespaces)。 您通常可以忽略此选项。auto_created
:True
如果自动创建字段,就像模型继承使用的OneToOneField
。 仅供高级使用。在上面列表中没有解释的所有选项具有与正常Django字段相同的含义。 有关示例和详细信息,请参阅field documentation。
写入__init__()
方法的目的是写入deconstruct()
方法。 这个方法告诉Django如何获取一个新字段的实例,并将其减少为序列化形式 - 特别是要传递给__init__()
的参数以重新创建它。
如果您没有在继承域的顶部添加任何额外的选项,则无需编写新的deconstruct()
方法。 但是,如果您正在更改在__init__()
中传递的参数(就像我们在HandField
),则需要补充正在传递的值。
deconstruct()
的契约很简单;它返回一个四项的元组:字段的属性名称,字段类的完整导入路径,位置参数(作为列表)和关键字参数(作为dict)。 注意,这不同于自定义类的deconstruct()
方法for custom classes
作为自定义领域的作者,您不需要关心前两个值;基础Field
类具有所有的代码来计算字段的属性名称和导入路径。 然而,你必须关心位置和关键字参数,因为这些可能是你正在改变的事情。
例如,在我们的HandField
类中,我们总是强制在__init__()
中设置max_length。 基础Field
类的deconstruct()
方法将看到这一点,并尝试将其返回到关键字参数中;因此,我们可以从关键字参数中删除它,以便可读性:
from django.db import models
class HandField(models.Field):
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 104
super(HandField, self).__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super(HandField, self).deconstruct()
del kwargs["max_length"]
return name, path, args, kwargs
如果您添加了一个新的关键字参数,则需要编写代码以将其值自动添加到kwargs
中:
from django.db import models
class CommaSepField(models.Field):
"Implements comma-separated storage of lists"
def __init__(self, separator=",", *args, **kwargs):
self.separator = separator
super(CommaSepField, self).__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super(CommaSepField, self).deconstruct()
# Only include kwarg if it's not the default
if self.separator != ",":
kwargs['separator'] = self.separator
return name, path, args, kwargs
更复杂的示例超出了本文档的范围,但请记住 - 对于Field实例的任何配置,deconstruct()
必须返回可以传递到__init__
那个状态。
如果您在Field
超类中设置参数的新默认值,请特别注意;你想确保它们总是包含在内,而不是消失,如果它们承担了旧的默认值。
另外,尽量避免返回值作为位置参数;在可能的情况下,返回值作为关键字参数,以实现最大的未来兼容性 当然,如果你改变事物的名字比它们在构造函数的参数列表中的位置更多,你可能更喜欢位置,但是请记住,人们将从序列化版本重构你的领域一段时间(可能是几年)这取决于你的迁移活多久。
您可以通过查看包含字段的迁移来查看解构的结果,您可以通过解构和重建字段来测试单元测试中的解构:
name, path, args, kwargs = my_field_instance.deconstruct()
new_instance = MyField(*args, **kwargs)
self.assertEqual(my_field_instance.some_attribute, new_instance.some_attribute)
您不能更改自定义字段的基类,因为Django将无法检测到该更改并进行迁移。 例如,如果你开始:
class CustomCharField(models.CharField):
...
然后决定你想使用TextField
,你不能像这样改变子类:
class CustomCharField(models.TextField):
...
相反,您必须创建一个新的自定义字段类并更新您的模型以引用它:
class CustomCharField(models.CharField):
...
class CustomTextField(models.TextField):
...
如removing fields中所述,您必须保留原始的CustomCharField
类,只要您具有引用它的迁移即可。
和往常一样,你应该记录你的字段类型,让用户知道它是什么。
除了为开发人员提供文档字符串外,您还可以允许管理应用程序的用户通过django.contrib.admindocs应用程序查看字段类型的简短说明。 为此,只需在自定义字段的description
类属性中提供描述性文本即可。 在上面的示例中,admindocs
应用程序为HandField
显示的描述将是“A手牌(桥牌)”。
在django.contrib.admindocs
显示中,字段描述由field.__dict__
,允许描述包含字段的参数。 例如,CharField
的描述是:
description = _("String (up to %(max_length)s)")
创建Field
子类后,您可能会考虑覆盖几个标准方法,具体取决于字段的行为。
下面的方法列表大约是重要性的降序,所以从顶部开始。
假设您创建了一个名为mytype
的PostgreSQL自定义类型。 您可以子类化Field
并实现db_type()
方法,如下所示:
from django.db import models
class MytypeField(models.Field):
def db_type(self, connection):
return 'mytype'
一旦您拥有MytypeField
,就可以在任何模型中使用它,就像任何其他Field
类型:
class Person(models.Model):
name = models.CharField(max_length=80)
something_else = MytypeField()
如果您打算构建一个不依赖于数据库的应用程序,则应考虑数据库列类型的差异。 例如,PostgreSQL中的日期/时间列类型称为timestamp
,而MySQL中的相同列称为datetime
。 在db_type()
方法中处理此问题的最简单的方法是检查connection.settings_dict['ENGINE']
属性。
像这样:
class MyDateField(models.Field):
def db_type(self, connection):
if connection.settings_dict['ENGINE'] == 'django.db.backends.mysql':
return 'datetime'
else:
return 'timestamp'
当框架构造CREATE TABLE t8时,Django会调用
您的应用程序的语句 - 也就是您首次创建表时。 当构造包含模型字段的db_type()
和rel_db_type()
WHERE
子句时,也会调用这些方法 - 也就是使用诸如get()
,filter()
和exclude()
,并将model字段作为参数。 它们在任何其他时间都不被调用,所以它可以执行稍微复杂的代码,例如上面的例子中的connection.settings_dict
。
某些数据库列类型接受参数,例如CHAR(25)
,其中参数25
表示最大列长度。 在这种情况下,如果在模型中指定参数,而不是在db_type()
方法中硬编码,则它更灵活。 例如,使用CharMaxlength25Field
没有什么意义,如下所示:
# This is a silly example of hard-coded parameters.
class CharMaxlength25Field(models.Field):
def db_type(self, connection):
return 'char(25)'
# In the model:
class MyModel(models.Model):
# ...
my_field = CharMaxlength25Field()
这样做的更好的方法是使参数在运行时可指定 - 即当类被实例化时。 要这样做,只需实现Field.__init__()
,像这样:
# This is a much more flexible example.
class BetterCharField(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length(最大长度) = max_length(最大长度)
super(BetterCharField, self).__init__(*args, **kwargs)
def db_type(self, connection):
return 'char(%s)' % self.max_length(最大长度)
# In the model:
class MyModel(models.Model):
# ...
my_field = BetterCharField(25)
最后,如果您的列需要真正复杂的SQL设置,请从db_type()
返回None
。 这将导致Django的SQL创建代码跳过此字段。 然后,您将负责以某种其他方式在正确的表中创建列,当然,但是这让你有一种方法来告诉Django脱离方式。
The 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. 例如,如果您有UnsignedAutoField
,则还需要指向该字段的外键才能使用相同的数据类型:
# 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'
添加了rel_db_type()
方法。
如果您的自定义Field
类处理比字符串,日期,整数或浮点数更复杂的数据结构,则可能需要覆盖from_db_value()
和to_python()
。
如果存在于字段子类,则在从数据库加载数据(包括在聚合和values()
调用)的所有情况下将调用from_db_value()
。
to_python()
通过反序列化和从表单使用的clean()
方法调用。
作为一般规则,to_python()
应优雅地处理以下任何参数:
Hand
)。None
(如果该字段允许null=True
)在我们 HandField
类种,我们使用 VARCHAR 域在数据库中存储我们的数据, 因此我们需要在None
函数中有能力处理字符串跟 from_db_value()
值。 在to_python()
中,我们还需要处理Hand
实例:
import re
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import ugettext_lazy as _
def parse_hand(hand_string):
"""Takes a string of cards and splits into a full hand."""
p1 = re.compile('.{26}')
p2 = re.compile('..')
args = [p2.findall(x) for x in p1.findall(hand_string)]
if len(args) != 4:
raise ValidationError(_("Invalid input for a Hand instance"))
return Hand(*args)
class HandField(models.Field):
# ...
def from_db_value(self, value, expression, connection, context):
if value is 没有:
return value
return parse_hand(value)
def to_python(self, value):
if isinstance(value, Hand):
return value
if value is 没有:
return value
return parse_hand(value)
注意,我们总是从这些方法返回Hand
实例。 这是我们要存储在模型属性中的Python对象类型。
对于to_python()
,如果在值转换期间出现任何错误,您应该引发一个ValidationError
异常。
由于使用数据库需要以两种方式进行转换,因此如果覆盖to_python()
,您还必须重写get_prep_value()
将Python对象转换回查询值。
像这样:
class HandField(models.Field):
# ...
def get_prep_value(self, value):
return ''.join([''.join(l) for l in (value.north,
value.east, value.south, value.west)])
警告
如果您的自定义字段使用MySQL的CHAR
,VARCHAR
或TEXT
类型,则必须确保get_prep_value()
当对这些类型执行查询并且提供的值是整数时,MySQL执行灵活和意外匹配,这可能导致查询在其结果中包含意外的对象。 如果您始终从get_prep_value()
返回字符串类型,则不会发生此问题。
某些数据类型(例如,日期)需要采用特定格式,才能供数据库后端使用。
get_db_prep_value()
是应进行这些转换的方法。 将用于查询的特定连接作为connection
参数传递。 这允许您使用后端特定的转换逻辑(如果需要)。
例如,Django对其BinaryField
使用以下方法:
def get_db_prep_value(self, value, connection, prepared=False):
value = super(BinaryField, self).get_db_prep_value(value, connection, prepared)
if value is not 没有:
return connection.Database.Binary(value)
return value
如果您的自定义字段在保存时需要进行特殊转换,但与用于常规查询参数的转换不同,则可以覆盖get_db_prep_save()
。
如果要在保存之前预处理值,可以使用pre_save()
。 例如,在auto_now
或auto_now_add
的情况下,Django的DateTimeField
使用此方法正确设置属性。
如果您覆盖此方法,则必须在结尾返回属性的值。 如果对值进行任何更改,您还应该更新模型的属性,以便保存对模型的引用的代码将始终看到正确的值。
要自定义ModelForm
使用的表单字段,您可以覆盖formfield()
。
表单域类可以通过form_class
和choices_form_class
参数指定;如果字段有指定的选择,则使用后者,否则为前者。 如果不提供这些参数,将使用CharField
或TypedChoiceField
。
所有kwargs
字典都直接传递到表单字段的__init__()
方法。 通常,你需要做的是为form_class
(也许choices_form_class
)参数设置好的默认值,然后委托进一步处理到父类。 这可能需要您编写自定义表单字段(甚至是窗体小部件)。 有关此信息,请参阅forms documentation。
继续我们正在进行的示例,我们可以将formfield()
方法写为:
class HandField(models.Field):
# ...
def formfield(self, **kwargs):
# This is a fairly standard way to set up some defaults
# while letting the caller override them.
defaults = {'form_class': MyFormField}
defaults.update(kwargs)
return super(HandField, self).formfield(**defaults)
这假设我们已经导入了一个MyFormField
字段类(它有自己的默认小部件)。 本文档不包括编写自定义表单字段的详细信息。
如果您创建了db_type()
方法,则不需要担心get_internal_type()
- 它不会被使用太多。 但有时,您的数据库存储在类型上与其他字段类似,因此您可以使用其他字段的逻辑来创建正确的列。
像这样:
class HandField(models.Field):
# ...
def get_internal_type(self):
return 'CharField'
不管我们使用哪个数据库后端,这将意味着migrate
和其他SQL命令创建用于存储字符串的正确的列类型。
如果get_internal_type()
返回Django对于您正在使用的数据库后端不知道的字符串,也就是说,它不会出现在django.db.backends.<db_name>.base.DatabaseWrapper.data_types
- 串行程序仍然使用字符串,但默认的db_type()
方法将返回None
。 有关这可能有用的原因,请参见db_type()
的文档。 如果你要在Django之外的其他地方使用serializer输出,那么将描述性字符串作为序列化字段的字段类型是一个有用的想法。
要自定义序列化程序如何序列化值,您可以覆盖value_to_string()
。 使用value_from_object()
是在序列化之前获取字段值的最佳方式。 例如,由于我们的HandField
使用字符串作为其数据存储,我们可以重用一些现有的转换代码:
class HandField(models.Field):
# ...
def value_to_string(self, obj):
value = self.value_from_object(obj)
return self.get_prep_value(value)
编写自定义字段可能是一个棘手的过程,特别是如果您在Python类型与数据库和序列化格式之间进行复杂的转换。 这里有几个提示,使事情进行得更顺利:
django/db/models/fields/__init__.py
)获取灵感。 尝试找到一个类似于你想要的字段,并扩展它一点,而不是从头创建一个全新的字段。__str__()
(__unicode__()
在Python 2)方法放在类中作为一个字段。 有很多地方的字段代码的默认行为是调用force_text()
上的值。 (在本文档的示例中,value
将是Hand
实例,而不是HandField
)。 如果你的 __str__()
方法 ( Python 2使用__unicode__()
) 会自动把你的Python对象转换成字符串, 你可以节约很多时间。FileField
子类¶除了上述方法,处理文件的字段还有一些其他特殊要求,必须考虑到。 FileField
提供的大多数机制(例如控制数据库存储和检索)可以保持不变,使子类处理支持特定类型文件的挑战。
Django提供了一个File
类,用作文件内容和操作的代理。 这可以被子类化以自定义如何访问文件,以及可用的方法。 它位于django.db.models.fields.files
,其默认行为在file documentation中解释。
一旦创建了File
的子类,就必须告诉新的FileField
子类使用它。 为此,只需将新的File
子类分配给attr_class
子类的特殊FileField
属性。
除了上面的细节,还有一些指南可以大大提高字段代码的效率和可读性。
ImageField
(在django/db/models/fields/files.py
)的源代码是一个很好的例子来说明如何子类化FileField
以支持特定类型的文件,因为其包含上述所有技术。2017年9月6日