Django模型层之建立表关系

关系型数据库的强大之处就在于将表相互关联,Django提供了定义三种最常见的数据库关系类型的方法:多对一、多对多和一对一。

一、表关系之多对一

1、多对一关系

要定义一个多对一关系,需要使用django.db.models.ForeignKey。您可以像使用任何其他字段类型一样去使用它:将其作为模型的类属性

# ForeignKey的参数:
1、to:所关联的模型类

2、to_field: 设置要关联表的字段
3、related_name: 此处暂且忽略,会在后续章节专门介绍
4、related_query_name: 此处暂且忽略,会在后续章节专门介绍
5、db_constraint:是否在数据库中创建外键约束,默认为True。

6、on_delete:当删除关联表中的数据时,当前表与其关联的行的行为,值可以是如下
  models.CASCADE
  删除关联数据,与之关联也删除

  models.DO_NOTHING
  删除关联数据,引发错误IntegrityError

  models.PROTECT
  删除关联数据,引发错误ProtectedError

  models.SET_NULL
  删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)

  models.SET_DEFAULT
  删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)

  models.SET(值)
  删除关联数据,与之关联的值设置为指定值

   models.SET(可执行对象)
  删除关联数据,与之关联的值设置为可执行对象的返回值,例如
   def func():
       return 10

   class MyModel(models.Model):
       user = models.ForeignKey(
            to="User",
            to_field="id",
            on_delete=models.SET(func)
       )

例如:

from django.db import models

class Manufacturer(models.Model):  # 汽车制造商
    # ...
    pass

class Car(models.Model):  # 汽车
    manufacturer = models.ForeignKey(to=Manufacturer, on_delete=models.CASCADE)
    # ...

建议(但不是必需的)ForeignKey字段的名称是模型的名称、小写(如上述manufacturer),这么做是为了在后期进行查询操作时方便自己识别。当然,你想叫什么就叫什么。例如:

class Car(models.Model):
    company_that_makes_it = models.ForeignKey(
        Manufacturer,
        on_delete=models.CASCADE,
    )
    # ...

2、惰性关系

上述关联关系的建立,由于ForeignKey的第一个位置参数是模型类,所以模型Car必须在Manufacturer之后,若想排除先后顺序带来的困扰,即需要创建与尚未定义的模型类的关系,可以将ForeignKey的第一个参数换成字符串形式,如下所示,这种定义关系的形式,称之为惰性关系

from django.db import models

class Car(models.Model):
    manufacturer = models.ForeignKey('Manufacturer', on_delete=models.CASCADE)
    # ...

class Manufacturer(models.Model):
    # ...
    pass

3、自关联

class Comment(models.Model):
    # 评论者
    user=models.ForeignKey(to='User',to_field='nid',on_delete=models.CASCADE)
    # 评论的文章
    article=models.ForeignKey(to='Article',to_field='nid',on_delete=models.CASCADE)
    # 评论的内容
    content=models.CharField(max_length=255)
    # 评论时间
    create_time=models.DateTimeField(auto_now_add=True)
    # 自关联
    parent_comment=models.ForeignKey(to='self',on_delete=models.CASCADE,null=True)

4、其他

# 1、数据库表示:
默认情况下,在数据库层面,django会在ForeignKey的字段后加后缀“_id”,即上述模型字段manufacturer对应的数据库表字段为manufacturer_id(我们可以用过db_column选项来进行自定义)

但是,我们在操作模型时只需要基于模型字段名字即可,只有在编写原生sql时才需要考虑数据库里的字段。


# 2、db_index
django会自动为ForeignKey字段创建索引,可以指定db_index=False来禁用此功能。

如果我们创建外键的目的是为了一致性而不是链接,或者你需要使用自定义的部分或多列索引,那么需要将db_index设置为False

# 3、ForeignKey的其他详细参数
https://docs.djangoproject.com/en/3.0/ref/models/fields/#arguments

二、表关系之多对多

1、多对多关系

定义多对多关系,需要使用ManyToManyField,您同样可以像使用任何其他字段类型一样去使用它:将其作为模型的类属性

# ManyToManyField参数:
1、to:所关联的模型类

2、to_field: 设置要关联表的字段
3、related_name: 同ForeignKey,此处暂且忽略,会在后续章节专门介绍
4、related_query_name: 同ForeignKey,此处暂且忽略,会在后续章节专门介绍
5、db_constraint:是否在数据库中创建外键约束,默认为True。
6、db_table: 默认创建第三张表时,数据库中该表的名称。

7、symmetrical:默认为True,此处暂且忽略,会在后续章节专门介绍
8、through: 详见本节"3、多对多额外的字段:中间模型"
9、through_fileds:详见本节"3、多对多额外的字段:中间模型"

# MannyToMany的其他详细参数:https://docs.djangoproject.com/en/3.0/ref/models/fields/#manytomany-arguments

例如

from django.db import models

class Topping(models.Model): # 馅料
    # ...
    pass

class Pizza(models.Model): # 披萨
    # ...
    toppings = models.ManyToManyField(Topping)
    
