Django-QuerySets 数据操作

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/boyun58/article/details/89072627

一旦您创建了数据模型,Django就会自动为您提供一个数据库抽象API,允许您创建,检索,更新和删除对象。

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

创建对象

为了在Python对象中表示数据库表数据,Django使用直观的系统:模型类表示数据库表,该类的实例表示数据库表中的特定记录。

要创建对象,请使用模型类的关键字参数对其进行实例化,然后调用save()以将其保存到数据库中。

假设模型存在于文件中mysite/blog/models.py

>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()
#这会INSERT在幕后执行SQL语句。在您明确调用save()之前,Django不会访问数据库。该save()方法没有返回值。

保存对象的更改

>>> b.name = 'New name'
>>> b.save()
#这会在幕后执行SQL UPDATE语句。在您明确调用之前,Django不会访问数据库save()。

保存ForeignKey和ManyToManyField字段

更新ForeignKey字段的工作方式与保存普通字段的方式完全相同 - 只需将正确类型的对象分配给相关字段即可。

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

更新ManyToManyField工作的方式略有不同 - 使用 add()字段上的方法向关系添加记录。

>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)

要ManyToManyField一次性添加多个记录,请在调用add()中包含多个参数

>>> john = Author.objects.create(name="John")
>>> paul = Author.objects.create(name="Paul")
>>> george = Author.objects.create(name="George")
>>> ringo = Author.objects.create(name="Ringo")
>>> entry.authors.add(john, paul, george, ringo)

检索对象

要从数据库中检索对象,请在模型类上构建一个 QuerySet 或者 Manager
一个QuerySet表示数据库中的对象集合。它可以有零个,一个或多个过滤器。过滤器根据给定的参数缩小查询结果范围。在SQL术语中,一个QuerySet等同于SELECT语句,过滤器是限制子句,如WHERE或LIMIT。
可以QuerySet使用你的模型获得Manager。每个模型至少有一个 Manager,objects默认情况下调用它 。通过模型类直接访问它。Managers 只能通过模型​​类而不是模型实例访问,以强制“表级”操作和“记录级”操作之间的分离。

>>> 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."

检索所有对象

从表中检索对象的最简单方法是获取所有这些对象。为此,请使用all()

>>> all_entries = Entry.objects.all()

使用过滤器检索特定对象

  • filter(**kwargs)
    返回包含QuerySet与给定查找参数匹配的新对象。

  • exclude(**kwargs)
    返回包含QuerySet与给定查找参数不匹配的新对象。

例如,要获取2006年的博客条目,请使用filter()如下

Entry.objects.filter(pub_date__year=2019)
#or
Entry.objects.all().filter(pub_date__year=2006)

链接过滤

>>> Entry.objects.filter(
...     headline__startswith='What'
... ).exclude(
...     pub_date__gte=datetime.date.today()
... ).filter(
...     pub_date__gte=datetime.date(2005, 1, 30)
... )
#添加过滤器,然后是排除,然后是另一个过滤器。

过滤的QuerySets是独一无二的

每当过滤一个QuerySet,你就会得到一个全新的QuerySet,与以前没有任何关系QuerySet。每个细化都会创建一个可以存储的QuerySet。

>>> q1 = Entry.objects.filter(headline__startswith="What")
>>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
>>> q3 = q1.filter(pub_date__gte=datetime.date.today())

QuerySets是惰性的

>>> 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)
#虽然这看起来像三条数据库SQL,但事实上它只在数据库中执行一次,在最后一行(print(q))。通常,在查询QuerySet它们之前,不会从数据库中获取结果 。

检索单个对象get()

可以使用直接返回对象的 get()方法

>>> one_entry = Entry.objects.get(pk=1)

在大多数情况下,当你需要从数据库中查找对象,你会使用all(), get(), filter()和 exclude()。然而,这并不是全部。有关所有各种方法的完整列表,请参阅QuerySet API参考

限制QuerySet

使用Python的数组切片语法将您限制 QuerySet为一定数量的结果。这相当于SQL LIMIT和OFFSET子句。

>>> Entry.objects.all()[:5]
#不支持负数索引

字段查找

字段查找是指定SQL WHERE子句内容的方式。它们被指定为QuerySet 方法的关键字参数filter(), exclude()。

基本查找关键字参数采用field__lookuptype=value。(这是一个双重下划线)

>>> Entry.objects.filter(pub_date__lte='2006-01-01')
#等价于
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

exact

“精确”匹配。

