创建数据模型,保存数据都是为了可以在需要多少时候通过访问数据库进行查询获取相应的数据数,Django提供一套完善、方便、高效的API。本节内容将以下面的模型为基础进行讲解。

from django.db import models


class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self):  # python3使用
        return self.name

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


class Author(models.Model):
    name = models.CharField(max_length=200)
    email = models.EmailField()

    def __str__(self):  # python3使用
        return self.name

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


class Entrt(models.Model):
    bolg = 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):  # python3使用
        return self.headline

    def __unicode__(self):  # python2使用
        return self.headline

一、创建对象

  • 使用save()方法
    blog = Blog(name="New Blog", tagline="This is a new blog.")
    blog.save()  # 使用该方法没有返回值。
    
  • 使用create()方法
    Blog.objects.create(name="New Blog", tagline="This is a new blog.")
    

二、保存对象

可以使用save()方法,保存对数据库内已有独对象的修改。如果指定或添加了错误类型的对象,Django会抛出相应的异常。

blog = Blog.objects.get(id=1)
blog.name = "New blog name"
blog.save()
  • 保存外键字段
    entry = Entry.objects.get(pk=1)
    blog = Blog.objests.get(pk=1)
    entry.blog = blog
    entry.save()
    
  • 保存多对多字段
    多对多字段保存时,需要调用add()方法,而不是直接给属性赋值,并且它不需要调用save()方法。
    author = Author.objects.create(name="Tom")
    entry.authors.add(author)
    
    在一样语句内,也可能同时添加多个对象到多对多字段。
    allen = Author.objects.create(name="Allen")
    bob = Author.objects.create(name="Bob")
    tom = Author.objects.create(name="Tom")
    entry.authors.add(allen, bob, tom)
    

三、检索对象

想要从数据库内检索对象,需要基于模型类,通过管理器构造一个查询结果集(QuerySet)。
每个QuerySet代表一些数据库对象的集合,它可以是零个、一个或者多个。

1.检索所有对象

使用all()方法,可以获取某张表的所有记录。

Entry.objects.all()

2.过滤对象

  • filtet(**kwargs):返回一个根据指定参数查询出来的QuerySet,**kwargs的参数格式必须是对应模型中设置的一些字段。
    Entry.objects.filter(n_comments=1)
    
  • exclude(**kwargs):返回除了根据指定参数查询出来的QuerySet,**kwargs的参数格式必须是对应模型中设置的一些字段。
    Entry.objects.exclude(n_comments=1)
    
链式操作

过滤操作是支持链式操作的,使用filetr()和exclude()的结果仍然是QuerySet,因此还可以继续使用过滤操作。

Entry.objects.filter(headline__startswith='What') \ 
    .exclude(pub_date__gte=datetime.date.today()) \ 
    .filter(pub_date__gte=datetime(2005, 1, 30))
被过滤的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())
QuerySet是惰性的

一个创建QuerySet的动作不会立刻导致任何的数据库行为,可以不断的进行filter操作,Django不会运行任何实际的数据库查询操作,直到QuerySet被计算是才执行查询操作。

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)

上面的示例代码看上去像是执行了三次数据库操作,但实际上只在最后一行print(q)才做了一次。一般而言,QuerySet的结果直到你要使用时才会从数据库中拿出数据。

3.检索单个对象

filter方法时钟返回是一个QuerySet,那是过滤后的对象只有一个,返回的也是只有一个对象的QuerySet,它是一个集合类型对象,可以进行迭代、循环、索引。
如果确保检索的对象有且只有一个,那么可是用get()方法来直接返回这个对象。

Entry.objects.get(id=1)

注意,get()方法看似和filter()方法获得QuerySet进行[0]切片相同,都是获取单一对象。但是如果在查询时没有匹配的对象,使用get()方法会抛出DoesNotExist异常。如果在使用get()方法查询时,如果结果查过1个,将会抛出MultipleObjectsReturned异常。

