【Django】Django框架进阶详述(三)

  • 2020 年 2 月 14 日
  • 笔记

1、Model

Model用于定义数据模型,使用python的形式操作数据库。

from django.db import models    class Person(models.Model):      first_name = models.CharField(max_length=30)      last_name = models.CharField(max_length=30)

将上述Model转换为SQL语句如下:

CREATE TABLE myapp_person (      "id" serial NOT NULL PRIMARY KEY,      "first_name" varchar(30) NOT NULL,      "last_name" varchar(30) NOT NULL  );

2、Field

(1)Field类型确定字段类型:

  • Field type
    • CharField
    • IntegerField
    • DatetimeField
    • AutoField

每个Field通常有一些参数,常见的有:

  • Field option
    • null
    • black
    • choices
    • default
    • help_text
    • primary_key
    • unique
    • verbose_name

关系:

  • Many-To-One ForeignKey
  • Many-To-Many ManyToManyField
  • One-To-One OneToOneField
class Manufacturer(models.Model):      # ...      pass    class Car(models.Model):      manufacturer = models.ForeignKey(Manufacturer)    class Group(models.Model):      #...      pass    Class User(models.Model):      groups = ManyToManyField(Group)    from django.db import models    class Place(models.Model):      name = models.CharField(max_length=50)      address = models.CharField(max_length=80)    class Restaurant(models.Model):      place = models.OneToOneField(Place, primary_key=True)      serves_hot_dogs = models.BooleanField(default=False)      serves_pizza = models.BooleanField(default=False)

(2)内置Field 详见:https://docs.djangoproject.com/en/1.10/ref/models/fields/ (3)Meta方法 详见:https://docs.djangoproject.com/en/1.10/ref/models/options/ (4)重写save方法

from django.db import models    class Blog(models.Model):      name = models.CharField(max_length=100)      tagline = models.TextField()        def save(self, *args, **kwargs):          do_something()          super(Blog, self).save(*args, **kwargs) 		# Call the "real" save() method.          do_something_else()
  • 一定要记得调用超类的save方法,否则不会保存到数据库。

(5)执行SQL

>>> for p in Person.objects.raw('SELECT * FROM myapp_person'):  ...     print(p)  John Smith  Jane Jones

3、模型继承

(1)抽象基类 你只想使用父类来持有一些信息,你不想在每个子模型中都敲一遍。这个类永远不会单独使用,使用abstract表明基类,数据库不会建该表。

from django.db import models    class CommonInfo(models.Model):      name = models.CharField(max_length=100)      age = models.PositiveIntegerField()        class Meta:          abstract = True          ordering = ['name']    class Student(CommonInfo):      home_group = models.CharField(max_length=5)        class Meta(CommonInfo.Meta):          db_table = 'student_info'
  • Meta也可以继承, 如果子类不实现Meta则默认会继承。

(2)多表继承 如果你继承一个已经存在的模型且想让每个模型具有它自己的数据库表,那么应该使用多表继承。每一个层级下的每个 model 都是一个真正意义上完整的 model 。 每个 model 都有专属的数据表,都可以查询和创建数据表。 继承关系在子 model 和它的每个父类之间都添加一个链接 (通过一个自动创建的 OneToOneField来实现)。

from django.db import models    class Place(models.Model):      name = models.CharField(max_length=50)      address = models.CharField(max_length=80)    class Restaurant(Place):      serves_hot_dogs = models.BooleanField(default=False)      serves_pizza = models.BooleanField(default=False)      >>> Place.objects.create(name='Tsinghua', address='Zhongguancun')  >>> Restaurant.objects.create(name='Bishengke', address='Beijing Wangjing', serves_hot_dogs=True, serves_pizza=True)  <Restaurant: Restaurant object>  >>> p0 = Place.objects.get(name='Tsinghua')  >>> p1 = Place.objects.get(name='Bishengke')  >>> p2 = Restaurant.objects.get(name='Bishengke')  >>> p3 = Restaurant.objects.get(name='Tsinghua')  ...  DoesNotExist: Restaurant matching query does not exist.  >>> p1.restaurant  <Restaurant: Restaurant object>  >>> p0.restaurant

