SQLAlchemy-onupdate不覆盖当前值

Tre*_*ent 4 python sqlalchemy

所有,

我正在研究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)
Run Code Online (Sandbox Code Playgroud)

更新的更新很好,因为我只需要在表级别记录最新更新;任何重要的审核将保存在单独的表格中,其中详细说明了更新/删除等。

我的问题是;我不希望created_dt / by列被更新。我知道,在我的代码中,我可以在更新对象时简单地忽略它们;但另一位编码员可以;因此,我真的想确保在每次更新之前,它都会覆盖自身的值,或者如果有人尝试更改它,则会引发错误(首选后者)。

我的SQLAlchemy技能仍在开发中,请问事件是否可以解决?还是可以通过重写一些通用的声明性功能(例如save()或before_save()或任何可能存在的东西)来完成?

我会一直在寻找答案-但最好能找到解决方案(我不希望获得代码)。

小智 7

您的问题是您没有将可调用对象用于“默认”和“onupdate”。它记录在这里

对于日期,它应该是(注意没有括号):

default=datetime.datetime.utcnow
onupdate=datetime.datetime.utcnow
Run Code Online (Sandbox Code Playgroud)

或用户名:

default=lambda: current_user.username
Run Code Online (Sandbox Code Playgroud)

后一个例子应该是一个函数而不是一个 lambda,以对 current_user 进行各种安全检查(例如,如果匿名呢?)

  • 也`onupdate=datetime.datetime.utcnow` 对我不起作用。 (3认同)

Tre*_*ent 5

[编辑]我正在使用flask.g-但我已经意识到,除非在某处进行了硬编码,否则它不是持久的。因此,在我的实际实现中,我已经移至用户会话[/ Edit]

好的,希望这对某人有帮助。我认为我已经为我的项目整理了解决方案,进行了测试并且足够安全(尽管它并不完美,并且我希望获得一些反馈):

这是审计混入:

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
Run Code Online (Sandbox Code Playgroud)

然后,我只需将扩展名和基础扩展名添加到需要它们的任何模型上:

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
Run Code Online (Sandbox Code Playgroud)

现在,我注意到这种方法有2个警告:-g.username是硬编码的-在此阶段,我不知道如何通过SQLAlchemy传递其他args以在Mapper中使用。这将要做。-仍然可以执行数据库级别的操作...在数据库上运行原始SQL不会阻止更新这些列。

这些警告中的第二个是有问题的-但是我认为定义模型级别触发器可能有助于防止任何数据库交互的麻烦。

关于其他方法的任何想法吗?我想我已经用这个锁定了任何基于SQLalchemy的小提琴...