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.
Most Python commands which affect the underlying state of an object create an undo/redo action which can then be undone or redone through Python or through the Edit > Undo and Redo menu items.
django-reversion is an extension to the Django web framework that provides version control for model instances.
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 Version
s into Revision
s in a flexible way.
So you can do something like that:
@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.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With