这个 model reference 文档解释了如何使用Django的标准字段类-- CharField
, DateField
等等,对于许多目的来说,这些类就是您所需要的。不过,有时django版本无法满足您的精确要求,或者您希望使用与django附带的字段完全不同的字段。
Django的内置字段类型不包括所有可能的数据库列类型——只包括常见的类型,例如 VARCHAR
和 INTEGER
. 对于更模糊的列类型,例如地理多边形,甚至用户创建的类型,例如 PostgreSQL custom types ,您可以定义自己的django Field
子类。
或者,您可能有一个复杂的python对象,该对象可以通过某种方式序列化以适合标准数据库列类型。这是另一个例子 Field
子类将帮助您在模型中使用对象。
创建自定义字段需要注意细节。为了使事情更容易理解,我们将在整个文档中使用一个一致的示例:将一个代表卡片交易的python对象包装在 Bridge. 别担心,你不必知道如何打桥牌来遵循这个例子。你只需要知道52张牌是平均分配给4个玩家的,他们通常被称为 北 , east , 南方 和 west . 我们班看起来像这样:
class Hand:
"""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特定的内容。我们希望能够在我们的模型中做这样的事情(我们假设 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()
我们分配并从 hand
属性在我们的模型中与其他任何Python类一样。诀窍是告诉Django如何处理保存和加载这样的对象。
为了使用 Hand
在我们的模型中,我们 不 这门课要改了。这是理想的,因为这意味着您可以在无法更改源代码的地方轻松地为现有类编写模型支持。
备注
您可能只希望利用自定义数据库列类型,并将数据作为模型中的标准python类型来处理;例如字符串或浮点数。这个案子和我们的案子很相似 Hand
举例来说,我们将在进行过程中注意到任何差异。
让我们从模型字段开始。如果将其分解,模型字段提供了一种获取普通Python对象的方法——string、boolean, datetime
或者更复杂的东西,比如 Hand
--并将其转换为处理数据库时有用的格式。(这种格式对于序列化也很有用,但是我们稍后将看到,一旦控制了数据库端,这就更容易了)。
模型中的字段必须以某种方式转换为适合现有数据库列类型的字段。不同的数据库提供不同的有效列类型集,但规则仍然相同:这些是您必须使用的唯一类型。要存储在数据库中的任何内容都必须适合其中一种类型。
通常,您要么编写一个Django字段来匹配特定的数据库列类型,要么需要一种将数据转换为字符串的方法。
为我们的 Hand
例如,我们可以将卡数据转换为104个字符的字符串,方法是按照预定的顺序将所有卡连接在一起--也就是说,将所有 north 首先是纸牌,然后是 east , south 和 west 扑克牌。所以 Hand
可以将对象保存到数据库中的文本或字符列。
所有Django的领域(当我们说 领域 在本文档中,我们总是指模型字段,而不是 form fields )是的子类 django.db.models.Field
. Django记录的关于字段的大多数信息对于所有字段都是通用的——名称、帮助文本、唯一性等等。存储所有处理的信息 Field
. 我们会详细了解 Field
以后可以做;现在,只要说一切都是从 Field
然后定制类行为的关键部分。
重要的是要认识到django字段类不是存储在模型属性中的类。模型属性包含普通的python对象。在模型中定义的字段类实际上存储在 Meta
创建模型类时的类(如何完成此操作的精确细节在此处不重要)。这是因为在创建和修改属性时不需要字段类。相反,它们提供了在属性值和存储在数据库中或发送到 serializer .
在创建自己的自定义字段时请记住这一点。丹乔 Field
您编写的子类以各种方式提供了在Python实例和数据库/序列化程序值之间进行转换的机制(例如,存储值和使用查找值之间存在差异)。如果这听起来有点棘手,不要担心——在下面的示例中会更清楚。记住,当需要自定义字段时,通常会创建两个类:
第一个类是用户将要操作的python对象。他们将把它分配给模型属性,他们将从中读取以显示目的,类似的事情。这就是 Hand
我们的例子中的类。
第二类是 Field
子类。这个类知道如何在第一个类的永久存储窗体和Python窗体之间来回转换。
当你计划 Field
子类,首先考虑一下 Field
类您的新字段与最相似。你能把现有的django字段子类化并保存一些工作吗?如果不是,则应将 Field
类,从中派生出所有内容。
初始化新字段是将特定于您的案例的任何参数与常见参数分开,并将后者传递给 __init__()
方法 Field
(或您的父类)。
在我们的示例中,我们将调用我们的字段 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().__init__(*args, **kwargs)
我们的 HandField
接受大多数标准字段选项(见下面的列表),但我们确保它有固定的长度,因为它只需要保存52个卡值加上它们的大小;总共104个字符。
备注
Django的许多模型字段接受它们不做任何事情的选项。例如,您可以同时通过 editable
和 auto_now
到A django.db.models.DateField
它会忽略 editable
参数 (auto_now
被设定意味着 editable=False
)在这种情况下不会出现错误。
这种行为简化了字段类,因为它们不需要检查不必要的选项。它们将所有选项传递给父类,然后以后不再使用它们。这取决于您是希望您的字段对它们选择的选项更加严格,还是使用当前字段更为宽松的行为。
这个 Field.__init__()
方法采用以下参数:
name
rel
:用于相关字段(如 ForeignKey
)仅供高级使用。
serialize
如果 False
,当模型传递到Django时,该字段将不会序列化。 serializers .默认为 True
.
db_tablespace
:仅用于创建索引,如果后端支持 tablespaces . 您通常可以忽略此选项。
auto_created
: True
如果字段是自动创建的,那么 OneToOneField
由模型继承使用。仅供高级使用。
上面列表中没有解释的所有选项都具有与普通django字段相同的含义。见 field documentation 举例说明。
与你写下你的 __init__()
方法正在将 deconstruct()
方法。它被用在 model migrations 告诉Django如何获取新字段的实例并将其简化为序列化形式-特别是传递给哪些参数 __init__()
来重新创造它。
如果在继承的字段上没有添加任何额外选项,则无需编写新的 deconstruct()
方法。但是,如果您正在更改传入的参数 __init__()
就像我们在 HandField
,您需要补充正在传递的值。
deconstruct()
返回包含四项的元组:字段的属性名、字段类的完整导入路径、位置参数(以列表形式)和关键字参数(作为dict)。注意这与 deconstruct()
方法 for custom classes 它返回三个元素的元组。
作为自定义字段作者,您不需要关心前两个值;基础 Field
类具有计算字段的属性名和导入路径的所有代码。但是,您必须关心位置参数和关键字参数,因为这些参数可能是您正在更改的内容。
例如,在我们的 HandField
我们总是强制设置最大长度 __init__()
. 这个 deconstruct()
基础上的方法 Field
类将看到这个,并尝试在关键字参数中返回它;因此,为了可读性,我们可以从关键字参数中删除它:
from django.db import models
class HandField(models.Field):
def __init__(self, *args, **kwargs):
kwargs["max_length"] = 104
super().__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
del kwargs["max_length"]
return name, path, args, kwargs
如果添加新的关键字参数,则需要将代码写入 deconstruct()
把它的价值 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().__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
# Only include kwarg if it's not the default
if self.separator != ",":
kwargs["separator"] = self.separator
return name, path, args, kwargs
更复杂的示例超出了本文档的范围,但请记住-对于您的字段实例的任何配置, 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)
您可以覆盖 Field.non_db_attrs
若要自定义不影响列定义的字段属性,请执行以下操作。在模型迁移过程中使用它来检测无操作 AlterField
行动。
例如::
class CommaSepField(models.Field):
@property
def non_db_attrs(self):
return super().non_db_attrs + ("separator",)
您不能更改自定义字段的基类,因为Django不会检测到更改并为其进行迁移。例如,如果以以下开头:
class CustomCharField(models.CharField):
...
然后决定要使用 TextField
相反,您不能像这样更改子类:
class CustomCharField(models.TextField):
...
相反,您必须创建一个新的自定义字段类并更新模型以引用它:
class CustomCharField(models.CharField):
...
class CustomTextField(models.TextField):
...
正如在 removing fields ,必须保留原件 CustomCharField
类,只要有引用它的迁移。
和往常一样,您应该记录字段类型,以便用户知道它是什么。除了为其提供docstring(这对开发人员很有用),您还可以允许管理应用程序的用户通过 django.contrib.admindocs 应用程序。为此,请在 description
自定义字段的类属性。在上面的示例中,由 admindocs
申请 HandField
将是“一手牌(桥牌式)”。
在 django.contrib.admindocs
显示,字段描述用 field.__dict__
它允许描述包含字段的参数。例如,对 CharField
是::
description = _("String (up to %(max_length)s)")
一旦你创建了你的 Field
子类,根据字段的行为,您可能会考虑重写一些标准方法。下面列出的方法大致按重要性递减的顺序排列,因此从顶部开始。
假设您创建了一个PostgreSQL自定义类型 mytype
. 您可以子类 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.vendor
属性。当前内置的供应商名称为: sqlite
, postgresql
, mysql
,以及 oracle
。
例如::
class MyDateField(models.Field):
def db_type(self, connection):
if connection.vendor == "mysql":
return "datetime"
else:
return "timestamp"
这个 db_type()
和 rel_db_type()
Django在框架构造 CREATE TABLE
语句用于您的应用程序--即在您第一次创建表时。这些方法在构造 WHERE
子句,即当您使用QuerySet方法检索数据时,如 get()
, filter()
,以及 exclude()
并将模型字段作为参数。
某些数据库列类型接受参数,例如 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().__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设置,请返回 None
从 db_type()
. 这将导致Django的SQL创建代码跳过该字段。然后,您负责以其他方式在右表中创建列,但这为您提供了一种告诉Django不要挡路的方法。
这个 rel_db_type()
方法由以下字段调用 ForeignKey
和 OneToOneField
指向另一个字段以确定其数据库列数据类型。例如,如果您有 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"
如果你的习惯 Field
类处理比字符串、日期、整数或浮点更复杂的数据结构,然后可能需要重写 from_db_value()
和 to_python()
.
如果字段子类存在, from_db_value()
当从数据库加载数据时,将在所有情况下调用,包括聚合和 values()
调用。
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 gettext_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):
if value is None:
return value
return parse_hand(value)
def to_python(self, value):
if isinstance(value, Hand):
return value
if value is None:
return value
return parse_hand(value)
注意,我们总是返回 Hand
这些方法的实例。这是我们要存储在模型属性中的python对象类型。
为了 to_python()
,如果在值转换过程中出现任何错误,则应引发 ValidationError
例外。
因为使用数据库需要以两种方式进行转换,如果重写 from_db_value()
您还必须重写 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)]
)
警告
如果自定义字段使用 CHAR
, VARCHAR
或 TEXT
mysql的类型,必须确保 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().get_db_prep_value(value, connection, prepared)
if value is not None:
return connection.Database.Binary(value)
return value
如果自定义字段在保存时需要特殊转换,而该转换与用于普通查询参数的转换不同,则可以重写 get_db_prep_save()
.
如果要在保存前对值进行预处理,可以使用 pre_save()
. 例如,Django的 DateTimeField
在以下情况下使用此方法正确设置属性 auto_now
或 auto_now_add
.
如果确实要重写此方法,则必须在末尾返回属性的值。如果对值进行了任何更改,也应该更新模型的属性,这样保存对模型引用的代码将始终看到正确的值。
自定义 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().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之外的其他地方使用序列化程序输出,将描述性字符串作为序列化程序字段的类型是一个很有用的想法。
要自定义序列化程序序列化值的方式,可以重写 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油田(在 django/db/models/fields/__init__.py )以获取灵感。尝试找到一个与您想要的字段相似的字段,并对其进行一点扩展,而不是从头开始创建一个全新的字段。
放一个 __str__()
方法对要包装为字段的类。在很多地方字段代码的默认行为是调用 str()
论价值。(在本文件的示例中, value
将是一个 Hand
实例,而不是 HandField
)所以如果你 __str__()
方法自动转换为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
以支持特定类型的文件,因为它结合了上述所有技术。
尽可能缓存文件属性。由于文件可能存储在远程存储系统中,因此检索它们可能需要额外的时间,甚至金钱,这并非总是必要的。一旦检索到一个文件以获取关于其内容的一些数据,就尽可能缓存这些数据,以减少在随后调用该信息时必须检索该文件的次数。
12月 18, 2023