SQLAlchemy 记录日期和用户的更改

2024-02-02

这与另一个 3 年前的问题非常相似:查看 SQLAlchemy 事务(包括经过身份验证的用户等)的通用方法是什么? https://stackoverflow.com/questions/1862029/whats-a-good-general-way-to-look-sqlalchemy-transactions-complete-with-authent/

我正在开发一个应用程序,我想记录对特定表的所有更改。目前有一个非常好的版本控制“食谱” http://www.sqlalchemy.org/docs/orm/examples.html#versioning-objects,但我需要修改它以记录发生更改的日期时间以及进行更改的用户 ID。我采用了与 SQLAlchemy 打包的 History_meta.py 示例,并使其记录时间而不是版本号,但我无法弄清楚如何传递用户 ID。

我上面提到的问题建议在会话对象中包含用户 ID。这很有道理,但我不知道该怎么做。我尝试过简单的事情session.userid = authenticated_userid(request)但在history_meta.py中,该属性似乎不再位于会话对象上。

我在金字塔框架中完成所有这些工作,我正在使用的会话对象定义为DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))。我认为session = DBSession()然后继续使用session。 (我不太确定这是否有必要,但这就是发生的事情)

这是我修改后的history_meta.py,以防有人发现它有用:

from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.orm import mapper, class_mapper, attributes, object_mapper
from sqlalchemy.orm.exc import UnmappedClassError, UnmappedColumnError
from sqlalchemy import Table, Column, ForeignKeyConstraint, DateTime
from sqlalchemy import event
from sqlalchemy.orm.properties import RelationshipProperty
from datetime import datetime

def col_references_table(col, table):
    for fk in col.foreign_keys:
        if fk.references(table):
            return True
    return False

def _history_mapper(local_mapper):
    cls = local_mapper.class_

    # set the "active_history" flag
    # on on column-mapped attributes so that the old version
    # of the info is always loaded (currently sets it on all attributes)
    for prop in local_mapper.iterate_properties:
        getattr(local_mapper.class_, prop.key).impl.active_history = True

    super_mapper = local_mapper.inherits
    super_history_mapper = getattr(cls, '__history_mapper__', None)

    polymorphic_on = None
    super_fks = []
    if not super_mapper or local_mapper.local_table is not super_mapper.local_table:
        cols = []
        for column in local_mapper.local_table.c:
            if column.name == 'version_datetime':
                continue

            col = column.copy()
            col.unique = False

            if super_mapper and col_references_table(column, super_mapper.local_table):
                super_fks.append((col.key, list(super_history_mapper.local_table.primary_key)[0]))

            cols.append(col)

            if column is local_mapper.polymorphic_on:
                polymorphic_on = col

        if super_mapper:
            super_fks.append(('version_datetime', super_history_mapper.base_mapper.local_table.c.version_datetime))
            cols.append(Column('version_datetime', DateTime, default=datetime.now, nullable=False, primary_key=True))
        else:
            cols.append(Column('version_datetime', DateTime, default=datetime.now, nullable=False, primary_key=True))

        if super_fks:
            cols.append(ForeignKeyConstraint(*zip(*super_fks)))

        table = Table(local_mapper.local_table.name + '_history', local_mapper.local_table.metadata,
           *cols
        )
    else:
        # single table inheritance.  take any additional columns that may have
        # been added and add them to the history table.
        for column in local_mapper.local_table.c:
            if column.key not in super_history_mapper.local_table.c:
                col = column.copy()
                col.unique = False
                super_history_mapper.local_table.append_column(col)
        table = None

    if super_history_mapper:
        bases = (super_history_mapper.class_,)
    else:
        bases = local_mapper.base_mapper.class_.__bases__
    versioned_cls = type.__new__(type, "%sHistory" % cls.__name__, bases, {})

    m = mapper(
            versioned_cls,
            table,
            inherits=super_history_mapper,
            polymorphic_on=polymorphic_on,
            polymorphic_identity=local_mapper.polymorphic_identity
            )
    cls.__history_mapper__ = m

    if not super_history_mapper:
        local_mapper.local_table.append_column(
            Column('version_datetime', DateTime, default=datetime.now, nullable=False, primary_key=False)
        )
        local_mapper.add_property("version_datetime", local_mapper.local_table.c.version_datetime)


