如何使用Python/Django实现“撤销”功能

16
我有一个 Django 应用程序,允许用户导入包含联系人数据(会员号码、名字、姓氏等)的 CSV 文件。
当他们导入文件时,应用程序会检查数据库是否存在匹配记录,并执行以下操作之一:1)如果没有匹配项,则插入新记录;2)如果已存在数据,则使用新数据更新现有数据。
我的问题是:使用 Django 或纯 Python,实现撤销功能以使用户可以撤销导入操作并将多个记录恢复到原始状态的最佳方法是什么?
我的初步想法是创建这样一个表(伪代码):
Table HISTORY
   unique_id
   record_affected_id
   old_value
   new_value

如果用户点击“撤消”,我可以查找与其交易相关联的唯一ID,并将该交易影响到的每个记录设置为旧值。

我想知道是否有更简单的方法来完成这个操作,或者是否有人对此有经验。

3个回答

12

看一下 django-reversion,它为Django模型提供版本控制。可以很容易地添加到现有项目中。

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

因此,您可以像这样做:

  • 当用户上传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的管理界面外管理版本和修订。例如,版本注释已经支持,这可能会简化一些事情。


3
你需要进行版本控制,问题不在于Python或Django,而是如何设计数据库来实现。一种常见的方式是使用唯一ID存储文档,并跟踪哪个是“当前”版本。撤销只需将“当前”指针返回到早期版本即可。就我所知,这正是你正在做的事情。
虽然这是常见的做法,但我不知道它是否是最好的。我从未看过其他方法,这可能意味着它是最好的,或者最佳方法并不明显。 :-)
在Django中以通用方式完成此操作可能很困难,但如果您以自定义方式支持Django应用程序,则会更容易。
然后,您会遇到有趣(不)的问题,例如如何在“未来”修订版中编辑内容,然后一次性发布整套文档,以内容的分阶段方式进行暂存。但希望您不需要那样做。 :-)

1

你的历史表看起来很好,除了不需要new_value字段来执行撤销操作。是的,“撤销”通常是这样实现的(另一种选择是Lennart的方法,将版本号放入所有记录中)。单独使用日志表的优点在于您无需在常规查询中处理版本号。


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