如何实现“撤消”操作使用 Python/Django 的功能

发布于 2024-10-08 03:14:14 字数 491 浏览 0 评论 0原文

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

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

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

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

Table HISTORY
   unique_id
   record_affected_id
   old_value
   new_value

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

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

I have a Django application where I allow a user to import a CSV file with contact data (membership #, first name, last name, etc).

When they import the file, the application checks the database for a matching record and either: 1) inserts a new record if no match exists, or 2) updates the existing data with the new data.

My question is: what is the best way to implement an undo feature, using Django or straight Python, so that a user can undo the import operation and revert multiple records back to their original state?

My initial thoughts are to create a table like this (pseudo code):

Table HISTORY
   unique_id
   record_affected_id
   old_value
   new_value

Then if the user clicks "Undo" I can look up the unique_id that's associated with their transaction and set every record affected by that transaction to the old_value.

I'm wondering if there's a simpler way to do this that I'm missing, or if anyone has experience with something like this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

南渊 2024-10-15 03:14:14

看一下 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 管理员之外的修订。例如,已经支持修订注释,这可能会稍微简化一些事情。

Take a look at django-reversion. It provides version control for Django models. Can be easily added to existing project.

It doesn't employ "current" pointer approach. Instead, it serializes object each time it's being saved and stores it in a separate Version model with generic foreign key pointing to this object. (Relationship fields are serialized as primary keys by default.) Also, it allows to group Versions into Revisions in a flexible way.

So you can do something like that:

  • When user uploads CSV, just save changes as usual, but add @revision.create_on_success decorator to the function which does the import—so that any changes to records made by that function will be stored under a single revision.
  • When user hits "Undo", you just revert the latest revision.

Here's how it could be done::

@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)

It relies on the fact that you create revisions only when user imports CSVs. That means, if you plan to also version control other data, then you'll need to implement some kind of a flag by which you can get records affected by the latest import. Then you can get a record by this flag, get it latest saved version, and revert the whole revision that version belongs to. Like this::

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()

It's not a beautiful solution, there most certainly are ways to do it better with this app. I recommend to take a look at the code to understand better how does django-reversion work—very well documented, couldn't find a function without a docstring. ^_^d

(Documentation is also good, but turned out to be a bit misleading for me, i.e. they write Version.objects.get_for_date(your_model, date), where your_model is actually a model instance.)

Update: django-reversion is actively maintained, so don't rely on the code above much, and better check their wiki on how to manage versions & revisions outside django's admin. For instance, revision comments are already supported, that may simplify things a bit.

终止放荡 2024-10-15 03:14:14

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

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

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

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

You need to have version control, and the issue there is not Python or Django, but rather how to design the database to do this. One common way is to store documents with unique IDs and keep track of which is the "current". Undo is then just matter of putting the "current" pointer back to an older revision. This is, as far as I can see, what you are doing.

Although this is the common way of doing it, I don't know if it's the best. I've never seen any other way, which might mean it's the best, or that the best way is unobvious. :-)

Doing this in a generic way in Django is probably A Hard Problem, but will be easier if you make your Django app support it in a custom way.

Then you get into Fun (not) Issues, like how to edit things in a "future" revision and then publish a whole set of documents at once, in a staging kind of way of content. But hopefully you don't need that. :-)

忘东忘西忘不掉你 2024-10-15 03:14:14

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

Your history table looks fine, except that you don't need the new_value field to perform the undo. And yes, that' how "undo" is often implemented (the other alternative being Lennart's approach of putting a version number into all records). The advantage of a separate journal table is that you don't need to deal with the version number in regular queries.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文