Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-27 07:41:45

0001 """Entry versioning — snapshots on save.
0002 
0003 Pre-save signal on Entry. When content or substantive data changes,
0004 insert an EntryVersion row capturing the PRE-save state (so "version
0005 history" means "what this entry looked like at this time").
0006 
0007 Thread-local changed_by: views set it via `set_changed_by(who)` before
0008 saving; defaults to 'unknown'. Skips pure operational-key updates (e.g.
0009 bumping timestamp_modified) via _is_substantive_change().
0010 
0011 Matches tjai's signals.py in spirit; simplified.
0012 """
0013 from __future__ import annotations
0014 
0015 import threading
0016 import time
0017 
0018 from django.db.models.signals import pre_save
0019 from django.dispatch import receiver
0020 
0021 from .models import Entry, EntryVersion
0022 
0023 
0024 _local = threading.local()
0025 
0026 
0027 def set_changed_by(who: str) -> None:
0028     _local.changed_by = who
0029 
0030 
0031 def get_changed_by() -> str:
0032     return getattr(_local, 'changed_by', 'unknown')
0033 
0034 
0035 # Data keys that change every save (e.g. engine heartbeats) and don't
0036 # warrant a new version — match tjai's noise-filter list.
0037 _OPERATIONAL_KEYS = {'last_seen', 'last_run', 'retry_count', 'cooldown_until'}
0038 
0039 
0040 def _data_substantive(old: dict | None, new: dict | None) -> bool:
0041     """True if anything outside _OPERATIONAL_KEYS changed between old/new."""
0042     old = dict(old or {})
0043     new = dict(new or {})
0044     for k in _OPERATIONAL_KEYS:
0045         old.pop(k, None)
0046         new.pop(k, None)
0047     return old != new
0048 
0049 
0050 @receiver(pre_save, sender=Entry)
0051 def snapshot_on_change(sender, instance: Entry, **kwargs):
0052     if not instance.pk:
0053         return  # brand-new row — nothing to snapshot yet
0054     try:
0055         prev = Entry.objects.get(pk=instance.pk)
0056     except Entry.DoesNotExist:
0057         return
0058 
0059     content_changed = prev.content != instance.content
0060     title_changed = prev.title != instance.title
0061     data_changed = _data_substantive(prev.data, instance.data)
0062     if not (content_changed or title_changed or data_changed):
0063         return
0064 
0065     last = (EntryVersion.objects
0066             .filter(entry=prev)
0067             .order_by('-version_num')
0068             .first())
0069     next_num = (last.version_num + 1) if last else 1
0070 
0071     EntryVersion.objects.create(
0072         entry=prev,
0073         version_num=next_num,
0074         title=prev.title,
0075         content=prev.content,
0076         data=prev.data,
0077         changed_by=get_changed_by(),
0078         timestamp=time.time(),
0079     )