Haste makes waste

自强学堂-Django基础教程(3)-模型

Posted on By lijun

无意中在网上发现了这个网站自强学堂(作者还是校友),上面整理的教程都很不错,正好适合刚学完DjangoBasic篇之后的扩展。本系列作为该自强学堂教程的笔记,为节省时间,与DjangoBasic部分重叠的话,会省略。

注意:

Django的标签导致Jekyll无法编译,都修改为了[%[{,使用时需要替换将[替换为{


关于模型,在DjangoBasic的 here 有所涉及。

1. 模型操作的基本函数-新建对象

假设已经建立了一个模型Person,有name和age的field。

  1. Person.objects.create(name=name,age=age),创建对象时直接写入所需字段

  2. 先新建对象,再保存

p = Person(name="WZ", age=23)
p.save()
  1. 新建对象,再赋值最后保存
p = Person(name="TWZ")
p.age = 23
p.save()
  1. Person.objects.get_or_create(name="WZT", age=23),这种方法是防止重复很好的方法,但是速度要相对慢些,返回一个元组,第一个为Person对象,第二个为True或False, 新建时返回的是True, 已经存在时返回False.

2. 模型操作的基本函数-查询对象

  1. Person.objects.all(),获取所有对象

  2. Person.objects.all()[:10] ,切片操作,获取10个人,不支持负索引,切片可以节约内存

  3. Person.objects.get(name=name),获取指定name的对象

  4. Person.objects.filter(name="abc") ,名称严格等于 “abc” 的人

  5. Person.objects.filter(name__exact="abc") ,名称严格等于 “abc” 的人

  6. Person.objects.filter(name__iexact="abc") ,名称为 abc 但是不区分大小写,可以找到 ABC, Abc, aBC,这些都符合条件

  7. Person.objects.filter(name__contains="abc"),名称中包含 “abc”的人

  8. Person.objects.filter(name__icontains="abc"),名称中包含 “abc”,且abc不区分大小写

  9. Person.objects.filter(name__regex="^abc"),正则表达式查询

  10. Person.objects.filter(name__iregex="^abc"),正则表达式不区分大小写

  11. Person.objects.exclude(name__contains="WZ"), 排除包含 WZ 的Person对象

  12. Person.objects.filter(name__contains="abc").exclude(age=23),找出名称含有abc, 但是排除年龄是23岁的

3. QuerySet API

前面讲解了基本的创建和查询,这里专门讲解数据库相关的接口,示例基于如下的模型:

from django.db import models

# Create your models here.
class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self):  
        return self.name

class Author(models.Model):
    name = models.CharField(max_length=50)
    email = models.EmailField()

    def __str__(self):  
        return self.name

class Entry(models.Model):
    blog = models.ForeignKey(Blog)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField()
    authors = models.ManyToManyField(Author)

    n_comments = models.IntegerField()
    n_pingbacks = models.IntegerField()
    rating = models.IntegerField()

    def __str__(self):  
        return self.headline

3.1 创建对象,与第一部分的方式一样

# 生成数据库sql文件
(myvenv) C:\Users\utane\OneDrive\pythonCode\zqxtTest>python manage.py makemigrations learn
Migrations for 'learn':
  learn\migrations\0001_initial.py
    - Create model Author
    - Create model Blog
    - Create model Entry

# 执行数据库sql文件
(myvenv) C:\Users\utane\OneDrive\pythonCode\zqxtTest>python manage.py migrate learn
Operations to perform:
  Apply all migrations: learn
Running migrations:
  Applying learn.0001_initial... OK

(myvenv) C:\Users\utane\OneDrive\pythonCode\zqxtTest>python manage.py shell

# 引入对应的模型
>>> from learn.models import Blog,Author,Entry

# 创建并保持对象
>>> b = Blog(name="lijun",tagline='love')
>>> b.save()

# 创建对象-方法1
>>> Author.objects.create(name="lijunhaha",email="lijun@qq.com")
<Author: lijunhaha>

# 创建对象-方法2
>>> wangling = Author(name="wangling",email="wanging@qq.com")
>>> wangling.save()

# 创建对象-方法3
>>> utane = Author()
>>> utane.name = "utane"
>>> utane.email = "utane@qq.com"
>>> utane.save()

# 创建对象-方法4
>>> Author.objects.get_or_create(name="utasuke",email="utasuke@qq.com")
(<Author: utasuke>, True)
>>>

3.2 相关对象查询以及赋值

如下代码的条件是entry已经被创建了,这里先取出,再将外key关联到其他对象(生成的blog对象)。

>>> entry = Entry.objects.get(pk=1)
>>> lj_blog = Blog.objects.get(name="lijun")
>>> entry.blog = lj_blog
>>> entry.save()

3.3 获取对象的方法

参考第二部分

3.4 删除/更新

与上面类似,先获取对象或是对象集合,然后delete就可以。危险操作,正式场合务必谨慎!

Person.objects.filter(name__contains="abc").delete()

people = Person.objects.filter(name__contains="abc")
people.delete()

批量更新,适用于 .all() .filter() .exclude() 等后面 (危险操作,正式场合操作务必谨慎)

Person.objects.filter(name__contains="abc").update(name='xxx') # 名称中包含 "abc"的人 都改成 xxx
Person.objects.all().delete() # 删除所有 Person 记录

单个 object 更新,适合于 .get(), get_or_create(), update_or_create() 等得到的 obj,和新建很类似。

twz = Author.objects.get(name="WeizhongTu")
twz.name="WeizhongTu"
twz.email="tuweizhong@163.com"
twz.save()  # 最后不要忘了保存!!!

3.5 其他属性


# 数量查询
>>> aut = Author.objects.all()
>>> aut.count()
4

# QuerySet转换为列表
>>> list(aut)
[<Author: lijunhaha>, <Author: wangling>, <Author: utane>, <Author: utasuke>]
>>> aut
<QuerySet [<Author: lijunhaha>, <Author: wangling>, <Author: utane>, <Author: utasuke>]>

# 切片及数量查询
>>> Author.objects.all()[:2].count()
2
>>> len(aut)
4

# 是否存在的验证
>>> aut.exists()
True
>>> Entry.objects.all().exists()
False

# 迭代(直接打印对象a,调用的是其__str__)
>>> for a in aut:
...   print(a)
...
lijunhaha
wangling
utane
utasuke
>>>

3.6 查询结果排序

# 正向排序
>>> aut.order_by("name")
<QuerySet [<Author: lijunhaha>, <Author: utane>, <Author: utasuke>, <Author: wangling>]>
# 反向排序
>>> aut.order_by("-name")
<QuerySet [<Author: wangling>, <Author: utasuke>, <Author: utane>, <Author: lijunhaha>]>
>>>

3.7 链式查询

>>> Author.objects.all()
<QuerySet [<Author: lijunhaha>, <Author: wangling>, <Author: utane>, <Author: utasuke>]>
>>> Author.objects.filter(name__contains="uta")
<QuerySet [<Author: utane>, <Author: utasuke>]>
>>> Author.objects.filter(name__contains="uta").filter(email__contains="suke")
<QuerySet [<Author: utasuke>]>
>>> Author.objects.filter(name__contains="uta").exclude(email__contains="suke")
<QuerySet [<Author: utane>]>


3.8 不支持负向索引

# 先反向,再取前两个,以达到实际取最后两个的效果
>>> Author.objects.all().reverse()[:2]
<QuerySet [<Author: lijunhaha>, <Author: wangling>]>

# 取ID最大的两个,这个id是django自动添加的主key
>>> Author.objects.order_by("-id")[:2]
<QuerySet [<Author: utasuke>, <Author: utane>]>

# 取id最小的两个(最早添加的两个元素)
>>> Author.objects.order_by("id")[:2]
<QuerySet [<Author: lijunhaha>, <Author: wangling>]>
>>>

3.9 去除重复元素

本身Queryset中不会出现重复记录,但是如果多个结果集合并后,就有可能重复,这时用distinct()函数去除重复元素。

qs1 = Pathway.objects.filter(label__name='x')
qs2 = Pathway.objects.filter(reaction__name='A + B >> C')
qs3 = Pathway.objects.filter(inputer__name='WeizhongTu')

# 合并到一起
qs = qs1 | qs2 | qs3
这个时候就有可能出现重复的

# 去重方法
qs = qs.distinct()

4. QuerySet进阶

4.0 准备工作

  • 准备数据模型
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=50)
    qq = models.CharField(default="qq",max_length=10)
    addr = models.TextField(default="address")
    email = models.EmailField(default='***@***.com')

    def __str__(self):
        return self.name

class Article(models.Model):
    title = models.CharField(max_length=50)
    author = models.ForeignKey(Author)
    content = models.TextField()
    score = models.IntegerField()  # 文章的打分
    tags = models.ManyToManyField('Tag')

    def __str__(self):
        return self.title

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

    def __str__(self):
        return self.name
  • 生成数据表
python manage.py makemigrations
python manage.py migrate
  • 在manage.py同目录下创建db的初始化文件 initdb.py
from __future__ import unicode_literals
import random
from zqxtTest.wsgi import *
from learn.models import Author, Article, Tag

author_name_list = ['WeizhongTu', 'twz915', 'dachui', 'zhe', 'zhen']
article_title_list = ['Django 教程', 'Python 教程', 'HTML 教程']

def create_authors():
    for author_name in author_name_list:
        author, created = Author.objects.get_or_create(name=author_name)
        # 随机生成9位数的QQ,
        author.qq = ''.join(
            str(random.choice(range(10))) for _ in range(9)
        )
        author.addr = 'addr_%s' % (random.randrange(1, 3))
        author.email = '%s@ziqiangxuetang.com' % (author.addr)
        author.save()

def create_articles_and_tags():
    # 随机生成文章
    for article_title in article_title_list:
        # 从文章标题中得到 tag
        tag_name = article_title.split(' ', 1)[0]
        tag, created = Tag.objects.get_or_create(name=tag_name)

        random_author = random.choice(Author.objects.all())

        for i in range(1, 21):
            title = '%s_%s' % (article_title, i)
            article, created = Article.objects.get_or_create(
                title=title, defaults={
                    'author': random_author,  # 随机分配作者
                    'content': '%s 正文' % title,
                    'score': random.randrange(70, 101),  # 随机给文章一个打分
                }
            )
            article.tags.add(tag)

def main():
    create_authors()
    create_articles_and_tags()

if __name__ == '__main__':
    main()
    print("Done!")
  • 执行上述py,生成样本数据库表 python initdb.py

  • 确认是否导入成功

python manage.py shell
>>> from blog.models import Article, Author, Tag
>>> Article.objects.all()

4.1 查看 Django queryset 执行的 SQL

有时我们需要查看Django到底是如何处理数据的,如下的语句能查询其生成的SQL语句。

>>> print(str(Author.objects.all().query))
SELECT "learn_author"."id", "learn_author"."name", "learn_author"."qq", "learn_author"."addr", "learn_author"."email" FROM "learn_author"
>>> print(str(Author.objects.all().filter(name__contains="wei").query))
SELECT "learn_author"."id", "learn_author"."name", "learn_author"."qq", "learn_author"."addr", "learn_author"."email" FROM "learn_author" WHERE "learn_author"."name" LIKE %wei% ESCAPE '\'
>>> print(str(Author.objects.all().filter(name__contains="wei")))
<QuerySet [<Author: WeizhongTu>]>
  • 生成的表名,是app名加model名(因为一个db中可能包含了不同的app,各个app的model可能会有重名的case)
  • 自动增加了id字段。

4.2 values_list 获取元组形式的结果集

# 获取所有作者的name和qq
>>> authors = Author.objects.values_list('name','qq')

# 只取前4个,结果集是QuerySet
>>> authors[:4]
<QuerySet [('lijunhaha', 'qq'), ('wangling', 'qq'), ('utane', 'qq'), ('utasuke', 'qq')]>

# 将其转化为了lsit,结果集是list
>>> list(authors[:4])
[('lijunhaha', 'qq'), ('wangling', 'qq'), ('utane', 'qq'), ('utasuke', 'qq')]

# 取一个字段
>>> authors = Author.objects.values_list('name')
>>> authors
<QuerySet [('lijunhaha',), ('wangling',), ('utane',), ('utasuke',), ('WeizhongTu',), ('twz915',), ('dachui',), ('zhe',), ('zhen',)]>

# 取一个字段,明确指定flat为true,看返回的QuerySet的区别
>>> authors = Author.objects.values_list('name',flat=True)
>>> authors
<QuerySet ['lijunhaha', 'wangling', 'utane', 'utasuke', 'WeizhongTu', 'twz915', 'dachui', 'zhe', 'zhen']>

# 将其结果集前4个转换为list
>>> list(authors[0:4])
['lijunhaha', 'wangling', 'utane', 'utasuke']

4.3 values 获取字典形式的结果

注意返回的结果集的数据类型,现在是字典形式(key:value)

>>> Author.objects.values('name','qq')[:4]
<QuerySet [{'name': 'lijunhaha', 'qq': 'qq'}, {'name': 'wangling', 'qq': 'qq'}, {'name': 'utane', 'qq': 'qq'}, {'name': 'utasuke', 'qq': 'qq'}]>
>>> list(Author.objects.values('name','qq')[:4])
[{'name': 'lijunhaha', 'qq': 'qq'}, {'name': 'wangling', 'qq': 'qq'}, {'name': 'utane', 'qq': 'qq'}, {'name': 'utasuke', 'qq': 'qq'}]

注意:

  1. values_list 和 values 返回的并不是真正的 列表 或 字典,也是 queryset,他们也是 lazy evaluation 的(惰性评估,通俗地说,就是用的时候才真正的去数据库查)
  2. 如果查询后没有使用,在数据库更新后再使用,你发现得到在是新内容!!!如果想要旧内容保持着,数据库更新后不要变,可以 list 一下
  3. 如果只是遍历这些结果,没有必要 list 它们转成列表(浪费内存,数据量大的时候要更谨慎!!!)

4.4 extra 实现 别名,条件,排序等

extra 中可实现别名,条件,排序等,后面两个用 filter, exclude 一般都能实现,排序用 order_by 也能实现。我们主要看一下别名这个 比如 Author 中有 name, Tag 中有 name 我们想执行:

# 给name取个别名 tag_name
>>> tags = Tag.objects.all().extra(select={"tag_name":"name"})
>>> tags[0].name
'Django'
>>> tags[0].tag_name
'Django'

# 查看下上面的SQL语句,发现查询了name两次,有重复
>>> tags = Tag.objects.all().extra(select={"tag_name":"name"}).query
>>> print(tags)
SELECT (name) AS "tag_name", "learn_tag"."id", "learn_tag"."name" FROM "learn_tag"

# 通过defer去除重复的name
>>> tags = Tag.objects.all().extra(select={"tag_name":"name"}).defer('name')
>>> tags[0].name
'Django'

# 上面查询的sql语句,确实只有一次name了
>>> print(tags.query)
SELECT (name) AS "tag_name", "learn_tag"."id" FROM "learn_tag"

下面的内容,需要的时候再来查阅,暂时跳过

4.5 annotate 聚合 计数,求和,平均数等

参考 5. annotate 聚合 计数,求和,平均数等

参考 6. select_related 优化一对一,多对一查询

参考 7. prefetch_related 优化一对多,多对多查询

4.8 defer 排除不需要的字段

参考 8. defer 排除不需要的字段

4.8 only 仅选择需要的字段

参考 9. only 仅选择需要的字段

4.9 自定义聚合功能

参考 10. 自定义聚合功能