4.切片操作

QuerySet同样可以是用Python操作列表的方法进行切片操作。但Django不允许出现负索引操作

Entry.objects.all()[:5]      # 返回前5个对象
Entry.objects.all()[5:10]    # 返回第6个到第10个对象

通常情况下,切片操作会放回一个新的QuerySet,并不会被立即执行。但如果在切片操作中指定了补偿,查询操作就立即执行。

Entry.objects.all()[:10:2]

若果要获取单一的对象而不是一个列表,可以使用索引而不是切片。

Entry.objects.order_by('headline')[0]  # 如果没有匹配对象,将会抛出IndexError异常
Entry.objects.order_by('headline')[0:1].get()  # 如果没有匹配对象,将会抛出DoesNotExists异常

5.字段查询

字段查询就是filter(),exclude()和get()等方法的关键字参数,基本格式为:filed__lookuptype=value(注意field后面是双下划线)。其中的字段必须是模型中定义的字段之一,但是如果字段为ForeignKey的话,也可以使用filed_id(使用单下划线,此情况下键值是外键模型的主键原生值)

Entry.objects.filter(blog_id=1)

如果传递一个非法的键值,将会抛出TypeError异常。

  • exact:
    默认类型。如果你不提供查询类型,或者关键字参数不包含一个双下划线,那么查询类型就是默认的exact。
    # 下面两个查询操作是等价的
    Blog.objects.get(id__exact=1)  # Explicit form
    Blog.objects.get(id=1)         # __exact is implied
    
  • iexact:
    不区分大小写。
    Blog.objects.get(name__iexact="beatles blog")  # 会匹配标题为"Beatles Blog","beatles blog",甚至"BeAtlES blOG"的Blog。
    
  • contains:
    大小写敏感的包含查询。
  • icontains:
    忽略大小写敏感的包含查询。
  • startswith和endswith:
    大小写敏感的以xxx开头和以xxx结尾。
  • istartswith和iendswith:
    忽略大小写敏感的以xxx开头和以xxx结尾。

6.跨关系查询

Django提供了一种强大而直观的方式来追踪查询中关联关系,在数据库操作中会执行带有JOIN的SQL语句。如果要使用跨关系查询,只需跨模型使用关联字段名,字段名与字段名之间使用双下划线连接。

Entry.objects.filter(blog__name='New Blog')

反向查询操作也是可以正常使用的。

Blog.objects.filter(entry__headline__contains='Lennon')

如果在跨多个关系进行筛选时,某个中间模型没有满足筛选条件的值,Django会将它当做一个空但是有效的对象。这样就意味着不会抛出异常。

Blog.objects.filter(entry__authors__name='Lennon')

如果Entry中没有任何关联的author,那么它将当昨没有name,而不会因为author引发一个错误。通常情况,下面的方法是比较符合逻辑的处理方式。

Blog.objects.filter(entry__authors__name__isnull=True)  # 表示查询authors的name为空的

7.使用F表达式引用模型的字段

Django 提供了F表达式实现将模型的一个字段与同一个模型的另外一个字段进行比较。 F()实例充当查询字段的引用。
查出所有评论数大于pingbacks的博客条目,我们构建了一个F()对象,指代pingbac 的数量,然后在查询中使用该F()对象:

from django.db.models import F
Entry.objects.filter(n_comments__gt=F('n_pingbacks'))

Django支持对F()对象进行加、减、乘、除、求余和次方,另一操作数既可以是常量,也可以是其它F()对象。要找到那些评论数两倍于pingbacks的博客条目,我们这样修改查询条件:

Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)

找出所有评分低于 pingback 和评论总数之和的条目。

Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))

你还可以在F()中使用双下划线来进行跨表查询。

Entry.objects.filter(authors__name=F('blog__name'))

对于date和date/time字段,还可以加或减去一个timedelta对象。

from datetime import timedelta
Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))

