所有
我正在为SQLAlchemy开发Audit mixin,但不确定如何做到这一点。我的班级看起来像这样:
class AuditColumns(object):
created_dt = Column(DateTime,
default=datetime.utcnow(),
nullable=False)
created_by = Column(String(64),
default=current_user,
nullable=False)
updated_dt = Column(DateTime,
default=datetime.utcnow(),
nullable=False,
onupdate=datetime.utcnow())
updated_by = Column(String(64),
default=current_user,
nullable=False,
onupdate=current_user)
更新后的版本很好,因为我只需要在桌面级别记录最新的更新;任何重要的审计都将在一个单独的表中进行,详细说明更新/删除等。
我的问题是;我不希望更新created_dt / by列。我知道,在我的代码中,我可以在更新对象时简单地省略它们;但另一个程序员可能会;所以我真的想要在每次更新之前确保它自己覆盖值,或者如果有人试图改变它会引发错误(后者是首选)。
我的SQLAlchemy技能仍处于开发阶段,事件是一个可以理解的地方;或者是否可以通过覆盖一些通用的声明性函数(如save()或before_save()或其他可能存在的东西来完成?
我会继续寻找答案 - 但是帮助找到解决方案(我宁愿不给代码)也是可取的。
答案 0 :(得分:5)
[编辑] 我正在使用flask.g - 但我意识到除非在某处硬编码,否则它不会持续存在;因此我在实际实现中转移到了用户会话 [/编辑]
好的家伙......希望这会对某人有所帮助。我想我已经整理了解决方案,经过测试并且对我的项目足够安全(尽管它不完美,我希望得到一些反馈):
以下是审计组合:
from datetime import datetime
from flask import g
from sqlalchemy import Column, DateTime, String
from sqlalchemy.orm import MapperExtension
class AuditColumns(object):
created_dt = Column(DateTime,
default=datetime.utcnow(),
nullable=False)
created_by = Column(String(64),
nullable=False)
updated_dt = Column(DateTime,
default=datetime.utcnow(),
nullable=False,
onupdate=datetime.utcnow())
updated_by = Column(String(64),
nullable=False)
class AuditExtension(MapperExtension):
def before_insert(self, mapper, connection, instance):
""" Make sure the audit fields are set correctly """
instance.created_dt = datetime.utcnow()
instance.created_by = g.username
instance.updated_dt = datetime.utcnow()
instance.updated_by = g.username
def before_update(self, mapper, connection, instance):
""" Make sure when we update this record the created fields stay unchanged! """
instance.created_dt = instance.created_dt
instance.created_by = instance.created_by
instance.updated_dt = datetime.utcnow()
instance.updated_by = g.username
然后我简单地将扩展和基本扩展添加到需要它们的任何模型上:
class Roles(db.Model, AuditColumns):
id = Column(BigInteger, primary_key=True)
username = Column(String(64), nullable=False, unique=True)
password = Column(String(255), nullable=False)
__mapper_args__ = {
'extension': AuditExtension()}
def __repr__(self):
return self.username
现在,我注意到这种方法有两个注意事项: - g.username是硬编码的 - 在这个阶段我不知道如何通过SQLAlchemy传递额外的args以在Mapper中使用,所以现在;这将是必须的。 - 仍然可以完成数据库级别操作...在数据库上运行原始SQL不会阻止更新这些列。
这些警告中的第二个是有问题的 - 但我认为定义模型级别触发器可能有助于防止任何数据库交互小提琴。
关于可能采用不同方法的任何想法?我想我已经锁定了任何基于SQLalchemy的小提琴......
答案 1 :(得分:3)
你的问题是你没有使用callables“default”和“onupdate”。 It's documented here
日期应该是(注意没有括号):
default=datetime.datetime.utcnow
onupdate=datetime.datetime.utcnow
或用户名:
default=lambda: current_user.username
后一个例子应该是一个函数而不是一个lambda,以对current_user进行各种安全检查(例如匿名的话怎么办?)