避免 Django 中一些不好的使用习惯


参考» django-antipatterns


使用Get请求 完成其他作用(非查询)

Problem

# 删除
def remove_comment(request, comment_pk):
    Comment.objects.filter(
        comment_id=comment_pk
    ).delete()
    # …

这样做违反了 HTTP 标准

GETHEAD 方法不应该具有采取除检索以外的动作的意义。

这些方法应该被认为是“安全的”

互联网上的其他参与者认为 GET 请求是安全的。 

例如,如果您使用额外的 GET 请求刷新浏览器,浏览器不会发出警告,
而大多数浏览器会为 POST 请求执行此操作

Solution

应该使用 POST、PUT、PATCH 或 DELETE 请求来更新实体

在 Django 中使用 @require_http_methods 装饰器 确保实例的请求方法

from django.views.decorators.http import require_POST

@require_POST
def remove_comment(request, comment_pk):
    Comment.objects.filter(
        comment_id=comment_pk
    ).delete()
    # …

数据表模型 使用 Model 后缀

Problem

from django.db import models

class CarModel(models.Model):
    # …
    pass

除非指定数据表的verbose_nameverbose_name_plural 否则Django 也会根据类名构造verbose name

>>> CarModel._meta.verbose_name
'car model'
>>> CarModel._meta.verbose_name_plural
'car models'

Solution

Django 模型不应该有 ...Model 后缀

from django.db import models

class Car(models.Model):
    # …
    pass

Signal信号使用不当

Django 有一个复杂的信号系统,可以在您保存、删除、更改多对多关系等时触发某些逻辑。 人们经常使用这些信号,对于某些边缘情况,这些确实是唯一有效的解决方案,但也有只有少数情况下使用信号是合适的

Problem

  • 信号的主要问题之一是信号并不总是运行

    批量保存或更新对象时,pre_save 和 post_save 信号不会运行

    人们通常假设信号会在这种情况下运行

      例如对信号执行计算:他们根据更新的值重新计算某个字段。 
      由于可以在不触发相应信号的情况下更新字段,因此这会导致不一致的值。 
        
      因此,信号给人一种错误的安全感,即处理程序确实会相应地更新对象
    
# 批量保存
Post.objects.bulk_create([
    Post(title='foo'),
    Post(title='bar'),
    Post(title='qux')
])
# 更新
Post.objects.all().update(views=0)

  • 信号可以引发异常并破坏代码流

如果信号运行,例如当我们在模型对象上调用 .save() 时,那么触发器将运行。

信号不是异步运行的,而是以同步方式运行的:有一个函数列表,这些函数都会运行。

信号可能会引发错误,从而导致触发视图的函数引发该错误。最终 .save() 调用将引发错误。

如果同一个信号有多个处理程序,那么某些处理程序可能已经进行了更改,而其他处理程序可能没有被调用。
因此,修复对象变得更加复杂,因为处理程序可能已经部分更改了对象
  • 信号会导致无限递归(有外键关联的数据)

    Especially if we use signals on two models that are related to each other.

from django.db import models
from django.db.models.signals import pre_delete
from django.dispatch import receiver

class Profile(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE
    )

# …

@receiver(pre_delete, sender=Profile)
def delete_profile(sender, instance, using):
    instance.user.delete()

# remove a Profile -->  trigger the signal -> remove a User -->(Django Django will look what to do when removing the user)  remove the Profile --> trigger the signal

Solution

通常最好避免使用信号。人们可以在没有信号的情况下实现很多逻辑

使用异步任务队列去完成使用信号完成的任务

Buy me a 肥仔水!