diff --git a/tcms/core/history.py b/tcms/core/history.py index 484de0041a..06ab82ba4f 100644 --- a/tcms/core/history.py +++ b/tcms/core/history.py @@ -110,9 +110,11 @@ def post_save(self, instance, created, using=None, **kwargs): # DB query so it is better to use the private field instead! # In older simple_history version this field wasn't private but was renamed # in 2.10.0 hence the pylint disable! - instance._change_reason = diff_objects( # pylint: disable=protected-access + diff = diff_objects( instance.previous, instance, self.fields_included(instance) ) + if diff: + instance._change_reason = diff # pylint: disable=protected-access super().post_save(instance, created, using, **kwargs) def finalize(self, sender, **kwargs): @@ -134,6 +136,30 @@ class ReadOnlyHistoryAdmin(SimpleHistoryAdmin): def Diff(self, obj): # pylint: disable=invalid-name return safe(f"
{obj.history_change_reason}
") + def set_history_delta_changes( + self, request, historical_records, foreign_keys_are_objs=True + ): + super().set_history_delta_changes( + request, historical_records, foreign_keys_are_objs + ) + for record in historical_records: + if getattr(record, "history_delta_changes", None): + continue + + reason = record.history_change_reason or "" + first_line = reason.split("\n")[0] + + if first_line.startswith("Tag added: "): + tag_name = first_line[len("Tag added: ") :] + record.history_delta_changes = [ + {"field": _("Tags"), "old": "", "new": tag_name} + ] + elif first_line.startswith("Tag removed: "): + tag_name = first_line[len("Tag removed: ") :] + record.history_delta_changes = [ + {"field": _("Tags"), "old": tag_name, "new": ""} + ] + def get_readonly_fields(self, request, obj=None): # make all fields readonly readonly_fields = list( diff --git a/tcms/rpc/tests/test_testcase.py b/tcms/rpc/tests/test_testcase.py index e32ba4d479..5afeb28049 100644 --- a/tcms/rpc/tests/test_testcase.py +++ b/tcms/rpc/tests/test_testcase.py @@ -716,6 +716,12 @@ def test_add_tag(self): ).exists() self.assertTrue(tag_exists) + history_entry = self.testcase.history.latest("history_id") + self.assertIn(self.tag1.name, history_entry.history_change_reason) + self.assertIn( + f"Tag added: {self.tag1.name}", history_entry.history_change_reason + ) + def test_add_tag_without_permissions(self): unauthorized_user = UserFactory() unauthorized_user.set_password("api-testing") @@ -760,6 +766,12 @@ def test_remove_tag(self): ).exists() self.assertFalse(tag_exists) + history_entry = self.testcase.history.latest("history_id") + self.assertIn(self.tag0.name, history_entry.history_change_reason) + self.assertIn( + f"Tag removed: {self.tag0.name}", history_entry.history_change_reason + ) + def test_remove_tag_without_permissions(self): unauthorized_user = UserFactory() unauthorized_user.set_password("api-testing") diff --git a/tcms/rpc/tests/test_testplan.py b/tcms/rpc/tests/test_testplan.py index 85775c220d..8395b5ee6c 100644 --- a/tcms/rpc/tests/test_testplan.py +++ b/tcms/rpc/tests/test_testplan.py @@ -92,6 +92,12 @@ def test_add_tag(self): ).exists() self.assertTrue(tag_exists) + history_entry = self.plans[0].history.latest("history_id") + self.assertIn(self.tag1.name, history_entry.history_change_reason) + self.assertIn( + f"Tag added: {self.tag1.name}", history_entry.history_change_reason + ) + def test_add_tag_without_permissions(self): unauthorized_user = UserFactory() unauthorized_user.set_password("api-testing") @@ -142,6 +148,12 @@ def test_remove_tag(self): ).exists() self.assertFalse(tag_exists) + history_entry = self.plans[0].history.latest("history_id") + self.assertIn(self.tag0.name, history_entry.history_change_reason) + self.assertIn( + f"Tag removed: {self.tag0.name}", history_entry.history_change_reason + ) + def test_remove_tag_without_permissions(self): unauthorized_user = UserFactory() unauthorized_user.set_password("api-testing") diff --git a/tcms/testcases/models.py b/tcms/testcases/models.py index aca6361033..f9499ecbda 100644 --- a/tcms/testcases/models.py +++ b/tcms/testcases/models.py @@ -116,7 +116,13 @@ def add_component(self, component): return TestCaseComponent.objects.get_or_create(case=self, component=component) def add_tag(self, tag): - return TestCaseTag.objects.get_or_create(case=self, tag=tag) + result = TestCaseTag.objects.get_or_create(case=self, tag=tag) + self._change_reason = ( # pylint: disable=protected-access + f"Tag added: {tag.name}\n\n" + f"--- tag\n+++ tag\n@@ -1,1 +1,1 @@\n-\n+{tag.name}" + ) + self.save() + return result def get_text_with_version(self, case_text_version=None): if case_text_version: @@ -138,6 +144,11 @@ def remove_component(self, component): def remove_tag(self, tag): self.tag.through.objects.filter(case=self.pk, tag=tag.pk).delete() + self._change_reason = ( # pylint: disable=protected-access + f"Tag removed: {tag.name}\n\n" + f"--- tag\n+++ tag\n@@ -1,1 +1,1 @@\n-{tag.name}\n+" + ) + self.save() def _get_absolute_url(self, request=None): return reverse( diff --git a/tcms/testplans/models.py b/tcms/testplans/models.py index 1c0492f5bb..b80a6083c1 100644 --- a/tcms/testplans/models.py +++ b/tcms/testplans/models.py @@ -63,10 +63,21 @@ def add_case(self, case, sortkey=None): )[0] def add_tag(self, tag): - return TestPlanTag.objects.get_or_create(plan=self, tag=tag) + result = TestPlanTag.objects.get_or_create(plan=self, tag=tag) + self._change_reason = ( # pylint: disable=protected-access + f"Tag added: {tag.name}\n\n" + f"--- tag\n+++ tag\n@@ -1,1 +1,1 @@\n-\n+{tag.name}" + ) + self.save() + return result def remove_tag(self, tag): TestPlanTag.objects.filter(plan=self, tag=tag).delete() + self._change_reason = ( # pylint: disable=protected-access + f"Tag removed: {tag.name}\n\n" + f"--- tag\n+++ tag\n@@ -1,1 +1,1 @@\n-{tag.name}\n+" + ) + self.save() def delete_case(self, case): TestCasePlan.objects.filter(case=case.pk, plan=self.pk).delete()