Model是django项目的基础, 如果一开始没有好好设计好, 那么在接下来的开发过程中就会遇到更多的问题. 然而, 大多数的开发人员都容易在缺少思考 的情况下随意的增加或修改model. 这样做的后果就是, 在接下来的开发过程中, 我们不得不做出更多努力来修正这些错误.
因此, 在修改model时, 一定尽可能的经过充分的考虑再行动! 以下列出的是我们经常用到的一些工具和技巧:
第一, 将model分布于不同的app中. 如果你的django项目中, 有一个app拥有超过20个model, 那么, 你就应当考虑分拆该app了. 我们推荐每个app拥 有不超过5个model.
第二, 尽量使用ORM. 我们需要的大多数数据库索引都能通过Object-Relational-Model实现, 且ORM带给我们许多快捷方式, 例如生成SQL语句, 读取/更新数据库时的安全验证. 因此, 如果能使用简单的ORM语句完成的, 应当尽量使用ORM. 只有当纯SQL语句极大地简化了ORM语句时, 才使用纯SQL语句. 并且, 在写纯SQL语句是, 应当优先考虑使用raw(), 再是extra().
第三, 必要时添加index. 添加db_index=True到model中非常简单, 但难的是理解何时应该添加. 在建立model时, 我们事先不会添加index, 只有当 以下情况时, 才会考虑添加index:
第四, 注意model的继承. model的继承在django中需要十分小心, django提供了三种继承方式, 1.abstract base class继承(不要和Pyhton标准库的abc模块 搞混), 2.多表(multi-table)继承, 3.proxy model继承. 下表罗列了这三种继承的优劣:
model继承类型 | 优势 | 劣势 |
不使用继承, 即每个相同的field会重复出现在不同的model中 | 容易明白model和数据表的关系 | 如果有大量相同的field, 会较难维护 |
abstract base class继承, 即只有继承自该类的model才会有数据表, 其自身没有对应的数据表 | 不用在每个model重复编写相同的field. 也没有多表继承带来的过多消耗 | 无法单独使用其共同的abstract base class |
多表(multi-table)继承 | 每个model都有数据表, 因此可以既使用母表, 又使用子表. 也能通过parent.child从母表访问子表 | 增加了消耗, 因为每次查询子表, 都会自动查询其母表. 强烈建议不使用这一方法! |
proxy model继承, 即只有原始model才会有相应的数据表 | 在不建立新数据表的情况下, 使我们拥有不同行为的model | 无法修改model的field |
django的创造者和其他许多开发人员都认为, 多表继承的方法不是一个良好的方法. 因此我们强烈建议大家不要使用该方法. 下面列举了一些常见的如何 选择model继承的情形:
django项目中, 创建时间和修改时间这两个field是最用到的, 下面给出一个abstract base class继承的例子:
# models.py from django.db import models class TimeStampedModel(models.Model): """ abstract base class, 提供创建时间和修改时间两个通用的field """ created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) class Meta: abstract = True
注意以上代码的最后两行, 正式这两行将这一model变味了abstract base class, 下面以TimeStampedModel为abstract base class建立model:
from django.db import models ... class Article(TimeStampedModel): title = models.CharField(max_length=200)
以上两个class, 在执行syncdb时, 只会建立一个数据表, 这也正是我们希望得到的.
第五, 使用south进行数据迁移, 可以参见以前的文章: 如何在 Django 中使用 django-south, 实现数据迁移 (data migrations)
如何设计出好的django model可能是最难也是最复杂的一个话题了, 在此, 我们看看一些基本的技巧吧:
我们首先建议了解数据库规范化(database normalization). 如果你还不清楚这是什么, 那么, 我们强烈建议你先阅读一下相关的书籍, 或搜索"关系 型数据库设计"或"数据库规范化". 在创建django model之前, 应当首先保证设计的数据库是规范化的.
正确的使用cache能帮助我们提高数据库的性能. 详细的信息, 我们会在今后的文章中作进一步介绍.
当定义model field时, 我们可以设置null=True和blank=True (默认都是False), 知道何时设置null和blank对于开发人员也是十分重要的, 在下 面的表格中, 我们一一列举了如何使用这两个选项:
Field 类型 | 设置null=True | 设置blank=True |
---|---|---|
CharField, TextField, SlugField, EmailField, CommaSeparatedIntegerField等 | 不要设置 django规定储存空字符串来代表空值, 当从数据库中读取NULL或空值时都为空字符串 | 可以设置 设置后允许接受widget中为空值(即不填写), 储存到数据库时空值变为空字符串 |
FileField, ImageField | 不要设置 django实际储存的是路径的字符串, 因此同上 | 可以设置 同上 |
BooleanField | 不要设置 因为有NullBooleanField代替 | 不要设置 |
IntegerField, FloatField, DecimalField等 | 可以设置 如果你希望在数据库中能储存NULL | 可以设置 设置后允许接受widget中为空值(即不填写), 设置为True时必须同时设置null=True |
DateTimeField, DateField, TimeField等 | 可以设置 如果你希望在数据库中能储存NULL | 可以设置 设置后允许接受widget中为空值(即不填写), 设置为True时必须同时设置null=True |
ForeignKey, ManyToManyField, OneToOneField | 可以设置 如果你希望在数据库中能储存NULL | 可以设置 设置后允许接受widget中为空值(即不填写) |
GenericIPAddressField | 可以设置 如果你希望在数据库中能储存NULL | 可以设置 设置后允许接受widget中为空值(即不填写) |
IPAddressField | 不推荐设置 用GenericIPAddressField代替 | 不推荐设置 用GenericIPAddressField代替 |
在django 1.6中, 新增了BinaryField, 用于储存二进制数据(binary data或 bytes). 对于BinaryField, 我们无法使用ORM的filters, excludes或其他SQL操作. 但在少数情况下, 我们会用到BinaryField, 例如MessagePack格式的内容, 传感器接受的原始数据和压缩数据等. 但需要注意 的是, Binary Data一般都十分庞大, 因此可能会拖慢数据库的速度. 如果发生这一现象, 我们可以将binary data储存在文件中, 然后使用FileField储 存该文件的路径信息.
还有, 不要从BinaryField中直接读取文件并呈献给用户. 因为, 1. 从数据库读写总是比从文件系统读写慢; 2. 数据库备份会变得十分庞大, 花费更多 的时间; 3. 获得文件的过程, 增加了从django到数据库的这一环节.
从ORM获取model, 实际上是通过django中的Model manager完成的, django为每一个model提供了默认的model manager, 我们不建议将其替换掉, 因为:
在django 1.6中, ORM默认会autocommit每一个数据库查询, 也就是说, 每次使用m.create()或m.update()时, 在数据库中马上就会做出相应的修 改. 这样做的好处就是简化了初学者对ORM的理解. 但坏处就是, 当一个view中包含两个数据库修改, 可能一个成功, 但另一个失败, 这就可能导致数据库不 完整, 给我们带来很大的危险.
解决这一问题的方法就是使用数据库transaction, 即将一系列数据库操作包含在一个transaction中, 当其中有一个失败时, 其他操作也会自动回退. Django 1.6 为我们带来了一套崭新的既简单又强大的transaction机制, 使我们方便的使用数据库transaction.
django给我们提供了一个简单地方法, 将一个http request中的所有数据库操作包裹在transaction中:
# setting/base.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': '', 'USER': '', 'PASSWORD': '', 'HOST': '', 'PORT': '', 'ATOMIC_REQUESTS': True, } }
只需要在数据库设置中加入'ATOMIC_REQUESTS': True选项, 就能将整个http request包裹在transaction中. 这样做的好处显而易见是是安全, 但 坏处则是性能可能会下降, 因此随着流量的增大, 我们必须采取更针对性的transaction. 其次, 需要注意的是, 回退的只是数据库的状态, 而不包括其他费 数据库项, 例如发送email等. 所以当涉及这些非数据库项时, 我们应当使用transaction.con_atomic_request()修饰(decorate)这些view:
# myapp/views.py # 此时'ATOMIC_REQUESTS'设置为 True from django.db import transaction from django.http import HttpResponse from django.shortcuts import get_object_or_404 from django.utils import timezone from .models import Article @transaction.non_atomic_requests # 以下http request不被包裹在一个transaction中 def do_something_to_article(request, pk, title): article = get_object_or_404(Article, pk=pk) # 以下代码会以django默认的autocommit模式执行 article.datetime = timezone.now() article.save() with transaction.atomic() # 以下代码被包裹在另一个transaction中 article.title = title article.datetime = timezone.now() article.save() return HttpResponse("success!") # 如果以上transaction失败了, 返回错误状态 return HttpResponse("oops! failed", status_code=400)
更明确地transaction控制意味着提高真题web app的性能, 但也意味着更多的开发时间. 大多数网站下, 由于有限的流量, 使用ATOMIC_REQUESTS已 经足够. 在使用手动transaction控制时, 应当注意:
需要注意的是, 当view返回的是django.http.StreamingHttpResponse时, 应当设置ATOMIC_REQUESTS为false, 或使用 transaction.non_atomic_requests将该view修饰. 因为对于view本身, 是可以使用transaction的, 但对于之后生成的response stream触发的额 外SQL查询, 会自动变为django默认的autocommit模式.
注意, 如果你使用的是MySQL, 且数据表类型是MyISAM, 那么transaction可能无法使用. 无论你的设置是怎么样, 当transaction无法使用时, django会自动转化为autocommit模式.
原文链接: http://www.weiguda.com/blog/8/
联系客服