Django documentation

当前文档仅适用于 Django SVN 版本,与上个版本有显著不同。上个版本文档请查阅 Django 1.0

编写自定义 model 字段(Writing custom model fields)

这部分是在 Django 1.0 新增的: 请查看版本文档

介绍(Introduction)

model 参考(model reference) 文档已经介绍了如何使用 Django 的标准字段类;例如 CharFieldDateField,等等。对于很多应用来说,这些类足够用了。但是在某些情况下, 你所用的Django 版本不提供你想要的某些功能,或是你想使用的字段与 Django 自带字段不同。

Django 内置的字段类型并不能覆盖所有可能遇到的数据库的列类型,仅仅是些普通的字段类型,例如 VARCHARINTEGER。对于更多不常用的列类型,比如地理定位数据和诸如 PostgreSQL custom types 的自定义字段,你可以定义你自己的Django Field 子类。

有两种实现方式:你可以编写一个复杂的 Python 对象,让它以某种方式将数据序列化,以适应某个数据库的列类型;或是你创建一个 Field 子类,从而让你可以使用 model 中的对象。

示例对象(Our example object)

创建自定义字段需要注意很多细节。为了使这一章内容容易理解,自始至终我们都只使用这一个例子:包装一个 Python 对象来表示手中桥牌的详细信息。不用担心,这个例子并不要求你会玩桥牌。你只要知道 52 张牌被分配个四个玩家,按惯例,他们被称之为北(north), 东(east), 南(south)西(west)。我们的类看起来就象这个样子:

class Hand(object):
    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 类,我们不必为这个类做任何的改动。这是非常有用的,它表示着你可以很容易地为已存在的类编写 model 支持,而不必改动类的原代码。

注意

你可能只想利用自定义数据库列类型,将数据处理成 model 中的标准 Python 类型,比如:字符串,浮点数等等。这种情况与我们的 Hand 例子非常相似,我们随着文档的展开对两者的差异进行比较。

后台原理(Background theory)

数据库存储(Database storage)

可以简单的认为 model 字段提供了一种方法来接受普通的 Python 对象,比如布尔值,时间 datetime,或是象 Hand 这样更复杂的对象,然后在操作数据库时,对对象进行格式转换以适应数据库。(还有序列化也是同杰处理,但接下来我们会看到,一旦我们掌握了数据库这方面的转换,再对序列化做处理就游刃有余了)

为了让 model 字段适应数据库列类型,必须对字段进行某种形式的转换。虽然不同的数据库提供了不同的列类型,但是有一条准则不是变的:你只能使用数据库本身提供的这些字段。任何你想存储在数据库的信息必须适用于这些字段中的某一个。

通常情况下,你可以编写一个字段,让它匹配数据库的某个列类型;也可以用一种简单而直接方法对数据进行转换,比如说,转换成字符串。

对于我们的 Hand 例子,我们可以将纸牌数据转换成长度为104的字符串。转换方法是按出牌顺序,将纸牌编号连在一起,也就是说,先是所有的北(north)牌手的牌,再是东(east),然后是南(south),最后是西(west)。因为 Hand 对象可以保存在数据库中的文本或是字符字段中。

字段类做了哪些工作(What does a field class do?)

所有的 Django 字段fields (我们在这篇文档中谈到的 字段(fields) 指的都是 model 字段而不是 表单字段(form fields)) 都是继承自 django.db.models.Field。 所有字段的大部分信息都是相同的,例如 -- name, help text, uniqueness 等等。这部分信息都是由 Field 类处理的,后面我们会深入谈到 Field 是怎么做到这一点的;现在,只要知道一切都源于 Field 类就行了,然后就自定义类当中关键的行为。

要认识到 Django 类并不是存储在 model 的属性中,这一点非常重要。类属性包含的是普通的 Python 对象,而自定义的字段类是在被它创建的时候存储在 Meta 类中的(如何做到这一点的对于本章而言并不重要)。这是因为在创建或是修改属性时,并不需要字段类。相反,字段类提供了一套机制,来完成属性值与要保存到数据库中的值(或是被序列化 serializer 的值)之间的互换。

