django-基础-模型&filds&queryset

模型

官方的modole和filed
https://docs.djangoproject.com/en/1.10/topics/db/models/
https://docs.djangoproject.com/en/dev/ref/models/fields/

models.py,支持的库需要在settings.py设置,不许修改models.py
一个app可以用在多个project中,一个project可以包含多个应用
工程:project
应用:people
把people添加到settings.py,同时配置SITE_ID = 1
打开people/models.py文件,修改

from django.db import models
 
class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

默认的数据库是SQLite3,同步一下

python manage.py makemigrations
python manage.py migrate

这时候会自动生成一些表,这时候只能在django shell中使用,并且返回的内容也是<Person: Person object>
注意

字段名不能包含 __、Python等

需要在Person中添加

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

执行

python manage.py shell
from people.models import Person
Person.objects.create(first_name="li", last_name="3")
Person.objects.get(first_name="li")

新建一个对象

#方法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.objects.all()
#切片操作,获取10个人,不支持负索引,切片可以节约内存

Person.objects.all()[:10] 				
#get是用来获取一个对象的,如果需要获取满足条件的一些人,就要用到filter

Person.objects.get(name=name)

#等于Person.objects.filter(name__exact="abc") 名称严格等于 "abc" 的人
Person.objects.filter(name="abc") 

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

#名称中包含 "abc"的人
Person.objects.filter(name__contains="abc") 

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

#filter是找出满足条件的,当然也有排除符合某条件的
#正则表达式查询
Person.objects.filter(name__regex="^abc")
#正则表达式不区分大小写
Person.objects.filter(name__iregex="^abc")

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

自定义Field

压缩和解压

#coding:utf-8
from django.db import models
 
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:
                return value.encode('utf-8').encode('bz2').encode('base64')
            except Exception:
                return value

to_python转换数据库字符到Python变量
get_prep_value将Python变量转化到数据库
1.8以上的版本使用from_db_value转换数据库字符到Python变量

保存列表

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 unicode(value) # use str(value) in Python 3
 
    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_db_prep_value(value)

使用时首先导入ListField

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

在终端上运行

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

然后

from blog.models import Article
a = Article()
a.labels.append('Django')
a.labels.append('custom fields')
a.labels
type(a.labels)
a.content = u'我正在写一篇关于自定义Django Fields的教程'
a.save()

数据表更改

1.7以后集成了South功能,在修改了models.py之后运行

python manage.py makemigrations
python manage.py migrate

这两个命令会检测models.py ,自动发现并加载到数据库中
把south加入到settings.py的INSTALL_APPS中,但是以前的版本,需要手动修改;
http://www.ziqiangxuetang.com/django/django-schema-migration.html

QuerySet API

参考:
https://docs.djangoproject.com/en/dev/ref/models/querysets/
从数据库查询,获取的是集合,叫做QuerySet
blog/models.py

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()
 
    def __unicode__(self):  # __str__ on Python 3
        return self.name
 
class Author(models.Model):
    name = models.CharField(max_length=50)
    email = models.EmailField()
 
    def __unicode__(self):  # __str__ on Python 3
        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 __unicode__(self):  # __str__ on Python 3
        return self.headline

创建对象

from blog.models import Blog
b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
b.save()

一共有四种方法

# 方法 1
Author.objects.create(name="WeizhongTu", email="tuweizhong@163.com")
 
# 方法 2
twz = Author(name="WeizhongTu", email="tuweizhong@163.com")
twz.save()
 
# 方法 3
twz = Author()
twz.name="WeizhongTu"
twz.email="tuweizhong@163.com"
twz.save()
 
# 方法 4,首先尝试获取,不存在就创建,可以防止重复
Author.objects.get_or_create(name="WeizhongTu", email="tuweizhong@163.com")
# 返回值(object, True/False)

前三种方法返回对应的object,最后一种返回元组(object, True/False)

获取对象

Person.objects.all() # 查询所有
Person.objects.all()[:10] 切片操作,获取10个人,不支持负索引,切片可以节约内存,不支持负索引,后面有相应解决办法,第7条
Person.objects.get(name="WeizhongTu") # 名称为 WeizhongTu 的一条,多条会报错
 
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="WZ") # 排除包含 WZ 的Person对象
Person.objects.filter(name__contains="abc").exclude(age=23) # 找出名称含有abc, 但是排除年龄是23岁的

可迭代

es = Entry.objects.all()
for e in es:
    print(e.headline)

Entry.objects.all() 或者 es 就是 QuerySet 是查询所有的 Entry 条目;
注意事项

