9

Tôi muốn có toàn bộ lịch sử của một trường văn bản lớn do người dùng chỉnh sửa, được lưu trữ bằng Django.Làm thế nào để làm toàn bộ lịch sử văn bản ở Django?

Tôi đã nhìn thấy các dự án:

Tôi đã một use-case đặc biệt mà có lẽ nằm ngoài phạm vi những gì những dự án này cung cấp. Hơn nữa, tôi cảnh giác với việc tài liệu, thử nghiệm và cập nhật những dự án này tốt như thế nào. Trong mọi trường hợp, đây là vấn đề tôi phải đối mặt:

Tôi đã là một người mẫu, likeso:

from django.db import models 

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

trường văn bản này có thể lớn - hơn 40k - và tôi muốn có một tính năng tự động lưu mà tiết kiệm lĩnh vực này cứ 30 giây một lần. Điều này có thể làm cho cơ sở dữ liệu khó sử dụng lớn, rõ ràng, nếu có rất nhiều tiết kiệm ở 40k mỗi (có lẽ vẫn còn 10k nếu nén). Giải pháp tốt nhất tôi có thể nghĩ là giữ sự khác biệt giữa phiên bản đã lưu gần nhất và phiên bản mới.

Tuy nhiên, tôi lo lắng về các điều kiện chủng tộc liên quan đến cập nhật song song. Có hai điều kiện chủng tộc khác biệt mà tôi suy nghĩ (thứ hai nghiêm trọng hơn nhiều so với lần đầu tiên):

  1. HTTP giao dịch đua điều kiện: Người dùng A và User B X0 tài liệu yêu cầu và thực hiện thay đổi cá nhân, sản xuất Xa và Xb. Xa được cứu, sự khác biệt giữa X0 và ​​Xa là "Xa-0" ("ít không"), Xa bây giờ được lưu trữ như là phiên bản chính thức trong cơ sở dữ liệu. Nếu Xb sau đó lưu lại, nó ghi đè Xa, diff là Xb-a ("b less a").

    Trong khi không lý tưởng, tôi không quá lo ngại về hành vi này. Các tài liệu được ghi đè lên nhau, và người dùng A và B có thể không biết lẫn nhau (mỗi tài khoản đã bắt đầu bằng tài liệu X0), nhưng lịch sử vẫn giữ nguyên tính toàn vẹn.

  2. Điều kiện cuộc đua đọc/cập nhật cơ sở dữ liệu: Điều kiện chủng tộc có vấn đề là khi Xa và Xb lưu cùng lúc với X0. Sẽ có (pseudo-) mã gì đó như:

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

    Nếu xA và xB cả đọc X0 từ cơ sở dữ liệu (ví dụ orig_doc là X0), sự khác biệt của họ sẽ trở thành Xá-0 và XB-0 (như trái ngược với Xê-ri được xê-ri hóa sau đó Xb-a, hoặc tương đương Xb-0 rồi Xa-b). Khi bạn cố gắng vá các diff khác nhau để tạo ra lịch sử, nó sẽ thất bại trên cả hai bản vá Xa-0 hoặc Xb-0 (cả hai đều áp dụng cho X0). Tính toàn vẹn của lịch sử đã bị xâm phạm (hoặc có nó?).

    Một giải pháp có thể là thuật toán điều chỉnh tự động, phát hiện các sự cố này xuất bản. Nếu việc xây dựng lại lịch sử thất bại, người ta có thể giả định rằng một điều kiện chủng tộc đã xảy ra, và vì vậy áp dụng các bản vá lỗi cho các phiên bản trước của lịch sử cho đến khi nó thành công.

Tôi rất vui khi có một số phản hồi và đề xuất về cách giải quyết vấn đề này.

Ngẫu nhiên, trong chừng mực đó là một cách hữu ích ra, tôi đã lưu ý rằng Django số nguyên tử sẽ được thảo luận ở đây:

Cảm ơn bạn vui lòng.

+0

Không phải là câu trả lời đầy đủ vì vậy tôi sẽ đưa nó vào phần bình luận. Thử nhìn vào trường Django RCS: http://code.google.com/p/django-rcsfield/ Hệ thống kiểm soát phiên bản để quản lý trường. Bài viết về cách làm việc: http://lethain.com/entry/2008/oct/15/setting-up-django-rcsfield/ –

Trả lời

1

Để quản lý các khác biệt, có thể bạn sẽ muốn điều tra số difflib của Python.