在创建自定义字段时要注意下面几点。 你编写的 Django Field 子类提供了一套机制,以某种方式来完成 Python 实例与数据库/序列器中的值的互换(例如,保存某个值和将它用做数据筛选是不同的)。这听起来有点棘手,但是接下来的例子会解释地比较清楚。在你编写自定义字段时,只要记住你通常要创建两个类就可以了:

  • 第一个类是使用者所使用的 Python 对象。使用者会根据它的命名了解到它的使用目的,并将它分配给类的属性,比如我们例子当中的 Hand 类。
  • 第二个类是 Field 的子类。它负责在永久保存形式和 Python 形式之间,对前面提到的第一个类进行转换。

编写一个 field 的子类(Writing a field subclass)

在准备编写你的 Field 子类时,首先要考虑的就是查找与新类相似的 Field 类,看看能否可以继承某个已存在的 Django 字段,从而节省工作量。如果不能这么做,你就应该继承 Field 类,它是一切行为的始祖。

初始化新字段的工作就是特定的参数从通用参数中分离出来,并将通用参数传递给 Field (就是你的父类)中的__init__() 方法。

在我们的例子中,将调用 HandField 字段。(使用 <Something>Field 的形式为你自定义的 Field 子类命名是一个好主意,因为这样很容易就被人理解成 Field 子类)。 因为没有任何类和我们的新类相似,所以我们就直接继承 Field

from django.db import models

class HandField(models.Field):
    def __init__(self, *args, **kwargs):
        kwargs['max_length'] = 104
        super(HandField, self).__init__(*args, **kwargs)

我们的 HandField 接受大多数标准的字段选项(见下面的列表),但是我们要确保它的长度固定,这是因为它需要容纳 52 张纸牌的数据,一共是104个字符。

注意

很多 Django 的 model 字段接受某些字段选项,而不做任何操作。例如,你可以将 editableauto_now 传递给一个 django.db.models.DateField 字段,但字段会忽略 editable 参数 (auto_now 会自动设置 editable=False)。 在这个过程中不会抛出错误。

这个行为简化了字段类,因为字段类无须再处理那些不必要的选项。它们只要将所有的选项传递给父类即可,无须再做处理。这取决于你是想让自定义字段严格地实现它所选的选项;还是想更简单点,对当前字段的行为更宽容一些。

__init__() 方法接收下面的参数:

上面这些加注解的选项,在自定义字段中所起的作用和在普通字段中一样。详见 field documentation

SubfieldBase 元类(The SubfieldBase metaclass)

我们在 介绍(introduction) 中提到,有两种原因使得我们需要字段子类:一是要使用自定义的数据库列类型;或是要处理复杂的 Python 类型。当然,也有可能是两者的组保。如果你只想使用自定义的数据库列类型,可以象使用从数据库中获得的标准 Python 类型一样来使用它,那么你不必读这一节。

如果你正在处理自定义的 Python 类型,比如我们的 Hand 类, Django 初始化我们的 model 实例并给自定义的字段属性分配一个要保存到数据库中的值,这时我们要将这个值转换成某个适用的 Python 对象。关于内部如何实现这些细节的,这有一点复杂。但是在 Field 类中要写的代码却是非溶点简单:确保你的字段子类使用了一个特别的元类(metaclass):

class django.db.models.SubfieldBase

例如:

class HandField(models.Field):
    __metaclass__ = models.SubfieldBase

    def __init__(self, *args, **kwargs):
        # ...

这段代码保证 to_python() 方法(会在接下来提到),会在初始化属性时被调用。

有用的方法(Useful methods)

一理你创建了 Field 子类并且编写了 __metaclass__ 时,你可能会根据你的字段行为,考虑去重写某些标准方法。下面的方法按重要性依次递减,所以让我们从头开始介绍。

定制数据库类型(Custom database types)

db_type(self)

根据 DATABASE_ENGINE 中定义的数据库设置,返回 Field 对应的数据库列类型。

