更新未覆盖当前日期时间值

11

我正在为SQLAlchemy编写审计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()或任何可能存在的函数)来完成的?
我会继续寻找答案-但帮助找到解决方案(我更喜欢不给出代码)是更可取的。
2个回答

12

您的问题在于您没有在“default”和“onupdate”中使用可调用函数。 文档在这里

对于日期,应该是(请注意括号的缺失):

default=datetime.datetime.utcnow
onupdate=datetime.datetime.utcnow

或者为用户名:

default=lambda: current_user.username

后面这个例子应该是一个函数而不是一个lambda表达式,这样可以对current_user进行各种安全检查(例如,如果是匿名用户怎么办?)


9
同时,对于我而言,onupdate=datetime.datetime.utcnow 没有生效。 - Vikas Prasad
1
更新的链接:http://docs.sqlalchemy.org/en/latest/core/defaults.html#python-executed-functions - Vikas Prasad
我最终通过以下方式在Postgres数据库上使其工作:created_at = db.Column(db.DateTime, server_default=UtcNow())updated_at = db.Column(db.DateTime, server_default=UtcNow(), onupdate=UtcNow()),其中 UtcNow 是一个类,如下所示:class UtcNow(expression.FunctionElement): type = DateTime(),而我们有 @compiles(UtcNow, 'postgresql') def pg_utc_now(element, compiler, **kw): return "TIMEZONE('utc', CURRENT_TIMESTAMP)" - Vikas Prasad
在SQLite内存数据库上对我有用。 - Navid
当与括号一起使用(onupdate=datetime.utcnow())时,它仅在第一次更新时被调用,并且生成的datetime.now()的值在每次更新时用于该列,但是当不带括号使用(onupdate=datetime.utcnow)时,除非您在持久化对象之前显式更改列,否则每次持久化对象时都会被调用。 - Navid
显示剩余2条评论

7

[编辑] 我之前使用的是flask.g,但我意识到它除非在硬编码某处,否则不会持久存在;因此,我在我的实际实现中转而使用了用户会话。 [/编辑]

好的各位...希望这能帮到有需要的人。我认为我解决了问题,已经测试并足够安全适用于我的项目(虽然它不是完美的,我很想得到一些反馈):

这里是审计Mixin:

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传递额外的参数以在Mapper内使用,所以现在只能这样做。 - 仍然可以进行数据库级别的操作...在数据库上运行原始SQL不会防止更新这些列。
第二个限制是有问题的 - 但我认为定义模型级触发器可以帮助防止任何数据库交互的小把戏。
对于也许有一个不同的方法的任何想法?我认为我用这个锁定了任何基于SQLalchemy的恶作剧...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接