Django_Models - QLGQ/learning-python GitHub Wiki

Django 模型(数据库)

Django 模型是与数据库相关的,与数据库相关的代码一般写在 models.py 中,Django 支持 sqlite3, MySQL, PostgreSQL等数据库,只需要在settings.py中配置即可,不用更改models.py中的代码,丰富的API极大的方便了使用。

django-admin.py startproject learn_models # 新建一个项目
cd learn_models # 进入到该项目的文件夹
django-admin.py startapp people # 新建一个 people 应用(app)

新建app也可以用 python manage.py startapp people, 需要指出的是,django-admin.py 是安装Django后多出的一个命令,并不是指一个 django-admin.py 脚本在当前目录下。那么project和app什么关系呢,一个项目一般包含多个应用,一个应用也可以用在多个项目中。

将我们新建的应用(people)添加到 settings.py 中的 INSTALLED_APPS中,也就是告诉Django有这么一个应用。

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
 
    'people',
)

我们打开 people/models.py 文件,修改其中的代码如下:

from django.db import models
 
class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()
     
    def __unicode__(self):
    # 在Python3中使用 def __str__(self)
        return self.name

我们新建了一个Person类,继承自models.Model, 一个人有姓名和年龄,这里用到了两种Field。

使用以下命令同步数据库:

python manage.py makemigrations
python manage.py migrate

Django提供了丰富的API, 下面演示如何使用它。

$ python manage.py shell
 
>>> from people.models import Person
>>> Person.objects.create(name="QiangWu", age=24)
<Person: QiangWu>
>>> Person.objects.get(name="QiangWu")
<Person: QiangWu>