假设你已经创建了一个 PostgreSQL 自定义类型,称为 mytype。你就可以通过继承 Field 和实现 db_type() 方法,在 Django 中使用这个字段:

from django.db import models

class MytypeField(models.Field):
    def db_type(self):
        return 'mytype'

一旦你有了 MytypeField,你就可以象使用其他 Field 类型一样,在 model 中使用它:

class Person(models.Model):
    name = models.CharField(max_length=80)
    gender = models.CharField(max_length=1)
    something_else = MytypeField()

如果你的目的建立一个数据库通用的应用,你就应该考虑不同数据库中列类型的差异。例如,PostgreSQL 中的日期/时间列类型被称为 timestamp,而同样的列在 MySQL 中被称为 datetime 。处理这个问题的最简单方法就是在 db_type() 方法中导入 Django 的设置模块,并检查 DATABASE_ENGINE 设置。例如:

class MyDateField(models.Field):
    def db_type(self):
        from django.conf import settings
        if settings.DATABASE_ENGINE == 'mysql':
            return 'datetime'
        else:
            return 'timestamp'

db_type() 方法仅仅被 Django 调用一次,就是在框架为你的应用生成 CREATE TABLE 语句的时候,这就是说,是在你第一次创建你的数据库这个时候,才会调用 db_type()。其他任何时候都不会调用这个方法,所以我们可以在它里面写一些很复杂的方法,比如上面的例子做 DATABASE_ENGINE 检测。

某些数据库列类型是允许有参数的,比如 CHAR(25)25 表示列数据最大的长度。在 model 中定义参数值要比在 db_type() 方法硬编码灵活的多。例如,做一个 CharMaxlength25Field 字段并没有多大意义:

# This is a silly example of hard-coded parameters.
class CharMaxlength25Field(models.Field):
    def db_type(self):
        return 'char(25)'

# In the model:
class MyModel(models.Model):
    # ...
    my_field = CharMaxlength25Field()

更好的方式是在运行时指定参数值-例如,在类实例化之时。在做到这一点,只要实现 django.db.models.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):
        return 'char(%s)' % self.max_length

# In the model:
class MyModel(models.Model):
    # ...
    my_field = BetterCharField(25)

最后,如果你的列在生成时需要很复杂的 SQL ,那就在 db_type() 中返回 None 。这会在 Django 在生成创建数据库代码时跳过这个字段。你需要以别的某种方式来创建这个字段对应的列,当然,这也告诉了你如何不使用 db_type() 这种方式来创建字段。

将数据库内容转换成 Python 对象(Converting database values to Python objects)

to_python(self, value)

将从数据库/序列器中取得的值转换成 Python 对象。

对于大我数应用来说,后台数据库返回格式正确的数据(比如字符串),默认的实现只返回 value

如果你的自定义 Field 类要处理比字符串,时间,整数或是浮点数更复杂的数据结构,你就需要重写这个方法。做为一般规则,该方法可以优雅地处理下列参数:

  • 一个合适的类型的实例 (例如,在我们的例子中就是 Hand )。
  • 一个字符串(例如,从反序列化中得到).
  • 数据根据你使用的列类型所返回的一切数据。

在我们的 HandField 类当中,我们将数据保存为数据库中的 VARCHAR 字段,所以我们需要在 to_python() 中处理字符串和 Hand 实例:

import re

class HandField(models.Field):
    # ...

    def to_python(self, value):
        if isinstance(value, Hand):
            return value

        # The string case.
        p1 = re.compile('.{26}')
        p2 = re.compile('..')
        args = [p2.findall(x) for x in p1.findall(value)]
        return Hand(*args)

要注意该方法总是返回一个 Hand 实例。这就是我们想保存在 model 属性中的 Python 对象。

牢记: 如果你的自定义字段需要 to_python() 方法在被创建时被调用,你应当使用早先提到的 The SubfieldBase metaclass,否则 to_python() 将不会被自动调用。

将 Python 对象转换成数据库的值(Converting Python objects to database values)

get_db_prep_value(self, value)