class Versioned(object):
    @declared_attr
    def __mapper_cls__(cls):
        def map(cls, *arg, **kw):
            mp = mapper(cls, *arg, **kw)
            _history_mapper(mp)
            return mp
        return map


def versioned_objects(iter):
    for obj in iter:
        if hasattr(obj, '__history_mapper__'):
            yield obj

def create_version(obj, session, deleted = False):
    obj_mapper = object_mapper(obj)
    history_mapper = obj.__history_mapper__
    history_cls = history_mapper.class_

    obj_state = attributes.instance_state(obj)

    attr = {}

    obj_changed = False

    for om, hm in zip(obj_mapper.iterate_to_root(), history_mapper.iterate_to_root()):
        if hm.single:
            continue

        for hist_col in hm.local_table.c:
            if hist_col.key == 'version_datetime':
                continue

            obj_col = om.local_table.c[hist_col.key]

            # get the value of the
            # attribute based on the MapperProperty related to the
            # mapped column.  this will allow usage of MapperProperties
            # that have a different keyname than that of the mapped column.
            try:
                prop = obj_mapper.get_property_by_column(obj_col)
            except UnmappedColumnError:
                # in the case of single table inheritance, there may be
                # columns on the mapped table intended for the subclass only.
                # the "unmapped" status of the subclass column on the
                # base class is a feature of the declarative module as of sqla 0.5.2.
                continue

            # expired object attributes and also deferred cols might not be in the
            # dict.  force it to load no matter what by using getattr().
            if prop.key not in obj_state.dict:
                getattr(obj, prop.key)

            a, u, d = attributes.get_history(obj, prop.key)

            if d:
                attr[hist_col.key] = d[0]
                obj_changed = True
            elif u:
                attr[hist_col.key] = u[0]
            else:
                # if the attribute had no value.
                attr[hist_col.key] = a[0]
                obj_changed = True

    if not obj_changed:
        # not changed, but we have relationships.  OK
        # check those too
        for prop in obj_mapper.iterate_properties:
            if isinstance(prop, RelationshipProperty) and \
                attributes.get_history(obj, prop.key).has_changes():
                obj_changed = True
                break

    if not obj_changed and not deleted:
        return

    attr['version_datetime'] = obj.version_datetime
    hist = history_cls()
    for key, value in attr.items():
        setattr(hist, key, value)
    session.add(hist)
    print(dir(session))
    obj.version_datetime = datetime.now()

def versioned_session(session):
    @event.listens_for(session, 'before_flush')
    def before_flush(session, flush_context, instances):
        for obj in versioned_objects(session.dirty):
            create_version(obj, session)
        for obj in versioned_objects(session.deleted):
            create_version(obj, session, deleted = True)

UPDATE:好吧,看来在 before_flush() 方法中我得到的会话是类型sqlalchemy.orm.session.Session我附加的会话user_id to was sqlalchemy.orm.scoping.scoped_session。因此,在某个时刻,对象层会被剥离。将 user_id 分配给scoped_session 内的Session 是否安全?我可以确定它不会出现在其他请求中吗?


老问题,但仍然非常相关。

您应该避免尝试将 Web 会话信息放在数据库会话上。它结合了不相关的关注点,每个关注点都有自己的生命周期(不匹配)。这是我在 Flask 中与 SQLAlchemy 一起使用的方法(不是 Flask-SQLAlchemy,但这也应该有效)。我试图评论金字塔的不同之处。

from flask import has_request_context  # How to check if in a Flask session
from sqlalchemy import inspect
from sqlalchemy.orm import class_mapper
from sqlalchemy.orm.attributes import get_history
from sqlalchemy.event import listen

from YOUR_SESSION_MANAGER import get_user  # This would be something in Pyramid
from my_project import models  # Where your models are defined

