-
Notifications
You must be signed in to change notification settings - Fork 490
Added Model Delta and HistoricalChanges #277
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,7 +44,7 @@ def __init__(self, verbose_name=None, bases=(models.Model,), | |
try: | ||
if isinstance(bases, six.string_types): | ||
raise TypeError | ||
self.bases = tuple(bases) | ||
self.bases = (HistoricalChanges,) + tuple(bases) | ||
except TypeError: | ||
raise TypeError("The `bases` option must be a list or a tuple.") | ||
|
||
|
@@ -311,3 +311,46 @@ def __get__(self, instance, owner): | |
values = (getattr(instance, f.attname) | ||
for f in self.model._meta.fields) | ||
return self.model(*values) | ||
|
||
|
||
class HistoricalChanges(models.Model): | ||
|
||
class Meta: | ||
abstract = True | ||
|
||
def __sub__(self, old_history): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My C++ background left me with a bad taste in my mouth for operator overloads. I tend to favor explicit method calls. delta = current.changesSince(prior) |
||
if not isinstance(old_history, type(self)): | ||
raise TypeError(("unsupported operand type(s) for -: " | ||
"'{}' and '{}'").format( | ||
type(self), | ||
type(old_history))) | ||
model_delta_class = getattr(self, 'model_delta_class', ModelDelta) | ||
changes = {} | ||
changed_fields = [] | ||
for field in self._meta.fields: | ||
if hasattr(self.instance, field.name) and \ | ||
hasattr(old_history.instance, field.name): | ||
old_value = getattr(old_history, field.name, '') | ||
new_value = getattr(self, field.name) | ||
if old_value != new_value: | ||
changes[field.name] = {'old_value': old_value, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the API might be a little easier to use if this were a
Not sure what the name and attributes should be. The name could be long but the attributes might be nice to keep short. Maybe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't know what nametuple are. I will look into this and get back at you! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great!
>>> from collections import namedtuple
>>> HistoricalChange = namedtuple('HistoricalChange', ['old', 'new'])
>>> change = HistoricalChange(old=5, new=6)
>>> change.old
5
>>> change.new
6
>>> old, new = change
>>> old
5
>>> new
6 (I'm not saying we should necessarily use those names as the name/attributes... just an example) |
||
'new_value': new_value} | ||
changed_fields.append(field.name) | ||
return model_delta_class(changes, | ||
changed_fields=changed_fields, | ||
date=self.history_date, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should I haven't given this much thought, but I'd like to hear an argument for one of these variations. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, on the case I applied here in our company we used this subtractions to build timelines. Since the history is stored as soon as the model changes, I added the user and date from the last historical record. But indeed this was a very specific case. Do you think we should add both? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we should add neither for now since We can always add them later if we decide there's a good use case for copying them over to make them more easily accessible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Random idea from a simple_history user: What about returning a timedelta? |
||
user=self.history_user, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure whether this should be the old or new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, as I said on the other comments, we used this to build timelines, so the user and date that the instance changed was very relevant. Maybe we should keep both. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think maybe we should do neither here as well. If it becomes burdensome to type I'm open to re-considering and adding this and/or the historical date feature, but I'd like to not add them unless we're sure they're particularly helpful. One less feature to maintain? 😄 |
||
old_history=old_history, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what we should call these ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That makes sense to me. Let's go with |
||
new_history=self) | ||
|
||
|
||
class ModelDelta: | ||
|
||
def __init__(self, changes, changed_fields, date, user, old_history, | ||
new_history): | ||
self.changes = changes | ||
self.changed_fields = changed_fields | ||
self.date = date | ||
self.user = user | ||
self.old_history = old_history | ||
self.new_history = new_history |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
tuple(bases)
will already inherit frommodels.Model
, so maybe this should be more of a mixin that just inherits fromobject
. I'm thinking something like this:Then the
class Meta: abstract = True
wouldn't be needed.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! I think this will be better as well