这是 to_python() 的反方法,用来将 Python 对象转换成数据库中的值或是序列化的值。 value 参数就是当前 model 的属性值。(字段中没有包含它的 modela 引用,所以它并不能获取自己的职。),该方法返回某种形式的数据,用来做为后台数据库查询中参数。

例如:

class HandField(models.Field):
    # ...

    def get_db_prep_value(self, value):
        return ''.join([''.join(l) for l in (value.north,
                value.east, value.south, value.west)])
get_db_prep_save(self, value)

和上面的方法一样,唯一的不同是该方法只在字段值必须被保存到数据库的时候才被调用。就象它的函式名 get_db_prep_value 所反映的那样,只有当字段值在保存时所做的转换与查询时有所不同的情况下,才使用该方法。

存储前对字段进行预处理(Preprocessing values before saving)

pre_save(self, model_instance, add)

在运行 get_db_prep_save() 之前,就会调用该方法,然后从 model_instance 为该字段返回相应的属性。属性名称在 self.attname (在 Field 中)。如果 model 第一次保存在数据库中, add 参数值就是 True,其他时候都是 False

如果你仅仅想在保存之存对数据库某种方式的预处理,只要重写该方法即可。例如,Django 的 DateTimeField 字段就利用该方法来正确的使用 auto_now 还是 auto_now_add

如果你要重写该方法,就必须在最后返回该属性的值。如果你改动了字段值,你也应该更新字段属性。所以对该 model 的引用始终都返回正确的值。

为在数据库筛选而做准备工作(Preparing values for use in database lookups)

get_db_prep_lookup(self, lookup_type, value)

如果 value 要在数据库筛选条件中使用 ( SQL 中的 WHERE 从句),就要提前进行准备工作。 lookup_type 得是可用的 Django 过滤器的筛选条件之一: exact, iexact, contains, icontains, gt, gte, lt, lte, in, startswith, istartswith, endswith, iendswith, range, year, month, day, isnull, search, regex, 和 iregex.

你的方法必须要为处理这些 lookup_type 值而做好准备,而且如果 value 是一个错误的类型(比如应该是一个对象,却用了一个列表),那就要抛出 ValueError 异常;或者该字段并不支持这种查询类型,就会抛出 TypeError 导常。对很多字段来说,你可以单独处理那些特殊的筛选条件,而将其他的交给父类的 get_db_prep_lookup() 方来来处理。

如果你要实现 get_db_prep_save(),通常你要先实现 get_db_prep_lookup()。 如果你不这么做, get_db_prep_value 就会调用默认地实现来管理 exact, gt, gte, lt, lte, inrange 筛选条件。

你也会想实现该方法来限制在自定义字段类型中所用到的筛选条件。

要注意,对 rangein 筛选而言, get_db_prep_lookup 将接收一个对象列表(可能是正确的类型) 并且需要将他们转换成适应数据库的列表。大多数情况下,你可以重用 get_db_prep_value(),或是至少重用一部分。

例如,下面的代码实现了 get_db_prep_lookup 来限制两种条件,而只使用 exactin :

class HandField(models.Field):
    # ...

    def get_db_prep_lookup(self, lookup_type, value):
        # We only handle 'exact' and 'in'. All others are errors.
        if lookup_type == 'exact':
            return [self.get_db_prep_value(value)]
        elif lookup_type == 'in':
            return [self.get_db_prep_value(v) for v in value]
        else:
            raise TypeError('Lookup type %r not supported.' % lookup_type)

为 model 字段指定表单字段(Specifying the form field for a model field)

formfield(self, form_class=forms.CharField, **kwargs)

在某字段在 model 中显示时,该方法返回字段默认的表单字段。我们通过 ModelForm 调用该方法。

所有的 kwargs 字典被直接传递给表单字段的 Field__init__() 方法。正常情况下,你所要做就是为 form_class 参数设置一个默认值,然后进一步委托给父类来处理。这可能要求你编写一个自定义字段(甚至可能是一个部件 widget)。 详见 表单文档(forms documentation) ,再参考一下 django.contrib.localflavor 中展示自定义部件的代码。

