Skip to content

Commit

Permalink
Improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
drozdowsky committed Jan 27, 2024
1 parent dd70bb9 commit 44d7f41
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 23 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ 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
Expand All @@ -84,6 +85,17 @@ class Example(models.Model):
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(models.Model):
TRACKER_CLASS = SuperTracker
```

## Requirements
* Python >= 2.7, <= 3.11
Expand Down
7 changes: 2 additions & 5 deletions tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,14 @@ class NarrowTrackedModel(TrackingModelMixin, models.Model):


class CustomTracker(Tracker):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def has_changed(self, field):
if field not in self.tracked_fields:
raise ValueError("%s is not tracked" % field)
return field in self.changed


class WithCustomTrackerModel(TrackingModelMixin, models.Model):
tracker_class = CustomTracker
TRACKER_CLASS = CustomTracker
TRACKED_FIELDS = ["first"]
first = models.TextField(null=True)
second = models.TextField(null=True)
Expand All @@ -60,7 +57,7 @@ class InvalidTracker:


class WithInvalidTrackerModel(TrackingModelMixin, models.Model):
tracker_class = InvalidTracker
TRACKER_CLASS = InvalidTracker
TRACKED_FIELDS = ["first"]
first = models.TextField(null=True)
second = models.TextField(null=True)
4 changes: 2 additions & 2 deletions tests/test_tracking_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,11 @@ def test_only_track_first(self):
class OverrideTrackerTests(TestCase):
def test_tracking_mixin_raises_error_if_tracker_class_is_invalid(self):
with self.assertRaises(TypeError) as e:
WithInvalidTrackerModel(first="Joh", second="Doe")
WithInvalidTrackerModel(first="Joh", second="Doe").tracker

self.assertEqual(
str(e.exception),
"tracker_class must be subclass of Tracker.",
"TRACKER_CLASS must be a subclass of Tracker.",
)

def test_instance_can_use_new_methods_of_tracker_class(self):
Expand Down
26 changes: 10 additions & 16 deletions tracking_model/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,31 @@ def __init__(self, instance):


class TrackingModelMixin(object):

TRACKED_FIELDS = None
tracker_class = Tracker
# TRACKER_CLASS = Tracker

def __init__(self, *args, **kwargs):
self._validate_tracker_class()
super(TrackingModelMixin, self).__init__(*args, **kwargs)
self._initialized = True

def _validate_tracker_class(self):
if not self.tracker_class:
raise AttributeError(
"Please set tracker_class attribute."
)

if not issubclass(self.tracker_class, Tracker):
raise TypeError(
"tracker_class must be subclass of Tracker."
)

@property
def tracker(self):
if hasattr(self._state, "_tracker"):
tracker = self._state._tracker
else:
# add possibility to change tracker class
TRACKER_CLASS = getattr(self, "TRACKER_CLASS", Tracker)
if not issubclass(TRACKER_CLASS, Tracker):
raise TypeError("TRACKER_CLASS must be a subclass of Tracker.")

# populate tracked fields for the first time
# by default all fields
if not self.TRACKED_FIELDS:
instance_class = type(self)
instance_class.TRACKED_FIELDS = {f.attname for f in instance_class._meta.concrete_fields}
tracker = self._state._tracker = self.tracker_class(self)
instance_class.TRACKED_FIELDS = {
f.attname for f in instance_class._meta.concrete_fields
}
tracker = self._state._tracker = TRACKER_CLASS(self)
return tracker

def save(
Expand Down

0 comments on commit 44d7f41

Please sign in to comment.