Django教程(二)基础:models

四、模型(数据库)

 Django 模型是与数据库相关的,与数据库相关的代码一般写在 models.py 中。Django 支持 sqlite3, MySQL, PostgreSQL等数据库,如需更换数据库,只需要在settings.py中配置即可,不用更改models.py中的代码。

 1.创建模型,即数据表

# learn/models.py
class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

  同步到数据库

# Django 1.7 及以上的版本需要用以下命令
python manage.py makemigrations
python manage.py migrate

2.数据库操作,也可以在pyhton下,使用python ORM提供的方法。

$ python manage.py shell
from myapp.models import Person
Person.objects.create(name='jerry', age=20)
Person.objects.get(name="jerry")  # 返回<Person: Person object>

 

  使用.objects.get()方法查询后,返回的是一个object;并没有直接得到想要的数据。如何解决?需要添加__str__方法,返回我们能直接识别的内容。添加的方法,对数据库本身没有什么影响。

# learn/models.py
class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

    def __str__(self):
        #python2中,使用__unicode__
        return self.name

  再使用python查询,将得到可识别的结果,显示内容。

Person.objects.get(name='jerry')  #<Person: jerry>

  name 和 age 等字段中不能有 __(双下划线,因为在Django QuerySet API中有特殊含义(用于关系,包含,不区分大小写,以什么开头或结尾,日期的大于小于,正则等)

 

 3.新建一个对象的方法,即添加数据:

方法一: Person.objects.create(name='jason', age=20)  #<Person: jason>
方法二: Person(name='bob', age=30).save()  #没有返回
方法三: Person.objects.get_or_create(name='tom', age=22) #(<Person: tom>, True)

  第三种方法,是防止重复的方法,但速度相对慢一点,先查询后创建。返回一个元组,第一个为Person对象;第二个为True或False,新建返回True,已经存在返回False

4.获取对象有以下方法:

Person.objects.first()
Person.objects.all()
Person.objects.all()[:10]  #切片
Person.objects.get(name='jerry')

  如果需要获取满足某条件的数据,要用到过滤器filter、exclude等

Person.objects.filter(name='jerry')   # 等于Person.objects.filter(name__exact="jerry") 
Person.objects.filter(name__iexact='JeRry')   #忽略大小写
Person.objects.filter(name__contains='jer')   #name中包含'jer'
Person.objects.filter(name__icontains='jer')  #name中包含'jer',且忽略大小写
Person.objects.filter(name__regex='^jer')  #正则查询;以'jer'开头的人
Person.objects.filter(name_iregex='^jer')   #正则查询,且忽略大小写
Person.objects.exclude(name='jerry')  #排除符合条件的
Person.objects.filter(name__contains='jer').exclude(name='jerry')  #链式查询,多次使用过滤器

  以上查询,条件之间都是"AND"关系。还有其它条件关系。

五.QuerySet API

这部分是数据库接口相关的接口(QuerySet API)。从数据库中查询出来的结果一般是一个集合,这个集合叫做 QuerySet。

 以以下数据库为例:

# learn/models.py
class Blog(models.Model):
    name = models.CharField(max_length=100, verbose_name='title')
    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

QuerySet 创建对象的方法,前面已介绍过

from blog.models import *
Blog(name='Beatles Blog', tagline='all...news').save()
Author.objects.create(name='jason')
Author(name='candy').save()
author = Author()
author.name = 'tim'
author.save()
Author.objects.get_or_create(name='dave')

  当有一对多,多对一,或者多对多的关系的时候,先把相关的对象查询出来

>>> from blog.models import Entry
>>> entry = Entry.objects.get(pk=1)
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()

获取对象的方法,前面已介绍过:

Person.objects.all()  # 查询所有
Person.objects.all()[:10]  #切片操作,获取10个人,不支持负索引,切片可以节约内存,不支持负索引,后面有相应解决办法,第7条
Person.objects.get(name="x")  # 名称为 x 的一条,多条会报错get是用来获取一个对象的,如果需要获取满足条件的一些人,就要用到filter
Person.objects.filter(name="abc")  # 等于Person.objects.filter(name__exact="abc") 名称严格等于 "abc" 的人
Person.objects.filter(name__iexact="abc")  # 名称为 abc 但是不区分大小写,可以找到 ABC, Abc, aBC,这些都符合条件
Person.objects.filter(name__contains="abc")  # 名称中包含 "abc"的人
Person.objects.filter(name__icontains="abc")  # 名称中包含 "abc",且abc不区分大小写
Person.objects.filter(name__regex="^abc")  # 正则表达式查询
Person.objects.filter(name__iregex="^abc")  # 正则表达式不区分大小写
# filter是找出满足条件的,当然也有排除符合某条件的
Person.objects.exclude(name__contains="aaa")  # 排除包含 aaa的Person对象
Person.objects.filter(name__contains="abc").exclude(age=23)  # 找出名称含有abc, 但是排除年龄是23岁的

delete删除符合条件的结果:和上面类似,得到满足条件的结果,然后 delete 就可以

Person.objects.filter(name__contains="abc").delete() # 删除 名称中包含 "abc"的人
people = Person.objects.filter(name__contains="abc")
people.delete()   #效果也是一样的,Django实际只执行一条 SQL 语句。

update更新某个内容: 

  批量更新,适用于 .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,和新建很类似。

a = Author.objects.get(name="jerry")
a.name="tom"
a.email="tome@ok.com"
a.save()  # 最后不要忘了保存!!!

QuerySet 是可迭代的.

  说明:(1). 如果只是检查 Entry 中是否有对象,应该用 Entry.objects.all().exists()

  (2). QuerySet 支持切片 Entry.objects.all()[:10] 取出10条,可以节省内存

  (3). 用 len(es) 可以得到Entry的数量,但是推荐用 Entry.objects.count()来查询数量,后者用的是SQL:SELECT COUNT(*)

  (4). list(es) 可以强行将 QuerySet 变成 列表

QuerySet 是可以用pickle序列化到硬盘再读取出来的

>>> import pickle
>>> query = pickle.loads(s)     # Assuming 's' is the pickled string.
>>> qs = MyModel.objects.all()
>>> qs.query = query            # Restore the original 'query'.

QuerySet 查询结果排序:

Author.objects.all().order_by('name')  #按作者排序,升序
Author.objects.all().order_by('-name') # 在 column name 前加一个负号,可以实现倒序,降序

QuerySet 支持链式查询:

Person.objects.filter(name__contains="abc").exclude(age=23)

QuerySet 重复的问题,使用 .distinct() 去重

  一般的情况下,QuerySet 中不会出来重复的,重复是很罕见的,但是当跨越多张表进行检索后,结果并到一起,可以会出来重复的值

qs1 = Pathway.objects.filter(label__name='x')
qs2 = Pathway.objects.filter(reaction__name='A + B >> C')
qs3 = Pathway.objects.filter(inputer__name='jerry')
# 合并到一起
qs = qs1 | qs2 | qs3 #这个时候就有可能出现重复的
qs = qs.distinct()  # 去重方法

 

六、QuerySet 进阶

 准备:mysite/blog/models.py

# _*_ coding: utf-8 _*_
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=50)
    qq = models.CharField(max_length=12, null=True)
    addr = models.TextField(null=True)
    email = models.EmailField(null=True)
    def __str__(self):
        return self.name