F()对象还支持.bitand()、.bitor()、.bitrightshift()和.bitleftshift()4种位操作。

F('somefield').bitand(16)

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

等效于LIKE的SQL语句的字段查询子句(iexact,contains,icontains,startswith,istartswith,endswith 和 iendswith)会将LIKE语句中有特殊用途的两个符号,即百分号和下划线自动转义。(在LIK 语句中,百分号匹配多个任意字符,而下划线匹配一个任意字符。)Django帮你自动转义了百分符号和下划线,你可以和普通字符一样使用它们。

# 下面两者是等效的
Entry.objects.filter(headline__contains='%')
SELECT ... WHERE headline LIKE '%\%%';

9.缓存与查询集

每个QuerySet都包含一个缓存,用于减少对数据库的实际操作。
新创建的 QuerySet 缓存是空的。一旦要计算 QuerySet 的值,就会执行数据查询,随后,Django 就会将查询结果保存在 QuerySet 的缓存中,并返回这些显式请求的缓存(例如,下一个元素,若 QuerySet 正在被迭代)。后续针对 QuerySet 的计算会复用缓存结果。
要想高效的利用查询结果,降低数据库负载,你必须善于利用缓存。下面的示例意味着同样的数据库查询会被执行两次,实际加倍了数据库负载。同时,有可能这两个列表不包含同样的记录,因为在两次请求间,可能 Entry被添加或删除了。,导致脏数据的问题:

print([e.headline for e in Entry.objects.all()])
print([e.pub_date for e in Entry.objects.all()])

为了避免上面的问题,好的使用方式如下,这只产生一次实际的查询操作,并且保持了数据的一致性:

queryset = Entry.objects.all()
print([p.headline for p in queryset])
print([p.pub_date for p in queryset])

何时不会被缓存
查询结果集并不总是缓存结果。当仅计算查询结果集的 部分 时,会校验缓存,若没有填充缓存,则后续查询返回的项目不会被缓存。特别地说,这意味着使用数组切片或索引的 限制查询结果集 不会填充缓存。
重复的从某个查询结果集对象中取指定索引的对象会每次都查询数据库:

queryset = Entry.objects.all()
print(queryset[5])  # 查询数据库
print(queryset[5])  # 再次查询数据库

不过,若全部查询结果集已被检出,就会去检查缓存:

queryset = Entry.objects.all()
[entry for entry in queryset]  # 查询数据库
print(queryset[5])  # 使用缓存
print(queryset[5])  # 使用缓存

以下展示一些例子,这些动作会触发计算全部的查询结果集,并填充缓存的过程:

[entry for entry in queryset]
bool(queryset)
entry in queryset
list(queryset)

简单的打印QuerySet并不会创建缓存,因为__repr__()调用只返回全部查询机的一个切片。

四、使用Q进行复杂操作

在类似filter()中,查询使用的关键字参数是通过”AND”连接起来的。如果你要执行更复杂的查询(例如,由OR语句连接的查询),你可以使用Q对象。
一个Q对象(django.db.models.Q)用于压缩关键字参数集合。
Q对象能通过&和|操作符连接起来。当操作符被用于两个Q对象之间时会生成一个新的Q对象。Q对象也可通过~操作符反转,允许在组合查询中组合普通查询或反向(NOT)查询

from django.db.models import Q
Q(question__startswith='What')
Q(question__startswith='Who') | Q(question__startswith='What')
Q(question__startswith='Who') | ~Q(pub_date__year=2005)

五、比较对象

要比较两个模型示例,只需要使用标准的python比较操作符,两个等号:== 。实际上这比较了两个模型示例的主键值。

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

六、删除对象

通常,删除方法被命名为 delete()。该方法立刻删除对象,并返回被删除的对象数量和一个包含了每个被删除对象类型的数量的字典。

Entry.objects.get(id=1).delete()

也可以批量删除所有QuerySet。