# 数据库表示:
在幕后,Django创建一个中间连接表来表示多对多关系。默认情况下,此表名是:包含该字段的模型的表名_多对多字段的名称。可以使用db_table选项手动设置连接表的名称。

建议(但不是必须)ManyToMany的字段名称(上例中的toppings)是一个关联的模型类名字的复数形式,用于描述相关模型对象集。

针对ManyToMany字段,只要不是同时放入两个模型中,在哪个模型中创建并不重要,但是通常情况下我们需要考虑逻辑上的通顺已经后期的使用方便,字段与类是一个从属关系,toppings字段在类Pizza里,我们可以说Pizza拥有toppings,翻译一下就是披萨有馅料,显而易见这比在Topping类里放入MannyToMany字段,逻辑上更通顺,从而使得后期的使用更为方便

2、惰性关系

同ForeignKey一样

3、多对多额外的字段:中间模型

使用标准的ManyToManyField创建多对多关系时,会自动生成第三张关系表,而关系表中只存放了最基本的数据,即两个模型的关联关系,大多数情况下这是没问题的,如下

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=32)


class Author(models.Model):
    name = models.CharField(max_length=32)
    books = models.ManyToManyField(to="Book")

# 会自动生成第三张表,用来存放基本的关联关系,转换成类似下面的sql
create table book(id int primary key);
create table author(id int primary key);

create table app01_author_books(
    id int primary key auto_increment,
    author_id int,
    book_id int,
    foreign key(author_id) references Author(id) on delete cascade,
    foreign key(book_id) references Book(id) on delete cascade,
    unique(author_id,book_id)
);

但如果我们需要在两个模型的多对多关系上添加额外的数据(比如有这样一个应用,它记录音乐家Person所属的音乐小组Group。我们想在多对多关系的基础上,知道更多成员关系的细节,比如成员是何时加入小组的,加入的理由是什么),
就需要我们自定义第三张关系表了,你可能会想到这么做

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=128)

class Group(models.Model):
    name = models.CharField(max_length=128)


class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateTimeField(auto_now_add=True)
    invite_reason = models.CharField(max_length=64)

    class Meta:
        # 指定本模型中的两个字段person与group联合唯一
        unique_together = ("person", "group")

此时第三张表便是一张完全独立的表,这本身是没有问题的,但Person与Group与其关系表Membership 完全独立开意味着我们将无法使用django提供的关联查询功能,为此,django提供了一种改进方案:"使用through参数指定中间模型",如下

 

class Person(models.Model):
    name = models.CharField(max_length=128)

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through='Membership',through_fields=('group','person'))

    """
    through='Membership',指定中间模型为Membership
    如果中介模型Membership中有多个外键都指向同一个模型,那么必须指定through_fields参数说明关联的两个字段是谁,比如through_fields=('person','group'),
    元组内第一个字段代表定义ManyToManyFiled的那个模型(此处为Group)对应的外键名称(此处为'group'),而元组的第二个字段代表目标模型(此处为Person)对应的外键名称(此处为'person'),切记顺序不可乱
    """

class Membership(models.Model): # 称之为:中间模型/中介模型
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateTimeField(auto_now_add=True)
    invite_reason = models.CharField(max_length=64)
 


# ===========================
这样Group便可以通过字段members进行正向查询,Person便可以通过模型名group进行反向
查询了

>>> p1=Person(name='牛逼鼓手')
>>> p2=Person(name='骚浪吉他手刘二蛋')
>>> p1.save()
>>> p2.save()


>>> g1=Group(name="狂拽炫酷屌炸天乐队")
>>> g2=Group(name="疯狂的唢呐乐队")
>>> g1.save()
>>> g2.save()


#当我们使用第上述方式创建多对多关联关系时,就无法使用set、add、remove、clear方法来管理多对多的关系了,需要通过第三张表的model来管理多对多关系。
>>> Membership.objects.create(person=p1,group=g1,invite_reason='需要一名牛逼的鼓手')
<Membership: Membership object (1)>
>>> Membership.objects.create(person=p2,group=g1,invite_reason='需要一名骚浪吉他手')
<Membership: Membership object (2)>
>>> Membership.objects.create(person=p2,group=g2,invite_reason='需要一名骚浪吉他手')
<Membership: Membership object (2)>


>>> g1.members.all()
<QuerySet [<Person: 牛逼鼓手>, <Person: 骚浪吉他手刘二蛋>]>
>>> g2.members.all()
<QuerySet [<Person: 骚浪吉他手刘二蛋>]>
>>> 
>>> p1.group_set.all()
<QuerySet [<Group: 狂拽炫酷屌炸天乐队>]>
>>> p2.group_set.all()
<QuerySet [<Group: 狂拽炫酷屌炸天乐队>, <Group: 疯狂的唢呐乐队>]>


#clear()方法却是可用的。它可以清空某个实例所有的多对多关系:
>>> g1.members.clear()
>>> p1.group_set.all()
<QuerySet []>
>>> p2.group_set.all()
<QuerySet [<Group: 疯狂的唢呐乐队>]>

4、自关联

自己跟自己关联,默认是数据是对称的