class Article(models.Model):
    title = models.CharField(max_length=60)
    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

创建一个初始化数据的脚本,在mysite根目录下:mysite/initdb.py

# _*_ coding: utf-8 _*_
import random
from testproject.wsgi import *
from blog.models import Author, Article, Tag

author_name_list = ['jerry', 'jacky', 'andy', 'vanco', 'bruse']
article_title_list = ['python', 'js', 'java','c']

def create_authors():
    for author_name in author_name_list:
        author, created = Author.objects.get_or_create(name=author_name)
        author.qq = ''.join(
            str(random.choice(range(10))) for _ in range(9)
        )
        author.addr = 'addr_%s' %(random.randrange(1, 3))
        author.email = '%s@qq.com' % (author.addr)
        author.save()

def create_articles_and_tags():
    for article_title in article_title_list:
        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 zhengwen' % title,
                    'score': random.randrange(70, 101),
                }
            )
            article.tags.add(tag)

if __name__ == '__main__':
    create_authors()
    create_articles_and_tags()
    print('done!')

执行初始化数据脚本: # python initdb.py

验证数据是否已添加到数据库中

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

1. 查看 Django queryset 执行的 SQL(1部分)

print(str(Author.objects.all().query))
# SELECT "blog_author"."id", "blog_author"."name", "blog_author"."qq", "blog_author"."addr", "blog_author"."email" FROM "blog_author"
#简单一下,就是SELECT id, name, qq, addr, email FROM blog_author;

