Как реализовать функцию отмены с помощью Python / Django

У меня есть приложение Django, в котором я разрешаю пользователю импортировать файл CSV с контактными данными (номер участника, имя, фамилия и т. Д.).

Когда они импортируют файл, приложение проверяет базу данных на соответствие записи и либо: 1) вставляет новую запись, если совпадения не существует, либо 2) обновляет существующие данные новыми данными.

У меня вопрос: как лучше всего реализовать функцию отмены с помощью Django или прямого Python, чтобы пользователь мог отменить операцию импорта и вернуть несколько записей в исходное состояние?

Мои первоначальные мысли - создать такую ​​таблицу (псевдокод):

Table HISTORY
   unique_id
   record_affected_id
   old_value
   new_value

Затем, если пользователь нажимает «Отменить», я могу найти unique_id, связанный с его транзакцией, и установить для каждой записи, затронутой этой транзакцией, old_value.

Мне интересно, есть ли более простой способ сделать это, чего мне не хватает, или есть ли у кого-то опыт работы с чем-то вроде этого.


person Ben S    schedule 18.12.2010    source источник


Ответы (3)


Взгляните на 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. Например, комментарии к ревизии уже поддерживаются, что может немного упростить ситуацию.

person Anton Strogonoff    schedule 18.12.2010

У вас должен быть контроль версий, и проблема здесь не в Python или Django, а скорее в том, как спроектировать базу данных для этого. Один из распространенных способов - хранить документы с уникальными идентификаторами и отслеживать, какой из них является «текущим». В таком случае отменить - просто вернуть «текущий» указатель на более старую ревизию. Насколько я понимаю, вы это делаете.

Хотя это обычный способ сделать это, я не знаю, лучший ли он. Я никогда не видел другого пути, что могло означать, что он лучший, или что лучший способ неочевиден. :-)

Сделать это обычным способом в Django, вероятно, является сложной проблемой, но будет проще, если вы сделаете так, чтобы ваше приложение Django поддерживало это индивидуально.

Затем вы перейдете к интересным (не) вопросам, например, к тому, как отредактировать что-то в «будущей» версии, а затем сразу опубликовать весь набор документов в виде промежуточного содержания. Но, надеюсь, вам это не нужно. :-)

person Lennart Regebro    schedule 18.12.2010

Ваша таблица истории выглядит нормально, за исключением того, что вам не нужно поле new_value для выполнения отмены. И да, именно так часто реализуется «отмена» (другой альтернативой является подход Леннарта, который помещает номер версии во все записи). Преимущество отдельной таблицы журнала в том, что вам не нужно иметь дело с номером версии в обычных запросах.

person Martin v. Löwis    schedule 18.12.2010