>>> Entry.objects.get(headline__exact="Cat bites dog")
#or
SELECT ... WHERE headline = 'Cat bites dog';

iexact

不区分大小写的匹配项

>>> Blog.objects.get(name__iexact="beatles blog")

contains

区分大小写的包含数据查询

Entry.objects.get(headline__contains='Lennon')
#等价于
SELECT ... WHERE headline LIKE '%Lennon%';

跨越关系的查找

Django提供了一种强大而直观的方式来“跟踪”查找中的关系JOIN,在后台自动为您处理SQL 。要跨越关系,只需使用跨模型的相关字段的字段名称,用双下划线分隔,直到到达所需的字段。

>>> Entry.objects.filter(blog__name='Beatles Blog')

#要引用“反向”关系,只需使用模型的小写名称。
>>> Blog.objects.filter(entry__headline__contains='Lennon')

#如果您要跨多个关系进行过滤,并且其中一个中间模型没有满足过滤条件的值,Django会将其视为空(所有值都是NULL),但有效,对象存在。所有这些意味着不会引发任何错误。
Blog.objects.filter(entry__authors__name='Lennon')

#(如果存在相关Author模型),如果没有author 与条目相关联,则将其视为没有name 附加,而不是因为缺失而引发错误author。
Blog.objects.filter(entry__authors__name__isnull=True)

#将返回Blog有一个entry不为空的author对象,以及那些具有空entry的authors的name。
Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)

#跨越多值关系
选择所有标题中包含“Lennon”且2008年发布的博客(满足这两个条件的相同条目)
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)

#要选择标题 中包含“Lennon”条目的所有博客以及 2008年发布的条目,我们会写:
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)

#下面的查询将排除包含博客Lennon的标题,并不在2008年出版。
Blog.objects.exclude(
    entry__headline__contains='Lennon',
    entry__pub_date__year=2008,
)

过滤器可以引用模型上的字段

如果要将模型字段的值与同一模型上的另一个字段进行比较。Django 允许进行这样的比较。作为查询中模型字段引用的实例。然后,可以在查询过滤器中使用这些引用来比较同一模型实例上的两个不同字段的值
Django F()支持对象使用加法,减法,乘法,除法,模运算和幂运算,包括常量和其他F()对象。还可以使用双下划线表示法来跨越F()对象中的关系。F()具有双下划线的对象将引入访问相关对象所需的任何连接。对于日期和日期/时间字段,可以添加或减去 timedelta对象。

>>> from django.db.models import F

#要查找包含比n_pingback更多注释的所有博客条目的列表
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))

#要查找所有博客条目的评论数量是n_pingback的两倍以上
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)

#要查找rating小于pingback计数和comments计数总和的所有条目
>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))

#要检索作者姓名与博客名称相同的所有条目
>>> Entry.objects.filter(authors__name=F('blog__name'))

#返回在发布后超过3天内修改的所有条目
>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))

#该F()对象通过支持位运算.bitand(),.bitor(), .bitrightshift(),和.bitleftshift()。
>>> F('somefield').bitand(16)

pk查找快捷方式

Django提供了一个pk查找快捷方式,代表“主键”。

>>> Blog.objects.get(id__exact=14) # Explicit form
>>> Blog.objects.get(id=14) # __exact is implied
>>> Blog.objects.get(pk=14) # pk implies id__exact

#使用pk不仅限于__exact查询 - 可以组合任何查询术语以pk对模型的主键执行查询:
# Get blogs entries with id 1, 4 and 7
>>> Blog.objects.filter(pk__in=[1,4,7])

# Get all blog entries with id > 14
>>> Blog.objects.filter(pk__gt=14)

#pk查找也适用于连接
>>> Entry.objects.filter(blog__id__exact=3) # Explicit form
>>> Entry.objects.filter(blog__id=3)        # __exact is implied
>>> Entry.objects.filter(blog__pk=3)        # __pk implies __id__exact

在LIKE语句中转义百分号和下划线

在一个LIKE 语句中,百分号表示多字符通配符,下划线表示单字符通配符。

>>> Entry.objects.filter(headline__contains='%')
#SQL
SELECT ... WHERE headline LIKE '%\%%';
#下划线也是如此。百分号和下划线都是透明处理的。

QuerySet缓存

每个QuerySet都包含一个缓存,以最小化访问数据库

>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])
#这意味着相同的数据库查询将执行两次,从而有效地使数据库负载翻倍。此外,两个列表可能包含不相同的数据库记录,因为Entry可能在两个请求之间的分秒中添加或删除了一个。