当不知道Django做了什么时,可以把执行的 SQL 打出来看看,也可以借助 django-debug-toolbar 等工具在页面上看到访问当前页面执行了哪些SQL,耗时等。还有一种办法就是修改一下 log 的设置,后面会讲到。

print(str(Author.objects.filter(name="jerry").query))
#SELECT "blog_author"."id", "blog_author"."name", "blog_author"."qq", "blog_author"."addr", "blog_author"."email" FROM "blog_author" WHERE "blog_author"."name" = jerry
#简化一下,就是SELECT id, name, qq, addr, email FROM blog_author WHERE name="jerry";

2. 获得的查询结果直接以类似list方式展示(2,3 部分)

(1). 获取元组形式结果:values_list

  比如我们要获取作者的 name 和 qq

  In [7]: authors = Author.objects.values_list('name', 'qq')

  In [8]: authors

  Out[8]: <QuerySet [('jason', None), ('candy', None), ('tim', None), ('dave', None), ('jerry', '542460339'), ('jacky', '270809971'), ('andy', '570664985'), ('vanco', '693364336'), ('bruse', '840487022')]>

  In [9]: list(authors)   #将query转化为列表
Out[9]:
[('jason', None),
 ('candy', None),
 ('tim', None),
 ('dave', None),
 ('jerry', '542460339'),
 ('jacky', '270809971'),
 ('andy', '570664985'),
 ('vanco', '693364336'),
 ('bruse', '840487022')]

  如果只需要 1 个字段,可以指定 flat=True

  In [11]: Author.objects.values_list('name',flat=True)
  Out[11]: <QuerySet ['jason', 'candy', 'tim', 'dave', 'jerry', 'jacky', 'andy', 'vanco', 'bruse']>

  In [12]: list(Author.objects.values_list('name',flat=True))

  Out[12]: ['jason', 'candy', 'tim', 'dave', 'jerry', 'jacky', 'andy', 'vanco', 'bruse']

(2).获取字典形式的结果: values

  In [13]: Author.objects.values('name', 'qq')

  Out[13]: <QuerySet [{'qq': None, 'name': 'jason'}, {'qq': None, 'name': 'candy'},

  In [15]: list(Author.objects.values('name', 'qq'))

  Out[15]:
  [{'name': 'jason', 'qq': None},
   {'name': 'candy', 'qq': None},
   {'name': 'tim', 'qq': None},.................

注意:

1). values_list 和 values 返回的并不是真正的 列表 或 字典,也是 queryset。同时,他们也是 lazy evaluation 的,用的时候才真正的去数据库查

2). 如果查询后没有使用,在数据库更新后再使用,你发现得到的是新内容!!!如果想要旧内容保持着,,可以 转化为list

3). 如果只是遍历这些结果,没有必要 list 它们转成列表,浪费内存

3. 如何在django中给一个字段取一个别名(4. 部分)  

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

这样的语句,就可以用 select 来实现,如下:

tags = Tag.objects.all().extra(select={'tag_name': 'name'})
tags[0].name    # python
tags[0].tag_name   # python

我们发现 name 和 tag_name 都可以使用,确认一下执行的 SQL

In [20]: Tag.objects.all().extra(select={'tag_name':'name'}).query.__str__()

Out[20]: 'SELECT (name) AS "tag_name", "blog_tag"."id", "blog_tag"."name" FROM "blog_tag"'

我们发现查询的时候弄了两次 (name) AS "tag_name" 和 "blog_tag"."name",如果我们只想其中一个能用,可以用 defer 排除掉原来的 name (后面有讲)

为什么要改个名称,最常见的需求就是数据转变成 list,然后可视化等