Entry.objects.all().delete()

七、复制模型实例

然没有用于拷贝模型实例的内置方法,但仍能很简单的拷贝所有字段值创建新实例。最简单的例子,你只需将 pk 设为 None。

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

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

如果使用了继承,事情会变得更加复杂,参考一下示例:

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有一个对Author的ManyToManyField的关联关系。在复制条目后,必须为新条目设置多对多关联关系。

entry = Entry.objects.all()[0]
old_authors = entry.authors.all()
entry.pk = None
entry.save()
entry.authors.set(old_authors)

对于OneToOneField关联,必须拷贝关联对象,并将其指定给新对象的关系字段,避免违反一对一唯一型约束。

detail = EntryDetail.objects.all()[0]
detail.pk = None
detail.entry = entry
detail.save()

八、一次修改多个对象

可以通过update()方法统一设置QuerySet中所有对象的某个字段。

Entry.objects.filter(pub_date__year=2007).update(headline="Everything is the same")

该方法只能设置非关联字段和ForeignKey字段。想要修改非关联字段,需要用常量提供新值。需要修改ForeignKey字段,需要将新值设置为目标模型的新实例。

blog = Blog.objects.get(id=1)
Entry.objects.all().update(blog=blog)

update()方法会被立刻执行,并返回操作匹配到的行的数目(若某些行早已是新值,则可能不等于实际匹配的行数)。更新QuerySet的唯一限制即它只能操作一个数据表:该模型的主表。可以基于关联字段进行筛选,但只能更新模型主表中的列。

b = Blog.objects.get(id=1)
Entrty.objects.select_related().filter(blog=b).update(headline="Everything is the same")  # 更新该博客的所有标题

update()方法会直接转换成一个SQL语句,并立刻批量执行。它不会运行模型的save()方法,或者长生pre_save或post_save信号。如果想保存QuerySet中每个条目并确保每个实例的save()方法都被调用,不需要使用任何特殊的函数来处理,只需要迭代它们并调用save()方法即可。

for item in my_queryset:
    item.save()

update()方法可以配合F表达式,这对于批量更新统一模型中某个字段特别有用。

Entry.objects.all().update(n_pingbacks=F("n_pingbacks") + 1)  # 增加Blog中每个Entry的pingbacks个数

但是,与filter()和exclude()中的F()对象不同,在update()中不可以使用F()对象进行跨表操作,只可以引用正在更新的模型的字段。如果使用F()对象引入另一张表的字段,将会抛出FieldError异常。

九、关联对象

当在模型中定义了关联关系后,该模型的示例将会自动获取一套API,能快捷地访问关联对象。