(3)代理继承 使用多表继承时,model的每个子类都会创建一张新数据表,通常情况下,这正是我们想要的操作。这是因为子类需要一个空间来存储不包含在基类中的字段数据。 但有时,你可能只想更改 model 在 Python 层的行为实现,比如:更改默认的 manager ,或是添加一个新方法。 而这正是代理继承要做的:为原始模型创建一个代理。你可以创建、删除、更新代理 model 的实例,而且所有的数据都可以像使用原始 model 一样被保存。 不同之处在于:你可以在代理 model 中改变默认的排序设置和默认的 manager ,更不会对原始 model 产生影响。 声明代理 model 和声明普通 model 没有什么不同。 设置Meta类中 proxy 的值为 True,就完成了对代理 model 的声明。

from django.db import models    class Person(models.Model):      first_name = models.CharField(max_length=30)      last_name = models.CharField(max_length=30)    class MyPerson(Person):      class Meta:          proxy = True        def do_something(self):          # ...          pass
  • MyPerson只是一个代理,数据库中不会建立该表,MyPerson类和它的父类 Person 操作同一个数据表。特别的是,Person 的任何实例也可以通过 MyPerson访问,反之亦然:
>>> p = Person.objects.create(first_name="foobar")  >>> MyPerson.objects.get(first_name="foobar")  <MyPerson: foobar>

(4)多重继承 多重继承和多表继承没什么大的差别,会自动添加两个OneToOneField, 需要指出,两个基类的默认自增id要重新命名,否则添加OneToOneField会有问题。也不允许子类重写父类字段。

class Article(models.Model):      article_id = models.AutoField(primary_key=True)      ...    class Book(models.Model):      book_id = models.AutoField(primary_key=True)      ...    class BookReview(Book, Article):      pass    >>> b = BookReview.objects.create(headline='', body='asdf', title='sdf')

4、Model查询

  • 增:
Model.objects.create(**kwargs)  Model(*kwargs).save()
  • 删:
QuerySet.delete()  object.delete()
  • 改:
object.attr = value  object.save()  QuerySet.update(**kwargs)
  • 查:
Model.objects.all(**kwargs)  Model.objects.filter(**kwargs)  Model.objects.get(**kwargs)
  • 结果集:
object  QuerySet
from django.db import models    class Blog(models.Model):      name = models.CharField(max_length=100)      tagline = models.TextField()        def __str__(self):              # __unicode__ on Python 2          return self.name    class Author(models.Model):      name = models.CharField(max_length=50)      email = models.EmailField()        def __str__(self):              # __unicode__ on Python 2          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()      authors = models.ManyToManyField(Author)      n_comments = models.IntegerField()      n_pingbacks = models.IntegerField()      rating = models.IntegerField()        def __str__(self):              # __unicode__ on Python 2          return self.headline
  • exclude:
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')  >>> b.save()  >>> a1 = Author.objects.create(name='guang', email='[email protected]')  >>> a2 = Author.objects.create(name='mage', email='[email protected]')  >>> Author.objects.exclude(name='mage')

关联对象查询: 一对多,多对多,一对一都存在对象间的关系,Django提供了简便的API来完成。

  • 前向查询:通过属性访问关联的(外部)对象。
>>> e = Entry.objects.get(id=2)  >>> e.blog # Returns the related Blog object.  >>> e.blog = None  >>> e.save()
  • 反向查询:如果模型有一个ForeignKey,那么该ForeignKey 所指的模型实例可以通过一个管理器返回前一个模型的所有实例。默认情况下,这个管理器的名字为foo_set,其中foo 是源模型的小写名称。该管理器返回的查询集可以用上一节提到的方式进行过滤和操作。
>>> 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()
  • 复杂查询 Q:filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR语句),你可以使用Q对象。 Q 对象 (django.db.models.Q) 对象用于封装一组关键字参数。这些关键字参数就是上文“字段查询” 中所提及的那些。
>>> from django.db.models import Q  >>> Author.objects.filter(Q(name='guang')|Q(name='mage'))

每个接受关键字参数的查询函数(例如filter()、exclude()、get())都可以传递一个或多个Q 对象作为位置(不带名的)参数。如果一个查询函数有多个Q 对象参数,这些参数的逻辑关系为“AND"。 查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。例如:

>>> Poll.objects.get(          Q(question__startswith='Who'),          Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))      )  >>> Poll.objects.get(          Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),          question__startswith='Who')
  • 保存ForignKey和ManyToManyField字段
>>> e = Entry.objects.create(blog=b,headline='Hello', body_text='Hello world', pub_date=timezone.now())  >>> e.blog = b  >>> e.authors = [a1]  >>> e.save()  >>> e.authors.add(a1, a2)  >>> e.save()  >>> e.authors.create(name='comyn', email='[email protected]')
  • QuerySet链式过滤
