Skip to content

Commit db8b262

Browse files
authored
Merge pull request #94 from rsinger86/update-fields-hook-tweaks
Only check if fields actually updated for certain condition checks
2 parents cf9dbd9 + b65aeae commit db8b262

File tree

2 files changed

+58
-32
lines changed

2 files changed

+58
-32
lines changed

django_lifecycle/mixins.py

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -205,27 +205,35 @@ def _run_hooked_methods(self, hook: str, **kwargs) -> List[str]:
205205

206206
when_field = callback_specs.get("when")
207207
when_any_field = callback_specs.get("when_any")
208-
209-
# None is explicit instead of an empty list; since Django aborts the save with an empty list
210208
update_fields = kwargs.get("update_fields", None)
211-
update_fields_exist = update_fields is not None
209+
is_partial_fields_update = update_fields is not None
212210

213211
if when_field:
214-
if update_fields_exist and when_field not in update_fields:
215-
continue
216-
if not self._check_callback_conditions(when_field, callback_specs):
212+
if not self._check_callback_conditions(
213+
when_field,
214+
callback_specs,
215+
is_synced=(
216+
is_partial_fields_update is False
217+
or when_field in update_fields
218+
),
219+
):
217220
continue
218221
elif when_any_field:
219-
filtered_when_any_fields = when_any_field
220-
if update_fields_exist:
221-
filtered_when_any_fields = list(set(update_fields) & set(when_any_field)) # Intersected.
222-
223-
if not any(
224-
[
225-
self._check_callback_conditions(field_name, callback_specs)
226-
for field_name in filtered_when_any_fields
227-
]
228-
):
222+
any_condition_matched = False
223+
224+
for field_name in when_any_field:
225+
if self._check_callback_conditions(
226+
field_name,
227+
callback_specs,
228+
is_synced=(
229+
is_partial_fields_update is False
230+
or field_name in update_fields
231+
),
232+
):
233+
any_condition_matched = True
234+
break
235+
236+
if not any_condition_matched:
229237
continue
230238

231239
# Save method name before potentially wrapping with `on_commit`
@@ -252,8 +260,13 @@ def _run_hooked_methods(self, hook: str, **kwargs) -> List[str]:
252260

253261
return fired
254262

255-
def _check_callback_conditions(self, field_name: str, specs: dict) -> bool:
256-
if not self._check_has_changed(field_name, specs):
263+
def _check_callback_conditions(
264+
self, field_name: str, specs: dict, is_synced: bool
265+
) -> bool:
266+
if not self._check_has_changed(field_name, specs, is_synced):
267+
return False
268+
269+
if not self._check_changes_to_condition(field_name, specs, is_synced):
257270
return False
258271

259272
if not self._check_is_now_condition(field_name, specs):
@@ -268,12 +281,12 @@ def _check_callback_conditions(self, field_name: str, specs: dict) -> bool:
268281
if not self._check_is_not_condition(field_name, specs):
269282
return False
270283

271-
if not self._check_changes_to_condition(field_name, specs):
272-
return False
273-
274284
return True
275285

276-
def _check_has_changed(self, field_name: str, specs: dict) -> bool:
286+
def _check_has_changed(self, field_name: str, specs: dict, is_synced: bool) -> bool:
287+
if not is_synced:
288+
return False
289+
277290
has_changed = specs["has_changed"]
278291
return has_changed is None or has_changed == self.has_changed(field_name)
279292

@@ -291,7 +304,12 @@ def _check_was_not_condition(self, field_name: str, specs: dict) -> bool:
291304
was_not = specs["was_not"]
292305
return was_not is NotSet or self.initial_value(field_name) != was_not
293306

294-
def _check_changes_to_condition(self, field_name: str, specs: dict) -> bool:
307+
def _check_changes_to_condition(
308+
self, field_name: str, specs: dict, is_synced: bool
309+
) -> bool:
310+
if not is_synced:
311+
return False
312+
295313
changes_to = specs["changes_to"]
296314
return any(
297315
[

tests/testapp/tests/test_mixin.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def test_run_hooked_methods_for_when_any(self):
136136
instance = UserAccount(first_name="Bob")
137137

138138
instance._potentially_hooked_methods = MagicMock(
139-
return_value = [
139+
return_value=[
140140
MagicMock(
141141
__name__="method_that_does_fires",
142142
_hooked=[
@@ -208,9 +208,9 @@ def test_has_changed_specs(self):
208208
UserAccount.objects.create(**data)
209209
user_account = UserAccount.objects.get()
210210

211-
self.assertFalse(user_account._check_has_changed("first_name", specs))
211+
self.assertFalse(user_account._check_has_changed("first_name", specs, True))
212212
user_account.first_name = "Ned"
213-
self.assertTrue(user_account._check_has_changed("first_name", specs))
213+
self.assertTrue(user_account._check_has_changed("first_name", specs, True))
214214

215215
def test_check_is_now_condition_wildcard_should_pass(self):
216216
specs = {"when": "first_name", "is_now": "*"}
@@ -312,23 +312,31 @@ def test_changes_to_condition_should_pass(self):
312312
data = self.stub_data
313313
UserAccount.objects.create(**data)
314314
user_account = UserAccount.objects.get()
315-
with self.assertRaises(CannotRename, msg="Oh, not Flanders. Anybody but Flanders."):
315+
with self.assertRaises(
316+
CannotRename, msg="Oh, not Flanders. Anybody but Flanders."
317+
):
316318
user_account.last_name = "Flanders"
317319
user_account.save()
318320

319321
def test_changes_to_condition_included_in_update_fields_should_fire_hook(self):
320322
user_account = UserAccount.objects.create(**self.stub_data)
321323
user_account.first_name = "Flanders"
322324
user_account.last_name = "Flanders"
323-
with self.assertRaises(CannotRename, msg="Oh, not Flanders. Anybody but Flanders."):
325+
with self.assertRaises(
326+
CannotRename, msg="Oh, not Flanders. Anybody but Flanders."
327+
):
324328
user_account.last_name = "Flanders"
325329
user_account.save(update_fields=["last_name"])
326330

327-
def test_changes_to_condition_not_included_in_update_fields_should_not_fire_hook(self):
331+
def test_changes_to_condition_not_included_in_update_fields_should_not_fire_hook(
332+
self,
333+
):
328334
user_account = UserAccount.objects.create(**self.stub_data)
329335
user_account.first_name = "Flanders"
330336
user_account.last_name = "Flanders"
331-
user_account.save(update_fields=["first_name"]) # `CannotRename` exception is not raised
337+
user_account.save(
338+
update_fields=["first_name"]
339+
) # `CannotRename` exception is not raised
332340

333341
user_account.refresh_from_db()
334342
self.assertEqual(user_account.first_name, "Flanders")
@@ -345,8 +353,8 @@ def test_changes_to_condition_should_not_pass(self):
345353

346354
def test_should_not_call_cached_property(self):
347355
"""
348-
full_name is cached_property. Accessing _potentially_hooked_methods
349-
should not call it incidentally.
356+
full_name is cached_property. Accessing _potentially_hooked_methods
357+
should not call it incidentally.
350358
"""
351359
data = self.stub_data
352360
data["first_name"] = "Bart"

0 commit comments

Comments
 (0)