如何使用Python/Django实现"撤消"功能

Ben*_*n S 14 python django undo undo-redo

我有一个Django应用程序,我允许用户导入带有联系人数据的CSV文件(成员资格#,名字,姓氏等).

当他们导入文件时,应用程序会检查数据库是否有匹配的记录,并且:1)如果不存在匹配则插入新记录,或者2)使用新数据更新现有数据.

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

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

Table HISTORY
   unique_id
   record_affected_id
   old_value
   new_value
Run Code Online (Sandbox Code Playgroud)

然后,如果用户单击"撤消",我可以查找与其事务关联的unique_id,并将受该事务影响的每条记录设置为old_value.

我想知道是否有一种更简单的方法可以做到这一点,我错过了,或者如果有人有这样的经历.

Ant*_*off 11

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

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

所以你可以这样做:

  • 当用户上传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)
Run Code Online (Sandbox Code Playgroud)

它依赖于仅在用户导入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()
Run Code Online (Sandbox Code Playgroud)

这不是一个漂亮的解决方案,大多数肯定是用这个应用程序做得更好的方法.我建议看一下代码,以便更好地理解django-reversion工作方式 - 文档很好,没有文档字符串就找不到函数.^ _ ^ d

(文档也很好,但结果对我有点误导,即他们写Version.objects.get_for_date(your_model, date),其中your_model实际上是一个模型实例.)

更新: django-reversion是主动维护的,所以不要依赖上面的代码,并且更好地检查他们的wiki如何管理django管理员之外的版本和修订版.例如,已经支持修订注释,这可能会简化一些事情.