以DRY方式添加创建/修改者及时间
像下面这样的内容
- 创建者(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 个回答
你能导入用户模型对象并调用 get_current() 吗?
另外,我觉得你可以在 admin.py 文件中调用视图。
如果你想要处理带时间戳的模型,可以看看 django-model-utils 或者 django-extensions。这两个工具都提供了抽象基类,可以自动管理创建时间和最后修改时间。你可以直接使用这些工具,或者看看它们是怎么解决这个问题的,然后自己想出一个办法。
关于你其他的问题:
你能做类似 @login_required 的装饰器,比如 @audit_changes 吗?
有可能可以,但你需要非常小心,以确保线程安全。你可以在你的 @audit_changes 装饰器里设置一个标志,用来在一个线程本地存储中启用审计功能。然后在你的模型的保存方法或者信号处理器中,你可以检查这个审计标志,如果标志被设置了,就记录审计信息。
你能在模型中获取请求和当前用户,并把逻辑集中在一起吗?
可以,但这样做会有一些权衡。正如你提到的,Django 的 ORM 和请求/认证处理之间有很明确的分离。你可以通过两种方式把请求中的信息(当前用户)传递到 ORM(你的模型)。一种是手动管理对象的创建者/修改者信息,另一种是设置一个机制来自动处理这些维护工作。如果你选择手动方式(通过方法调用从视图传递信息到 ORM),虽然需要维护和测试的代码会更多,但你可以保持这种关注点的分离。如果你将来需要在请求/响应周期之外使用这些对象(例如定时任务、延迟任务、交互式命令行),手动方式会让你更轻松。如果你愿意打破这种关注点的分离,你可以在中间件中设置一个线程本地存储当前用户,然后在模型的保存方法中查看这个线程本地存储。与手动方式相反,这样你需要处理的代码会少一些,但如果你想在请求/响应周期之外使用对象,就会更困难。此外,使用这种更自动化的方法时,你需要非常小心,确保一切都是线程安全的。
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
。