Metadata-Version: 2.4
Name: django-tracking-model
Version: 0.1.8
Summary: Track changes made to django model instance fields.
Author-email: drozdowsky <hdrozdow+github@pm.me>
License-Expression: MIT
Project-URL: Homepage, https://github.com/drozdowsky/django-tracking-model/
Requires-Python: <4,>=2.7
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Django>=1.11
Provides-Extra: dev
Requires-Dist: Django; extra == "dev"
Requires-Dist: flake8; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-django; extra == "dev"
Requires-Dist: tox; extra == "dev"
Requires-Dist: psycopg2-binary; extra == "dev"
Dynamic: license-file

# Django Tracking Model / DTM 🏁
Track changes made to your model's instance fields.
Changes are cleared on save.
This package works well with [signals](https://seddonym.me/2018/05/04/django-signals/).
Mutable fields (e.g. JSONField) are not handled with deepcopy to keep it fast and simple.
Meant to be [model_utils](https://github.com/jazzband/django-model-utils)'s FieldTracker fast alternative.

*Available on [PyPi](https://pypi.org/project/django-tracking-model/)*

## Installation
```sh
pip install django-tracking-model
```

## Usage
```python
from django.db import models
from tracking_model import TrackingModelMixin

# order matters
class Example(TrackingModelMixin, models.Model):
    text = models.TextField(null=True)
    myself = models.ForeignKey("self", null=True)
    array = models.ArrayField(TextField())
```
```python
In [1]: e = Example.objects.create(id=1, text="Sample Text")
In [2]: e.tracker.changed, e.tracker.newly_created
Out[1]: ({}, True)

In [3]: e.text = "Different Text"
In [4]: e.tracker.changed
Out[2]: {"text": "Sample Text"}

In [5]: e.save()
In [6]: e.tracker.changed, e.tracker.newly_created
Out[3]: ({}, False)
```
DTM will also detect changes made to ForeignKey/OneToOne fields.
```python
In [1]: Example.objects.create(myself=e)
In [2]: e.myself = None
In [3]: e.tracker.changed
Out[1]: {"myself_id": 1}
```
Because DTM does not handle mutable fields well, you handle them with copy/deepcopy.
```python
In [1]: e = Example.objects.create(array=["I", "am", "your"])
In [2]: copied = copy(e.array)
In [3]: copied.append("father")
In [4]: e.array = copied
In [5]: e.tracker.changed
Out[1]: {"array": ["I", "am", "your"]}

In [6]: e.array = ["Testing", "is", "the", "future"]  # in this case copy not needed
```
DTM works best with \*\_save signals.
```python
def pre_save_example(instance, *args, **kwargs):
    # .create() does not populate .changed, we use newly_created
    if "text" in instance.tracker.changed or instance.tracker.newly_created:
      if instance.text
          instance.array = instance.text.split()

pre_save.connect(pre_save_example, sender=Example)
```
```python
In [1]: e = Example.objects.create(text="I am your father")
In [2]: e.refresh_from_db() # not needed
In [3]: e.array
Out[1]: ["I", "am", "your", "father"]
```
DTM handles deferred fields well.
```python
# from django.db.models.query_utils import DeferredAttribute
In [1]: e = Example.objects.only("array").first()
In [2]: e.text = "I am not your father"
In [3]: e.tracker.changed
Out[4]: {"text": DeferredAttribute}
```
You can narrow choice of tracked fields. By default everything is tracked.
```python
class Example(models.Model):
    TRACKED_FIELDS = ["first"]
    first = models.TextField()
    second = models.TextField()
```
You can also implement your own Tracker class:
```python
from tracking_model import Tracker

class SuperTracker(Tracker):
    def has_changed(self, field):
      return field in self.changed

class Example(TrackingModelMixin, models.Model):
    TRACKER_CLASS = SuperTracker
```

## Requirements
 * Python >= 2.7, <= 3.13
 * Django >= 1.11, <= 5.X.Y
