Django Model

读者:王蒙
标签:网络编程,Web Framework
简介:Model 使得 Django 操作数据库变得非常简单。

目标读者

Python 开发,网站开发

预备知识

Python, ORM

问题

  • Model 带来了那些好处?

  • Model 的使用:

    • Model 定义
    • django migrate
    • Model 增删改查
    • Model 和 ModelAdmin
    • 自定义字段
    • Model 继承
      • Multi
      • Proxy
      • Abstract
    • 优化查询效率

解决办法

Model 带来了那些好处?

  • 很容易改用别的数据库去实现。比如代码之前使用 mysql, 后来想改用 postgesql , 只要去 settings.py 文件中修改配置即可。
  • Model 建立了数据库记录与对象的映射,使得可以使用处理对象的方式处理数据库,特别是在处理外键的时候非常方便。
  • Model 提供了很多抽象层次很高的字段,比如 EmailField 会自动确保输入的字符串是符合邮箱格式的字符串。除此之外,还可以自定义字段。
  • Model 提供了很多非常简洁的筛选过滤的操作。

Model 的使用

Model 的定义

  • 给出该 Model 需要的字段。

  • Django 提供了丰富的字段(比数据库提供的字段要丰富,比如提供了 FileField 等字段)。Django 有哪些字段请参考 Model Field Reference

  • 注意 relationship fields 字段。

  • Model 中的嵌套类 class Meta 定义了该 Model 的 metadata。具体参考 Model meta options 。举个例子:class Meta: ordering = (‘title’, ‘-created’) 表示查询结果以 title 升序, created 降序排列。

  • Model class Meta 中要特别注意 Model 的三种**继承模式**。

  • Abstract: 被继承的 Model, 不会建立对应的表。继承的 Model 会在被继承 Model 的基础上添加字段。

    from django.db import models
        class BaseContent(models.Model):
        title = models.CharField(max_length=100)
        created = models.DateTimeField(auto_now_add=True)
        class Meta:
            # abstract inherit
            abstract = True
    
    class Text(BaseContent):
        body = models.TextField()
    
  • Multi-table: 被继承的 Model, 也会建立对应的表。继承的 Model 会在被继承 Model 的基础上添加字段。

    from django.db import models
    class BaseContent(models.Model):
        title = models.CharField(max_length=100)
        created = models.DateTimeField(auto_now_add=True)
    
    class Text(BaseContent):
        body = models.TextField()
    
  • Proxy:被继承的 Model 会建立对应的表,但是继承的 Model 对应的表就是被继承 Model 的表。继承的 Model 是添加了新的方法(不更改字段),方便使用。

    from django.db import models
    from django.utils import timezone
    class BaseContent(models.Model):
        title = models.CharField(max_length=100)
        created = models.DateTimeField(auto_now_add=True)
    
    
    class OrderedContent(BaseContent):
        class Meta:
            proxy = True
            ordering = ['created']
    
        def created_delta(self):
            return timezone.now() - self.created
    

在数据库中创建 model 对应的数据

下面两句命令,会在数据库中创建以及更新 model 对应的数据:

$ python manage.py makemigrations {app_name} $ python manage.py migrate

Model 的增删改查

增加记录

# 新建 model 时,注意一点,就是外键取值是个对象,不是键值
m = Module(course=course, title='title', description='description')
# 不执行 save 不会把该对象保存到数据库中
m.save()

# Model manager 的 create 方法也能新建对象。而且 create 创建对象,不必再调用 save 方法。
Module.objects.create(title='title', description='description')

删除记录:

# m is an instance of a kind of Model.
m.delete()

# 批量删除一批
User.objects().all().delete()

更新记录:

# m is an instance of a kind of Model.
m.title = 'change_title'
# you have to call save method, to update change to database.
m.save()
  • 查询,Django 查询需要了解 QuerySet , 详细的 QuerySet API, 参见 Make Query

    • 双下划线查询字段。
    • Q 函数,组合查询条件。
    • F 函数,选择自身的字段。
    • aggregate 聚合查询,annotate per-object 聚合查询。

自定义字段

