Skip to content

angee.messaging.tracking

Odoo-style field-change tracking for record chatter.

ThreadedModelMixin logs configured field changes into a record's chatter on save. This module owns the generic field-diff mechanism it composes: :class:FieldTracker reads the configured tracked fields, snapshots their pre-save values, diffs them against the post-save values, and renders human displays, emitting :class:TrackingChange rows. Keeping the ~130-line mechanism here (instead of on the mixin) keeps it out of every consumer model's MRO and lets the mixin stay the thin, permission-gated verb owner.

:class:TrackingChange is the tracked old→new row shape authored once, so the mixin's tracker builds it and the message write path persists it from the same shape — the row shape is not declared in the mixin and re-validated again in the manager.

TrackingChange

python
@dataclass(frozen=True)
class TrackingChange()

One tracked field's old→new values, in TrackingValue's row shape.

Emitted by :class:FieldTracker and consumed by the message write path (MessageManager.post_to_thread via _normalise_tracking_value), so the tracked field set is declared in exactly one place.

FieldTracker

python
class FieldTracker()

The field-diff mechanism ThreadedModelMixin composes to track record changes.

Bound to one record instance and its configured thread_tracking_fields; the mixin delegates snapshotting/diffing/rendering here and keeps only the chatter verbs.

snapshot

python
def snapshot(
        update_fields: Iterable[str] | None = None
) -> tuple[dict[str, Any], ...]

Return the pre-save old values for the tracked fields, before save.

changes

python
def changes(
        snapshot: tuple[dict[str, Any], ...]) -> tuple[TrackingChange, ...]

Return the tracked fields whose value changed since snapshot.

create_changes

python
def create_changes() -> tuple[TrackingChange, ...]

Return the tracked initial (non-default) values for the record's first save.

Released under the AGPL-3.0 License.