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()