Javascript is required
Django对象模型关系

模型关系

Django模型默认在关系数据库系统(RDBMS)上的关系。

Django遵循三种模型关系:

  1. 一对一关系
  2. 一对多关系
  3. 多对多关系

一对一关系

OneToOneField

一个表中的一个记录与另一个表中的一个记录相关联。并且仅与另一个表的记录相关联。

比如用户表与用户扩展表

image.png

class User(models.Model):
    username = models.CharField(max_length=20, default='')
    password = models.CharField(max_length=200, default='')

    def __str__(self):
        return " User: {}".format(self.username)

class UserExtension(models.Model):
    nickname = models.CharField(max_length=50)
    sex = models.CharField(max_length=50)
    age = models.IntegerField()

    user = models.OneToOneField("User", on_delete=models.CASCADE)

image.png

image.png

其实是为我们默添加了一个user_id 来和user表来进行关联。并且这个外键数据表中必须是唯一的。来保证一对一。

其本质也是一个外键,只不过这个外键有了一个唯一约束,来实现一对一。

如果想要反向引用 模型的名字转化为小写的名字来访问比如模型类UserExtension 则查询 xxx.userextension

查询

# 查看扩展表中的对应用户信息
ex = UserExtension.objects.first()
print(ex.user)
# 通过用户获取扩展表信息
user = User.objects.first() # 获取一个用户
print(user) 

res = user.userextension 
print(res)
  • 当然也可以为OneToOneField添加一个related_name

后续的操作就可以通过自定义的属性来获取数据。

image.png

user = User.objects.first() # 获取一个用户
print(user)

res = user.gugu
print(res)

获取某个分类下的所有文章

category =  Category.objects.all()
category.article_set.all() # 字段_set

一对多关系

ForeignKey

一个表的一个记录可以与另一个表一个或多个记录相关联。

实现方式

Django为我们专门提供了一个字段OneToOneField来实现一对一操作

多对一的关系,通常被称为外键。

数据库设计中,多对一(或一对多)是指一个或多个实体之间的关系,其中单个实体可以具有许多连接的实体

外键需要两个位置参数,一个是关联的模型,另一个是on_delete选项(使用外键引用数据的被删除了,采取什么策略。)。

实际上,在目前版本中,on_delete选项也可以不设置,但Django极力反对如此,因此在Django2.0版本后,该选项会设置为必填

在MySQL中,表有两种引擎,一种是InnoDB,另外一种是msisam

如果使用的InnoDB,是支持外键约束的。

Tip: 外键要定义在‘多’的一方!

外键删除操作

如果一个模型使用了外键,那么对方在哪个模型被删除后,该进行什么操作。可以用过on_delete来指定

  • CASCADE 级联操作。外键对应的表删除了,这条记录也会被删除。
  • PROTECT 受保护。只要这个数据有外键数据引用,就不能删除外键的哪个数据。
  • SET_NULL 设置为空。 可以被删除,设置外键的表的引用索引会设置为null
  • SET_DEFAULT 设置默认
  • SET() 如果外键那个数据被删除了,获取SET()函数的值来作为外键的值
  • DO_NOTHING 不采取任何行为

image.png

准备工作

打开MySQL查看数据支持引擎

mysql> show engines;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine             | Support | Comment                                                        | Transactions | XA   | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| InnoDB             | YES     | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        |
| MRG_MYISAM         | YES     | Collection of identical MyISAM tables                          | NO           | NO   | NO         |
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables      | NO           | NO   | NO         |
| BLACKHOLE          | YES     | /dev/null storage engine (anything you write to it disappears) | NO           | NO   | NO         |
| MyISAM             | DEFAULT | MyISAM storage engine                                          | NO           | NO   | NO         |
| CSV                | YES     | CSV storage engine                                             | NO           | NO   | NO         |
| ARCHIVE            | YES     | Archive storage engine                                         | NO           | NO   | NO         |
| PERFORMANCE_SCHEMA | YES     | Performance Schema                                             | NO           | NO   | NO         |
| FEDERATED          | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
9 rows in set (0.00 sec)

设置InnoDB为默认引擎:在配置文件my.ini中的 mysqld 下面加入default-storage-engine=INNODB

案例

比如有一个用户表和一个文章表两个模型。一个用户可以发布多个文章,一个文章只能有一个作者。通过外键来引用。相关代码

from django.db import models

# Create your models here.
class User(models.Model):
    username = models.CharField(max_length=20, default='')
    password = models.CharField(max_length=200, default='')

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()

    user = models.ForeignKey("User", on_delete=models.CASCADE)

image.png

自动生成外键user_id

image.png

image.png

视图

from django.http import HttpResponse
from .models import  User, Article

# 添加操作
def add(request):
    user = User(username="张三", password="123456")
    user.save() # 保存
    article = Article(title='文章标题1', content='文章噢!')
    article.user = user
    article.save()

    return HttpResponse("Add Ok!")

# 删除操作
def delete(request):
    user = User(pk=1)
    user.delete()
    return HttpResponse("Delete")

当我们删除了用户ID为1的记录,那么查看文章表会发现对应的ID文章也会被删除掉。

当我们操作用户表,也会影响文章表。

案例

class User(models.Model):
    username = models.CharField(max_length=20, default='')
    password = models.CharField(max_length=200, default='')

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()

    user = models.ForeignKey("User", on_delete=models.CASCADE)

在写文章的时候指定作者。

user = User(username="张三", password="123456")
user.save()

article = Article(title='文章标题1', content='文章噢!')

article.user = user # 指定文章的作者是上面保存的User
article.save()

查询时候

user本来没有article_set的字段的但是Django为我们自动生成了。

def search(request):
    # 获取某个用户下所有文章
    user = User.objects.first() # 获取一个用户
    print(user)

    artlist = user.article_set.all() # 获取用户的文章
    print(artlist)


    return HttpResponse("Search")

>>> 
 User: 李四
<QuerySet [<Article: Article: 文章标题1>, <Article: Article: 文章标题3>]>

添加操作

那么我们如何将某个文章写入到用户呢?

def add(request):
    user = User.objects.first() # 获取一个用户
    print(user)
  
    article = Article(title="bbb", content="asdasd5")

    user.article_set.add(article, bulk=False) # 用户添加一个文章
    
    # bulk = False 表示上面的article可以不用保存就可以存储。Django已经为我们做了操作

    return HttpResponse("Add Success")

多对多的关系

ManyToManyField

案例

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()

    tags = models.ManyToManyField("Tag", related_name="tags")

    def __str__(self):
        return "Article: {}".format(self.title)

class Tag(models.Model):
    name = models.CharField(max_length=50)

在数据库层面,实际为多对多的关系建立了一个中间表。这个表分别定义了两个外键。引用两个表的主键。

image.png

image.png

获取操作

# 获取某个文章下的所有标签
article = Article.objects.first()

taglist = article.tags.all()
print(taglist)

image.png

保存操作

article = Article.objects.first()
tag = Tag('热门文章')
tag.save()

article.tag_set.add(tag) 

API

用于关联对象反向引用模型的名称。以前面车和工厂的例子解释,就是从工厂反向关联到车的关系名称。

通常情况下,这个参数我们可以不设置,Django会默认以模型的小写加上_set作为反向关联名,比如对于工厂就是car_set,如果你觉得car_set还不够直观

反向关联查询名。用于从目标模型反向过滤模型对象的名称。

技巧

Django外键查询及@property在model中的妙用

有两张表,Person表是主表,Car是字表,Car表外键至Person表

class Person(models.Model):
    age = models.IntegerField()
    name = models.CharField(max_length=100)

    @property
    def all_cars(self):
        return self.cars.all()

    @property
    def message(self):
        return '{}-{}'.format(self.name, self.age)

    class Meta:
        db_table = 'person'

    def __str__(self):
        return "<Person: {}>".format(self.name)


class Car(models.Model):
    owner = models.ForeignKey(Person, on_delete=models.CASCADE, related_name='cars')
    name = models.CharField(max_length=64)
    price = models.FloatField()

    class Meta:
        db_table = 'car'
# 子表查询主表
In [3]: car = Car.objects.get(id=1)
    
In [4]: car
Out[4]: <Car: Car object (1)>
        
In [6]: print(car.owner.name)
Luck

# 主表查子表
    # 方式一
    # Django默认每个主表对象都有一个外键的属性
    # 可以通过它来查询所有属于主表的子表信息
    # 查询方式:主表.子表_set()
    # 返回值为一个queryset对象
luck.car_set().all()
    
    # 方式二:
    # 通过在外键中设置related_name属性值既可
luck.cars.all()

    # 方式三:
    # 通过@property装饰器在model中预定义方法实现
luck = Person.objects.filter(id=1).first()
print(luck.all_cars)

其他

引用其他APP下的模型

如果想要引用另外APP下的模型。

格式

app.模型的名字

引用自身模型

如果模型的外键引用自己的这个模型。那么to参数设置为self。或者是这个模型的名字。在评论模型中,一般评论都可以进行二级评论,既可以针对另一个评论进行评论。那么可以定义模型的时候就需要外键来引用自身。

yuque_diagram