def get_object_changes(obj):
    """ Given a model instance, returns dict of pending
    changes waiting for database flush/commit.

    e.g. {
        'some_field': {
            'before': *SOME-VALUE*,
            'after': *SOME-VALUE*
        },
        ...
    }
    """
    inspection = inspect(obj)
    changes = {}
    for attr in class_mapper(obj.__class__).column_attrs:
        if getattr(inspection.attrs, attr.key).history.has_changes():
            if get_history(obj, attr.key)[2]:
                before = get_history(obj, attr.key)[2].pop()
                after = getattr(obj, attr.key)
                if before != after:
                    if before or after:
                        changes[attr.key] = {'before': before, 'after': after}
    return changes

def my_model_change_listener(mapper, connection, target):
    changes = get_object_changes(target)
    changes.pop("modify_ts", None)  # remove fields you don't want to track

    user_id = None
    if has_request_context():
        # Call your function to get active user and extract id
        user_id = getattr(get_user(), 'id', None)

    if user_id is None:
        # What do you want to do if user can't be determined
        pass

    # You now have the model instance (target), the user_id who is logged in,
    # and a dictionary of changes.

    # Either do somthing "quick" with it here or call an async task (e.g.
    # Celery) to do something with the information that may take longer
    # than you want the request to take.

# Add the listener
listen(models.MyModel, 'after_update', my_model_change_listener)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SQLAlchemy 记录日期和用户的更改 的相关文章

  • Python 3 os.urandom

    在哪里可以找到完整的教程或文档os urandom 我需要获得一个随机 int 来从 80 个字符的字符串中选择一个字符 如果你只需要一个随机整数 你可以使用random randint a b 来自随机模块 http docs pytho
  • Sublime Text 插件开发中的全局 Python 包

    一 总结 我不知道 Sublime Text 插件开发人员如何使用 Sublime Text 查找全局 Python 包 而不是 Sublime Text 目录的 Python 包 Sublime Text使用自己的Python环境 而不是
  • 使用 python 中的公式函数使从 Excel 中提取的值的百分比相等

    import xlrd numpy excel Users Bob Desktop wb1 xlrd open workbook excel assignment3 xlsx sh1 wb1 sheet by index 0 colA co
  • pyspark 数据框中的自定义排序

    是否有推荐的方法在 pyspark 中实现分类数据的自定义排序 我理想地寻找 pandas 分类数据类型提供的功能 因此 给定一个数据集Speed列 可能的选项是 Super Fast Fast Medium Slow 我想实现适合上下文的
  • Python3.0 - 标记化和取消标记化

    我正在使用类似于以下简化脚本的内容来解析较大文件中的 python 片段 import io import tokenize src foo bar src bytes src encode src io BytesIO src src l
  • 搜索多个字段

    我想我没有正确理解 django haystack 我有一个包含多个字段的数据模型 我希望搜索其中两个字段 class UserProfile models Model user models ForeignKey User unique
  • 没有名为 StringIO 的模块

    我有Python 3 6 我想从另一个名为 run py 的 python 文件执行名为 operation py 的 python 文件 In operation py I do from cStringIO import StringI
  • 一段时间后终止线程的最 Pythonic 方法

    我想在线程中运行一个进程 它正在迭代一个大型数据库表 当线程运行时 我只想让程序等待 如果该线程花费的时间超过 30 秒 我想终止该线程并执行其他操作 通过终止线程 我的意思是我希望它停止活动并优雅地释放资源 我认为最好的方法是通过Thre
  • 从扫描文档中提取行表 opencv python

    我想从扫描的表中提取信息并将其存储为 csv 现在我的表提取算法执行以下步骤 应用倾斜校正 应用高斯滤波器进行去噪 使用 Otsu 阈值进行二值化 进行形态学开局 Canny 边缘检测 进行霍夫变换以获得表格行 去除重复行 10像素范围内相
  • Django send_mail SMTPSenderRefused 530 与 gmail

    一段时间以来 我一直在尝试使用 Django 从我正在开发的网站接收电子邮件 现在 我还没有部署它 并且我正在使用Django开发服务器 我不知道这是否会影响它 这是我的 settings py 配置 EMAIL BACKEND djang
  • 使用 Pandas 计算 delta 列

    我有一个数据框 如下所示 Name Variable Field A 2 3 412 A 2 9 861 A 3 5 1703 B 3 5 1731 A 4 0 2609 B 4 0 2539 A 4 6 2821 B 4 6 2779 A
  • 使用 Keras np_utils.to_categorical 的问题

    我正在尝试将整数的 one hot 向量数组制作为 keras 将能够使用的 one hot 向量数组来拟合我的模型 这是代码的相关部分 Y train np hstack np asarray dataframe output vecto
  • Python:IndexError:修改代码后列表索引超出范围

    我的代码应该提供以下格式的输出 我尝试修改代码 但我破坏了它 import pandas as pd from bs4 import BeautifulSoup as bs from selenium import webdriver im
  • 在系统托盘中隐藏 tkinter 窗口 [重复]

    这个问题在这里已经有答案了 我正在制作一个程序来提醒我朋友的生日 这样我就不会忘记祝福他们 为此 我制作了两个 tkinter 窗口 1 First one is for entering name and birth date 2 Sec
  • 当数据库不是 Django 模型时,是否可以使用数据库中的表?

    是否可以从应用程序数据库中的表获取查询集 该表不是应用程序中的模型 如果我有一个不是名为 cartable 的模型的表 从概念上讲 我想这样做 myqueryset cartable objects all 有没有相对简单的方法来做到这一点
  • 混淆矩阵不支持多标签指示符

    multilabel indicator is not supported是我在尝试运行时收到的错误消息 confusion matrix y test predictions y test is a DataFrame其形状为 Horse
  • 如何根据第一列创建新列,同时考虑Python Pandas中字母和列表的大小? [复制]

    这个问题在这里已经有答案了 我在 Python Pandas 中有 DataFrame 如下所示 col1 John Simon prd agc Ann White BeN and Ann bad list Ben Wayne 我需要这样做
  • 将上下文管理器的动态可迭代链接到单个 with 语句

    我有一堆想要链接的上下文管理器 第一眼看上去 contextlib nested看起来是一个合适的解决方案 但是 此方法在文档中被标记为已弃用 该文档还指出最新的with声明直接允许这样做 自 2 7 版起已弃用 with 语句现在支持此
  • 查找总和为给定数字的值组合的函数

    这个帖子查找提供的 Sum 值的组合 https stackoverflow com a 20194023 1561176呈现函数subsets with sum 它在数组中查找总和等于给定值的值的组合 但由于这个帖子已经有6年多了 我发这
  • 如何为不同操作系统/Python 版本编译 Python C/C++ 扩展?

    我注意到一些成熟的Python库已经为大多数架构 Win32 Win amd64 MacOS 和Python版本提供了预编译版本 针对不同环境交叉编译扩展的标准方法是什么 葡萄酒 虚拟机 众包 我们使用虚拟机和Hudson http hud

