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) 收藏 举报
浙公网安备 33010602011771号