4. annotate 聚合 计数,求和,求平均数等(5. 部分)

1).计数:我们来计算一下每个作者的文章数

  In [21]: from django.db.models import Count

  In [23]: Article.objects.all().values('author_id').annotate(count=Count('author')).valu
    ...: es('author_id', 'count')

  Out[23]: <QuerySet [{'count': 40, 'author_id': 5}, {'count': 20, 'author_id': 7}, {'count': 20, 'author_id': 9}]>

  这条语句做了什么,怎么工作的?用query.__str__()查看一下:

  In [25]: Article.objects.all().values('author_id').annotate(count=Count('author')).valu
    ...: es('author_id', 'count').query.__str__()

  Out[25]: '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 [27]: Article.objects.all().values('author__name').annotate(count=Count('author')).v
    ...: alues('author__name', 'count')

  Out[27]: <QuerySet [{'count': 20, 'author__name': 'andy'}, {'count': 20, 'author__name': 'bruse'}, {'count': 40, 'author__name': 'jerry'}]>

2).求和与平均值:

  求一个作者的所有文章的得分的平均值:

  In [29]: from django.db.models import Avg

  In [32]: Article.objects.values('author_id').annotate(avg_score=Avg('score')).values('a
    ...: uthor_id', 'avg_score')

  Out[32]: <QuerySet [{'avg_score': 84.175, 'author_id': 5}, {'avg_score': 88.7, 'author_id': 7}, {'avg_score': 86.95, 'author_id': 9}]>

  In [34]: Article.objects.values('author_id').annotate(avg_score=Avg('score')).values('a
    ...: uthor_id', 'avg_score').query.__str__()

  Out[34]: 'SELECT "blog_article"."author_id", AVG("blog_article"."score") AS "avg_score" FROM "blog_article" GROUP BY "blog_article"."author_id"'

  求一个作者所有文章的总分

  In [37]: Article.objects.values('author__name').annotate(sum_score=Sum('score')).values
    ...: ('author__name', 'sum_score')

  Out[37]: <QuerySet [{'sum_score': 1774, 'author__name': 'andy'}, {'sum_score': 1739, 'author__name': 'bruse'}, {'sum_score': 3367, 'author__name': 'jerry'}]>

  In [38]: Article.objects.values('author__name').annotate(sum_score=Sum('score')).values
    ...: ('author__name', 'sum_score').query.__str__()

  Out[38]: '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"'

5. 优化SQL,减少多对一,一对多,多对多时查询次数(6,7 部分)

  开始之前我们修改一个 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 语句

In [2]: Author.objects.all()
Out[2]: (0.087) SELECT "blog_author"."id", "blog_author"."name", "blog_author"."qq", "blog_author"."addr", "blog_author"."email" FROM "blog_author" LIMIT 21; args=()
<QuerySet [<Author: jason>, <Author: candy>, <Author: tim>, <Author: dave>, <Author: jerry>, <Author: jacky>, <Author: andy>, <Author: vanco>, <Author: bruse>]>

1).假如,我们取出10篇Django相关的文章,并需要用到作者的姓名

  In [3]: articles = Article.objects.all()[:10]

  In [4]: a1 = articles[0]

  (0.002) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."content", "blog_article"."score" FROM "blog_article" LIMIT 1; args=()

  In [12]: a1.title

  Out[12]: 'python_1'

  In [13]: a1.author_id
  Out[13]: 9

  In [14]: a1.author.name
  (0.007) SELECT "blog_author"."id", "blog_author"."name", "blog_author"."qq", "blog_author"."addr", "blog_author"."email" FROM "blog_author" WHERE "blog_author"."id" = 9; args=(9,)
  Out[14]: 'bruse'

  这样的话我们遍历查询结果的时候就会查询很多次数据库,能不能只查询一次,把作者的信息也查出来呢?  

  当然可以,这时就用到 select_related,我们的数据库设计的是一篇文章只能有一个作者,一个作者可以有多篇文章。  

2).现在要查询文章的时候连同作者一起查询出来,“文章”和“作者”的关系就是多对一,换句说说,就是一篇文章只可能有一个作者。

  In [16]: articles = Article.objects.all().select_related('author')[:10]

  In [17]: a1 = articles[0]
  
