Como fazer texto histórico completo no Django?

votos
9

Eu gostaria de ter a história completa de um campo de texto grande editada por usuários, armazenados usando Django.

Eu vi os projetos:

Eu tenho um caso de uso especial que provavelmente está fora do escopo do que esses projetos fornecer. Além disso, eu estou desconfiado de quão bem documentado, testado e atualizado esses projetos são. De qualquer forma, aqui está o problema que enfrentam:

Eu tenho um modelo, likeso:

from django.db import models

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

Este campo de texto pode ser grande - mais de 40k - e eu gostaria de ter um recurso de gravação automática que salva o campo a cada 30 segundos ou assim. Isso poderia tornar o banco de dados unwieldly grande, obviamente, se há um monte de salva em 40k cada (provavelmente ainda 10k se zipado). A melhor solução que eu posso pensar é manter uma diferença entre a versão salva mais recente e a nova versão.

No entanto, estou preocupado com as condições de corrida que envolvem atualizações paralelas. Há duas condições de corrida distintos que vêm à mente (o segundo muito mais grave do que o primeiro):

  1. HTTP condição de corrida transação : usuário A e B X0 solicitação de documento e fazer alterações individualmente, produzindo Xa e Xb. Xa é salvo, a diferença entre X0 e Xa ser Xa-0 ( a menos não), Xa agora a ser armazenado como a versão oficial no banco de dados. Se Xb salva posteriormente, ele substituirá Xa, o diff sendo Xb-a ( B menos a).

    Embora não seja ideal, eu não estou muito preocupado com este comportamento. Os documentos está substituindo uns aos outros, e os usuários A e B podem ter tido conhecimento do outro (cada um tendo começado com X0 documento), mas a história mantém integridade.

  2. Banco de dados ler condição de corrida / update : A condição de corrida problemático é quando Xa e Xb salvar ao mesmo tempo sobre X0. Haverá (pseudo-) código algo como:

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

    Se Xa e Xb quer ler X0 a partir da base de dados (isto é orig_doc X0), as suas diferenças serão tornar-se-Xa e Xb 0-0 (em oposição ao serializados Xa-0, em seguida, Xb-um, ou equivalentemente Xb-0, em seguida, XA b). Ao tentar corrigir os diffs em conjunto para produzir a história, ele irá falhar em qualquer patch de Xa-0 ou Xb-0 (que tanto se aplica a X0). A integridade da história foi comprometido (ou é?).

    Uma possível solução é um algoritmo de reconciliação automática, que detecta estes problemas ex-post . Se reconstruir a história falhar, pode-se supor que uma condição de corrida ocorreu, e assim aplicar o patch falhou em versões anteriores da história até obter êxito.

Eu ficaria feliz de ter algum feedback e sugestões sobre como ir sobre a resolução deste problema.

Aliás, na medida em que é uma maneira útil para fora, eu tenho notado que Django atomicidade é discutido aqui:

Muito obrigado.

Publicado 07/01/2009 em 16:36
fonte usuário
Em outras línguas...                            


5 respostas

votos
3

Aqui está o que eu tenho feito para salvar a história de um objeto:

Para Histórico aplicação Django:

história / __ 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()

história / 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)

Espero que ajude alguém, e comentários seria apreciada.

Note que isso não resolver a condição de corrida da minha maior preocupação. Se, em _pre_handler "antes = sender.objects.get (pk = instance.pk)" é chamado antes de outra instância salva, mas depois que outra instância actualiza a história, e o presente exemplo salva em primeiro lugar, haverá um 'quebrado história'(ou seja, fora-de-ordem). Felizmente diff_match_patch tenta lidar com graciosamente breaks "não fatais", mas não há nenhuma garantia de sucesso.

Uma solução é a atomicidade. Eu não tenho certeza de como proceder para fazer a condição de corrida acima (ou seja _pre_handler) uma operação atômica em todas as instâncias de Django, embora. (? Memcached) Uma tabela HistoryLock, ou um hash compartilhada na memória seria ótimo - sugestões?

A outra solução, como mencionado, é um algoritmo de reconciliação. No entanto, concorrente salva pode ter conflitos "genuínos" e requerem a intervenção do usuário para determinar a reconciliação correta.

Obviamente, montando a história de volta juntos não faz parte dos trechos acima.

Respondeu 11/01/2009 em 20:13
fonte usuário

votos
2

A questão de armazenamento: Eu acho que você só deve armazenar os diffs de duas versões válidas consecutivas do documento. Como salienta, o problema torna-se obter uma versão válida quando edições simultâneas acontecem.

A questão de concorrência:

  1. Você pode evitá-los todos juntos como Jeff sugere ou por bloqueio do documento?
  2. Se não, eu acho que você é, em última instância no paradigma de editores em tempo real de colaboração online, como o Google Docs .

Para se ter uma ideia ilustrada da lata de vermes que você está abrindo captura esta tech-talk google em 9m21s (trata-se de edição em tempo real de colaboração do Eclipse)

Como alternativa, há um par de patentes que detalham formas de lidar com essas coincidências sobre o artigo da Wikipedia sobre editores em tempo real de colaboração .

Respondeu 08/01/2009 em 00:24
fonte usuário

votos
1

Eu já descobri django-reversão , também, que parece funcionar bem e ser mantido ativamente, embora ele não faz diff de armazenar de forma eficiente pequenos diffs para grandes pedaços de texto.

Respondeu 03/02/2009 em 16:30
fonte usuário

votos
1

Sua auto salvar, eu assumo, salva uma versão de rascunho antes de o usuário realmente aperta o botão salvar, certo?

Se assim for, você não tem que manter o projecto de salva, simplesmente descartá-los depois que o usuário decideds para salvar de verdade, e só manter a história do real / explícita salva.

Respondeu 08/01/2009 em 01:38
fonte usuário

votos
1

Para gerir os diffs, você provavelmente quer investigar de Python difflib .

Em relação atomicidade, eu provavelmente iria lidar com ele o mesmo que o Wikis (Trac, etc.). Se o conteúdo foi alterado desde que o usuário última recuperou, solicitar que eles substituem com a nova versão. Se você está armazenando o texto e diffs no mesmo registro, ele não deve ser difícil de evitar condições de corrida de banco de dados usando as técnicas nos links que você postou.

Respondeu 07/01/2009 em 19:18
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more