【问题标题】:How to implement an "undo" feature using Python/Django如何使用 Python/Django 实现“撤消”功能
【发布时间】:2023-09-06 23:04:01
【问题描述】:

我有一个 Django 应用程序,允许用户在其中导入包含联系人数据(会员编号、名字、姓氏等)的 CSV 文件。

当他们导入文件时,应用程序会检查数据库中的匹配记录,然后:1) 如果不存在匹配项,则插入新记录,或 2) 使用新数据更新现有数据。

我的问题是:使用 Django 或直接 Python 实现撤消功能的最佳方法是什么,以便用户可以撤消导入操作并将 多个 记录恢复到他们的原始状态?

我最初的想法是创建一个这样的表(伪代码):

Table HISTORY
   unique_id
   record_affected_id
   old_value
   new_value

然后,如果用户单击“撤消”,我可以查找与他们的交易关联的 unique_id,并将受该交易影响的每条记录设置为 old_value。

我想知道是否有我想念的更简单的方法来做到这一点,或者是否有人有类似的经验。

【问题讨论】:

    标签: python django undo undo-redo


    【解决方案1】:

    看看django-reversion。它为 Django 模型提供版本控制。可以轻松添加到现有项目中。

    它不采用“当前”指针方法。相反,它会在每次保存对象时对其进行序列化,并将其存储在单独的Version 模型中,通用外键指向该对象。 (关系字段默认序列化为主键。)另外,它允许以灵活的方式将Versions 分组到Revisions。

    所以你可以这样做:

    • 当用户上传 CSV 时,只需像往常一样保存更改,但将 @revision.create_on_success 装饰器添加到执行导入的函数中,这样该函数对记录所做的任何更改都将存储在单个修订版下。
    • 当用户点击“撤消”时,您只需还原最新版本。

    这是怎么做的::

    @revision.create_on_success
    def import_csv(request, csv):
        # Old versions of all objects save()d here will
        # belong to single revision.
    
    def undo_last_csv_import(request):
        # First, get latest revision saved by this user.
        # (Assuming you create revisions only when user imports a CSV
        # and do not version control other data.)
        revision = Revision.objects.filter(user=request.user)\
            .order_by('-date_created')[0]
        # And revert it, delete=True means we want to delete
        # any newly added records as well
        revision.revert(delete=True)
    

    它依赖于您仅在用户导入 CSV 时创建修订的事实。这意味着,如果您还计划对其他数据进行版本控制,那么您将需要实现某种标志,通过该标志您可以获得受最新导入影响的记录。然后您可以通过此标志获取记录,获取最新保存的版本,并还原该版本所属的整个修订。像这样::

    def undo_last_csv_import(request):
        some_record = Record.objects.by_user(request.user).from_the_last_import()[0]
        latest_saved_version_of_some_record = Version.objects.get_for_date(
            some_record,
            datetime.now(), # The latest saved Version at the moment.
            )
        # Revert all versions that belong to the same revision
        # as the version we got above.
        latest_saved_version_of_some_record.revision.revert()
    

    这不是一个漂亮的解决方案,肯定有办法通过这个应用程序做得更好。我建议查看代码以更好地了解django-reversion 是如何工作的——文档很好,找不到没有文档字符串的函数。 ^_^d

    (文档也不错,但对我来说有点误导,即他们写了Version.objects.get_for_date(your_model, date),其中 your_model 实际上是一个模型实例。)

    更新: django-reversion 得到积极维护,所以不要过多依赖上面的代码,最好查看他们的wiki,了解如何在 django 管理员之外管理版本和修订。例如,已经支持修订 cmets,这可能会简化一些事情。

    【讨论】:

      【解决方案2】:

      您的历史记录表看起来不错,只是您不需要 new_value 字段来执行撤消。是的,这就是“撤消”通常的实现方式(另一种选择是 Lennart 将版本号放入所有记录的方法)。单独的日志表的好处是你不需要在常规查询中处理版本号。

      【讨论】:

        【解决方案3】:

        你需要有版本控制,问题不是 Python 或 Django,而是如何设计数据库来做到这一点。一种常见的方法是存储具有唯一 ID 的文档并跟踪哪个是“当前”。然后撤消只是将“当前”指针放回旧版本的问题。据我所知,这就是你在做什么。

        虽然这是常见的做法,但我不知道它是否是最好的。我从未见过任何其他方式,这可能意味着它是最好的,或者最好的方式是不明显的。 :-)

        在 Django 中以通用方式执行此操作可能是一个难题,但如果您让 Django 应用程序以自定义方式支持它会更容易。

        然后您会遇到有趣(非)问题,例如如何在“未来”修订版中编辑内容,然后以暂存的内容方式一次发布一整套文档。但希望你不需要那个。 :-)

        【讨论】:

          最近更新 更多