(0.003) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."content", "blog_article"."score", "blog_author"."id", "blog_author"."name", "blog_author"."qq", "blog_author"."addr", "blog_author"."email" FROM "blog_article" INNER JOIN "blog_author" ON ("blog_article"."author_id" = "blog_author"."id") LIMIT 1; args=()

  In [18]: a1.title

  Out[18]: 'python_1'

  In [19]: a1.author.name

   Out[19]: 'bruse'

3).prefetch_related 优化一对多,多对多查询

和 select_related 功能类似,但是实现不同。select_related 是使用 SQL JOIN 一次性取出相关的内容。

prefetch_related 用于 一对多,多对多 的情况,这时 select_related 用不了,因为当前一条有好几条与之相关的内容。prefetch_related是通过再执行一条额外的SQL语句,然后用 Python 把两次SQL查询的内容关联(joining)到一起

我们来看个例子,查询文章的同时,查询文章对应的标签。“文章”与“标签”是多对多的关系。

In [20]: articles = Article.objects.all().prefetch_related('tags')[:10]

In [21]: articles

Out[21]: (0.001) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."content", "blog_article"."score" FROM "blog_article" LIMIT 10; args=()
(0.001) SELECT ("blog_article_tags"."article_id") AS "_prefetch_related_val_article_id", "blog_tag"."id", "blog_tag"."name" FROM "blog_tag" INNER JOIN "blog_article_tags" ON ("blog_tag"."id" = "blog_article_tags"."tag_id") WHERE "blog_article_tags"."article_id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); args=(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
<QuerySet [<Article: python_1>, <Article: python_2>, <Article: python_3>, <Article: python_4>, <Article: python_5>, <Article: python_6>, <Article: python_7>, <Article: python_8>, <Article: python_9>, <Article: python_10>]>

4).遍历查询的结果:  

In [23]: articles = Article.objects.all()[:3]

In [24]: for a in articles:  

  print(a.title, a.tags.all())

(0.003) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."content", "blog_article"."score" FROM "blog_article" LIMIT 3; args=()
(0.001) SELECT "blog_tag"."id", "blog_tag"."name" FROM "blog_tag" INNER JOIN "blog_article_tags" ON ("blog_tag"."id" = "blog_article_tags"."tag_id") WHERE "blog_article_tags"."article_id" = 1 LIMIT 21; args=(1,)
python_1 <QuerySet [<Tag: python>]>
(0.000) SELECT "blog_tag"."id", "blog_tag"."name" FROM "blog_tag" INNER JOIN "blog_article_tags" ON ("blog_tag"."id" = "blog_article_tags"."tag_id") WHERE "blog_article_tags"."article_id" = 2 LIMIT 21; args=(2,)
python_2 <QuerySet [<Tag: python>]>
(0.000) SELECT "blog_tag"."id", "blog_tag"."name" FROM "blog_tag" INNER JOIN "blog_article_tags" ON ("blog_tag"."id" = "blog_article_tags"."tag_id") WHERE "blog_article_tags"."article_id" = 3 LIMIT 21; args=(3,)
python_3 <QuerySet [<Tag: python>]>

5.用 prefetch_related 我们看一下是什么样子

In [25]: articles = Article.objects.all().prefetch_related('tags')[:3]

In [26]: for a in articles:
    ...:     print(a.title, a.tags.all())

(0.001) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."content", "blog_article"."score" FROM "blog_article" LIMIT 3; args=()
(0.001) SELECT ("blog_article_tags"."article_id") AS "_prefetch_related_val_article_id", "blog_tag"."id", "blog_tag"."name" FROM "blog_tag" INNER JOIN "blog_article_tags" ON ("blog_tag"."id" = "blog_article_tags"."tag_id") WHERE "blog_article_tags"."article_id" IN (1, 2, 3); args=(1, 2, 3)
python_1 <QuerySet [<Tag: python>]>
python_2 <QuerySet [<Tag: python>]>
python_3 <QuerySet [<Tag: python>]>

我们可以看到第二条 SQL 语句,一次性查出了所有相关的内容。