1.一对多关联

  • 正向查询:
    使用点方法加属性名,可以直接访问外键。
    e = Entry.objects.get(id=2)
    e.blog
    
    对外建的修改,必须调用save()方法进行保存。
    e = Entry.objects.get(id=2)
    e.blog = some_blog
    e.save()
    
    如果一个外键字段设置有null = True属性,那么可用过给该字段赋值为None的方法移除外键值。
    e = Entry.objects.get(id=2)
    e.blog = None
    e.save()
    
    首次对一个外键关系正向访问时,关系对象将会被缓存。后续再对同一对象的访问会使用这个缓存。
    e = Entry.objects.get(id=2)
    print(e.blog)  # 访问数据库,获取实际数据
    print(e.blog)  # 不访问数据库,使用缓存版本
    
    QuerySet的select_related()方法会预先用所有一堆多关联对象填充缓存。
    e = Entry.objects.select_related().get(id=2)
    print(e)  # 不访问数据库,使用缓存版本
    print(e)  # 不访问数据库,使用缓存版本
    
  • 反向查询
    如果一个模型有ForeignKey,那么该ForeignKey所指向的外键模型的实例可以通过一个管理器进行反向查询,返回源模型的所有实例。默认情况下,这个管理器的名字为FOO_set,FOO代表源模型的小写名字。该管理器返回的QuerySet可以进行上面提到的过滤和操作。
    b = Blog.objects.get(id=1)
    b.entry_set.all()
    b.entry_set.filter(headline__contains="Lennon")
    b.entry_set.count()
    
    如果在定义ForeignKey的时设置了related_name参数重写FOO_set,如将Entry模型的blog修改为:blog = ForeignKey("Blog", on_delete=models.CASCADE, related_name="entries),那么需要使用下面示例的操作方式。
    b = Blog.objects.get(id=1)
    b.entries.all()
    b.entries.filter(headline__contains="Lennon")
    b.entries.count()
    
  • 使用自定义的反向管理器
    默认情况下,用于反向关系的RelatedManager是该模型的默认管理器的子类。如果想为某个查询指定一个不同管理器,可以使用如下语法
    from django.db import models
    class Entry(models.Model):
      objects = models.Manager()  # 默认管理器
      entries = EntryManager()  # 自定义管理器
    b = Blog.objects.get(id=2)
    b.entry_set(manager="entries").all()
    
    当然,指定一个自定义反向管理器也允许调用模型自定义方法。
    b.entry_set(manager="entries").is_published()
    
  • 管理关联对象的额外方法
    ForeignKey管理器处理前面所提到的方外一样,还有一些额外的方法可以使用。
    • add(obj1, obj2, ...)
      将退订的模型对象加入关联对象集合。
    • create(**kwargs)
      创建一个新对象保存,并将其放入关联对象集合中。返回新创建的对象
    • remove(obj1, obj2, ...)
      从关联对象集合删除指定模型对象。
    • clear()
      从关联对象集合删除所有对象。
    • set(objs)
      替换关联对象集合。
      要指定关联集合的成员,可以调用set()方法,并传入可迭代的对象实例集合。
      # 假设e1, e2都是Entry的实例
      b = Blog.objects.get(id=2)
      b.entry_set.set([e1, e2])
      
      如果能是使用clear()方法,entry_set中所有旧对象会在将可迭代集合中的对象加入其中之前被删除。如果不能使用clear()方法,添加新对象时不会删除旧对象。

2.多对多关联

多对多关联的两端均自动获取访问另一端的API。该API的工作方式类似上面的反向一对多关联。不同点在为属性命名上:定义了ManyToManyField的模型使用字段名作为属性名,而反向模型使用源模型名的小写形式加上”_set”。

e = Entry.objects.get(id=3)
e.authors.all()
e.authors.count()
e.authors.filter(name__contains="Jhon")

a = Author.objects.get(id=5)
a.entry_set.all()

和ForeignKey一样,ManyToManyField能指定related_name。
另一个和一对多关联不同的地方是,除了模型示例以外,多对多关联中的add(),set()和remove()方法能接受主键值。

# 假设e1, e2都是Entry的实例
a = Author.objects.get(id=5)
a.entry_set.set([e1, e2])
a.entry_set.set([e1.pk, e2.pk])

3.一对一关联

一对一关联与多对一关联非常类似。如果在模型中定义了OneToOneField,该模型的实例只需要通过其属性就能访问关联对象。

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


ed = EntryDetail.objects.get(id=1)
ed.entry

不同的在于反向查询。一对一关联所关联的对象也能访问Manager对象,但这个仅代表一个对象,而不是对象的集合。

e = Entry.objects.get(id=1)
e.entrydetail

若未为关联关系指定对象,Django将会抛出DoesNotExists异常。

0条评论

相关推荐

django教程

r

Django 2019-05-20 10:53:53

Celery

celery学习资料

Django 2019-05-25 18:41:55

Django-rest-framework教程

django-rest-framework教程。

Django 2019-07-18 16:33:26

django实用资料

django项目从0到1自己总结的实用的资料,大部分常用的功能这里都有

Django 2019-05-08 18:21:34