# 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
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()
```
## Requirements
* Python >= 2.7, <= 3.11
* Django >= 1.11, <= 4.x.y
Raw data
{
"_id": null,
"home_page": "https://github.com/drozdowsky/django-tracking-model/",
"name": "django-tracking-model",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "",
"author": "drozdowsky",
"author_email": "hdrozdow+github@pm.me",
"download_url": "",
"platform": null,
"description": "# Django Tracking Model / DTM \ud83c\udfc1\nTrack changes made to your model's instance fields. \nChanges are cleared on save. \nThis package works well with [signals](https://seddonym.me/2018/05/04/django-signals/). \nMutable fields (e.g. JSONField) are not handled with deepcopy to keep it fast and simple. \nMeant to be [model_utils](https://github.com/jazzband/django-model-utils)'s FieldTracker fast alternative.\n\n*Available on [PyPi](https://pypi.org/project/django-tracking-model/)* \n\n## Installation\n```sh\npip install django-tracking-model\n```\n\n## Usage\n```python\nfrom django.db import models\nfrom tracking_model import TrackingModelMixin\n\n# order matters\nclass Example(TrackingModelMixin, models.Model):\n text = models.TextField(null=True)\n myself = models.ForeignKey(\"self\", null=True)\n array = models.ArrayField(TextField())\n```\n```python\nIn [1]: e = Example.objects.create(id=1, text=\"Sample Text\")\nIn [2]: e.tracker.changed, e.tracker.newly_created\nOut[1]: ({}, True)\n\nIn [3]: e.text = \"Different Text\"\nIn [4]: e.tracker.changed\nOut[2]: {\"text\": \"Sample Text\"}\n\nIn [5]: e.save()\nIn [6]: e.tracker.changed, e.tracker.newly_created\nOut[3]: ({}, False)\n```\nDTM will also detect changes made to ForeignKey/OneToOne fields.\n```python\nIn [1]: Example.objects.create(myself=e)\nIn [2]: e.myself = None\nIn [3]: e.tracker.changed\nOut[1]: {\"myself_id\": 1}\n```\nBecause DTM does not handle mutable fields well, you handle them with copy/deepcopy.\n```python\nIn [1]: e = Example.objects.create(array=[\"I\", \"am\", \"your\"])\nIn [2]: copied = copy(e.array)\nIn [3]: copied.append(\"father\")\nIn [4]: e.array = copied\nIn [5]: e.tracker.changed\nOut[1]: {\"array\": [\"I\", \"am\", \"your\"]}\n\nIn [6]: e.array = [\"Testing\", \"is\", \"the\", \"future\"] # in this case copy not needed\n```\nDTM works best with \\*\\_save signals.\n```python\ndef pre_save_example(instance, *args, **kwargs):\n # .create() does not populate .changed, we use newly_created\n if \"text\" in instance.tracker.changed or instance.tracker.newly_created:\n if instance.text\n instance.array = instance.text.split()\n\npre_save.connect(pre_save_example, sender=Example)\n```\n```python\nIn [1]: e = Example.objects.create(text=\"I am your father\")\nIn [2]: e.refresh_from_db() # not needed\nIn [3]: e.array\nOut[1]: [\"I\", \"am\", \"your\", \"father\"]\n```\nDTM handles deferred fields well.\n```python\nIn [1]: e = Example.objects.only(\"array\").first()\nIn [2]: e.text = \"I am not your father\" \nIn [3]: e.tracker.changed\nOut[4]: {\"text\": DeferredAttribute}\n```\nYou can narrow choice of tracked fields. By default everything is tracked.\n```python\nclass Example(models.Model):\n TRACKED_FIELDS = [\"first\"]\n first = models.TextField()\n second = models.TextField()\n```\n\n## Requirements\n * Python >= 2.7, <= 3.11\n * Django >= 1.11, <= 4.x.y\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Track changes made to django model instance fields.",
"version": "0.1.7",
"project_urls": {
"Homepage": "https://github.com/drozdowsky/django-tracking-model/"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "d2dc90c2081ee1f8dd12995dde15dfadd23840b1a3f21e7be9af7e75ec6cf37f",
"md5": "ea463c69217b0ae1a95ba12e5189fe53",
"sha256": "719df534c450fbe841dd77e55f26fb69741723780add13ddb77961e90b64ec46"
},
"downloads": -1,
"filename": "django_tracking_model-0.1.7-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "ea463c69217b0ae1a95ba12e5189fe53",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": null,
"size": 4255,
"upload_time": "2023-09-08T13:59:27",
"upload_time_iso_8601": "2023-09-08T13:59:27.791336Z",
"url": "https://files.pythonhosted.org/packages/d2/dc/90c2081ee1f8dd12995dde15dfadd23840b1a3f21e7be9af7e75ec6cf37f/django_tracking_model-0.1.7-py2.py3-none-any.whl",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-09-08 13:59:27",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "drozdowsky",
"github_project": "django-tracking-model",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [
{
"name": "Django",
"specs": []
},
{
"name": "flake8",
"specs": []
},
{
"name": "pytest",
"specs": []
},
{
"name": "pytest-django",
"specs": []
},
{
"name": "tox",
"specs": []
},
{
"name": "psycopg2-binary",
"specs": []
}
],
"tox": true,
"lcname": "django-tracking-model"
}