-
-
Notifications
You must be signed in to change notification settings - Fork 366
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
Undocument breaking changes in FieldTracker in Version 4.1 #491
Comments
Hello @MRigal, thank's for calling. First, your solution with When working on FieldsTracker, you should also consider abstract parent models, polymorphic models, model mixins and nested One more argument for I agree that such breaking changes should be placed to major release, but now we have what we have. Now we can consider two definitions of "changed": that one above and any another. If we don't change the definition, current tracker behavior is correct (but maybe we should add another field set |
@tumb1er thanks for replying! As it is sometimes easier to speak around code, I've created a PR with a basic test, that would work on django-model-utils <4.1 and fail on 4.1+, as well as separately, a change proposal to keep the nice work you've added when using I think it shows how it is possible to work with a dynamic I agree that what matters is the definition of changed field, but this is where I disagree with you. I don't think it should be the exact moment where the I think we might agree that we disagree here, but:
|
@MRigal yes, I disagree with almost all your statements.
Another proposal is to tag 4.1 as 5.0 and bypass step 3 (adding breaking changes to documentation). And I think there should be third party person as a decision maker. |
Hi @tumb1er, My reason for speaking out here nevertheless is that just today, I worked on another piece of code where the exact topic of this issue turned out to be relevant: The class MyModel(Model):
name = models.CharField(max_length=64)
tracker = FieldTracker()
def my_post_super_save_logic(self):
if self.tracker.changed('name'):
# Do complex stuff
else
# Do other complex stuff
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
self.my_post_super_save_logic() This was my first approach to a post-4.1 solution: class MyModel(Model):
name = models.CharField(max_length=64)
tracker = FieldTracker()
def my_post_super_save_logic(self, name_has_changed):
if name_has_changed:
# Do complex stuff
else
# Do other complex stuff
def save(self, *args, **kwargs):
name_has_changed = self.tracker.changed('name')
super().save(*args, **kwargs)
self.my_post_super_save_logic(name_has_changed=name_has_changed) As you can see, django-model-utils’ new tracker behaviour forces the implementation to either distribute the method’s logic across different parts of the the Since I found this unsatisfactory, I came up with another idea and implemented the method as a context manager: from contextlib import contextmanager
class MyModel(Model):
name = models.CharField(max_length=64)
tracker = FieldTracker()
@contextmanager
def my_post_super_save_logic(self):
name_has_changed = self.tracker.changed('name')
yield
if name_has_changed:
# Do complex stuff
else
# Do other complex stuff
def save(self, *args, **kwargs):
with self.my_post_super_save_logic():
super().save(*args, **kwargs) This solution has two advantages: It makes the While this is viable to us, there remains the drawback of increased complexity in the code that was not necessary before. Therefore, if there is a significant group of users who take advantage of the tracker being reset in Thank you very much for considering our perspective. |
Hello! Interesting idea with context manager. # monkey-patched MyModel.save_base
def save_base(self):
with self.tracker:
super().save_base()
# reset here
# MyModel.save
def save(self):
with self.tracker:
super().save()
# not reset here!
# reset here I think we can replace django-model-utils internal fields reset code with a context manager, and allow users to use it as in "outermost-reset" manner.
Your logic will look like this: @reset_fields(tracker='tracker')
def save(self):
super().save()
if self.tracker.changed('name'):
self.my_post_super_save_logic() |
@gregorgaertner if you agree, let's decide who will make a PR for this. |
@tumb1er we discussed offline with @gregorgaertner and we do agree that this is a great idea! If you're willing to, you could start with a PR and we could both review it. Else I was thinking to try to work on it, but I'm not sure when I'll get the time for it. |
I'll try to find some time at weekend. |
@MRigal @gregorgaertner Hello! Review please: #494 |
* Add tracker context manager and decorator * Handle auto-field warning in tests * Use tracker context in monkey-patched methods * Test delaying set_saved_fields call with context manager * Docs for tracker context * Describe FieldsContext context manager in changes * Fix unused import * #494 add breaking changes note on 4.1.1 release for #404 * #494 fix typo * #494 add docstring for FieldsContext * #494 move breaking changes from 4.1.1 to 4.1.0 * #494 fix typo and add some more docstrings
Problem
The Release 4.1, after moving the patch from the
save()
to thesave_base()
method in #404 (@tumb1er FYI) introduced a serioulsy breaking change that was neither announced nor documented (and was obviously not tested brefore). It would have deserved a 5.x update IMHO.Since the values of the tracker are restored after the call to the
save_base()
method, any check/assumption made on the tracker inside thesave()
Method but after thesuper().save()
call fails, as the tracker is then "new/empty". This is especially problematic when tracking changes depending on related objects, when working with nested serializers per example.Environment
Code examples
Just calling
MyModel.create()
would result in 2 prints in version <4.1 and in only one in version 4.1+ (the second check will fail as the tracker is resetted on thesave_base()
call inside the `super().save() call)Discussion / proposal
@auvipy A work-around is to test before the call to
save()
and then use the variable afterwards for such cases.But I honestly find it quite a dramatic regression for the IMHO limited benefit of being able to modify
update_fields
in thesave()
Method.I would seriously prefer to restore the previous behaviour of patching the
save()
Method, but still add a functionality to support overridingupdate_fields
: I would prefer to add a new kwarg_always_update
toFieldTracker
where you could define the list of fields that you always want to update. I think also in term of design and readbility it would be a better feature. @tumb1er what do you think about the suggestion? If you validate it, I could draft a PR for thisThe text was updated successfully, but these errors were encountered: