ORM之字段属性与表关系
关系类型字段:
除了普通类型字段,Django还定义了一组关系类型字段,用来表示模型与模型之间的关系。
一对一(OneToOneField)
一对一关系类型的定义如下:
class OneToOneField(to, on_delete, parent_link=False, **options)[source]
一对一关系非常类似具有unique=True
属性的外键关系,但是反向关联对象只有一个。这种关系类型多数用于当一个模型需要从别的模型扩展而来的情况。该关系的第一位置参数为关联的模型。
一对一的这个关系字段写在两个表的任意一个表里面都可以。
如果没有给一对一关系设置related_name
参数,增删改查等操作时,Django将使用当前模型的小写名作为默认值。
from django.conf import settings
from django.db import models
# 两个字段都使用一对一关联到了Django内置的auth模块中的User模型
class MySpecialUser(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE, # 就是foreignkey+unique,并且orm会自动给这个字段名字拼上一个_id,数据库中字段名称为user_id
)
supervisor = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='supervisor_of',
)
这样下来,User模型将拥有下面的属性:
>>> user = User.objects.get(pk=1)
>>> hasattr(user, 'myspecialuser')
True
>>> hasattr(user, 'supervisor_of')
True
OneToOneField一对一关系拥有和多对一外键关系一样的额外可选参数,只是多了一个parent_link参数。
跨模块的模型:
如果关联的模型并不在当前模型的文件内,就可以像导入第三方库一样的从别的模块内导入进来,如下例所示:
from django.db import models
from geography.models import ZipCode
class Restaurant(models.Model):
# ...
zip_code = models.ForeignKey(
ZipCode,
on_delete=models.SET_NULL,
blank=True,
null=True,
)
多对一(ForeignKey)
多对一的关系,通常被称为外键。外键字段类的定义如下:
class ForeignKey(to, on_delete, **options)[source]
外键需要两个位置参数,一个是关联的模型,另一个是on_delete
选项。
外键要定义在‘多’的一方
from django.db import models
class Car(models.Model):
manufacturer = models.ForeignKey(
'Manufacturers',
on_delete=models.CASCADE,
)
# ...
class Manufacturers(models.Model):
# ...
pass
# 每辆车都会有一个生产工厂,一个工厂可以生产N辆车,于是用一个外键字段manufacturer表示,并放在Car模型中。
假设Manufacturers模型存在于production这个app中,则Car模型的定义如下:
class Car(models.Model):
manufacturer = models.ForeignKey(
'production.Manufacturers', # 如果要关联的对象在另外一个app中,可以显式的指出
on_delete=models.CASCADE,
)
如果要创建一个递归的外键,也就是自己关联自己的的外键,使用下面的方法:
models.ForeignKey('self', on_delete=models.CASCADE) # 核心在于‘self’这个引用
自己引用自己的外键典型的例子就是评论系统,一条评论可以被很多人继续评论,如下所示:
class Comment(models.Model):
title = models.CharField(max_length=128)
text = models.TextField()
parent_comment = models.ForeignKey('self', on_delete=models.CASCADE)
# .....
注意上面的外键字段定义的是父评论,而不是子评论。因为外键要放在‘多’的一方。
在实际的数据库后台,Django会为每一个外键添加_id
后缀,并以此创建数据表里的一列。在上面的工厂与车的例子中,Car模型对应的数据表中,会有一列叫做manufacturer_id
。但在Django代码中并不需要使用这个列名,一般都直接使用字段名manufacturer
,只有书写原生的SQL语句才会用到。
verbose_name
参数用于设置字段的别名。很多情况下,为了方便,我们都会设置这么个值,并且作为字段的第一位置参数。但是对于关系字段,其第一位置参数永远是关系对象,不能是verbose_name
。
注意事项:
- 表的名称
myapp_modelName
,是根据 模型中的元数据自动生成的,也可以覆写为别的名称 id
字段是自动添加的- 对于外键字段,Django 会在字段名上添加
"_id"
来创建数据库中的列名。 - Django 会根据settings 中指定的数据库类型来使用相应的SQL 语句。
- 外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。
参数说明:
on_delete
# 例:有一个可为空的外键,并且它在关联的对象被删除时,自动设为null,可以如下定义:
user = models.ForeignKey(
User,
models.SET_NULL,
blank=True,
null=True,
)
这个参数在Django2.0之后,不可以省略,需要显式的指定。
该参数可选的值都内置在django.db.models
中,包括:
- CASCADE:模拟SQL语言中的
ON DELETE CASCADE
约束,删除关联数据时,将定义有外键的模型对象同时删除。 - PROTECT:阻止删除关联数据操作,引发
ProtectedError
异常 - SET_NULL:删除关联数据,与之关联的值设置为null。只有当字段设置了
null=True
时,方可使用该值(前提FK字段需要设置为可空)。 - SET_DEFAULT:删除关联数据,与之关联的值设置为默认值。只有当字段设置了default参数时,方可使用。
- DO_NOTHING:什么也不做。
- SET():设置为一个传递给SET()的值或者一个回调函数的返回值。
- 删除关联数据,与之关联的值设置为指定值,设置:models.SET(值)
- 删除关联数据,与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
def get_sentinel_user():
return get_user_model().objects.get_or_create(username='deleted')[0]
class MyModel(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET(get_sentinel_user),
)
limit_choices_to
该参数用于限制外键所能关联的对象,只能用于Django的ModelForm(Django的表单模块)和admin后台,对其它场合无限制功能。其值可以是一个字典、Q对象或者一个返回字典或Q对象的函数调用,如下例所示:
staff_member = models.ForeignKey(
User,
on_delete=models.CASCADE,
limit_choices_to={'is_staff': True},
)
这样定义,则ModelForm的staff_member
字段列表中,只会出现那些is_staff=True
的Users对象。。
可以参考下面的方式,使用函数调用:
def limit_pub_date_choices():
return {'pub_date__lte': datetime.date.utcnow()}
# ...
limit_choices_to = limit_pub_date_choices
# ...
related_name
用于关联对象反向引用模型的名称。反向操作时,使用的字段名,用于代替原反向查询时的表名_set
。
通常情况下,这个参数可以不设置,Django会默认以模型的小写加上_set
作为反向关联名。示例:
class Car(models.Model):
manufacturer = models.ForeignKey(
'production.Manufacturer',
on_delete=models.CASCADE,
related_name='car_producted_by_this_manufacturer',
)
以后从工厂对象反向关联到它所生产的汽车,就必须使用maufacturer.car_producted_by_this_manufacturer
。
related_query_name
反向关联查询名,反向查询操作时,使用的连接前缀,用于替换表名。用于从目标模型反向过滤模型对象的名称。
class Tag(models.Model):
article = models.ForeignKey(
Article,
on_delete=models.CASCADE,
related_name="tags",
related_query_name="tag", # 注意这一行
)
name = models.CharField(max_length=255)
# 现在可以使用‘tag’作为查询名了
Article.objects.filter(tag__name="important")
to_field
默认情况下,外键都是关联到被关联对象的主键上(一般为id)。如果指定这个参数,可以关联到指定的字段上,但是该字段必须具有unique=True
属性,也就是具有唯一属性。
db_constraint
默认情况下,这个参数被设为True,表示遵循数据库约束。如果设为False,那么将无法保证数据的完整性和合法性。在下面的场景中,可能需要将它设置为False:
- 有历史遗留的不合法数据,没办法的选择
- 你正在分割数据表
当它为False,并且你试图访问一个不存在的关系对象时,会抛出DoesNotExist 异常。
ForeignKey的db_contraint参数
关系和约束:不加外键依然能表示两个表之间的关系。但就不能使用ORM外键相关的方法了。
db_constraint=False只加两者的关系,没有强制约束的效果,并且ORM外键相关的接口(方法)还能使用,所以如果建立外键,并且不能有强制的约束关系,那么就可以将这个参数改为False
customer = models.ForeignKey(verbose_name='关联客户', to='Customer',db_constraint=False)
swappable
控制迁移框架的动作,如果当前外键指向一个可交换的模型。使用场景非常稀少,通常请将该参数保持默认的True。
多对多(ManyToManyField)
class ManyToManyField(to, **options)[source]
例:
class Book(models.Model):
title = models.CharField( max_length=32)
publishDate=models.DateField()
authors=models.ManyToManyField(to='Author') # 不管是一对多还是多对多,to参数的值是个字符串,如果是变量,就需要将要关联的那个表放到这个表的上面(从上往下加载)。
多对多关系需要一个位置参数:关联的对象模型。
在数据库后台,Django实际上会额外创建一张用于体现多对多关系的中间表。默认情况下,该表的名称是“多对多字段名+关联对象模型名+一个独一无二的哈希码”,例如‘author_books_9cdf4’,可以通过db_table
选项,自定义表名。
多对多表的三种创建方式:
方式一:自行创建第三张表,但无法使用orm提供的set、add、remove、clear方法来管理多对多的关系。
class Book(models.Model):
title = models.CharField(max_length=32, verbose_name="书名")
class Author(models.Model):
name = models.CharField(max_length=32, verbose_name="作者姓名")
# 自己创建第三张表,分别通过外键关联书和作者
class Author2Book(models.Model):
author = models.ForeignKey(to="Author")
book = models.ForeignKey(to="Book")
class Meta:
unique_together = ("author", "book")
方式二:通过ManyToManyField自动创建第三张表,但自动生成的第三张表我们是没有办法添加其他字段。在中间表中,并不是将两张表的数据都保存在一起,而是通过id的关联进行映射。
通常情况下,多对多第三张表在数据库内的结构是:
中间表的id列....模型对象的id列.....被关联对象的id列
# 各行数据
方式三:设置ManyTomanyField并指定自行创建的第三张表(称为中介模型),可以在第三张关系表中存储额外的字段,可以使用多对多关联关系操作的接口(all、add、clear等等)
class Book(models.Model):
title = models.CharField(max_length=32, verbose_name="书名")
# 自己创建第三张表,并通过ManyToManyField指定关联
class Author(models.Model):
name = models.CharField(max_length=32, verbose_name="作者姓名")
books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book"))
# through_fields接受一个2元组('field1','field2'):
# 其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名。
class Author2Book(models.Model):
author = models.ForeignKey(to="Author")
book = models.ForeignKey(to="Book")
#可以扩展其他的字段了
class Meta:
unique_together = ("author", "book")
自定义中间表结构:自定义中间表并添加时间字段,则在数据库内的表结构如下
中间表的id列....模型对象的id列.....被关联对象的id列.....时间对象列
# 各行数据
参数说明:
related_name、related_query_name、
limit_choices_to
对于使用through
参数自定义中间表的多对多字段无效。
symmetrical
默认情况下,Django中的多对多关系是对称的。:
from django.db import models
class Person(models.Model):
friends = models.ManyToManyField("self")
Django不会为Person模型添加person_set
属性用于反向关联。如果不想使用这种对称关系,可以将symmetrical设置为False,这将强制Django为反向关联添加描述符。
through(定义中间表)
自定义多对多关系的那张额外的关联表,参数的值为一个中间模型。
例子:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=50)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(
Person,
through='Membership', ## 自定义中间表
through_fields=('group', 'person'),
)
class Membership(models.Model): # 这就是具体的中间表模型
group = models.ForeignKey(Group, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
inviter = models.ForeignKey(
Person,
on_delete=models.CASCADE,
related_name="membership_invites",
)
invite_reason = models.CharField(max_length=64)
through_fields
through_fields
参数接收一个二元元组('field1', 'field2'),field1是指向定义有多对多关系的模型的外键字段的名称,另外一个则是指向目标模型的外键字段的名称,through_fields
参数指定从中间表模型中选择哪两个字段,作为关系连接字段。
db_table
设置中间表的名称。不指定的话,则使用默认值。
db_constraint
参考外键的相同参数。
swappable
参考外键的相同参数。
ManyToManyField多对多字段不支持Django内置的validators验证功能。
null参数对ManyToManyField多对多字段无效,设置null=True毫无意义
>>> # 无效
>>> beatles.members.add(john)
>>> # 无效
>>> beatles.members.create(name="George Harrison")
>>> # 无效
>>> beatles.members.set([john, paul, ringo, george])
字段的参数
所有的模型字段都可以接收一定数量的参数,比如CharField至少需要一个max_length参数。下面的这些参数是所有字段都可以使用的可选的参数。
null
该值为True时,Django在数据库用NULL保存空值。默认null=False
。对于保存字符串类型数据的字段,应尽量避免将此参数设为True,那样会导致两种‘没有数据’的情况,一种是NULL,另一种是‘空字符串’。
blank
True时,字段可以为空。默认False。和null参数不同的是,null是纯数据库层面的,而blank是验证相关的,它与表单验证是否允许输入框内为空有关,与数据库无关。要小心一个null为False,blank为True的字段接收到一个空值可能会出bug或异常。
default
字段的默认值,可以是值或者一个可调用对象。如果是可调用对象,那么每次创建新对象时都会调用。设置的默认值不能是一个可变对象,比如列表、集合等等。lambda匿名函数也不可用于default的调用对象,因为匿名函数不能被migrations序列化。
注意:在某种原因不明的情况下将default设置为None,可能会引发intergyerror:not null constraint failed
,即非空约束失败异常,导致python manage.py migrate
失败,此时可将None改为False或其它的值,只要不是None就行。
unique
设为True时,在整个数据表内该字段的数据不可重复。
注意:
对于ManyToManyField和OneToOneField关系类型,该参数无效。
当unique=True时,db_index参数无须设置,因为unqiue隐含了索引。
自1.11版本后,unique参数可以用于FileField字段。
primary_key
如果没有给模型的任何字段设置这个参数为True,Django将自动创建一个AutoField自增字段,名为‘id’,并设置为主键。也就是id = models.AutoField(primary_key=True)
。
如果为某个字段设置了primary_key=True,则当前字段变为主键,并关闭Django自动生成id主键的功能。
primary_key=True
隐含null=False
和unique=True
的意思。一个模型中只能有一个主键字段
另外,主键字段不可修改,如果你给某个对象的主键赋个新值实际上是创建一个新对象,并不会修改原来的对象。
from django.db import models
class Fruit(models.Model):
name = models.CharField(max_length=100, primary_key=True)
###############
>>> fruit = Fruit.objects.create(name='Apple')
>>> fruit.name = 'Pear'
>>> fruit.save()
>>> Fruit.objects.values_list('name', flat=True)
['Apple', 'Pear']
choices
用于页面上的选择框标签,需要先提供一个二维的二元元组,第一个元素表示存在数据库内真实的值,第二个表示页面上显示的具体内容。在浏览器页面上将显示第二个元素的值。例如:
YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
('SO', 'Sophomore'),
)
一般来说,最好将选项定义在类里,并取一个直观的名字,如下所示:
from django.db import models
class Student(models.Model):
FRESHMAN = 'FR'
SOPHOMORE = 'SO'
YEAR_IN_SCHOOL_CHOICES = (
(FRESHMAN, 'Freshman'),
(SOPHOMORE, 'Sophomore'),
)
year_in_school = models.CharField(
max_length=2,
choices=YEAR_IN_SCHOOL_CHOICES,
default=FRESHMAN,
)
def is_upperclass(self):
return self.year_in_school in (self.JUNIOR, self.SENIOR)
要获取一个choices的第二元素的值,可以使用get_FOO_display()
方法,其中的FOO用字段名代替。
db_index
该参数接收布尔值。如果为True,数据库将为该字段创建索引。
db_column
该参数用于定义当前字段在数据表内的列名。如果未指定,Django将使用字段名作为列名。
db_tablespace
用于字段索引的数据库表空间的名字,前提是当前字段设置了索引。默认值为工程的DEFAULT_INDEX_TABLESPACE
设置。如果使用的数据库不支持表空间,该参数会被忽略。
editable
如果设为False,那么当前字段将不会在admin后台或者其它的ModelForm表单中显示,同时还会被模型验证功能跳过。参数默认值为True。
error_messages
用于自定义错误信息。参数接收字典类型的值。字典的键可以是null
、 blank
、 invalid
、 invalid_choice
、 unique
和unique_for_date
其中的一个。
help_text
额外显示在表单部件上的帮助文本。使用时请注意转义为纯文本,防止脚本攻击。
unique_for_date
日期唯一。例:如果你有一个名叫title的字段,并设置了参数unique_for_date="pub_date"
,那么Django将不允许有两个模型对象具备同样的title和pub_date。有点类似联合约束。
unique_for_month
同上,只是月份唯一。
unique_for_year
同上,只是年份唯一。
verbose_name
为字段设置一个人类可读,更加直观的别名。
对于每一个字段类型,除了ForeignKey
、ManyToManyField
和OneToOneField
这三个特殊的关系类型,其第一可选位置参数都是verbose_name
。如果没指定这个参数,Django会利用字段的属性名自动创建它,并将下划线转换为空格。
下面这个例子的verbose name
是"person’s first name":
first_name = models.CharField("person's first name", max_length=30)
下面这个例子的verbose name
是"first name":
first_name = models.CharField(max_length=30)
对于外键、多对多和一对一字字段,由于第一个参数需要用来指定关联的模型,因此必须用关键字参数verbose_name
来明确指定。如下:
poll = models.ForeignKey(
Poll,
on_delete=models.CASCADE,
verbose_name="the related poll",
)
sites = models.ManyToManyField(Site, verbose_name="list of sites")
place = models.OneToOneField(
Place,
on_delete=models.CASCADE,
verbose_name="related place",
)
另外,你无须大写verbose_name
的首字母,Django自动为你完成这一工作。
validators
运行在该字段上的验证器的列表。
其他参数
DatetimeField、DateField、TimeField这个三个时间字段,都可以设置如下属性:
auto_now_add
:配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。
auto_now
配置上auto_now=True,每次更新数据记录的时候会更新该字段,标识这条记录最后一次的修改时间。
关于auto_now:
当需要更新时间的时候,我们尽量通过datetime模块来创建当前时间,并保存或者更新到数据库里面,看下面的分析:
假如我们的表结构是这样的
class User(models.Model):
username = models.CharField(max_length=255, unique=True, verbose_name='用户名')
is_active = models.BooleanField(default=False, verbose_name='激活状态')
那么我们修改用户名和状态可以使用如下两种方法:
方法一:
User.objects.filter(id=1).update(username='nick',is_active=True)
方法二:
_t = User.objects.get(id=1)
_t.username='nick'
_t.is_active=True
_t.save()
方法一适合更新一批数据,类似于mysql语句update user set username='nick' where id = 1
方法二适合更新一条数据,也只能更新一条数据,当只有一条数据更新时推荐使用此方法,另外此方法还有一个好处,我们接着往下看
具有auto_now属性字段的更新
我们通常会给表添加三个默认字段
- 自增ID,这个django已经默认加了,就像上边的建表语句,虽然只写了username和is_active两个字段,但表建好后也会有一个默认的自增id字段
- 创建时间,用来标识这条记录的创建时间,具有auto_now_add属性,创建记录时会自动填充当前时间到此字段
- 修改时间,用来标识这条记录最后一次的修改时间,具有auto_now属性,当记录发生变化时填充当前时间到此字段
就像下边这样的表结构
class User(models.Model):
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
username = models.CharField(max_length=255, unique=True, verbose_name='用户名')
is_active = models.BooleanField(default=False, verbose_name='激活状态')
当表有字段具有auto_now属性且你希望他能自动更新时,必须使用上边方法二的更新,不然auto_now字段不会更新,也就是:
_t = User.objects.get(id=1)
_t.username='nick'
_t.is_active=True
_t.save()
json/dict类型数据更新字段
目前主流的web开放方式都讲究前后端分离,分离之后前后端交互的数据格式大都用通用的jason型,那么如何用最少的代码方便的更新json格式数据到数据库呢?同样可以使用如下两种方法:
方法一:
data = {'username':'nick','is_active':'0'}
User.objects.filter(id=1).update(**data)
同样这种方法不能自动更新具有auto_now属性字段的值
通常我们再变量前加一个星号(*)表示这个变量是元组/列表,加两个星号表示这个参数是字典
方法二:
data = {'username':'nick','is_active':'0'}
_t = User.objects.get(id=1)
_t.__dict__.update(**data)
_t.save()
方法二和方法一同样无法自动更新auto_now字段的值
注意这里使用到了一个__dict__方法
方法三:
_t = User.objects.get(id=1)
_t.role=Role.objects.get(id=3)
_t.save()
#想让auto_now更新数据时自动更新时间,必须使用save方法来更新数据,所以很不方便,所以这个创建时自动添加时间或者更新时间的auto_now方法我们最好就别用了,比较恶心,并且支持我们自己来给这个字段更新时间:
models.py:
class Book(models.Model):
name = models.CharField(max_length=32)
date1 = models.DateTimeField(auto_now=True,null=True)
date2 = models.DateTimeField(auto_now_add=True,null=True)
views.py:
import datetime
models.Book.objects.filter(id=1).update(
name='chao',
date1=datetime.datetime.now(),
date2=datetime.datetime.now(),
)