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时,在整个数据表内该字段的数据不可重复。

注意:

  1. 对于ManyToManyField和OneToOneField关系类型,该参数无效。

  2. 当unique=True时,db_index参数无须设置,因为unqiue隐含了索引。

  3. 自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=Falseunique=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

用于自定义错误信息。参数接收字典类型的值。字典的键可以是nullblankinvalidinvalid_choiceuniqueunique_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

为字段设置一个人类可读,更加直观的别名。

对于每一个字段类型,除了ForeignKeyManyToManyFieldOneToOneField这三个特殊的关系类型,其第一可选位置参数都是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(),
        )
posted @ 2020-07-28 21:44  虫萧  阅读(408)  评论(0编辑  收藏  举报