随机推荐

  • HBase:复制是如何工作的?

    我目前正在将 HBase 作为数据存储进行评估 但有一个问题没有得到解答 HBase 在许多节点上存储同一对象的许多副本 也称为复制 由于HBase具有所谓的强一致性 相比之下最终一致 它保证每个副本在读取时返回相同的值 据我了解 HBas
  • 如何检查公共MSMQ是否为空

    有没有办法检查公共 MSMQ 是否为空 对于私有 MSMQ 来说很简单 private bool IsQueueEmpty string path bool isQueueEmpty false var myQueue new Messag
  • TFS 2010:服务不可用 503

    我已经尝试解决这个问题大约一周了 我在微软网站上发布了这个问题 但只收到了一条回复 这并没有解决我的问题 以下是我在微软网站上的帖子的组合 我在 Window 7 PC 64 位上安装并配置了 TFS 2010 当我导航到http loca
  • python 中压缩时出现 LEN 错误

    def shufflemode import random combined zip question answer random shuffle combined question answer zip combined 但后来我收到错误
  • 为什么 IQueryables 没有 SingleOrDefaultAsync?

    以下代码无法编译 因为 SingleOrDefaultAsync 不是 GetAppointments 的合适扩展 我只是想知道为什么 public IQueryable
  • Internet Explorer 7/8 和窗口函数是空对象

    在 Internet Explorer 8 中 也适用于 IE7 8 模式下的 IE9 以下代码会发出警报object and undefined而不是预期的function和类似的东西function native code alert
  • Lambda 函数作为基类

    在使用 Lambda 时 我发现了一个我并不完全理解的有趣行为 假设我有一个struct Overload派生自 2 个模板参数 并且有一个using F1 operator clause 现在 如果我从两个函子派生 我只能访问 F1 的运
  • MySQL COUNT() 跨多列

    我已经为这个问题绞尽脑汁有一段时间了 但似乎无法让它发挥作用 我有一个表 其中除了其他标准字段外 还有一些用于同类不同值的字段 例如 INT 每个字段的值都是唯一的 gt 意思是如果它出现在val 1它不会出现在val 2 and val
  • 如何在 PHP 中获取字符串的字节值?

    假设我在 php 中有一个字符串 它打印到一个文本文件中 如下所示 n 9q1F 我如何将其字节码而不是时髦的 ascii 字符获取到我的文本文件中 使用 ord 函数 http ca php net ord http ca php net
  • 如何加速 Jekyll/Octopress 生成?

    我使用 Octopress 作为我的博客引擎 这是完美的 但如果帖子很多 比如400 个帖子 生成的速度就太慢了 那么 有什么办法可以加快 Jekyll Octopress 的生成速度呢 Thanks 显然 如果您只处理一篇文章 则无需等待
  • Android Studio 构建时间与 Gradle 依赖项与 Jar 依赖项

    我正在开发一个使用 4 个外部库的产品 所有这些库都通过外部罐子包含在内 Jar 依赖项的构建时间约为 10 分钟 我用 Gradle 依赖项更改了 Jar 依赖项 然后构建时间达到了 3 5 分钟 之前的依赖关系 10 分钟构建时间 co
  • Swift 中的元组数组

    我有一个功能 func parseJSON3 inputData NSData gt NSArray var tempDict id Int ccomments Int post date String post title String
  • WordPress 中的自定义重写规则

    我在内部 WordPress 重写规则方面遇到了麻烦 我已阅读此主题 但仍然无法得到任何结果 WordPress 插件中的 wp rewrite https stackoverflow com questions 2210826 need
  • 在 HANA 中上传数组

    我是 SAP HANA 的新手 最近将一些数据库迁移到它 发现它的性能非常好 我面临的问题是我无法上传array column在哈纳 但我发现有一种方法可以使用以下方法在 HANA 中插入数组 INSERT INTO T1 VALUES 1
  • MySQL中没有子查询字段的模式计算?

    在我的应用中 每个产品组有很多产品 每个产品有一个制造商 这些关系由 MySQL 存储在 InnoDB 表中product groups与id场 以及products with id product group and manufactur
  • 将 html 文件中的阿拉伯数字转换为阿拉伯/波斯数字

    我正在尝试将纯文本阿拉伯数字转换为东方阿拉伯数字 所以基本上采取1 2 3 并将它们转换成 该函数转换all数字 包括标签中包含的任何数字 即H1 private void LoadHtmlFile object sender EventA
  • 从给定列表中选择随机字符串

    我试图让 Java 从给定列表中选择 1 个随机字符串 字符串列表的示例 1153 3494 9509 2 0 0 0 0 1153 3487 9509 2 0 0 0 0 1153 3491 9525 2 0 0 0 0 1153 346
  • 使用 *ngFor 索引将多个迭代分组在一行中

    我一直在尝试构建一个具有多列的动态输入对话框 基本上有一个字段列表 我想为每两个字段构造一行 我的尝试看起来像这样 甚至不确定这是否可能 div div class row div div class col 3 div div div d
  • 通过 Facebook 应用程序在用户墙上发布帖子

    我需要从 facebook 用户个人资料图片生成一张图片并将其发布到一些用户的墙上 例如 attch array 媒体 gt 数组 数组 类型 gt 图像 源 gt https graph facebook com https graph
  • SQLAlchemy 记录日期和用户的更改

    这与另一个 3 年前的问题非常相似 查看 SQLAlchemy 事务 包括经过身份验证的用户等 的通用方法是什么 https stackoverflow com questions 1862029 whats a good general