继续回到我们的例子中,我们可以编写一个 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 字段类 (它有默认的部件)。 这个文档并不涉及编写编写自定义表单字段的细节。

模仿内置的字段类型(Emulating built-in field types)

get_internal_type(self)

根据给定的在数据库级别的模仿的 Field 子类的名称,而得到一个字符串。该方法用来确定简单情况下的数据库列类型。

如果你已经创建了一个 db_type() 方法,那你就不必担心 get_internal_type() -- 它不会被使用了。但是有时,你所需的数据库存储的类型与其他字段相似,所以你要使用其他字段的逻辑来创建正确的列。

例如:

class HandField(models.Field):
    # ...

    def get_internal_type(self):
        return 'CharField'

无论我们使用何种数据库后台,都是使用 syncdb 和其他 SQL 命令为保存字符来创建正确的字段的。

针对你所用的数据库后台,如果 get_internal_type() 返回了一个 Djanog 不知如何确认存储类型的字符串 -- 也就是说,它并没有出现在 django.db.backends.<db_name>.creation.DATA_TYPES 中 -- 那么这么字符串仍会被序列器所用,但默认的 db_type() 方法将返回 None。 详见 db_type() ,了解为什么这么做的原因。将一个描述性的字符发给序列化器做为字段的类型,是一个很好的主意。这样不仅仅是在 Django 中,也可以在其他地方使用序列化器。

对字段数据进行序列化(Converting field data for serialization)

value_to_string(self, obj)

由序列化器使用该方法将字段值转换成用出输出的字符串。调用 Field._get_val_from_obj(obj)() 是得到序例化值的最佳方法。例如,因为我们的 HandField 使用了字符串来存储数据,所以我们可以重用已有的转换代码:

class HandField(models.Field):
    # ...

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_db_prep_value(value)

一些常用的意建(Some general advice)

编写自定义字段并不是一个很简单的事情,特别是在你需要在 Python 类型和数据库以及序列化格式进行转换的情况下。以下有两点意建会让这个过程变得顺利一些:

  1. 查看已有的 Django fields (在django/db/models/fields/__init__.py) 找找灵感。尽量找一个与你想要的字段非常类似,但是略有不同字段,而避免完全从头开始写一个全新的字段。
  2. 在你要包装成字段的类当中,添加 __str__()__unicode__() 方法。字段代码中有很多默认行为要对字段值使用用 force_unicode() 方法。(在我们的例子中, value 就是一个 Hand 实例,而不是一个 HandField)。所以如果你用 __unicode__() 方法自动将 Python 对象转换为字符串,那么就会节省很多工作。

编写文件字段子类(Writing a FileField subclass)

做为上述方法的补充,用来处理文件的字段与其他字段有所不同,有一些必须考虑到的特殊要求。FileField 提供了大多数机制,比如,控制数据库存储和读取,这些机制可以保持不变,而让子类去处理支持特殊文件类型的需求。

Django 提供了 File 类,它做为一个访问文件和进行文件操作的代理使用。它可以被继承,从而自定义子类如何访问文件,也定义了哪些方法在子类中可用。它的代码在 django.db.models.fields.files,关于该字段默认的行为在 文件操作文档(file documentation)有详细介绍。

一旦继承 File 的子类被创建,新的 FileField 子类必须得使用父类。要做到这一点,简单将新的 File 子类分配给 FileField 子类的 attr_class 属性即可。

一些建议(A few suggestions)

除了上述内容,这还有一些建议,可以大大提高编写代码的效率,改善代码的可读性。

  1. Django 自带的 ImageField ( django/db/models/fields/files.py) 是一个学习如何继承 FileField 来支持某个特殊文件类型的极好例子,因为它包含了我们上面提到的所有技术。
  2. 尽可能的缓存文件属性,这是因为文件一般是存储在远程文件系统中的,检索他们要花费不少的时间,甚至是金钱,而这些代价并不是必要的。一旦检索某个文件来获得文件内容的某些数据,就尽可能的缓存数据,从而减少其后再次获取数据时的检索文件的次数。

Questions/Feedback

Having trouble? We'd like to help!