#要避免此问题,只需保存 QuerySet并重复使用它:
>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset]) # Evaluate the query set.
>>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.

#QuerySets没有被缓存时
#查询集并不总是缓存其结果。仅评估部分查询集时,将检查缓存,但如果未填充,则不会缓存后续查询返回的项。具体而言, 限制所述查询集使用切片或索引不会填充缓存。
#在queryset对象中重复获取某个索引将每次查询数据库
>>> 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

#以下是一些其他操作的示例,这些操作将导致整个查询集被评估,从而填充缓存:
>>> [entry for entry in queryset]
>>> bool(queryset)
>>> entry in queryset
>>> list(queryset)
#只需打印查询集就不会填充缓存。这是因为调用__repr__()仅返回整个查询集的一部分。

使用Q对象进行复杂查找

关键字参数filter()查询是“AND”编辑在一起的。如果需要执行更复杂的查询(例如,带OR语句的查询),则可以使用Q()

#例如,此Q对象封装了一个LIKE查询
from django.db.models import Q
Q(question__startswith='What')

#Q可以使用&和|运算符组合对象。当在两个Q对象上使用运算符时,它会生成一个新Q对象。
#例如,此语句生成一个Q表示两个"question__startswith"查询的“OR”的对象
Q(question__startswith='Who') | Q(question__startswith='What')
#这相当于以下SQL WHERE子句:
WHERE question LIKE 'Who%' OR question LIKE 'What%'

#可以通过合并组成任意复杂的Q查询与对象&和|运算,并使用括号分组。此外Q 可以使用~运算符取消对象,允许组合查询(NOT)表达:
Q(question__startswith='Who') | ~Q(pub_date__year=2005)

#每个查找函数,采用关键字参数(例如filter(), exclude(), get())也可以通过一个或多个 Q对象作为位置(未命名的)参数。如果Q为查找函数提供多个 对象参数,则参数将“AND”连接在一起。
Poll.objects.get(
    Q(question__startswith='Who'),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
#SQL语句
SELECT * from polls WHERE question LIKE 'Who%'
    AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

#查找函数可以混合使用Q对象和关键字参数。提供给查找函数的所有参数(无论是关键字参数还是Q 对象)都是“AND”连接在一起的。但是,如果Q提供了对象,则它必须位于任何关键字参数的定义之前。
Poll.objects.get(
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
    question__startswith='Who',
)

比较对象

要比较两个模型实例,只需使用标准的Python比较运算符,即双等号:==。

>>> some_entry == other_entry
>>> some_entry.id == other_entry.id

删除对象

delete()方法立即删除对象并返回已删除的对象数和具有每个对象类型的删除数的字典。

>>> e.delete()
(1, {'weblog.Entry': 1})

#也可以批量删除对象
>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})

复制模型实例

虽然没有用于复制模型实例的内置方法,但可以轻松创建复制了所有字段值的新实例。在最简单的情况下,可以设置pk为None。

blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1

blog.pk = None
blog.save() # blog.pk == 2

#如果使用继承,事情会变得更复杂。考虑一个子类 Blog
class ThemeBlog(Blog):
    theme = models.CharField(max_length=200)

django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python')
django_blog.save() # django_blog.pk == 3

#由于继承的工作原理,您必须将pk和id都设置为None:
django_blog.pk = None
django_blog.id = None
django_blog.save() # django_blog.pk == 4

#此过程不会复制不属于模型数据库表的关系。例如,Entry有一个ManyToManyField到Author。复制条目后,必须为新条目设置多对多关系:
entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry.save()
entry.authors.set(old_authors)

#对于a OneToOneField,您必须复制相关对象并将其分配给新对象的字段,以避免违反一对一唯一约束。
detail = EntryDetail.objects.all()[0]
detail.pk = None
detail.entry = entry
detail.save()

一次更新多个对象

可以使用该update()方法执行此操作,该update()方法立即应用并返回查询匹配的行数,该update()方法直接转换为SQL语句。这是直接更新的批量操作。它不运行任何 save()你的模型的方法,或发出 pre_save或post_save信号

# Update all the headlines with pub_date in 2007.
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')

#调用更新还可以用于根据模型中另一个字段的值更新一个字段。这对于基于其当前值递增计数器特别有用。例如,要增加博客中每个条目的pingback计数
>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)

关系对象

一对多关系

>>> e = Entry.objects.get(id=2)
>>> e.blog # Returns the related Blog object.

>>> e = Entry.objects.get(id=2)
>>> e.blog = some_blog
>>> e.save()

