Подтвердить что ты не робот

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

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

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

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

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

Table HISTORY
   unique_id
   record_affected_id
   old_value
   new_value

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

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

4b9b3361

Ответ 1

Посмотрите django-reversion. Он обеспечивает контроль версий для моделей Django. Может быть легко добавлен в существующий проект.

В нем не используется подход "текущего" указателя. Вместо этого он сериализует объект каждый раз при его сохранении и сохраняет его в отдельной модели Version с общим внешним ключом, указывающим на этот объект. (Поля отношений по умолчанию сериализуются как первичные ключи.) Кроме того, он позволяет гибко группировать Version в Revision.

Итак, вы можете сделать что-то подобное:

  • Когда пользователь загружает CSV, просто сохраняйте изменения как обычно, но добавьте @revision.create_on_success decorator в функцию, которая выполняет импорт, чтобы любые изменения в записи, сделанные этой функцией, сохранялись в одной версии.
  • Когда пользователь нажимает "Отменить", вы просто возвращаете последнюю версию.

Вот как это можно сделать:

@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 - очень хорошо документировано, не удалось найти функцию без docstring. ^ _ ^ Д

(Документация также хороша, но для меня это немного вводит в заблуждение, то есть они пишут Version.objects.get_for_date(your_model, date), где ваш_модель на самом деле является экземпляром модели.)

Обновление: django-reversion активно поддерживается, поэтому не стоит больше полагаться на код и лучше проверить их wiki о том, как управлять версиями и версиями за пределами администратора django. Например, комментарии к версии уже поддерживаются, что может немного упростить.

Ответ 2

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

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

Выполнение этого в Django в общем случае, вероятно, является трудной задачей, но будет проще, если вы сделаете свое приложение Django соответствующим образом.

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

Ответ 3

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