6. 如何只取出需要的字段,排除某些字段(8,9部分)

在复杂的情况下,表中可能有些字段内容非常多,取出来转化成 Python 对象会占用大量的资源。

1).defer:这时候可以用 defer 来排除这些字段,比如我们在文章列表页,只需要文章的标题和作者,没有必要把文章的内容也获取出来(因为会转换成python对象,浪费内存)

In [28]: Article.objects.all().defer('content')

Out[28]: (0.000) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."score" FROM "blog_article" LIMIT 21; args=()  # 注意这里没有查 content 字段了

2).only:和 defer 相反,only 用于取出需要的字段,假如我们只需要查出 作者的名称

In [30]: Author.objects.all().only('name')
Out[30]: (0.001) SELECT "blog_author"."id", "blog_author"."name" FROM "blog_author" LIMIT 21; args=()

 <QuerySet [<Author: jason>, <Author: candy>, <Author: tim>,.........

 在用only查 name时 , id 也查了,这个 id 是 主键,能不能没有这个 id 呢?django queryset是不允许的,会包含主键。

原生SQL是可以的:

# python manage.py dbshell

sqlite> select name from blog_author limit 1;

7. 自定义一个自定义聚合功能,比如 group_concat(10. 部分)

 django.db.models 中有 Count, Avg, Sum 等,但是有一些没有的,比如 GROUP_CONCAT,它用来聚合时将符合某分组条件(group by)的不同的值,连到一起,作为整体返回。

先来演示一下,如果实现 GROUP_CONCAT 功能。新建一个文件 比如 my_aggregate.py

from django.db.models import Aggregate, CharField

class GroupConcat(Aggregate):
    function = 'GROUP_CONCAT'
    template = '%(function)s(%(distinct)s%(expressions)s%(ordering)s%(separator)s)'
    def __init__(self, expression, distinct=False, ordering=None, separator=',', **extra):
        super(GroupConcat, self).__init__(
            expression,
            distinct='DISTINCT ' if distinct else '',
            ordering=' ORDER BY %s' % ordering if ordering is not None else '',
            separator=' SEPARATOR "%s"' % separator,
            output_field=CharField(),
            **extra)

使用时先引入 GroupConcat 这个类,比如聚合后的错误日志记录有这些字段 time, level, info

我们想把 level, info 一样的 聚到到一起,按时间和发生次数倒序排列,并含有每次日志发生的时间。

In [1]: from blog.my_aggregate import GroupConcat

ErrorLogModel.objects.values('level''info').annotate(
    count=Count(1), time=GroupConcat('time', ordering='time DESC', separator=' | ')
).order_by('-time''-count')

 

七、自定义Field

Django 的官方提供了很多的 Field,但是有时候还是不能满足我们的需求,不过Django提供了自定义 Field 的方法:

 示例,减少文本的长度,保存数据的时候压缩,读取的时候解压缩,如果发现压缩后更长,就用原文本直接存储:

Django 1.7 以下

class CompressedTextField(models.TextField):
    """    model Fields for storing text in a compressed format (bz2 by default)    """

    __metaclass__ = models.SubfieldBase
    def to_python(self, value):
        if not value:
            return value
        try:
            return value.decode('base64').decode('bz2').decode('utf-8')
        except Exception:
            return value

    def get_prep_value(self, value):
        if not value:
            return value
        try:
            value.decode('base64')
            return value
        except Exception:
            try:
                tmp = value.encode('utf-8').encode('bz2').encode('base64')
            except Exception:
                return value
            else:
                if len(tmp) > len(value):
                    return value
                return tmp

to_python 函数用于转化数据库中的字符到 Python的变量, get_prep_value 用于将Python变量处理后(此处为压缩)保存到数据库,使用和Django自带的 Field 一样。

Django 1.8 以上版本,可以用

class CompressedTextField(models.TextField):
    """    model Fields for storing text in a compressed format (bz2 by default)    """

    def from_db_value(self, value, expression, connection, context):
        if not value:
            return value
        try:
            return value.decode('base64').decode('bz2').decode('utf-8')
        except Exception:
            return value
        
    def to_python(self, value):
        if not value:
            return value
        try:
            return value.decode('base64').decode('bz2').decode('utf-8')
        except Exception:
            return value

    def get_prep_value(self, value):
        if not value:
            return value
        try:
            value.decode('base64')
            return value
        except Exception:
            try:
                value.encode('utf-8').encode('bz2').encode('base64')
            except Exception:
                return value

Django 1.8及以上版本中,from_db_value 函数用于转化数据库中的字符到 Python的变量

示例2,比如我们想保存一个 列表到数据库中,在读取用的时候要是 Python的列表的形式,我们来自己写一个 ListField:这个ListField继承自 TextField,代码如下:

from django.db import models
import ast 

class ListField(models.TextField):
    __metaclass__ = models.SubfieldBase

    description = "Stores a python list"

    def __init__(self, *args, **kwargs):
        super(ListField, self).__init__(*args, **kwargs)

    def to_python(self, value):
        if not value:
            value = []
        if isinstance(value, list):
            return value
        return ast.literal_eval(value)

    def get_prep_value(self, value):
        if value is None:
            return value
        return  str(value) # use unicode(value) in Python 2

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_db_prep_value(value)

使用它很简单,首先导入 ListField,像自带的 Field 一样使用:

class Article(models.Model):
    labels = ListField()

在终端上尝试(运行 python manage.py shell 进入):

>>> from app.models import Article
>>> d = Article()
>>> d.labels   # []
>>> d.labels = ["Python", "Django"]
>>> d.labels   #["Python", "Django"]

 

七、数据表更改

Django的第三方app “South”,是专门做数据库表结构自动迁移工作,South 名列最受欢迎的第三方 app。事实上,它现在已经俨然成为 Django 事实上的数据库表迁移标准,很多第三方 app 都会带 South migrations 脚本,Django 1.7 开始集成了 South 的功能。

变更表只需运行以下命令即可:

python manage.py makemigrations
python manage.py migrate

 这两行命令就会对我们的models.py 进行检测,自动发现需要更改的,应用到数据库中去。

 1.7以下的版本:

1.安装South:(sudo) pip install South

 2. 使用方法:一个好的程序使用起来必定是简单的,South和它的宗旨一样,使用简单。只需要简单几步,针对已经建好model和创建完表的应用。

  把south加入到settings.py中的INSTALL_APPS中,INSTALLED_APPS = ('south',)

  修改好后运行一次 python manage.py syncdb,Django会新建一个 south_migrationhistory 表,用来记录数据表更改(Migration)的历史纪录。

  如果要把之前建好的比如 blog 这个 app 使用 South 来管理:$ python manage.py convert_to_south blog

  你会发现blog文件夹中多了一个 migrations 目录,里面有一个 0001_initial.py 文件。

注:如果 blog 这个 app 之前就创建过相关的表,可以用下面的来“假装”用 South 创建(伪创建,在改动 models.py 之前运行这个)

python manage.py migrate blog --fake

意思是这个表我以前已经建好了,用 South 只是纪一下这个创建记录,下次 migrate 的时候不必再创建了。

原理就是 south_migrationhistory 中记录下了 models.py 的修改的历史,下次再修改时会和最近一次记录比较,发现改变了什么,然后生成相应的对应文件,最终执行相应的 SQL 更改原有的数据表。

 接着,当你对 Blog.models 做任何修改后,只要执行:

$ python manage.py schemamigration blog --auto

South就会帮助我们找出哪些地方做了修改,如果你新增的数据表没有给default值,并且没有设置null=True, south会问你一些问题,因为新增的column对于原来的旧的数据不能为Null的话就得有一个值。顺利的话,在migrations文件夹下会产生一个0002_add_mobile_column.py,但是这一步并没有真正修改数据库的表,我们需要执行 python manage.py migrate ,这样所做的更改就写入到了数据库中了。

3.恢复到以前

South好处就是可以随时恢复到之前的一个版本,比如我们想要回到最开始的那个版本:

 python manage.py migrate blog 0001

这样就搞定了,数据库就恢复到以前了,比你手动更改要方便太多了。

 

 

 

 

 

 

posted on 2014-06-02 22:27  myworldworld  阅读(278)  评论(0)    收藏  举报

导航