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
Andrey Antukh 2014-09-13 13:51:11 +02:00
parent 92276a1589
commit 23f349a73f
4 changed files with 100 additions and 25 deletions

View File

@ -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,
),
]

View File

@ -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):

View File

@ -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")

View File

@ -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