我有一个Django应用程序,我允许用户导入带有联系人数据的CSV文件(成员资格#,名字,姓氏等)。
当他们导入文件时,应用程序会检查数据库是否有匹配的记录,并且:1)如果不存在匹配则插入新记录,或者2)使用新数据更新现有数据。
我的问题是:使用Django或直接Python实现撤消功能的最佳方法是什么,以便用户可以撤消导入操作并将多个记录还原为他们的原始状态?
我最初的想法是创建一个这样的表(伪代码):
Table HISTORY
unique_id
record_affected_id
old_value
new_value
然后,如果用户点击“撤消”,我可以查找与其交易相关联的unique_id,并将受该交易影响的每条记录设置为old_value。
我想知道是否有一种更简单的方法可以解决这个问题,或者是否有人遇到类似这样的事情。
答案 0 :(得分:10)
看看django-reversion
。它为Django模型提供版本控制。可以轻松添加到现有项目中。
它不采用“当前”指针方法。相反,它会在每次保存对象时对其进行序列化,并将其存储在单独的Version
模型中,并使用指向此对象的通用外键。 (默认情况下,关系字段被序列化为主键。)此外,它允许以灵活的方式将Version
分组为Revision
。
所以你可以这样做:
@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管理员之外的修改。例如,已经支持修订注释,这可能会简化一些事情。
答案 1 :(得分:3)
你需要有版本控制,问题不是Python或Django,而是如何设计数据库来做到这一点。一种常见的方法是存储具有唯一ID的文档并跟踪哪个是“当前”。然后撤销只是将“当前”指针放回旧版本。就我所知,这是你在做什么。
虽然这是常见的做法,但我不知道它是否是最好的。我从未见过任何其他方式,这可能意味着它是最好的,或者最好的方式是不明显的。 : - )
在Django中以通用方式执行此操作可能是一个难题,但如果您让Django应用程序以自定义方式支持它,将会更容易。
然后你进入Fun(not)问题,比如如何编辑“未来”修订版中的内容,然后一次性地发布一整套文档。但希望你不需要那个。 : - )
答案 2 :(得分:1)
您的历史记录表看起来很好,但您不需要new_value字段来执行撤消操作。是的,经常实施“如何”撤销“(另一种选择是Lennart将版本号放入所有记录的方法)。单独的日志表的优点是您不需要在常规查询中处理版本号。