class Person(models.Model):
    id=models.AutoField(primary_key=True)
    name=models.CharField(max_length=30,null=True)
    friends=models.ManyToManyField('self')

>>> obj1=Person(name='egon')
>>> obj2=Person(name='tom')
>>> obj1.save()
>>> obj2.save()
>>> obj1.friends.add(obj2)
朋友关系表里会对称着,有两条记录

数据库表里app01_person_friends
id       from_person_id          to_person_id
1                1                    2
2                2                    1

可以指定参数symmetrical=False,来关闭对称
class Person(models.Model):
    id=models.AutoField(primary_key=True)
    name=models.CharField(max_length=30,null=True)
    friends=models.ManyToManyField('self',symmetrical=False)


如果是自关联, 并且定制了中间模型,那么必须设置symmetrical=False
class Person(models.Model):
    id=models.AutoField(primary_key=True)
    name=models.CharField(max_length=30,null=True)
    friends=models.ManyToManyField('self',through='PersonShip',symmetrical=False,through_fields=('t_friend','f_friend'))

class PersonShip(models.Model):
    create_time=models.DateTimeField(auto_now_add=True)
    f_friend=models.ForeignKey('Person',on_delete=models.CASCADE,)
    t_friend=models.ForeignKey('Person',on_delete=models.CASCADE,related_name='aaaa')

三、表关系之一对一

1、一对一关系

定义一对一关系,需要使用OneToOneField.您同样可以像使用任何其他字段类型一样去使用它:将其作为模型的类属性

# OneToOneField参数
1、to:所关联的模型类

2、to_field: 设置要关联表的字段

3、on_delete:同ForeignKey

示例

class visitor(models.Model):  # 访客
    pass


class Customer(models.Model):  # 客户
    visitor = models.OneToOneField(to='visitor', on_delete=models.CASCADE)
	# 也可以写成
    # visitor = models.ForeignKey(to='visitor', unique=True, on_delete=models.CASCADE)

OneToOneField就是在ForeignKey的基础上设置一个unique=True,只不过反向查询出的结果就一个对象,因为本质与ForeignKey类似,所以注意on_delete也是要加的。

ps:因为一对一满足“是”的关系,所以事实上,为了处理这个问题,你通常会使用继承,其中就已经包含一个隐含的一对一关系)。

2、惰性关系

同ForeignKey一样

3、自关联

同ForeignKey一样

4、其他

注意


现在可以在一个模型上有多个OneToOneField类型的字段。
在以前的版本中,OneToOneField 字段会自动变成模型 的主键。 不过现在已经不这么做了(不过要是你愿意的话,你仍可以传递 primary_key参数来创建主键字段)。 所以一个模型中可以有多个OneToOneField 字段。

四、跨文件建立模型关系

与另外一个app中的模型建立关联是完全可以的,如何实现呢?
在我们定义模型的文件顶部导入其他app的模型,然后在需要的地方进行关联即可,如下
# app01下的models.py
from django.db import models
from app02.models import ZipCode

class Restaurant(models.Model):
    # ...
    zip_code = models.ForeignKey(
        ZipCode,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )

五、META选项

在模型类内部定义Meta类(可选的,所有选项都不是必须的),可以配置模型的元数据,

例如:

1、如排序选项(ordering)

2、数据库表名(db_table)

3、人类可读的单复数名称(verbose_name 和verbose_name_plural)。

from django.db import models


class Person(models.Model):
    PRIORITY_CHOICES = [
        (0,'admin'),
        (1,'user'),
        (2,'visitor'),
    ]

    id=models.AutoField(primary_key=True)
    name=models.CharField(max_length=30,null=True)
    priority=models.IntegerField(choices=PRIORITY_CHOICES)
    create_time=models.DateTimeField(auto_now_add=True)

    class Meta:
        # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
        db_table='my_table'

        # 联合索引
        index_together = [
            ("priority", "create_time"),
        ]

        # 建立联合索引推荐使用下面这种
        indexes = [
            models.Index(fields=["priority", "create_time"]), # 联合索引
            models.Index(fields=['priority'], name='priority_idx'), # 单独的索引
            # 关于索引名称:如果没有提供name,Django将自动生成一个索引名称。 为了兼容不同的数据库,索引名称不能超过30个字符,不能以数字(0-9)或下划线(_)开头。
        ]

        # 联合唯一索引
        unique_together = (("name", "priority"),)

        # 执行Person.objects.all()
        # 会先按照权限priority字段降序排,权限相同则按照创建时间create_time字段升序排
        ordering = ('-priority','create_time') 

        # 主界面的表名会默认加s,我们可以自己指定,如下
        verbose_name_plural = "table1"

        # admin中详情页显示的表名称
        verbose_name = '表1'
    
        # 执行Person.objects.latest()
        # 会先按照权限priority字段降序排列,权限相同则按照id字段升序排列,然后取最后一个
        get_latest_by=['create_time','id'] 


    def __str__(self):
        return '%s:%s:%s' %(self.name,self.priority,self.create_time)

猜你喜欢

转载自blog.csdn.net/weixin_47035302/article/details/131455985