在第一部分中,主要是关于如何创建更好数据模型用来存储数据。而在django中,还有一个部分也很重要。那就是关于提取和使用数据,而这就涉及到这些模型类的方法。
参考资料:
一旦建立好数据模型之后,django会自动给你一个数据库抽象api来让你创建、撤回、更新和删除这些对象。
范例
Copy from django.db import models
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=200)
email = models.EmailField()
def __str__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
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
一、创建对象
使用save()方法
Copy from blog.models import Blog
b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
b.save()
只有执行了save()方法,所有对对象做的操作才会保存到数据库层次。
使用create()可以完成创建和保存的操作。
二、修改对象
1、普通字段类型
同样使用save()方法
Copy b5.name = 'New name'
b5.save()
2、外键字段
外键与普通的完全一致
Copy from blog.model import Blog, Entry
entry = Entry.objects.get(pk=1)
cheese_blog = Blog.objects.get(name="Cheddar Talk")
entry.blog = cheese_blog
entry.save()
哇,还真的是很简单呢
3、多对多类型的字段
之前也提到过,普通的方法不适用于使用了关系表的多对多模型,但是这里好像还是可以使用add方法,是不是因为没有使用关系表呢?确实没有!
Copy from blog.models import Author
# add one record
joe = Author.objects.create(name = 'Joe')
entry.authors.add(joe)
# add multiple records
join = Author.objects.create(name = 'John')
paul = Author.objects.create(name = 'Paul')
george = Author.objects.create(name = 'George')
ringo = Author.objects.create(name = 'Ringo')
entry.author.add(john, paul, george, ringo)
三、检索对象
为了检索对象,需要通过Manager构造QuerySet。一个QuerySet代表着多个数据库记录的对象,他可以有零个或多个筛选条件,类似于sql中的select语句。
QuerySet是通过manager得到的,每一个模型至少有一个Manager,默认为objects。
Copy >>> Blog.objects
<django.db.models.manager.Manager object at ...>
>>> b = Blog(name='Foo', tagline='Bar')
>>> b.objects
Traceback:
...
AttributeError: "Manager isn't accessible via Blog instances."
Manger是模型QuerySet的主要来源,比如Blog.objects.all(),这个会返回所有Blog模型中的对象。
1、检索所有对象
Copy all_entries = Entry.objects.all()
2、检索特定对象
两种方式:
filter(**kwargs):返回所有符合 条件的对象
exclude(**kwargs):返回所有不符合 条件 对象
示例:
Copy Entry.ojects.filter(pub_date__year=2006)
Entry.objects.all().filter(pub_date__year=2006)
# same result
我终于明白了。前面的是模型中的字段,而双下划线后面的是这个字段的属性!慢着,不是还有一个get吗?
这是错误的理解。
3、链式筛选
Copy Entry.objects.filter(
... headline__startswith='What'
... ).exclude(
... pub_date__gte=datetime.date.today()
... ).filter(
... pub_date__gte=datetime.date(2005, 1, 30)
... )
这个可能跟我们想的不一样。。。
4、懒惰的QuerySet
大部分queryset筛选操作不会涉及到数据库,如下
Copy q = Entry.objects.filter(headline__startswith='What')
q = q.filter(pub_date__lte=datetime.date.today())
q = q.exclude(body_text__icontains='food')
print(q)
1、gt:大于某个时间
now = datetime.datetime.now()
#前一天
start = now – datetime.timedelta(hours=23, minutes=59, seconds=59)
a=yourobject.objects .filter(youdatetimcolumn__gt=start)
2、gte:大于等于某个时间:
a=yourobject.objects .filter(youdatetimcolumn__gte=start)
3、lt:小于
a=yourobject.objects .filter(youdatetimcolumn__lt=start)
4、lte:小于等于
a=yourobject.objects .filter(youdatetimcolumn__lte=start)
5、range:查询时间段
start_date = datetime.date(2005, 1, 1)
end_date = datetime.date(2005, 3, 31)
Entry.objects.filter(pub_date__range=(start_date, end_date))
qs.filter(name__contains="e")
qs.filter(name__icontains="e")
对应sql
'contains': 'LIKE BINARY %s', 其中的BINARY是 精确大小写
'icontains': 'LIKE %s', 而’icontains’中的’i’表示忽略大小写
其中只有最后一步print涉及到数据库,
5、用get获取单个对象
filter永远都会返回一个QuerySet,即使只有一个符合条件的对象。而get()方法可以保证回复一个对象
Copy one_entry = Entry.objects.get(pk=1)
值得注意的是,符合筛选条件的对象只能有一个,如果没有或者多个,都会导致报错。
Note that there is a difference between using get(), and using filter() with a slice of [0].
这是什么意思?
6、其他的查询方式
除了get、all、filter、exclude等,django中还有很多很多查询方式。
7、使用分片限制查询结果
Copy Entry.objects.all()[:5]
Entry.objects.all()[5:10]
# 但是负值不支持
Entry.objects.all()[-1]
一般来说,切片不会影响原本的查询结果,但是有一个例外,那就是使用跳步的方式切片,这样的话query中会 修改部分来得到这个结果
Copy Entry.objects.all()[:10:2]
然后这个分片只能执行一次,为了避免歧义。
四、字段查询
基本的格式是field__lookuptype=value
Copy Entry.objects.filter(pub_date__lte='2006-01-01')
一般来说,查询的字段名必须与模型属性名一致,但是有一个例外情况,那就是外键,它可以以id为后缀,但是也只能在主键没有设置,默认为id的情况下才可以吧?
Copy Entry.objects.filter(blog_id=4)
1、exact
Copy Entry.objects.get(headline__exact="Cat bites dog")
Entry.objects.get(headline="Cat bites dog")
当然这也是默认的情况,如果不加后面的exact,直接相等也是一样的
2、iexact
Copy Entry.objects.get(name__iexact="beatles blog")
# match "Beatles Blog"/"beatles blog"/"BeAtlES blOG"
忽略大小写
3、contains
Copy Entry.objects.get(headline__contains='Lennon')
也就是匹配包含这个字符串的
icontains忽略大小写
4、startswith,endwith
之前我以为是调用的是字符串的方法,但是看到这里才知道不是。
istartwith, iendwith忽略大小写
五、跨关系查找
django这种跨关系查找的功能确实很让人吃惊。
Copy Entry.objects.filter(blog__name='Beatles Blog')
上面的例子是沿着外键去查外键连接的数据表,django甚至还可以反过来查询,这时只需要把表名小写放在前面即可。
Copy Blog.objects.filter(entry__headline__contains='Leenon')
如果全部查完之后,没有找到一个值,django也不会报错,只是返回空的对象。哪怕查的字段在表中都不存在(假如headline不存在entry),也不会报错。
1、多值查询
先看两个例子
Copy # 1
Blog.objects.filter(entry__headline__contains='Lennon',entry__pub_date__year=2008)
# 2
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_datr__year=2008)
我之前一直以为第二个是指同时满足两个条件的查询,但现在看起来并不是,方式一才是同时满足两个条件查询,而方式是指满足条件一和满足条件二的结果的返回。那假如我把满足条件一的queryset保存为q,再对q做filter操作,那算是什么呢?应该是在q的结果中继续筛选吧?
2、比较操作
有时候我们并不是想直接与一个常量比较,而希望跟同一个模型中的另一个字段的值作比较,这s时候可以使用F expression
Copy from django.db.models import F
Entry.objects.filter(n_comments_gt=F('n_pingbacks'))
或者
Copy Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
Entry.objects.filter(authors__name=F('blog__name'))
靠,居然还支持位操作!
Copy .bitand(), .bitor(), .bitrightshift(), and .bitleftshift().
3、pk快速查找
pk也就是primary key,使用pk快速查找可以实现快速查找对象
Copy Blog.objects.get(id__exact=14)
Blog.objects.get(id=14)
Blog.objects.get(pk=14)
# 三个都是相同的
Blog.objects.filter(pk__in=[1,4,7])
Blog.objects.filter(pk__gt=14)
4、缓存queryset
每当新建一个queryset的时候,缓存其实是空的,当它真的查询数据库的时候,才会将结果缓存起来。
Copy print([e.headline for e in Entry.objects.all()])
print([e.pub_date for e in Entry.objects.all()])
如果这样操作的话,会进行两次数据库的查询,因为是两次,所以在这期间,数据库可能会被修改,也因此,对应的数据可能会发生改变。
Copy >>> queryset = Entry.objects.all()
>>> print(queryset[5]) # Queries the database
>>> print(queryset[5]) # Queries the database again
>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # Queries the database
>>> print(queryset[5]) # Uses cache
>>> print(queryset[5]) # Uses cache
为了避免上面的情况,保证取出的数据是同一个数据,这时候就可以执行缓存。
5、使用Q来实现复杂的查询
之前的查询主要用到的查询条件是“and”,如果需要使用其他的查询条件的话,可以使用Q 对象。
Copy from django.db.modelsimport Q
Poll.objects.get(
Q(question__startswith='Who'),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
目前我看到就是可以使用or,应该还有很多类型。
六、删除对象
简单使用实例对象的delete方法即可
Copy e.delete()
# 也可以这样
Entry.objects.filter(pub_date__year=2005).delete()
这里值得注意的是,如果你已经自定义了delete方法,就不能使用第二方方法来删除多个实例对象,只能通过循环一个一个地调用实例对象的delete方法来实现。还有就是,django默认的是ON DELETE CASCADE,也就是说当在数据库中删除一个记录时,它会自动删除所有连接到它的外键记录。而这个可以在设置外键的时候修改。
同时,delete也是唯一一个queryset有但是manager没有的方法,这是为了避免意外删除所有对象,如果想要删除全部的对象,可以
Copy Entry.objects.all().delete()
七、一次更新多个对象
使用update()方法
Copy Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
只有非关系型字段和外键字段可以使用这个方法,如果是菲关系型,就设置为对应的数据类型常量即可,如果是外键,可以设置为某个模型的实例。这个方法会返回修改的记录数。唯一一个麻烦一点的是一个模型的主键,等会
The only restriction on the QuerySet being updated is that it can only access one database table: the model's main table. You can filter based on related fields, but you can only update columns in the model's main table.
不对吧,前面的pub_date并不是主键啊,为什么它说只能通过主键才能找到?而且主键往往是唯一值,也不可能对应多个值,这样update不就没有意义了吗?
这里并不是主键,看一下例子就明白了,是外键的问题
Copy b = Blog.objects.get('pk=1')
Entry,objects.select_related().filter(blog=b).update(headline='Everything is the same')
当它想要找所有外键连接到某个特定的记录时,才会用这就related的方式。
另外,update不需要save,它会直接执行到数据库上。
八、相关对象或者反转manager
相关对象的额外操作