Как сделать полную историю текста в Django?

Я бы хотел, чтобы полная история большого текстового поля, редактируемого пользователями, сохранялась с помощью Django.

Я видел проекты:

У меня есть особый вариант использования, который, вероятно, выходит за рамки того, что предоставляют эти проекты. Кроме того, я настороженно отношусь к тому, насколько хорошо документированы, протестированы и обновлены эти проекты. В любом случае, вот проблема, с которой я сталкиваюсь:

У меня есть такая модель, как:

from django.db import models

class Document(models.Model):
   text_field = models.TextField()

Это текстовое поле может быть большим - более 40 КБ - и я хотел бы иметь функцию автосохранения, которая сохраняет поле каждые 30 секунд или около того. Это может сделать базу данных слишком большой, очевидно, если будет много сохранений по 40 КБ каждое (возможно, все еще 10 КБ, если заархивировать). Лучшее решение, которое я могу придумать, - это сохранить разницу между самой последней сохраненной версией и новой версией.

Однако меня беспокоят условия гонки, связанные с параллельными обновлениями. На ум приходят два различных состояния гонки (второе намного серьезнее первого):

  1. Состояние гонки HTTP-транзакций: пользователь A и пользователь B запрашивают документ X0 и вносят изменения индивидуально, создавая Xa и Xb. Xa сохраняется, разница между X0 и Xa составляет «Xa-0» («меньше нет»), причем Xa теперь хранится как официальная версия в базе данных. Если Xb впоследствии сохраняет, он перезаписывает Xa, при этом разница будет Xb-a («b меньше a»).

    Хотя это не идеально, меня это поведение не особо беспокоит. Документы перезаписывают друг друга, и пользователи A и B могли не знать друг друга (каждый из которых начал с документа X0), но история сохраняет целостность.

  2. Состояние гонки чтения / обновления базы данных: проблемное состояние гонки - это когда Xa и Xb сохраняются одновременно в X0. Будет (псевдо) код вроде:

     def save_history(orig_doc, new_doc):
         text_field_diff = diff(orig_doc.text_field, new_doc.text_field)
         save_diff(text_field_diff)
    

    Если Xa и Xb оба читают X0 из базы данных (т. Е. Orig_doc - это X0), их различия станут Xa-0 и Xb-0 (в отличие от сериализованных Xa-0, затем Xb-a, или эквивалентно Xb-0, затем Xa- б). Когда вы пытаетесь склеить различия для создания истории, это не сработает либо на патче Xa-0, либо на Xb-0 (оба применяются к X0). Целостность истории была нарушена (или есть?).

    Одно из возможных решений - алгоритм автоматического согласования, который обнаруживает эти проблемы Постфактум. Если восстановление истории завершается неудачно, можно предположить, что возникла гонка, и поэтому применять неудачный патч к предыдущим версиям истории до тех пор, пока он не будет успешным.

Я был бы рад получить отзывы и предложения о том, как решить эту проблему.

Кстати, поскольку это полезный выход, я заметил, что здесь обсуждается атомарность Django:

Большое спасибо.

Это не полный ответ, поэтому я просто оставлю его в комментариях. Попробуйте посмотреть на поле Django RCS: code.google.com/p/django-rcsfield Это системы контроля версий для управления полем. Статья о том, как заставить работать: lethain.com/entry/2008/oct/15/setting-up-django-rcsfield

jb. 08.01.2009 06:50
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
10
1
2 658
5

Ответы 5

Для управления различиями вы, вероятно, захотите изучить Python дифлиб.

Что касается атомарности, я бы, вероятно, поступил так же, как и вики (Trac и т. д.). Если содержимое изменилось с момента последнего его получения пользователем, запросите его замену новой версией. Если вы храните текст и различия в одной и той же записи, не составит труда избежать состояния гонки базы данных, используя методы, указанные в размещенных вами ссылках.

Difflib великолепен, спасибо. Я до сих пор не выяснил атомарность, но думаю, что это выполнимо.

Brian M. Hunt 08.01.2009 19:47

Проблема с хранением: Я думаю, вам следует хранить различия только двух последовательных действительных версий документа. Как вы отметили, проблема заключается в получении действительной версии при одновременном редактировании.

Проблема параллелизма:

  1. Могли бы вы избежать их всех вместе, как Джефф предлагает, или заблокировав документ?
  2. Если нет, то я думаю, что вы в конечном итоге находитесь в парадигме онлайн-редакторы для совместной работы в режиме реального времени, такие как Google Docs.

Чтобы получить иллюстрированный вид банки с червями, вы открываете catch этот технический разговор Google в 9:21 (речь идет о совместном редактировании Eclipse в реальном времени)