(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 变成 列表

可以用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链式查询

Author.objects.filter(name__contains="WeizhongTu").filter(email="tuweizhong@163.com")
Author.objects.filter(name__contains="Wei").exclude(email="tuweizhong@163.com")
 
# 找出名称含有abc, 但是排除年龄是23岁的
Person.objects.filter(name__contains="abc").exclude(age=23)

QuerySet不支持负索引

Person.objects.all()[:10] 切片操作,前10条
Person.objects.all()[-10:] 会报错!!!
 
# 1. 使用 reverse() 解决
Person.objects.all().reverse()[:2] # 最后两条
Person.objects.all().reverse()[0] # 最后一条
 
# 2. 使用 order_by,在栏目名(column name)前加一个负号
Author.objects.order_by('-id')[:20] # id最大的20条

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()

QuerySet 进阶

聚合相关:
https://docs.djangoproject.com/en/dev/topics/db/aggregation/

建立新的模型

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2016-11-26 18:54:49
# @Author  : Weizhong Tu (mail@tuweizhong.com)
# @Link    : http://www.tuweizhong.com
# @Version : 0.0.1
 
from __future__ import unicode_literals
 
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
 
@python_2_unicode_compatible
class Author(models.Model):
    name = models.CharField(max_length=50)
    qq = models.CharField(max_length=10)
    addr = models.TextField()
    email = models.EmailField()
 
    def __str__(self):
        return self.name
 
@python_2_unicode_compatible
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
 
@python_2_unicode_compatible
class Tag(models.Model):
    name = models.CharField(max_length=50)
 
    def __str__(self):
        return self.name

然后同步数据库

python manage.py makemigrations
python manage.py migrate

生成一些数据initdb.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2016-11-26 19:10:14
# @Author  : Weizhong Tu (mail@tuweizhong.com)
# @Link    : http://www.tuweizhong.com
# @Version : 0.0.1
from __future__ import unicode_literals
import random
from zqxt.wsgi import *
from blog.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!")

查看django执行的sql
调用print即可

print str(Author.objects.all().query)

values_list获取元组形式结果
要获取name和qq

authors = Author.objects.values_list('name', 'qq')

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

#查找QuerySet
Author.objects.values_list('name', flat=True)
#变成list形式
list(Author.objects.values_list('name', flat=True))

查询某个人的文章

Article.objects.filter(author__name='twz915').values_list('title', flat=True)

values获取字典形式结果
获取name和qq

Author.objects.values('name', 'qq')

列表存储

list(Author.objects.values('name', 'qq'))

查询 twz915 这个人的文章标题

Article.objects.filter(author__name='twz915').values('title')

values_list和values返回的不是真正的列表/字典,其实也是queryset,但是是lazy的
如果查询后,又更新过数据库,内容会被更新,如果需要保持使用list
如果只是遍历,没有必要转成列表

extra
extra可以实现别名、条件、排序
条件和排序用:filter, exclude也能实现
排序用order_by也能实现
比如要实现SELECT name AS tag_name FROM blog_tag;

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

这里name和tag_name都可以使用,如果想拍出旧的可以用defer

annotate
计数

#计算一下每个作者的文章数
Article.objects.all().values('author_id').annotate(count=Count('author')).values('author_id', 'count')

我们也可以获取作者的名称及作者的文章数

Article.objects.all().values('author__name').annotate(count=Count('author')).values('author__name', 'count')

求和与平均值

from django.db.models import Avg
Article.objects.values('author_id').annotate(avg_score=Avg('score')).values('author_id', 'avg_score')

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

from django.db.models import Sum
Article.objects.values('author__name').annotate(sum_score=Sum('score')).values('author__name', 'sum_score')

select_related
修改settings.py,让django打印sql

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'level': 'DEBUG' if DEBUG else 'INFO',
        },
    },
}
from blog.models import *
Author.objects.all()

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

articles = Article.objects.all()[:10]
a1 = articles[0]  # 取第一篇
a1.title
a1.author_id
a1.author.name   # 再次查询了数据库,注意!!!

这样查询结果时会多次访问数据库,如何只访问一次,把全部内容查出呢;

articles = Article.objects.all().select_related('author')[:10]
a1 = articles[0]  # 取第一篇
a1.title
a1.author.name #这里不会再次查询

prefetch_related
select_relateds使用join一次性取出相关内容;
prefetch_related用于一对多,多对多;
prefetch_related通过再执行一次sql,然后python把两次sql关联到一起;
查询文章时,也查询对应的标签

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

defer
有些列特别大,而且不需要;

Article.objects.all()
Article.objects.all().defer('content')

only
选择仅仅需要的字段

Author.objects.all().only('name')

也可以django中直接执行必须包含主键,否则会报错

authors =  Author.objects.raw('select name from blog_author limit 1')
author = authors[0]

自定义聚合功能
Count, Avg, Sum这类聚合函数可以自定义;
比如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一样的 聚到到一起,按时间和发生次数倒序排列,并含有每次日志发生的时间;

ErrorLogModel.objects.values('level', 'info').annotate(
    count=Count(1), time=GroupConcat('time', ordering='time DESC', separator=' | ')
).order_by('-time', '-count')
posted @ 2017-01-12 10:27  zhangshihai1232  阅读(259)  评论(0)    收藏  举报