以DRY方式添加创建/修改者及时间

79 投票
3 回答
45379 浏览
提问于 2025-04-16 10:21

像下面这样的内容

  • 创建者(created_by)
  • 创建日期(created_date)
  • 修改者(modified_by)
  • 修改日期(modified_date)

在很多表格中,这是一种非常常见的模式。

1) 你可以在 model.py 文件中自动设置创建日期(但其他的不能),方法是使用

created_date = models.DateTimeField(auto_now_add=True, editable=False)

2) 你可以在 model.py 文件中设置创建和修改日期(但不能设置创建者和修改者,因为没有请求上下文),方法是使用

def save(self):
    if self.id:
        self.modified_date = datetime.now()
    else:
        self.created_date = datetime.now()
    super(MyModel,self).save()

3) 你可以在 admin.py 文件中设置创建和修改日期以及创建者和修改者,但这不适用于非管理员的更新。

def save_model(self, request, obj, form, change):
    if change:
        obj.modified_by = request.user
        obj.modified_date = datetime.now()
    else:
        obj.created_by = request.user
        obj.created_date = datetime.now()
    obj.save()

4) 最后一个地方是在 view.py 文件中,这里可以处理所有四个字段,但同样不包括管理员的更新。

所以实际上,你需要把逻辑分散开来,至少在 3 和 4 中重复(或者在模型中定义一个方法,从这两个地方调用,但这样可能会遗漏)。

有没有更好的方法?(我刚开始接触 python/django,可能会漏掉一些明显的东西)

  • 你能做类似 @login_required 的装饰器,比如 @audit_changes 吗?
  • 你能在模型中获取请求和当前用户,并把逻辑集中在那儿吗?

3 个回答

-2

你能导入用户模型对象并调用 get_current() 吗?

另外,我觉得你可以在 admin.py 文件中调用视图。

12

如果你想要处理带时间戳的模型,可以看看 django-model-utils 或者 django-extensions。这两个工具都提供了抽象基类,可以自动管理创建时间和最后修改时间。你可以直接使用这些工具,或者看看它们是怎么解决这个问题的,然后自己想出一个办法。

关于你其他的问题:

你能做类似 @login_required 的装饰器,比如 @audit_changes 吗?

有可能可以,但你需要非常小心,以确保线程安全。你可以在你的 @audit_changes 装饰器里设置一个标志,用来在一个线程本地存储中启用审计功能。然后在你的模型的保存方法或者信号处理器中,你可以检查这个审计标志,如果标志被设置了,就记录审计信息。

你能在模型中获取请求和当前用户,并把逻辑集中在一起吗?

可以,但这样做会有一些权衡。正如你提到的,Django 的 ORM 和请求/认证处理之间有很明确的分离。你可以通过两种方式把请求中的信息(当前用户)传递到 ORM(你的模型)。一种是手动管理对象的创建者/修改者信息,另一种是设置一个机制来自动处理这些维护工作。如果你选择手动方式(通过方法调用从视图传递信息到 ORM),虽然需要维护和测试的代码会更多,但你可以保持这种关注点的分离。如果你将来需要在请求/响应周期之外使用这些对象(例如定时任务、延迟任务、交互式命令行),手动方式会让你更轻松。如果你愿意打破这种关注点的分离,你可以在中间件中设置一个线程本地存储当前用户,然后在模型的保存方法中查看这个线程本地存储。与手动方式相反,这样你需要处理的代码会少一些,但如果你想在请求/响应周期之外使用对象,就会更困难。此外,使用这种更自动化的方法时,你需要非常小心,确保一切都是线程安全的。

116

Django现在可以处理创建和修改日期,所以可以这样实现:

class BaseModel(models.Model):
    created_date = models.DateTimeField(auto_now_add=True)
    modified_date = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

把这个添加到一个抽象模型的基类中,就可以很方便地加到应用程序的所有模型里,比如:

class Pizza(BaseModel):
    ....

class Topping(BaseModel):
    ...

存储用户信息就比较复杂,因为在这个时候request.user是不可用的。正如SeanOC提到的,这涉及到网络请求和模型层之间的职责分离。你要么每次都传递这个字段,要么把request.user存储在一个线程本地的地方。Django CMS就是这样做的,用于他们的权限系统。

from django.utils.deprecation import MiddlewareMixin

class CurrentUserMiddleware(MiddlewareMixin):
    def process_request(self, request):
        set_current_user(getattr(request, 'user', None))

而用户追踪是在其他地方进行的:

from threading import local
_thread_locals = local()

def set_current_user(user):
    _thread_locals.user=user

def get_current_user():
    return getattr(_thread_locals, 'user', None)

对于非网络环境(比如管理命令),你需要在脚本开始时调用set_current_user

撰写回答