Implemented the ability of mark some history entries as hidden.
Snapshots with order fields (that are considered not important for preview) are automatically marked as hidden.remotes/origin/enhancement/email-actions
parent
92276a1589
commit
23f349a73f
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('history', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='historyentry',
|
||||||
|
name='is_hidden',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
preserve_default=True,
|
||||||
|
),
|
||||||
|
]
|
|
@ -48,8 +48,6 @@ class HistoryEntry(models.Model):
|
||||||
user = JsonField(blank=True, default=None, null=True)
|
user = JsonField(blank=True, default=None, null=True)
|
||||||
created_at = models.DateTimeField(default=timezone.now)
|
created_at = models.DateTimeField(default=timezone.now)
|
||||||
type = models.SmallIntegerField(choices=HISTORY_TYPE_CHOICES)
|
type = models.SmallIntegerField(choices=HISTORY_TYPE_CHOICES)
|
||||||
is_snapshot = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
key = models.CharField(max_length=255, null=True, default=None, blank=True)
|
key = models.CharField(max_length=255, null=True, default=None, blank=True)
|
||||||
|
|
||||||
# Stores the last diff
|
# Stores the last diff
|
||||||
|
@ -68,9 +66,15 @@ class HistoryEntry(models.Model):
|
||||||
delete_comment_date = models.DateTimeField(null=True, blank=True, default=None)
|
delete_comment_date = models.DateTimeField(null=True, blank=True, default=None)
|
||||||
delete_comment_user = JsonField(blank=True, default=None, null=True)
|
delete_comment_user = JsonField(blank=True, default=None, null=True)
|
||||||
|
|
||||||
@cached_property
|
# Flag for mark some history entries as
|
||||||
def is_comment(self):
|
# hidden. Hidden history entries are important
|
||||||
return self.type == HistoryType.comment
|
# for save but not important to preview.
|
||||||
|
# Order fields are the good example of this fields.
|
||||||
|
is_hidden = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
# Flag for mark some history entries as complete
|
||||||
|
# snapshot. The rest are partial snapshot.
|
||||||
|
is_snapshot = models.BooleanField(default=False)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def owner(self):
|
def owner(self):
|
||||||
|
|
|
@ -55,6 +55,13 @@ _freeze_impl_map = {}
|
||||||
# Dict containing registred containing with their values implementation.
|
# Dict containing registred containing with their values implementation.
|
||||||
_values_impl_map = {}
|
_values_impl_map = {}
|
||||||
|
|
||||||
|
# Not important fields for models (history entries with only
|
||||||
|
# this fields are marked as hidden).
|
||||||
|
_not_important_fields = {
|
||||||
|
"userstories.userstory": frozenset(["backlog_order", "sprint_order", "kanban_order"]),
|
||||||
|
"tasks.task": frozenset(["us_order", "taskboard_order"]),
|
||||||
|
}
|
||||||
|
|
||||||
log = logging.getLogger("taiga.history")
|
log = logging.getLogger("taiga.history")
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,7 +137,30 @@ def freeze_model_instance(obj:object) -> FrozenObj:
|
||||||
|
|
||||||
key = make_key_from_model_object(obj)
|
key = make_key_from_model_object(obj)
|
||||||
impl_fn = _freeze_impl_map[typename]
|
impl_fn = _freeze_impl_map[typename]
|
||||||
return FrozenObj(key, impl_fn(obj))
|
snapshot = impl_fn(obj)
|
||||||
|
assert isinstance(snapshot, dict), "freeze handlers should return always a dict"
|
||||||
|
|
||||||
|
return FrozenObj(key, snapshot)
|
||||||
|
|
||||||
|
|
||||||
|
def is_hidden_snapshot(obj:FrozenDiff) -> bool:
|
||||||
|
"""
|
||||||
|
Check if frozen object is considered
|
||||||
|
hidden or not.
|
||||||
|
"""
|
||||||
|
content_type, pk = obj.key.rsplit(":", 1)
|
||||||
|
snapshot_fields = frozenset(obj.diff.keys())
|
||||||
|
|
||||||
|
if content_type not in _not_important_fields:
|
||||||
|
return False
|
||||||
|
|
||||||
|
nfields = _not_important_fields[content_type]
|
||||||
|
result = snapshot_fields - nfields
|
||||||
|
|
||||||
|
if len(result) == 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def make_diff(oldobj:FrozenObj, newobj:FrozenObj) -> FrozenDiff:
|
def make_diff(oldobj:FrozenObj, newobj:FrozenObj) -> FrozenDiff:
|
||||||
|
@ -235,20 +265,7 @@ def take_snapshot(obj:object, *, comment:str="", user=None, delete:bool=False):
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("Unexpected condition")
|
raise RuntimeError("Unexpected condition")
|
||||||
|
|
||||||
kwargs = {
|
|
||||||
"user": {"pk": user_id, "name": user_name},
|
|
||||||
"key": key,
|
|
||||||
"type": entry_type,
|
|
||||||
"comment": "",
|
|
||||||
"comment_html": "",
|
|
||||||
"diff": None,
|
|
||||||
"values": None,
|
|
||||||
"snapshot": None,
|
|
||||||
"is_snapshot": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
fdiff = make_diff(old_fobj, new_fobj)
|
fdiff = make_diff(old_fobj, new_fobj)
|
||||||
fvals = make_diff_values(typename, fdiff)
|
|
||||||
|
|
||||||
# If diff and comment are empty, do
|
# If diff and comment are empty, do
|
||||||
# not create empty history entry
|
# not create empty history entry
|
||||||
|
@ -258,21 +275,29 @@ def take_snapshot(obj:object, *, comment:str="", user=None, delete:bool=False):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
kwargs.update({
|
fvals = make_diff_values(typename, fdiff)
|
||||||
|
is_hidden = is_hidden_snapshot(fdiff)
|
||||||
|
|
||||||
|
kwargs = {
|
||||||
|
"user": {"pk": user_id, "name": user_name},
|
||||||
|
"key": key,
|
||||||
|
"type": entry_type,
|
||||||
"snapshot": fdiff.snapshot if need_real_snapshot else None,
|
"snapshot": fdiff.snapshot if need_real_snapshot else None,
|
||||||
"is_snapshot": need_real_snapshot,
|
"diff": fdiff.diff,
|
||||||
"values": fvals,
|
"values": fvals,
|
||||||
"comment": comment,
|
"comment": comment,
|
||||||
"diff": fdiff.diff,
|
|
||||||
"comment_html": mdrender(obj.project, comment),
|
"comment_html": mdrender(obj.project, comment),
|
||||||
})
|
"is_hidden": is_hidden,
|
||||||
|
"is_snapshot": need_real_snapshot,
|
||||||
|
}
|
||||||
|
|
||||||
return entry_model.objects.create(**kwargs)
|
return entry_model.objects.create(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
# High level query api
|
# High level query api
|
||||||
|
|
||||||
def get_history_queryset_by_model_instance(obj:object, types=(HistoryType.change,)):
|
def get_history_queryset_by_model_instance(obj:object, types=(HistoryType.change,),
|
||||||
|
include_hidden=False):
|
||||||
"""
|
"""
|
||||||
Get one page of history for specified object.
|
Get one page of history for specified object.
|
||||||
"""
|
"""
|
||||||
|
@ -280,6 +305,9 @@ def get_history_queryset_by_model_instance(obj:object, types=(HistoryType.change
|
||||||
history_entry_model = get_model("history", "HistoryEntry")
|
history_entry_model = get_model("history", "HistoryEntry")
|
||||||
|
|
||||||
qs = history_entry_model.objects.filter(key=key, type__in=types)
|
qs = history_entry_model.objects.filter(key=key, type__in=types)
|
||||||
|
if not include_hidden:
|
||||||
|
qs = qs.filter(is_hidden=False)
|
||||||
|
|
||||||
return qs.order_by("created_at")
|
return qs.order_by("created_at")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@ def test_take_two_snapshots_with_changes():
|
||||||
|
|
||||||
qs_all = HistoryEntry.objects.all()
|
qs_all = HistoryEntry.objects.all()
|
||||||
qs_created = qs_all.filter(type=HistoryType.create)
|
qs_created = qs_all.filter(type=HistoryType.create)
|
||||||
|
qs_hidden = qs_all.filter(is_hidden=True)
|
||||||
|
|
||||||
assert qs_all.count() == 0
|
assert qs_all.count() == 0
|
||||||
|
|
||||||
|
@ -62,6 +63,7 @@ def test_take_two_snapshots_with_changes():
|
||||||
services.take_snapshot(issue, user=issue.owner)
|
services.take_snapshot(issue, user=issue.owner)
|
||||||
assert qs_all.count() == 2
|
assert qs_all.count() == 2
|
||||||
assert qs_created.count() == 1
|
assert qs_created.count() == 1
|
||||||
|
assert qs_hidden.count() == 0
|
||||||
|
|
||||||
|
|
||||||
def test_take_two_snapshots_without_changes():
|
def test_take_two_snapshots_without_changes():
|
||||||
|
@ -69,6 +71,7 @@ def test_take_two_snapshots_without_changes():
|
||||||
|
|
||||||
qs_all = HistoryEntry.objects.all()
|
qs_all = HistoryEntry.objects.all()
|
||||||
qs_created = qs_all.filter(type=HistoryType.create)
|
qs_created = qs_all.filter(type=HistoryType.create)
|
||||||
|
qs_hidden = qs_all.filter(is_hidden=True)
|
||||||
|
|
||||||
assert qs_all.count() == 0
|
assert qs_all.count() == 0
|
||||||
|
|
||||||
|
@ -79,7 +82,7 @@ def test_take_two_snapshots_without_changes():
|
||||||
|
|
||||||
assert qs_all.count() == 1
|
assert qs_all.count() == 1
|
||||||
assert qs_created.count() == 1
|
assert qs_created.count() == 1
|
||||||
|
assert qs_hidden.count() == 0
|
||||||
|
|
||||||
def test_take_snapshot_from_deleted_object():
|
def test_take_snapshot_from_deleted_object():
|
||||||
issue = f.IssueFactory.create()
|
issue = f.IssueFactory.create()
|
||||||
|
@ -174,3 +177,23 @@ def test_issue_resource_history_test(client):
|
||||||
assert qs_created.count() == 1
|
assert qs_created.count() == 1
|
||||||
assert qs_changed.count() == 0
|
assert qs_changed.count() == 0
|
||||||
assert qs_deleted.count() == 1
|
assert qs_deleted.count() == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_take_hidden_snapshot():
|
||||||
|
task = f.TaskFactory.create()
|
||||||
|
|
||||||
|
qs_all = HistoryEntry.objects.all()
|
||||||
|
qs_hidden = qs_all.filter(is_hidden=True)
|
||||||
|
|
||||||
|
assert qs_all.count() == 0
|
||||||
|
|
||||||
|
# Two snapshots with modification should
|
||||||
|
# generate two snapshots.
|
||||||
|
services.take_snapshot(task, user=task.owner)
|
||||||
|
task.us_order = 3
|
||||||
|
task.save()
|
||||||
|
|
||||||
|
services.take_snapshot(task, user=task.owner)
|
||||||
|
assert qs_all.count() == 2
|
||||||
|
assert qs_hidden.count() == 1
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue