Python之Django 模型Model

1. 定义模型

1.1 定义模型

在项目子应用的models.py 文件中定义模型类

1) 数据库表名

  • 模型类如果未指明表名,Django默认以小写app应用名_小写模型类名为数据库表名。
  • 可通过内部类Meta的db_table指明数据库表名。

2) 关于主键

  • django会为表创建自动增长的主键列,每个模型只能有一个主键列。
  • 如果使用选项设置某属性为主键列后(primary_key=True)django不会再创建自动增长的主键列。
  • 默认创建的主键列属性为id,可以使用pk代替,pk全拼为primary key。

3) 属性命名限制

  • 不能是python的保留关键字。
  • 不允许使用连续的下划线,这是由django的查询方式决定的。
  • 定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:
    • 属性=models.字段类型(选项)

4)字段类型

  • 使用方式为model.XXXField

  • 详情见官网说明:字段类型详情

5) 选项

  • 字段类型如果为models.CharField 必须指定max_length属性

  • choices为一个元组,其内的元素也是元组。每个元组中的第一个元素是要在模型上设置的实际值,第二个元素是人可读的名称

  • 详情见官网说明:字段选项详情

6) 外键

  • 外键字段必须制定 on_delete属性,在django.db.models中包含了可选常量:
    • CASCADE级联,删除主表数据时连通一起删除外键表中数据

    • PROTECT保护,通过抛出ProtectedError异常,来阻止删除主表中被外键应用的数据

    • SET_NULL设置为NULL,仅在该字段null=True允许为null时可用

    • SET_DEFAULT设置为默认值,仅在该字段设置了默认值时可用

    • SET()设置为特定值或者调用特定方法

    • DO_NOTHING不做任何操作,如果数据库前置指明级联性,此选项会抛出IntegrityError异常

  • 详情见官网说明:外键字段

如在 BMS项目的book子应用中的models.py 中定义模型类:

from django.db import models

# Create your models here.
class BookInfo(models.Model):
    bname = models.CharField(max_length=128,unique=True, verbose_name='书名')
    bpub_date = models.DateTimeField(null=True, verbose_name='发布日期')
    bread_count = models.IntegerField(default=0, verbose_name='阅读量')
    bcomment_count = models.IntegerField(default=0, verbose_name='评论量')
    is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')

    class Meta:
        db_table = 'bookinfo'   # 指明数据库表名 默认 appname_modelname
        verbose_name = '图书信息'  # 在admin站点中显示的名称

    def __str__(self):
        """定义每个数据对象的显示信息"""
        return self.bname


class HeroInfo(models.Model):
    GENDER_CHOICES = (
        ('MALE' ,'男'),
        ('FEMALE', '女')
    )
    hname = models.CharField(max_length=128,verbose_name='人物名字')
    hgender = models.CharField(max_length=6,choices=GENDER_CHOICES, verbose_name='性别')
    hdesc = models.CharField(max_length=1024,null=True,verbose_name='图书描述信息')
    # 外键约束
    book = models.ForeignKey(to=BookInfo, on_delete=models.CASCADE, verbose_name='所属书籍')
    is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')

    class Meta:
        db_table = 'heroinfo'
        verbose_name = '图书人物信息'

    def __str__(self):
        return self.hname

1.2 模型迁移

1)生成迁移文件

python manage.py makemigrations

2)迁移模型到数据库(根据模型定义建表)

python manage.py migrate

3)添加一些测试数据

insert into bookinfo(bname, bpub_date, bread_count,bcomment_count, is_delete) values
('射雕英雄传', '1980-5-1', 12, 34, 0),
('天龙八部', '1986-7-24', 36, 40, 0),
('笑傲江湖', '1995-12-24', 20, 80, 0),
('雪山飞狐', '1987-11-11', 58, 24, 0);

insert into heroinfo(hname, hgender, book_id, hdesc, is_delete)  values
    ('孙悟空', 'MALE', 1, '七十二变', 0),
    ('猪八戒', 'MALE', 1, '三十二变', 0),
    ('唐僧', 'MALE', 1, '唠叨', 0),
    ('郭靖', 'MALE', 2, '降龙十八掌', 0),
    ('黄蓉', 'FEMALE', 2, '打狗棍法', 0),
    ('黄药师', 'MALE', 2, '弹指神通', 0),
    ('欧阳锋', 'MALE', 2, '蛤蟆功', 0),
    ('梅超风', 'FEMALE', 2, '九阴白骨爪', 0),
    ('乔峰', 'MALE', 3, '降龙十八掌', 0),
    ('段誉', 'MALE', 3, '六脉神剑', 0),
    ('虚竹', 'MALE', 3, '天山六阳掌', 0),
    ('王语嫣', 'FEMALE', 3, '神仙姐姐', 0),
    ('令狐冲', 'MALE', 4, '独孤九剑', 0),
    ('任盈盈', 'FEMALE', 4, '弹琴', 0),
    ('岳不群', 'MALE', 4, '华山剑法', 0),
    ('东方不败', 'FEMALE', 4, '葵花宝典', 0),
    ('胡斐', 'MALE', 5, '胡家刀法', 0),
    ('苗若兰', 'FEMALE', 5, '黄衣', 0),
    ('程灵素', 'FEMALE', 5, '医术', 0),
    ('袁紫衣', 'FEMALE', 5, '六合拳', 0);

3. 增删改

3.1 django 的 shell 工具

Django的manage工具提供了shell命令,帮助我们配置好当前工程的运行环境(如连接好数据库等),以便可以直接在终端中执行测试python语句。

通过如下命令进入shell

python manage.py shell

3.2 增加

增加有两种方式

  • save 通过创建模型类对象,执行对象的save()方法保存到数据库中。
  • create 通过模型类.objects.create()保存,返回创建的对象的QueryDict

1)save

# 启动shell工具
#python manage.py shell
>>> from book.models import BookInfo, HeroInfo
>>> book = BookInfo(bname='Python入门',bpub_date='2021-12-03')
>>> book.save()

Python之Django 模型Model_第1张图片
2)create

>>> HeroInfo.objects.create(hname='coder',hgender='MALE',hdesc='007',book=book)
<HeroInfo: coder>

3.3 修改

修改更新有两种方法

  • save 修改模型类对象的属性,然后执行save()方法
  • update 使用模型类.objects.filter().update(),会返回受影响的行数

1)save

>>> hero = HeroInfo.objects.get(hname='coder')
>>> hero.hname='杰克马'
>>> hero.save()

2)update

>>> HeroInfo.objects.filter(hname='杰克马').update(hdesc='我对钱没有概念')
1

3.4 删除

删除有两种方法

  • 模型类对象delete
  • 模型类.objects.filter().delete()

1)模型类对象delete

>>> hero = HeroInfo.objects.get(hname='杰克马')
>>> hero.delete()
(1, {'book.HeroInfo': 1})

2) 模型类.objects.filter().delete()

>>> BookInfo.objects.filter(bname='Python入门').delete()
(1, {'book.BookInfo': 1})

4. 查询

  • 基础条件查询
  • F和Q对象
  • 聚合函数和排序函数
  • 关联查询
  • 查询集QuerySet
  • 详情请参考 官方文档

4.1 基础条件查询

4.1.1 基本查询

  • get 查询单一结果,如果不存在会抛出模型类.DoesNotExist异常。

返回值是一个模型类对象,因此在前面修改或删除时才不能使用 模型类.objects.get().xxx()

  • all 查询多个结果。返回值是一个QuerSet

  • count 查询结果数量。

>>> b1 = BookInfo.objects.get(pk=1)
>>> type(b1)
<class 'book.models.BookInfo'>
>>> b1
<BookInfo: 西游记>

>>> b2 = BookInfo.objects.all()
>>> type(b2)
<class 'django.db.models.query.QuerySet'>
>>> b2
<QuerySet [<BookInfo: 西游记>, <BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 笑傲江湖>, <BookInfo: 雪山飞狐>, <BookInfo: 金瓶梅>]>

>>> BookInfo.objects.count()
6

4.1.2 过滤查询

实现SQL中的where功能,包括

  • filter过滤出多个结果
  • exclude排除掉符合条件剩下的结果
  • get过滤单一结果

对于过滤条件的使用,上述三个方法相同,故仅以filter进行讲解。

过滤条件的表达语法如下:

属性名称__比较运算符=值
属性名称和比较运算符间使用两个下划线,所以属性名不能包括多个下划

1)相等

  • exact:表示判等。可简写为 =
# 例:查询编号为1的图书。
BookInfo.objects.filter(id__exact=1)
# 可简写为
BookInfo.objects.filter(id=1)

2) 模糊查询

  • contains 是否包含

说明:如果要包含%无需转义,直接写即可。

# 例:查询书名以'部'结尾的图书
>>> BookInfo.objects.filter(bname__contains='部')
  • startwith、endswith 以指定值开头或结尾
# 查询书名以'部'结尾的图书
>>> BookInfo.objects.filter(bname__endswith='部')

以上运算符都区分大小写,在这些运算符前加上i表示不区分大小写,如iexact、icontains、istartswith、iendswith.

3) 空查询

  • isnull:是否为null。
# 例:查询书名为空的图书。
>>> BookInfo.objects.filter(bname__isnull=True)

4) 范围查询

  • in:是否包含在范围内。
# 例:查询编号为1或3或5的图书
>>> BookInfo.objects.filter(id__in=[1,3,5])

5) 比较查询

  • gt大于 (greater then)
  • gte大于等于 (greater then equal)
  • lt小于 (less then)
  • lte小于等于 (less then equal)
# 例:查询编号大于3的图书
>>> BookInfo.objects.filter(id__gt=3)

不等于的运算符,使用exclude()过滤器。

# 例:查询编号不等于3的图书
>>> BookInfo.objects.exclude(id__exact=3)

6) 日期查询

year、month、day、week_day、hour、minute、second:对日期时间类型的属性进行运算。

# 例:查询1980年发表的图书。
>>> BookInfo.objects.filter(bpub_date__year=1980)

# 例:查询1990年1月1日后发表的图书。
>>> BookInfo.objects.filter(bpub_date__gt='1990-1-1')

4.2 F对象和Q对象

4.2.1 F对象

之前的查询都是对象的属性与常量值比较,两个属性怎么比较呢?
答:使用F对象,被定义在django.db.models中。

语法如下:

F(‘属性名’)

# 例:查询阅读量大于等于评论量的图书。
>>> from django.db.models import F,Q
>>> BookInfo.objects.filter(bread_count__gt=F('bcomment_count'))

可以在F对象上使用算数运算。

# 例:查询阅读量大于2倍评论量的图书。
>>> BookInfo.objects.filter(bread_count__gt=F('bcomment_count')*2)

4.2.2 Q对象

  • 多个过滤器逐个调用表示逻辑与关系,同sql语句中where部分的and关键字。
    • 与条件查询可在过滤条件后直接跟另一个条件也可以
  • 如果需要实现逻辑或or的查询,需要使用Q()对象结合|运算符
    • 或条件查询只能使用Q对象,Q() | Q()

Q对象被义在django.db.models中。

  • Q对象可以使用&、|连接,&表示逻辑与,|表示逻辑或。
  • Q对象前可以使用~操作符,表示非not。

语法如下:

Q(属性名__运算符=值)

# 查询阅读量大于20,并且编号小于4的图书。
>>> BookInfo.objects.filter(bread_count__gt=20, id__lt=4)
# 或
>>> BookInfo.objects.filter(bread_count__gt=20).filter(pk__lt=4)
# 或
>>> BookInfo.objects.filter(Q(bread_count__gt=20) & Q(pk__lt=4))

# 例:查询阅读量大于20,或编号小于3的图书,只能使用Q对象实现
>>> BookInfo.objects.filter(Q(bread_count__gt=20) | Q(id__lt=3))

# 例:查询编号不等于3的图书。
>>> BookInfo.objects.filter(~Q(id=3))

4.3 聚合函数和排序函数

4.3.1 聚合函数

使用aggregate()过滤器调用聚合函数。聚合函数包括:Avg平均,Count数量,Max最大,Min最小,Sum求和,被定义在django.db.models中。

# 例:查询图书的总阅读量。
>>> from django.db.models import Sum

>>> BookInfo.objects.aggregate(Sum('bread_count'))
{'bread_count__sum': 126}

注意:aggregate的返回值是一个字典类型,格式如下:
{‘属性名__聚合类小写’:值} 如:{‘readcount__sum’: 126}

使用count时一般不使用aggregate()过滤器。而是直接使用count()函数,如BookInfo.objects.count(…)

4.3.2 排序

使用order_by对结果进行排序

# 默认升序
>>> BookInfo.objects.all().order_by('readcount')

# 降序
>>> BookInfo.objects.all().order_by('-readcount')

4.4 关联查询

4.4.1 管理查询

  • 由一到多的访问语法:
    • 一对应的模型类对象.多对应的模型类名小写_set ,如 book.heroinfo_set.all()

例:

>>> b = BookInfo.objects.get(pk=1)
>>> b.heroinfo_set.all()
<QuerySet [<HeroInfo: 孙悟空>, <HeroInfo: 猪八戒>, <HeroInfo: 唐僧>]>

注意:管理查询是需要通过对象关联,不是QueySet

  • 由多到一的访问语法:
    • 多对应的模型类对象.多对应的模型类中的关系类属性名, 如 hero.book

例:

>>> h = HeroInfo.objects.get(hname__startswith='郭靖')
>>> h
<HeroInfo: 郭靖>
>>> h.book
<BookInfo: 射雕英雄传>

  • 访问一对应的模型类关联对象的id语法:
    • 多对应的模型类对象.关联类属性_id
>>> h = HeroInfo.objects.get(hname__startswith='郭靖')
>>> h.book_id
2

4.4.2 管理过滤查询

1) 由多模型类条件查询一模型类数据:

语法如下:

关联模型类名小写__属性名__条件运算符=值
注意:如果没有"__运算符"部分,表示等于。

# 例:查询图书,要求图书人物为"郭靖"
>>> BookInfo.objects.filter(heroinfo__hname='郭靖')
<QuerySet [<BookInfo: 射雕英雄传>]>

# 查询图书,要求图书中人物的描述包含"八"
>>> BookInfo.objects.filter(heroinfo__hdesc__contains='八')
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>]>

2) 由一模型类条件查询多模型类数据:

语法如下:

一模型类关联的属性名__多模型类中对应的属性名__条件运算符=值
注意:如果没有"__运算符"部分,表示等于。

# 查询书名为“天龙八部”的所有人物。
>>> HeroInfo.objects.filter(book__bname='天龙八部')
<QuerySet [<HeroInfo: 乔峰>, <HeroInfo: 段誉>, <HeroInfo: 虚竹>, <HeroInfo: 王语嫣>]>

# 查询图书阅读量大于30的所有人物
>>> HeroInfo.objects.filter(book__bread_count__gt=30)
<QuerySet [<HeroInfo: 乔峰>, <HeroInfo: 段誉>, <HeroInfo: 虚竹>, <HeroInfo: 王语嫣>, <HeroInfo: 胡斐>, <HeroInfo: 苗若兰>, <HeroInfo: 程灵素>, <HeroInfo: 袁紫衣>]>

5 查询结果集QuerySet

5.1 概念

Django的ORM中存在查询集的概念。

查询集,也称查询结果集、QuerySet,表示从数据库中获取的对象集合。

当调用如下过滤器方法时,Django会返回查询集(而不是简单的列表):

  • all():返回所有数据。
  • filter():返回满足条件的数据。
  • exclude():返回满足条件之外的数据。
  • order_by():对结果进行排序。

对查询集可以再次调用过滤器进行过滤,如

>>> books = BookInfo.objects.filter(readcount__gt=30).order_by('pub_date')
>>> books
<QuerySet [<BookInfo: 天龙八部>, <BookInfo: 雪山飞狐>]>

也就意味着查询集可以含有零个、一个或多个过滤器。过滤器基于所给的参数限制查询的结果。

从SQL的角度讲,查询集与select语句等价,过滤器像where、limit、order by子句。

判断某一个查询集中是否有数据:

exists():判断查询集中是否有数据,如果有则返回True,没有则返回False。

5.2 两大特性

1) 惰性执行

创建查询集不会访问数据库,直到调用数据时,才会访问数据库,调用数据的情况包括迭代、序列化、与if合用

例如,当执行如下语句时,并未进行数据库查询,只是创建了一个查询集books

books = BookInfo.objects.all()

继续执行遍历迭代操作后,才真正的进行了数据库的查询

for book in books:
    print(book.name)

2) 缓存

使用同一个查询集,第一次使用时会发生数据库的查询,然后Django会把结果缓存下来,再次使用这个查询集时会使用缓存的数据,减少了数据库的查询次数。

情况一:如下是两个查询集,无法重用缓存,每次查询都会与数据库进行一次交互,增加了数据库的负载。

>>> from book.models import BookInfo
>>> [book.id for book in BookInfo.objects.all()]
(0.000) SELECT "bookinfo"."id", "bookinfo"."bname", "bookinfo"."bpub_date", "bookinfo"."bread_count", "bookinfo"."bcomment_count", "bookinfo"."is_delete" FROM "bookinfo"; args=()[1, 2, 3, 4, 5, 7]
>>> [book.id for book in BookInfo.objects.all()]
(0.000) SELECT "bookinfo"."id", "bookinfo"."bname", "bookinfo"."bpub_date", "bookinfo"."bread_count", "bookinfo"."bcomment_count", "bookinfo"."is_delete" FROM "bookinfo"; args=()[1, 2, 3, 4, 5, 7]

情况二:经过存储后,可以重用查询集,第二次使用缓存中的数据。

>>> books = BookInfo.objects.all()
>>> [book.id for book in books]
(0.000) SELECT "bookinfo"."id", "bookinfo"."bname", "bookinfo"."bpub_date", "bookinfo"."bread_count", "bookinfo"."bcomment_count", "bookinfo"."is_delete" FROM "bookinfo"; args=()[1, 2, 3, 4, 5, 7]
>>> [book.id for book in books]
[1, 2, 3, 4, 5, 7]

3) 限制查询集

可以对查询集进行取下标或切片操作,等同于sql中的limit和offset子句

注意:不支持负数索引。

对查询集进行切片后返回一个新的查询集,不会立即执行查询。

如果获取一个对象,直接使用[0],等同于[0:1].get(),但是如果没有数据,[0]引发IndexError异常,[0:1].get()如果没有数据引发DoesNotExist异常。

# 示例:获取第1、2项,运行查看。
>>> books = BookInfo.objects.all()[0:2]

4) 分页

详情见官方文档

# 导入分页类
>>> from django.core.paginator import Paginator
# 查询数据
>>> books = BookInfo.objects.all()
# 创建分页实例(后面的数字代表每页多少条数据)
>>> paginator = Paginator(books, 2)
# 获取指定页码的数据
>>> page_info = paginator.page(1)
(0.016) SELECT COUNT(*) AS "__count" FROM "bookinfo"; args=()
# 获取本页数据详情
>>> page_info.object_list
(0.000) SELECT "bookinfo"."id", "bookinfo"."bname", "bookinfo"."bpub_date", "bookinfo"."bread_count", "bookinfo"."bcomment_count", "bookinfo"."is_delete" FROM "bookinfo" LIMIT 2; args=()<QuerySet [<BookInfo: 西游记>, <BookInfo: 射雕英雄传>]>
# 获取分页页数
>>> total_page = paginator.num_pages
>>> total_page
3

你可能感兴趣的:(python,django,python,后端)