#如果ForeignKey字段已设置null=True(即,它允许NULL值),则可以指定None删除关系。
>>> e = Entry.objects.get(id=2)
>>> e.blog = None
>>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"

#第一次访问相关对象时,将缓存对一对多关系的转发访问。对高速缓存在同一对象实例上的外键的后续访问。
>>> e = Entry.objects.get(id=2)
>>> print(e.blog)  # Hits the database to retrieve the associated Blog.
>>> print(e.blog)  # Doesn't hit the database; uses cached version.

#select_related()该方法提前预先填充所有一对多关系的缓存。例:
>>> e = Entry.objects.select_related().get(id=2)
>>> print(e.blog)  # Doesn't hit the database; uses cached version.
>>> print(e.blog)  # Doesn't hit the database; uses cached version.

反向访问

如果模型具有一个ForeignKey,则外键模型的实例将具有对其Manager返回第一模型的所有实例的访问权。默认情况下,这 Manager是命名的FOO_set,FOO源模型名称是小写的。此Manager返回 QuerySets。

>>> b = Blog.objects.get(id=1)
>>> b.entry_set.all() # Returns all Entry objects related to Blog.

# b.entry_set is a Manager that returns QuerySets.
>>> b.entry_set.filter(headline__contains='Lennon')
>>> b.entry_set.count()

#FOO_set通过related_name在ForeignKey定义中设置参数来覆盖名称 。例如,如果Entry 模型被更改为,上面的示例代码将如下所示:blog = ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries')
>>> b = Blog.objects.get(id=1)
>>> b.entries.all() # Returns all Entry objects related to Blog.

# b.entries is a Manager that returns QuerySets.
>>> b.entries.filter(headline__contains='Lennon')
>>> b.entries.count()

使用自定义反向管理器

默认情况下,RelatedManager用于反向关系的是该模型的默认管理器的子类。如果要为给定查询指定其他管理器,可以使用以下语法:

from django.db import models

class Entry(models.Model):
    #...
    objects = models.Manager()  # Default Manager
    entries = EntryManager()    # Custom Manager

b = Blog.objects.get(id=1)
b.entry_set(manager='entries').all()

处理相关对象的其他方法

add(obj1, obj2, …)
将指定的模型对象添加到相关的对象集。

create(**kwargs)
创建一个新对象,保存它并将其放入相关的对象集中。返回新创建的对象。

remove(obj1, obj2, …)
从相关对象集中删除指定的模型对象。

clear()
从相关对象集中删除所有对象。

set(objs)
替换相关对象集。
要分配相关集的成员,请将该set()方法与可迭代的对象实例一起使用。

#例如,ie1和e2是Entry 实例:
b = Blog.objects.get(id=1)
b.entry_set.set([e1, e2])
#如果该clear()方法可用,则将从entry_set迭代中的所有对象添加到集合之前删除任何预先存在的对象。如果该clear()方法不可 用,则将添加iterable中的所有对象,而不删除任何现有元素。

每个“反向”操作都会立即对数据库产生影响。每次添加,创建和删除都会立即自动保存到数据库中。

多对多关系

多对多关系的两端都可以自动访问另一端的API。API的工作方式类似于上面的一对多关系。

一个区别在于属性命名:定义的模型 ManyToManyField使用该字段本身的属性名称,而“反向”模型使用原始模型的小写模型名称,加上’_set’(就像反向一对多关系一样) 。

e = Entry.objects.get(id=3)
e.authors.all() # Returns all Author objects for this Entry.
e.authors.count()
e.authors.filter(name__contains='John')

a = Author.objects.get(id=5)
a.entry_set.all() # Returns all Entry objects for this Author.

使用ForeignKey, ManyToManyField可以指定 related_name。在上面的示例中,如果ManyToManyFieldin Entry指定了 related_name=‘entries’,那么每个Author实例都将具有一个 entries属性而不是entry_set。

从一到多的关系的另一个区别是,除了实例,模型add(),set()和remove()许多一对多的关系,接受主键值的方法。例如,如果e1和e2是 Entry实例,那么这些set()调用相同的工作:

a = Author.objects.get(id=5)
a.entry_set.set([e1, e2])
a.entry_set.set([e1.pk, e2.pk])

一对一的关系

一对一关系与多对一关系非常相似。如果在模型上定义一个OneToOneField,则该模型的实例将通过模型的简单属性访问相关对象。

class EntryDetail(models.Model):
    entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
    details = models.TextField()

ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.

猜你喜欢

转载自blog.csdn.net/boyun58/article/details/89072627