Django模型中最重要的、并且也是唯一必须执行的工作就是字段定义。字段在类中进行定义,对应于实体数据库的字段。另外,定义模型字段名时为了避免冲突,不建议使用模型API中已定义的关键字。
字段类型用以指定数据库的数据类型,比如:INTEGER、VARCHAR和TEXT这几种比较常用的数据类型。在Django模型定义中,字段类型均派生自Field类的实例。Django框架的中Field类是一个抽象类,专门用于定义数据库表的列表项。
Django模型一共内置了多种字段类型,基本能够满足一般应用的设计需求。Django模型的主要字段类型说明如下:
● AutoField:自动增加的Integer类型。一般情况下,AutoField类型不能直接使用,会作为主键被自动添加到模型中。
● BigAutoField:类似AutoField类型,一个自动增加的长Integer(64-bit)类型(1~9223372036854775807)。
● IntegerField:Integer类型(-2147483648~2147483647)。
● BigIntegerField:长Integer类型(-9223372036854775808~9223372036854775807)。
● SmallIntegerField:Small Integer类型(-32768~32767)。
● BinaryField:用来存储二进制数据的类型。
● BooleanField:用来存储布尔值(True / False)的类型。
● NullBooleanField:类似BooleanField(null=True)类型。
● FloatField:用来存储浮点型数据的类型,表示Python语言中的float实例。
● CharField:用来存储字符串的类型。CharField类型必须额外定义一个表示最大长度的参数:CharField.max_length。
● DateField:用来存储日期的类型,表示Python语言中的datetime.date实例。
● DateTimeField;用来存储日期和时间的类型,表示Python语言中的datetime.datetime实例。
● TimeField:用来存储时间的类型,表示Python语言中的datetime.time实例。
● DecimalField:用来存储十进制数值的类型,表示Python语言中的Decimal实例。
● DurationField:用来存储时间间隔的类型,表示Python语言中的timedelta。
● EmailField:CharField类型,用于表示电子邮件类型。
● FileField:用于文件上传类型。该类型需要定义两个必选参数:FileField.upload_to和FileField.storage。其中,FileField.upload_to参数表示存储路径,FileField.storage参数表示存储对象。
● TextField:用于文本类型,在表单域中默认是使用Textarea小部件(Widget)。
● ImageField:用来存储Image文件的类型,继承自FileField类型。该类型需要定义两个必选参数:ImageField.height_field和ImageField.width_field。其中,ImageField.height_field参数表示Image文件的高度,ImageField.width_field参数表示Image文件的宽度。
● GenericIPAddressField:用来存储原生IP(IPv4或IPv6)地址的类型,在表单域中默认是使用TextInput。
● URLField:用来存储URL的类型,继承自CharField类型,在表单域中默认使用TextInput。
每一种字段类型都需要指定一些特定的参数。例如:CharField(及其子类)需要接收一个max_length参数,用以指定数据库存储VARCHAR数据时使用的字节数。
一些可选的参数是通用的,可以用于任何字段类型。下面具体介绍一些经常用到的通用参数。
(1)null(类型:Field.null):默认值为False;如果设置为True,则当该字段为空时,Django模型会将数据库中该字段设置为NULL。
避免在基于字符串的字段(如:CharField和TextField)上使用null类型,Django模型在使用惯例上是使用空字符串,而不是NULL。
(2)blank(类型:Field.blank):默认值为False;如果设置为True,则该字段允许为空。
blank类型与null类型是有所区别的。blank类型主要用于表单验证,如果某个表单域设为“blank=True”,则验证时会允许该域为空值;如果某个表单域设为“blank=False”,则验证时该域不能为空值。
(3)default(类型:Field.default):字段的默认值;该值可以是一个值或者是一个可调用的对象;如果是可调用对象,每次实例化模型时都会调用该对象。
下面是default类型的代码示例。
【代码3-4】
【代码分析】
● 第01~02行代码中,定义了一个方法(contact_default()),返回一个email对象。
● 第04~06行代码中,定义了一个JSON域变量(contact_info),其default值引用了
contact_default()方法返回的email对象。
(4)choices(类型:Field.choices):用来选择值的二维元组。其中,元组第1个值是实际存储的值,第2个值用来方便进行选择。
choices类型最好是在Django模型中使用,请看下面官方文档给出的代码示例,这个代码示例实现了一个大学生年级类。
【代码3-5】
【代码分析】
● 第03行代码中,定义了一个大学生类(CollegeStudent)。
● 第04~07行代码中,定义了一组字符串变量(FRESHMAN、SOPHOMORE、JUNIOR和SENIOR),分别表示大学生四个年级的名称。
● 第08~13行代码中,定义了一个choices类型的变量(YEAR_IN_COLLEGE_CHOICES),其中包含了4个元组,每个元组使用了第04~07行代码中定义的变量。
● 第14~18行代码中,定义了一个字符型域变量(year_in_college),将choices值定义为变量(YEAR_IN_COLLEGE_CHOICES),默认值为FRESHMAN。
● 第20~21行代码中,定义了一个方法(is_upperclass()),用于返回大学生的高年级元组。
(5)unique(类型:Field.unique):如果值设置为True,则这个字段必须在整个表中保持值唯一;unique类型还定义了一组关于日期和时间子类型,例如:unique_for_date(唯一日期)、unique_for_month(唯一月份)、unique_for_year(唯一年份)。
(6)editable(类型:Field.editable):默认值为True(真);如果值为假,则在admin模式下不能改写。
(7)primary_key(类型:Field.primary_key):用于设置主键,一个字段只能设置一个主键;如果没有设置,则在Django框架创建表时会自动加上。
id = meta.AutoField('ID', primary_key=True)
如果值设置为True,则表示将该字段设置为该模型的主键。
(8)help_text(类型:Field.help_text):额外的“帮助”文本,随表单控件一同显示。
help_text="Please use the following format: <em>YYYY-MM-DD</em>."
即便某个字段未用于表单,该类型对于生成文档也很有用。
(9)verbose_name(类型:Field.verbose_name):admin模式中字段的显示名称。
(10)validators(类型:Field.validators):某个域的有效性检查列表。
(11)db_column(类型:Field.db_column):为某个域指定的数据库列表的名称;如果未指定,则使用该域的名称。
(12)db_index(类型:Field.db_index):如果该值为True,则为该域创建数据库索引。
(13)db_tablespace(类型:Field.db_tablespace):为某个域的索引指定数据库表空间的名称。
Django模型中同样也定义了一组代表关系的字段—外键(ForeignKey),这一点与传统关系型数据库的设计是保持一致的。
在Django模型中,外键通过名为ForeignKey的类实现,具体声明如下:
class ForeignKey(to, on_delete, **options)
其中,参数to(必需的)表示要关联的类,参数on_delete表示删除操作时的级联关系,此外还有一些可选参数**options。而在创建“多对一”的关系时,必须要设置参数to和参数on_delete两个位置的选项。
如果要创建一个递归关系,既一个与其自身有“多对一”关系的对象,则可以按照如下的写法:
models.ForeignKey('self', on_delete=models.CASCADE)
其中,使用models对象上的CASCADE参数,表示在删除关联数据时,与之关联的全部数据也删除。
关于在参数“on_delete”上使用的各个选项值,请看下面的详细说明。
● models.CASCADE:表示在删除关联数据时,与之关联的全部数据也删除。
● models.DO_NOTHING:表示在删除关联数据时,将会引发“IntegrityError”错误。
● models.PROTECT:表示在删除关联数据时,将会引发“ProtectedError”错误。
● models.SET_NULL:表示在删除关联数据时,与之关联的值设置为null(前提是FK字段需要设置为可空)。
● models.SET_DEFAULT:表示在删除关联数据时,与之关联的值设置为默认值(前提FK字段需要设置默认值)。
● models.SET:表示在删除关联数据时,如果与之关联的值设置为指定值,则设置models.SET值;如果与之关联的值设置为可执行对象的返回值,则设置models.SET可执行对象。
关于可选参数**options,请看下面的详细说明。
● related_name=None:表示反向操作时,使用的字段名。
● related_query_name=None:表示反向操作时,使用的连接前缀。
● limit_choices_to=None:表示在Admin或ModelForm中显示关联数据时,提供的条件。
● db_constraint=True:表示是否在数据库中创建外键约束。
● parent_link=False:表示在Admin中是否显示关联数据。
关于在Django模型中使用外键(ForeignKey)的方法,请看下面官方文档给出的代码示例。
【代码3-6】
【代码分析】
● 第10行代码中,定义了一个“制造商”类(Manufacturer)。
● 第03~08行代码中,定义了一个“汽车”类(Car)。
● 第04~07行代码中,通过models对象的ForeignKey()方法创建了“汽车”类(Car)的外键(manufacturer)。
● 第05行代码中,参数to引用了“制造商”类(Manufacturer);参数on_delete设置为models.CASCADE选项值。
在关联关系字段的外键(Foreign Key)使用中,除了“多对一”关系之外,还有一种“一对一”关系。
在Django模型中,“一对一”关系是通过一个名称为OneToOneField的类来实现的,具体声明如下:
class OneToOneField(to, on_delete, **options)
其中,参数to(必需的)表示要关联的类,参数on_delete表示删除操作时的级联关系,此外还有一些可选参数**options。在创建“一对一”的关系时,必须设置参数to和参数on_delete两个位置的选项。
对于“一对一”关系,生活中比较典型的例子就是银行“账户”和“联系人”之间的关系,如下面的代码示例。
【代码3-7】
【代码分析】
● 第03~06行代码中,定义了一个“账户”类(Account)。
● 第07~16行代码中,定义了一个“联系人”类(Contact)。
● 第12~15行代码中,通过models对象的ForeignKey()方法创建了“联系人”类(Contact)的外键(account)。
● 第13行代码中,参数to引用了“账户”类(Account)。
● 第14行代码中,参数on_delete设置为models.CASCADE选项值。
这样,在删除某个“账户”时,基于“联系人”类(Contact)中外键(account)的设置,相关联的“联系人”也会一同被删除。
在关联关系字段的外键(ForeignKey)使用中,除了“多对一”和“一对一”关系之外,最常用的就是“多对多”关系了。
在Django模型中,“多对多”关系是通过一个名称为ManyToManyField的类来实现的,具体声明如下:
class ManyToManyField(to, **options)
其中,参数to(必需的)表示要关联的类,此外还有一些可选参数**options。在创建“多对多”的关系时,必须要设置参数to这个位置的选项。
对于“多对多”关系,生活中比较典型的例子就是“作者”和“图书”之间的关系。简单讲,就是一个作者可以出版多本书,而一本书也可以有多个作者,这个就是典型的“多对多”关系。请看下面的代码示例。
【代码3-8】
【代码分析】
● 第03~07行代码中,定义了一个“作者”类(Author)。
● 第08~16行代码中,定义了一个“图书”类(Book)。
● 第13~15行代码中,通过models对象的ManyToManyField()方法创建了“作者”类(Author)的外键(author),实现了“多对多”关联关系。其中,第14行代码中,参数to引用了“作者”类(Author)。
上面的代码实现了“作者”表与“图书”表之间“多对多”的关联关系,但是如果还想要实现某个作者写作的某一本图书的出版时间时,因为表已经存在了,所以再增加一个字段处理起来就会比较麻烦。
对于这样的情形,Django模型允许指定一个用于管理“多对多”关联关系的中间模型。然后把这些额外的字段添加到这个中间模型中,具体的方法就是在ManyToManyField()方法中指定through参数作为中介的中间模型。
下面在【代码3-8】的基础上略作修改,实现添加一个“版本”字段的功能。具体请看下面的代码示例。
【代码3-9】
【代码分析】
● 第03~07行代码中,定义了一个“作者”类(Author)。
● 第08~17行代码中,定义了一个“图书”类(Book)。
● 第13~16行代码中,通过models对象的ManyToManyField()方法创建了“作者”类(Author)的外键(author),实现了“多对多”关联关系。
● 第15行代码中,参数through引用了第三个类(BookVersion)。
● 第18~29行代码中,定义的是第三个“图书版本”类(BookVersion)。
● 第19~22行代码中,通过models对象的ForeignKey()方法创建了“作者”类(Author)的外键(author)。
● 第23~26行代码中,通过models对象的ForeignKey()方法创建了“图书”类(Book)的外键(book)。
● 第27行代码中,通过models对象的CharField()方法新增了一个图书“版本”变量,该图书“版本”变量就是新增的字段。
如果已经存在的模型字段不能满足最初的需求,或者希望支持一些不太常见的字段类型,Django模型支持可以创建自定义的字段类。在编写自定义模型字段(model fields)中提供了创建自定义字段的相应内容。
Django模型内置的字段类型并未覆盖所有可能的数据库字段类型,一般只有类似VARCHAR和INTEGER这样的常见类型。对于更多模糊的列类型,就需要用户自己创建自定义类型了。自定义类型是一个相对复杂的Python对象,该对象可以以某种形式序列化,适应标准的字段类型。
这里,我们举一个创建“桥牌”自定义模型字段的例子。对于这个“桥牌”自定义模型字段,读者不需要知道“桥牌”具体的游戏规则,只需要知道一副“桥牌”共计52张牌、会平均分配给4个玩家。一般地,这4个玩家被称为“北”“东”“南”和“西”。
那么,这个“桥牌”自定义模型的Python类就可以定义如下:
【代码3-10】
【代码分析】
● 第01行代码中,定义了这个“桥牌”类的名称为(Hand)。
● 第04~09行代码,在定义的初始化方法(__init__())中,依次将“北(north)”“东(east)”“南(south)”和“西(west)”4个玩家设置为Hand类的self内置属性。
在Django模型中使用自定义模型字段Hand类,是不需要修改这个类的。对象属性的赋值与取值操作与其他Python类是一样的,关键技巧是告诉Django如何保存和加载对象。