Django Model¶
读者: | 王蒙 |
---|---|
标签: | 网络编程,Web Framework |
简介: | Model 使得 Django 操作数据库变得非常简单。 |
Contents
问题¶
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.profileselect_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')
参考文献¶
- Django by Example.
- Django 中的 select_related 与 prefetch_related: http://baijiahao.baidu.com/s?id=1589827357928613968
- Aggregation: https://docs.djangoproject.com/en/2.0/topics/db/aggregation/
- When QuerySets are evaluated: https://docs.djangoproject.com/en/2.0/ref/models/querysets/#when-querysets-are-evaluated
- Model Field Reference: https://docs.djangoproject.com/en/2.0/ref/models/fields/
- Make Query: https://docs.djangoproject.com/en/2.0/topics/db/queries/
- custom model fields: https://docs.djangoproject.com/en/2.0/howto/custom-model-fields/