>>> Entry.objects.filter(  ...     headline__startswith='What'  ... ).exclude(  ...     pub_date__gte=datetime.date.today()  ... ).filter(  ...     pub_date__gte=datetime(2005, 1, 30)  ... )
  • 高级条件过滤 lookup
    • 大于 gt
    • 大于等于 gte
    • 小于 lt
    • 小于等于 lte
    • 包含 contains
    • 精确匹配 exact 也是默认方式
    • 开始于 startswith
    • 结束于 endswith
    • regex 正则匹配
    • 忽略大小写 icontains iexact istartswith iendswith iregex
    • 在列表中 in
    • 范围之内 range
    • 时间日期类型 date year month day

这种形式django称之为 lookup,使用__(双下划线)来查询。

>>> Entry.objects.filter(pub_date__lte='2006-01-01') 			# SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';  >>> Entry.objects.get(headline__exact="Man bites dog")  >>> Blog.objects.get(name__iexact="beatles blog")  >>> Blog.objects.filter(pk__in=[1,4,7])  >>> Entry.objects.filter(pub_date__range=(start_date, end_date))  >>> Entry.objects.get(title__regex=r'^(An?|The) +')
  • 跨关联关系查询
>>> Entry.objects.filter(blog__name='Beatles Blog')  >>> Blog.objects.filter(entry__headline__contains='Lennon')  		# 反向  >>> Blog.objects.filter(entry__authors__name__isnull=True) 			# 多层  >>> Blog.objects.filter(entry__authors__isnull=False,              entry__authors__name__isnull=True) 						# 多个过滤条件
  • 引用模型字段查询 F 通常我们是将模型字段与常量比较,或者比较两个字段值。
>>> from django.db.models import F  >>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))  >>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
  • 限制返回个数
>>> Entry.objects.all()[:5]  		# 返回前面5 个对象(LIMIT 5):  >>> Entry.objects.all()[5:10]  		# 返回第6 至第10 个对象(OFFSET 5 LIMIT 5)
  • 查询缓存

查询集不会永远缓存它们的结果。当只对查询集的部分进行求值时会检查缓存, 但是如果这个部分不在缓存中,那么接下来查询返回的记录都将不会被缓存。特别地,这意味着使用切片或索引来限制查询集将不会填充缓存。例如,重复获取查询集对象中一个特定的索引将每次都查询数据库:

>>> 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
  • 查询对象比较

为了比较两个模型实例,只需要使用标准的Python 比较操作符,即双等于符号:==。在后台,它会比较两个模型主键的值。

>>> some_entry == other_entry  			# equal behind  >>> some_entry.id == other_entry.id
  • 所有查询api,详见:https://docs.djangoproject.com/en/1.10/ref/models/querysets/
  • annotate:添加注释属性,与聚合函数一起使用返回聚合值。
>>> from django.db.models import Count  >>> qs = Question.objects.annotate(Count('choices'))  >>> for q in qs:  ...:     print(q.choices__count)  >>> qs = Question.objects.annotate(choices_num=Count('choices'))  >>> for q in qs:  ...:     print(q.choices_num)
  • aggregate:聚合函数返回结果字典。
>>> from django.db.models import Count  >>> q = Blog.objects.aggregate(Count('entry'))  {'entry__count': 16}  >>> q = Blog.objects.aggregate(number_of_entries=Count('entry'))  {'number_of_entries': 16}
  • 聚合类函数 聚合类函数配合 聚合函数 或 注释函数使用,在 django.db.models模块中。
    • class Avg(expression, output_field=FloatField(), **extra)
    • class Count(expression, distinct=False, **extra)
    • class Max(expression, output_field=None, **extra)
    • class Min(expression, output_field=None, **extra)
    • class StdDev(expression, sample=False, **extra) # 标准除
    • class Sum(expression, output_field=None, **extra)
    • class Variance(expression, sample=False, **extra) # 方差
  • distinct:去重
>>> Author.objects.distinct()  >>> Entry.objects.order_by('pub_date').distinct('pub_date')  >>> Entry.objects.order_by('blog').distinct('blog')
  • values:返回ValuesQuerySet(类字典),而不是模型实例对象
# This list contains a Blog object.  >>> Blog.objects.filter(name__startswith='Beatles')  [<Blog: Beatles Blog>]    # This list contains a dictionary.  >>> Blog.objects.filter(name__startswith='Beatles').values()  [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]    >>> Blog.objects.values()  [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}],  >>> Blog.objects.values('id', 'name')  [{'id': 1, 'name': 'Beatles Blog'}]
  • values_list 与values类似,但返回元组而不是字典。