В качестве альтернативы, есть пара патентов, в которых подробно описаны способы решения этих проблем в Статья в Википедии о совместных редакторах в реальном времени.

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

Brian M. Hunt 08.01.2009 19:46

Ваше автосохранение, я полагаю, сохраняет черновую версию до того, как пользователь действительно нажмет кнопку сохранения, верно?

Если это так, вам не нужно хранить черновики сохранений, просто удалите их после того, как пользователь решит сохранить их в реальном времени, и сохраните только историю реальных / явных сохранений.

Хорошее предложение. Мне нравится идея неявного ведения истории - чтобы вы могли вернуться назад и сказать: «О, верно». Однако за это приходится платить. :)

Brian M. Hunt 08.01.2009 19:48

Вот что я сделал, чтобы сохранить историю объекта:

Для истории приложений Django:

история / __ init__.py:

"""
history/__init__.py
"""
from django.core import serializers
from django.utils import simplejson as json
from django.db.models.signals import pre_save, post_save

# from http://code.google.com/p/google-diff-match-patch/
from contrib.diff_match_patch import diff_match_patch

from history.models import History

def register_history(M):
  """
  Register Django model M for keeping its history

  e.g. register_history(Document) - every time Document is saved,
  its history (i.e. the differences) is saved.
  """
  pre_save.connect(_pre_handler, sender=M)
  post_save.connect(_post_handler, sender=M)

def _pre_handler(signal, sender, instance, **kwargs):
  """
  Save objects that have been changed.
  """
  if not instance.pk:
    return

  # there must be a before, if there's a pk, since
  # this is before the saving of this object.
  before = sender.objects.get(pk=instance.pk)

  _save_history(instance, _serialize(before).get('fields'))

def _post_handler(signal, sender, instance, created, **kwargs):
  """
  Save objects that are being created (otherwise we wouldn't have a pk!)
  """
  if not created:
     return

  _save_history(instance, {})

def _serialize(instance):
   """
   Given a Django model instance, return it as serialized data
   """
   return serializers.serialize("python", [instance])[0]

def _save_history(instance, before):
  """
  Save two serialized objects
  """
  after = _serialize(instance).get('fields',{})

  # All fields.
  fields = set.union(set(before.keys()),set(after.keys()))

  dmp = diff_match_patch()

  diff = {}

  for field in fields:
    field_before = before.get(field,False)
    field_after = after.get(field,False)

    if field_before != field_after:
      if isinstance(field_before, unicode) or isinstance(field_before, str):
      # a patch
        diff[field] = dmp.diff_main(field_before,field_after)
      else:
        diff[field] = field_before

  history = History(history_for=instance, diff=json.dumps(diff))
  history.save()

history / models.py

"""
history/models.py
"""

from django.db import models

from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic

from contrib import diff_match_patch as diff

class History(models.Model):
     """
     Retain the history of generic objects, e.g. documents, people, etc..
  """

  content_type = models.ForeignKey(ContentType, null=True)

  object_id = models.PositiveIntegerField(null=True)

  history_for = generic.GenericForeignKey('content_type', 'object_id')

  diff = models.TextField()

  def __unicode__(self):
       return "<History (%s:%d):%d>" % (self.content_type, self. object_id, self.pk)

Надеюсь, что это поможет кому-то, и комментарии будут оценены.

Обратите внимание, что это нет устраняет состояние гонки, которое меня больше всего беспокоит. Если в _pre_handler "before = sender.objects.get (pk = instance.pk)" вызывается до сохранения другого экземпляра, но после того, как этот другой экземпляр обновил историю, и текущий экземпляр сохраняет сначала, будет история »(т.е. не по порядку). К счастью, diff_match_patch пытается изящно обрабатывать «нефатальные» прерывания, но нет гарантии успеха.

Одно из решений - атомарность. Однако я не уверен, как сделать вышеупомянутое условие гонки (т.е. _pre_handler) атомарной операцией для всех экземпляров Django. Таблица HistoryLock или общий хеш в памяти (memcached?) Подойдут - предложения?

Другое решение, как уже упоминалось, - это алгоритм согласования. Однако одновременные сохранения могут иметь «настоящие» конфликты и требовать вмешательства пользователя для определения правильного согласования.

Очевидно, что сбор истории по кусочкам не является частью приведенных выше фрагментов.

С тех пор я обнаружил также джанго-реверсия, который, кажется, хорошо работает и активно поддерживается, хотя он не выполняет различий для эффективного хранения небольших различий в больших фрагментах текста.

Другие вопросы по теме