Django model 可以自定义字段,下面是个自定义字段的例子。更多内容查看 custom-model-fields

class OrderField(models.PositiveIntegerField):

    def __init__(self, for_fields=None, *args, **kwargs):
        self.for_fields = for_fields
        super(OrderField, self).__init__(*args, **kwargs)

    def pre_save(self, model_instance, add):
        if getattr(model_instance, self.attname) is None:
            # no current value
            try:
                qs = self.model.objects.all()
                if self.for_fields:
                    # filter by objects with the same field values for the fields in "for_fields"
                    query = {field: getattr(model_instance, field) for field in self.for_fields}
                    qs = qs.filter(**query)
                # get the order of the last item
                last_item = qs.latest(self.attname)
                value = last_item.order + 1
            except ObjectDoesNotExist:
                value = 0
            setattr(model_instance, self.attname, value)
            return value
        else:
            return super(OrderField, self).pre_save(model_instance, add)

ModelAdmin

Django 自身提供了 Admin 管理界面。ModelAdmin 定义了 Model 在 Admin 管理界面如何展示。

class PostAdmin(admin.ModelAdmin):
    list_display = ('title', 'slug', 'author', 'publish', 'status')
    list_filter = ('status', 'created', 'publish', 'author')
    search_fields = ('title', 'body')
    prepopulated_fields = {'slug': ('title', )}
    raw_id_fields = ('author',)
    date_hierarchy = 'publish'
    ordering = ['status', 'publish']

admin.site.register(Post, PostAdmin)

优化查询效率

Django 中 QuerySet 负责实际查询数据库(把ORM访问方式翻译成 SQL 语句,访问数据库)。QuerySet 是 lazy evaluated(延迟计算的,只有到实际用的时候才会运算)。除此之外 QuerySet 在某些条件会下缓存查询结果,尽可能多地使用缓存的结果,少访问数据库,是可以提高效率的。 下面举例说明几个提高查询效率的细节:

lazy evaluated: QuerySet 是 lazy evaluated。如下代码其实就访问了数据库一次,是 print 触发了数据访问。

       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)

特别注意,外键也是 lazy evaluated。就是说 QuerySet 首先获得外键的键值(id 号),到真需要外键对应的对象时,才会查询数据库得到外键对应的对象。

比如对于如下代码,只有到 print(b.a) 才会访问数据库,得到 a instance 。

select_related:是针对 ForeignKey 和 OneToOne 字段的优化。比如

actions = actions.filter(user_id__in=following_ids)
                .select_related('user', 'user__profile')
actions = actions.filter(user_id__in=following_ids)

for action in actions:
        # action.user 会执行一次 SQL 查询,多次执行会使性能变差。
        user = action.user
        # user.profile 会执行一次 SQL 查询,多次执行会使性能变差。
        user_profile = user.profile

select_related 的优势在于,把多次数据库查询整成了一次数据库查询(通过 SQL 中的 join 完成),提高了效率。

select_related 用于 ForeignKey 和 OneToOne 字段。

prefetch_related:

prefetch_related 用户 ManyToMany 字段和反向 ForeignKey 关系。

prefetch_related 能提高性能的原因在于会缓存查询结果,减少数据库访问次数。

https://docs.djangoproject.com/zh-hans/2.0/ref/models/querysets/#prefetch-related 中的例子为例。

prefetch_related 会提交执行 Toppings.objects.all() ,把所有的 toppings 缓存到本地。然后用 Python 对每个 Pizza instance 和 toppings 做 join。得到每个 Pizza instance 对应的 toppings 。

todo: 我常常想,如果数据库比较大,prefetch_related 会不会导致内存错误。

Pizza.objects.all().prefetch_related('toppings')

Manager

manager 是访问 Model 的接口。每个 Model 至少有一个 manager。

继承 models.Manager 类,重写 get_queryset 方法,返回 QuerySet 就能自定义 manager。

objects 是 Model 的默认的 manager。

我认为自定义 manager,主要为了少写代码。比如很多 sql 都需要做某些过滤操作,那么可以把这些过滤操作放到自定义 manager 中,使用该自定义 manager, 就相当于预先做了过滤。