>>> Entry.objects.values_list('id').order_by('id')  [(1,), (2,), (3,), ...]    >>> Entry.objects.values_list('id', flat=True).order_by('id')  [1, 2, 3, ...]
  • defer和only 有时模型包含一些含有大量数据的类型,通常模型会将数据库取出的数据转换为python对象,有时我们不需要浪费这性能。 defer:排除;only:仅转换。
>>> Entry.objects.defer("headline")  >>> Entry.objects.only("headline")
  • using:使用哪个数据库
# queries the database with the 'default' alias.  >>> Entry.objects.all()    # queries the database with the 'backup' alias  >>> Entry.objects.using('backup')
  • select_for_update 返回一个 queryset ,会锁定相关行直到事务结束。在支持的数据库上面产生一个SELECT … FOR UPDATE 语句。
entries = Entry.objects.select_for_update().filter(author=request.user)
  • raw:执行原始SQL
>>> for p in Person.objects.raw('SELECT * FROM myapp_person'):  ...     print(p)  John Smith  Jane Jones
  • get_or_create
try:      obj = Person.objects.get(first_name='John', last_name='Lennon')  except Person.DoesNotExist:      obj = Person(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9))      obj.save()    Equal    obj, created = Person.objects.get_or_create(      first_name='John',      last_name='Lennon',      defaults={'birthday': date(1940, 10, 9)},  )
  • update_or_create
  • bulk_create
>>> Entry.objects.bulk_create([  ...     Entry(headline="Django 1.0 Released"),  ...     Entry(headline="Django 1.1 Announced"),  ...     Entry(headline="Breaking: Django is awesome")  ... ])
  • in_bulk
>>> Blog.objects.in_bulk([1])  {1: <Blog: Beatles Blog>}  >>> Blog.objects.in_bulk([1, 2])  {1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>}
  • latest earliest first last
>>> Entry.objects.latest('pub_date')  			# Or meta define  get_latest_by()

5、事务

事务是绑在view中实现的。

from django.db import transaction    @transaction.non_atomic_requests  def my_view(request):      do_stuff()    @transaction.atomic  def viewfunc(request):      # This code executes inside a transaction.      do_stuff()

6、自定义管理器

  • 添加额外管理器方法

添加额外管理器方法是为你的类增加“表级”功能的首选方式。 (如果要添加行级功能,比如只对某个模型的实例起作用,应使用模型方法,而不是管理器方法。) 自定义的管理器方法可以返回你想要的任何数据,而不需要返回一个查询集。 例如,下面这个自定义管理器提供一个with_counts() 方法,它返回所有OpinionPoll 对象的列表,列表的每项都有一额外num_responses 属性,该属性保存一个聚合查询的结果(注:对应的应是SQL查询语句中的COUNT(*)生成的项):

from django.db import models    class PollManager(models.Manager):      def with_counts(self):          from django.db import connection          cursor = connection.cursor()          cursor.execute("""              SELECT p.id, p.question, p.poll_date, COUNT(*)              FROM polls_opinionpoll p, polls_response r              WHERE p.id = r.poll_id              GROUP BY p.id, p.question, p.poll_date              ORDER BY p.poll_date DESC""")          result_list = []          for row in cursor.fetchall():              p = self.model(id=row[0], question=row[1], poll_date=row[2])              p.num_responses = row[3]              result_list.append(p)          return result_list    class OpinionPoll(models.Model):      question = models.CharField(max_length=200)      poll_date = models.DateField()      objects = PollManager()    class Response(models.Model):      poll = models.ForeignKey(OpinionPoll)      person_name = models.CharField(max_length=50)      response = models.TextField()
  • 添加自定义manager
class AuthorManager(models.Manager):      def get_queryset(self):          return super(AuthorManager, self).get_queryset().filter(role='A')    class EditorManager(models.Manager):      def get_queryset(self):          return super(EditorManager, self).get_queryset().filter(role='E')    class Person(models.Model):      first_name = models.CharField(max_length=50)      last_name = models.CharField(max_length=50)      role = models.CharField(max_length=1, choices=(('A', _('Author')), ('E', _('Editor'))))      people = models.Manager()      authors = AuthorManager()      editors = EditorManager()    >>> Person.authors.all()  >>> Person.editors.all()  >>> Person.people.all()