Về nguyên tử, có lẽ tôi sẽ xử lý nó giống như Wikis (Trac, v.v.). Nếu nội dung đã thay đổi kể từ khi người dùng truy lục lần cuối, hãy yêu cầu họ ghi đè bằng phiên bản mới. Nếu bạn đang lưu trữ văn bản và các khác biệt trong cùng một bản ghi, nó không phải là khó khăn để tránh điều kiện cuộc đua cơ sở dữ liệu bằng cách sử dụng các kỹ thuật trong các liên kết bạn đăng.

+0

Difflib thật tuyệt vời, cảm ơn bạn. Tôi vẫn chưa tìm ra nguyên tử, nhưng tôi nghĩ nó có thể làm được. –

2

Sự cố lưu trữ: Tôi nghĩ bạn chỉ nên lưu trữ các khác biệt của hai phiên bản hợp lệ liên tiếp của tài liệu. Khi bạn chỉ ra, sự cố sẽ trở thành phiên bản hợp lệ khi các chỉnh sửa đồng thời diễn ra.

Vấn đề đồng thời:

  1. Ông có thể tránh tất cả chúng với nhau như Jeff suggests hoặc bằng cách khóa tài liệu?
  2. Nếu không, tôi nghĩ bạn cuối cùng là người mẫu của các biên tập viên cộng tác trực tuyến thời gian thực như Google Documents.

Để có được một minh họa xem của lon worms bạn đang mở bắt this google tech-talk at 9m21s (đó là về chỉnh sửa thời gian thực hợp tác của Eclipse)

Ngoài ra còn có một vài bằng sáng chế mà chi tiết cách đối phó với những sự trùng hợp này trên Wikipedia article on collaborative real-time editors.

+0

Các liên kết rất hữu ích, cảm ơn bạn. Vấn đề rất thú vị. Tôi có lẽ đang tìm kiếm trung gian: chỉnh sửa đồng thời mà không có sự phức tạp của việc chỉnh sửa cộng tác trong thời gian thực. –

1

Tự động lưu, tôi giả định, lưu phiên bản nháp trước khi người dùng thực sự nhấn nút lưu, phải không?

Nếu vậy, bạn không phải giữ bản nháp lưu, chỉ cần vứt bỏ chúng sau khi người dùng quyết định lưu thành thật và chỉ giữ lịch sử lưu trữ thực/rõ ràng.

+0

Đề xuất tốt. Tôi thích ý tưởng giữ một lịch sử ngầm - vì vậy bạn có thể quay trở lại và đi "oh, phải". Nó đi kèm với một mức giá, mặc dù. :) –

3

Dưới đây là những gì tôi đã thực hiện để lưu lịch sử của một đối tượng:

Đối với Django Lịch sử ứng dụng:

lịch sử/__ 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() 

lịch sử/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) 

Hy vọng rằng sẽ giúp ai đó, và ý kiến ​​sẽ được đánh giá cao.

Lưu ý rằng điều này không không phải giải quyết tình trạng cuộc đua của mối quan tâm lớn nhất của tôi. Nếu, trong _pre_handler "before = sender.objects.get (pk = instance.pk)" được gọi trước khi một cá thể khác lưu, nhưng sau đó cá thể khác đã cập nhật lịch sử, và cá thể hiện tại lưu trước, sẽ có một lịch sử '(tức là không theo thứ tự). Rất may diff_match_patch cố gắng xử lý một cách duyên dáng "không gây tử vong", nhưng không đảm bảo thành công.

Một giải pháp là nguyên tử. Tôi không chắc chắn làm thế nào để đi về làm cho điều kiện cuộc đua ở trên (ví dụ _pre_handler) một hoạt động nguyên tử trên tất cả các trường hợp của Django, mặc dù. Một bảng HistoryLock, hoặc một băm chia sẻ trong bộ nhớ (memcached?) Sẽ là tốt - đề nghị?

Giải pháp khác, như đã đề cập, là thuật toán điều chỉnh. Tuy nhiên, việc lưu đồng thời có thể có xung đột "chính hãng" và yêu cầu sự can thiệp của người dùng để xác định điều chỉnh chính xác.

Rõ ràng, việc ghép nối lịch sử lại với nhau không phải là một phần của các đoạn mã trên.

1

Kể từ đó, tôi đã phát hiện ra django-reversion, hoạt động tốt và được duy trì tích cực, mặc dù nó không làm khác biệt để lưu trữ hiệu quả các phần khác nhau của văn bản.

Các vấn đề liên quan