name 和 age 等字段中不能有 __(双下划线,因为在Django QuerySet API中有特殊含义(用于关系,包含,不区分大小写,以什么开头或结尾,日期的大于小于,正则等)。也不能有Python中的关键字,name 是合法的,student_name 也合法,但是student__name不合法,try, class, continue 也不合法,因为它是Python的关键字。( import keyword; print(keyword.kwlist) 可以打出所有的关键字)

Objects

新建一个对象的方法有以下几种:

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

获取对象有以下方法:

  1. Person.objects.all()
  2. Person.objects.all()[:10] 切片操作,获取10个人,不支持负索引,切片可以节约内存
  3. Person.objects.get(name=name)
  4. Person.objects.filter(name="abc") # 等于Person.objects.filter(name__exact="abc") 名称严格等于 "abc" 的人
  5. Person.objects.filter(name__iexact="abc") # 名称为 abc 但是不区分大小写,可以找到 ABC, Abc, aBC,这些都符合条件
  6. Person.objects.filter(name__contains="abc") # 名称中包含 "abc"的人
  7. Person.objects.filter(name__icontains="abc") #名称中包含 "abc",且abc不区分大小写
  8. Person.objects.filter(name__regex="^abc") # 正则表达式查询
  9. Person.objects.filter(name__iregex="^abc")# 正则表达式不区分大小写
  10. Person.objects.exclude(name__contains="WZ") # 排除包含 WZ 的Person对象
  11. Person.objects.filter(name__contains="abc").exclude(age=23) # 找出名称含有abc, 但是排除年龄是23岁的
  • 检查Person中是否有对象,应该用Person.objects.all().exists()
  • 用 len(Person.objects.all()) 可以得到Person的数量,但是推荐用Person.objects.count()来查询数量,后者用的是SQL:SELECT COUNT(*)。
  • list(Person.objects.all()) 可以强行将 QuerySet 变成列表。
  • 一般的情况下,QuerySet 中不会出来重复的,重复是很罕见的,但是当跨越多张表进行检索后,结果并到一起,可以会出来重复的值,QuerySet 重复的问题,使用.distinct()去重。
  • defer排除不需要的字段:Article.objects.all().defer('content')
  • only仅选择需要的字段:Author.objects.all().only('name')
  • 原生的SQL查询: authors = Author.objects.raw('select name from blog_author limit 1'),原生SQL查询必须包含 主键!
  • select_related 优化一对一,多对一查询:articles = Article.objects.all().select_related('author')[:10]
  • prefetch_related 优化一对多,多对多查询:articles = Article.objects.all().prefetch_related('tags')[:10]
  • extra 实现别名
比如 Author 中有 name, Tag 中有 name 我们想执行
SELECT name AS tag_name FROM blog_tag;
这样的语句,就可以用 select 来实现,如下:
In [44]: tags = Tag.objects.all().extra(select={'tag_name': 'name'})
In [45]: tags[0].name
Out[45]: u'Django'
In [46]: tags[0].tag_name
Out[46]: u'Django'
我们发现 name 和 tag_name 都可以使用,确认一下执行的 SQL
In [47]: Tag.objects.all().extra(select={'tag_name': 'name'}).query.__str__()
Out[47]: u'SELECT (name) AS "tag_name", "blog_tag"."id", "blog_tag"."name" FROM "blog_tag"'
我们发现查询的时候弄了两次 (name) AS "tag_name" 和 "blog_tag"."name"
如果我们只想其中一个能用,可以用 defer 排除掉原来的 name (后面有讲)
In [49]: Tag.objects.all().extra(select={'tag_name': 'name'}).defer('name').query.__str__()
Out[49]: u'SELECT (name) AS "tag_name", "blog_tag"."id" FROM "blog_tag"'
  • annotate 聚合 计数,求和,平均数等(下例中的跨表查询 Article.objects.values('author__name') 使用的是双下划线!)
5.1 计数
我们来计算一下每个作者的文章数(我们每个作者都导入的Article的篇数一样,所以下面的每个都一样)
In [66]: from django.db.models import Count
In [66]: Article.objects.all().values('author_id').annotate(count=Count('author')).values('author_id', 'count')
Out[66]: <QuerySet [{'count': 20, 'author_id': 1}, {'count': 20, 'author_id': 2}, {'count': 20, 'author_id': 4}]>
这是怎么工作的呢?
In [67]: Article.objects.all().values('author_id').annotate(count=Count('author')).values('author_id', 'count').query.__str__()
Out[67]: u'SELECT "blog_article"."author_id", COUNT("blog_article"."author_id") AS "count" FROM "blog_article" GROUP BY "blog_article"."author_id"'
简化一下SQL: SELECT author_id, COUNT(author_id) AS count FROM blog_article GROUP BY author_id
我们也可以获取作者的名称 及 作者的文章数
In [72]: Article.objects.all().values('author__name').annotate(count=Count('author')).values('author__name', 'count')
Out[72]: <QuerySet [{'count': 20, 'author__name': u'WeizhongTu'}, {'count': 20, 'author__name': u'twz915'}, {'count': 20, 'author__name': u'xiaoming'}]>
这时候会查询两张表,细心的同学会发现,因为作者名称中 blog_author 这张表中,author_id 在 blog_article 表中本身就有的
5.2 求和 与 平均值
5.2.1 求一个作者的所有文章的得分(score)平均值
In [6]: from django.db.models import Avg
In [7]: Article.objects.values('author_id').annotate(avg_score=Avg('score')).values('author_id', 'avg_score')
Out[7]: <QuerySet [{'author_id': 1, 'avg_score': 86.05}, {'author_id': 2, 'avg_score': 83.75}, {'author_id': 5, 'avg_score': 85.65}]>
执行的SQL
In [8]: Article.objects.values('author_id').annotate(avg_score=Avg('score')).values('author_id', 'avg_score').qu
   ...: ery.__str__()
Out[8]: u'SELECT "blog_article"."author_id", AVG("blog_article"."score") AS "avg_score" FROM "blog_article" GROUP BY "blog_article"."author_id"'
5.2.2 求一个作者所有文章的总分
In [12]: from django.db.models import Sum
In [13]: Article.objects.values('author__name').annotate(sum_score=Sum('score')).values('author__name', 'sum_score')
Out[13]: <QuerySet [{'author__name': u'WeizhongTu', 'sum_score': 1721}, {'author__name': u'twz915', 'sum_score': 1675}, {'author__name': u'zhen', 'sum_score': 1713}]>
执行的SQL
In [14]: Article.objects.values('author__name').annotate(sum_score=Sum('score')).values('author__name', 'sum_sco
    ...: re').query.__str__()
Out[14]: u'SELECT "blog_author"."name", SUM("blog_article"."score") AS "sum_score" FROM "blog_article" INNER JOIN "blog_author" ON ("blog_article"."author_id" = "blog_author"."id") GROUP BY "blog_author"."name"'

打印出在数据库中执行的语句

修改settings.py让Django打印出在数据库中执行的语句,在settings.py尾部加上:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'level': 'DEBUG' if DEBUG else 'INFO',
        },
    },
}

这样当DEBUG为True的时候,我们可以看出django执行了什么SQL语句。

Reference

Django QuerySet 进阶