def make_diff(oldobj:FrozenObj, newobj:FrozenObj) -> FrozenDiff: """ Compute a diff between two frozen objects. """ assert isinstance(newobj, FrozenObj), "newobj parameter should be instance of FrozenObj" if oldobj is None: return FrozenDiff(newobj.key, {}, newobj.snapshot) first = oldobj.snapshot second = newobj.snapshot diff = make_diff_from_dicts(first, second) return FrozenDiff(newobj.key, diff, newobj.snapshot)
def values_diff(self): result = {} users_keys = ["assigned_to", "owner"] def resolve_diff_value(key): value = None diff = get_diff_of_htmls( self.diff[key][0] or "", self.diff[key][1] or "" ) if diff: key = "{}_diff".format(key) value = (None, diff) return (key, value) def resolve_value(field, key): data = self.values[field] key = str(key) if key not in data: return None return data[key] for key in self.diff: value = None # Note: Hack to prevent description_diff propagation # on old HistoryEntry objects. if key == "description_diff": continue elif key == "content_diff": continue elif key == "blocked_note_diff": continue elif key in["description", "content", "blocked_note"]: (key, value) = resolve_diff_value(key) elif key in users_keys: value = [resolve_value("users", x) for x in self.diff[key]] elif key == "watchers": value = [[resolve_value("users", x) for x in self.diff[key][0]], [resolve_value("users", x) for x in self.diff[key][1]]] elif key == "points": points = {} pointsold = self.diff["points"][0] pointsnew = self.diff["points"][1] # pointsold = pointsnew if pointsold is None: for role_id, point_id in pointsnew.items(): role_name = resolve_value("roles", role_id) points[role_name] = [None, resolve_value("points", point_id)] else: for role_id, point_id in pointsnew.items(): role_name = resolve_value("roles", role_id) oldpoint_id = pointsold.get(role_id, None) points[role_name] = [resolve_value("points", oldpoint_id), resolve_value("points", point_id)] # Process that removes points entries with # duplicate value. for role in dict(points): values = points[role] if values[1] == values[0]: del points[role] if points: value = points elif key == "attachments": attachments = { "new": [], "changed": [], "deleted": [], } oldattachs = {x["id"]:x for x in self.diff["attachments"][0]} newattachs = {x["id"]:x for x in self.diff["attachments"][1]} for aid in set(tuple(oldattachs.keys()) + tuple(newattachs.keys())): if aid in oldattachs and aid in newattachs: changes = make_diff_from_dicts(oldattachs[aid], newattachs[aid], excluded_keys=("filename", "url")) if changes: change = { "filename": newattachs.get(aid, {}).get("filename", ""), "url": newattachs.get(aid, {}).get("url", ""), "changes": changes } attachments["changed"].append(change) elif aid in oldattachs and aid not in newattachs: attachments["deleted"].append(oldattachs[aid]) elif aid not in oldattachs and aid in newattachs: attachments["new"].append(newattachs[aid]) if attachments["new"] or attachments["changed"] or attachments["deleted"]: value = attachments elif key == "custom_attributes": custom_attributes = { "new": [], "changed": [], "deleted": [], } oldcustattrs = {x["id"]:x for x in self.diff["custom_attributes"][0] or []} newcustattrs = {x["id"]:x for x in self.diff["custom_attributes"][1] or []} for aid in set(tuple(oldcustattrs.keys()) + tuple(newcustattrs.keys())): if aid in oldcustattrs and aid in newcustattrs: changes = make_diff_from_dicts(oldcustattrs[aid], newcustattrs[aid], excluded_keys=("name")) if changes: change = { "name": newcustattrs.get(aid, {}).get("name", ""), "changes": changes } custom_attributes["changed"].append(change) elif aid in oldcustattrs and aid not in newcustattrs: custom_attributes["deleted"].append(oldcustattrs[aid]) elif aid not in oldcustattrs and aid in newcustattrs: custom_attributes["new"].append(newcustattrs[aid]) if custom_attributes["new"] or custom_attributes["changed"] or custom_attributes["deleted"]: value = custom_attributes elif key in self.values: value = [resolve_value(key, x) for x in self.diff[key]] else: value = self.diff[key] if not value: continue result[key] = value return result
def values_diff(self): if self.values_diff_cache is not None: return self.values_diff_cache result = {} users_keys = ["assigned_to", "owner"] def resolve_diff_value(key): value = None diff = get_diff_of_htmls( self.diff[key][0] or "", self.diff[key][1] or "" ) if diff: key = "{}_diff".format(key) value = (None, diff) return (key, value) def resolve_value(field, key): data = self.values[field] key = str(key) if key not in data: return None return data[key] for key in self.diff: value = None if key in IGNORE_DIFF_FIELDS: continue elif key in["description", "content", "blocked_note"]: (key, value) = resolve_diff_value(key) elif key in users_keys: value = [resolve_value("users", x) for x in self.diff[key]] elif key == "assigned_users": diff_in, diff_out = self.diff[key] value_in = None value_out = None if diff_in: users_list = [resolve_value("users", x) for x in diff_in if x] value_in = ", ".join(filter(None, users_list)) if diff_out: users_list = [resolve_value("users", x) for x in diff_out if x] value_out = ", ".join(filter(None, users_list)) value = [value_in, value_out] elif key == "points": points = {} pointsold = self.diff["points"][0] pointsnew = self.diff["points"][1] # pointsold = pointsnew if pointsold is None: for role_id, point_id in pointsnew.items(): role_name = resolve_value("roles", role_id) points[role_name] = [None, resolve_value("points", point_id)] else: for role_id, point_id in pointsnew.items(): role_name = resolve_value("roles", role_id) oldpoint_id = pointsold.get(role_id, None) points[role_name] = [resolve_value("points", oldpoint_id), resolve_value("points", point_id)] # Process that removes points entries with # duplicate value. for role in dict(points): values = points[role] if values[1] == values[0]: del points[role] if points: value = points elif key == "attachments": attachments = { "new": [], "changed": [], "deleted": [], } oldattachs = {x["id"]: x for x in self.diff["attachments"][0]} newattachs = {x["id"]: x for x in self.diff["attachments"][1]} for aid in set(tuple(oldattachs.keys()) + tuple(newattachs.keys())): if aid in oldattachs and aid in newattachs: changes = make_diff_from_dicts(oldattachs[aid], newattachs[aid], excluded_keys=("filename", "url", "thumb_url")) if changes: change = { "filename": newattachs.get(aid, {}).get("filename", ""), "url": newattachs.get(aid, {}).get("url", ""), "thumb_url": newattachs.get(aid, {}).get("thumb_url", ""), "changes": changes } attachments["changed"].append(change) elif aid in oldattachs and aid not in newattachs: attachments["deleted"].append(oldattachs[aid]) elif aid not in oldattachs and aid in newattachs: attachments["new"].append(newattachs[aid]) if attachments["new"] or attachments["changed"] or attachments["deleted"]: value = attachments elif key == "custom_attributes": custom_attributes = { "new": [], "changed": [], "deleted": [], } oldcustattrs = {x["id"]: x for x in self.diff["custom_attributes"][0] or []} newcustattrs = {x["id"]: x for x in self.diff["custom_attributes"][1] or []} for aid in set(tuple(oldcustattrs.keys()) + tuple(newcustattrs.keys())): if aid in oldcustattrs and aid in newcustattrs: changes = make_diff_from_dicts(oldcustattrs[aid], newcustattrs[aid], excluded_keys=("name", "type")) newcustattr = newcustattrs.get(aid, {}) if changes: change_type = newcustattr.get("type", TEXT_TYPE) if change_type in [NUMBER_TYPE, CHECKBOX_TYPE]: old_value = oldcustattrs[aid].get("value") new_value = newcustattrs[aid].get("value") value_diff = [old_value, new_value] else: old_value = oldcustattrs[aid].get("value", "") new_value = newcustattrs[aid].get("value", "") value_diff = get_diff_of_htmls(old_value, new_value) change = { "name": newcustattr.get("name", ""), "changes": changes, "type": change_type, "value_diff": value_diff } custom_attributes["changed"].append(change) elif aid in oldcustattrs and aid not in newcustattrs: custom_attributes["deleted"].append(oldcustattrs[aid]) elif aid not in oldcustattrs and aid in newcustattrs: newcustattr = newcustattrs.get(aid, {}) change_type = newcustattr.get("type", TEXT_TYPE) if change_type in [NUMBER_TYPE, CHECKBOX_TYPE]: old_value = None new_value = newcustattrs[aid].get("value") value_diff = [old_value, new_value] else: new_value = newcustattrs[aid].get("value", "") value_diff = get_diff_of_htmls("", new_value) newcustattrs[aid]["value_diff"] = value_diff custom_attributes["new"].append(newcustattrs[aid]) if custom_attributes["new"] or custom_attributes["changed"] or custom_attributes["deleted"]: value = custom_attributes elif key == "user_stories": user_stories = { "new": [], "deleted": [], } olduss = {x["id"]: x for x in self.diff["user_stories"][0]} newuss = {x["id"]: x for x in self.diff["user_stories"][1]} for usid in set(tuple(olduss.keys()) + tuple(newuss.keys())): if usid in olduss and usid not in newuss: user_stories["deleted"].append(olduss[usid]) elif usid not in olduss and usid in newuss: user_stories["new"].append(newuss[usid]) if user_stories["new"] or user_stories["deleted"]: value = user_stories elif key in self.values: value = [resolve_value(key, x) for x in self.diff[key]] else: value = self.diff[key] if not value: continue result[key] = value self.values_diff_cache = result # Update values_diff_cache without dispatching signals HistoryEntry.objects.filter(pk=self.pk).update(values_diff_cache=self.values_diff_cache) return self.values_diff_cache
def values_diff(self): result = {} users_keys = ["assigned_to", "owner"] def resolve_value(field, key): data = self.values[field] key = str(key) if key not in data: return None return data[key] for key in self.diff: value = None # Note: Hack to prevent description_diff propagation # on old HistoryEntry objects. if key == "description_diff": continue elif key == "description": description_diff = get_diff_of_htmls(self.diff[key][0], self.diff[key][1]) if description_diff: key = "description_diff" value = (None, description_diff) elif key in users_keys: value = [resolve_value("users", x) for x in self.diff[key]] elif key == "watchers": value = [[ resolve_value("users", x) for x in self.diff[key][0] ], [resolve_value("users", x) for x in self.diff[key][1]]] elif key == "points": points = {} pointsold = self.diff["points"][0] pointsnew = self.diff["points"][1] # pointsold = pointsnew if pointsold is None: for role_id, point_id in pointsnew.items(): role_name = resolve_value("roles", role_id) points[role_name] = [ None, resolve_value("points", point_id) ] else: for role_id, point_id in pointsnew.items(): role_name = resolve_value("roles", role_id) oldpoint_id = pointsold.get(role_id, None) points[role_name] = [ resolve_value("points", oldpoint_id), resolve_value("points", point_id) ] # Process that removes points entries with # duplicate value. for role in dict(points): values = points[role] if values[1] == values[0]: del points[role] if points: value = points elif key == "attachments": attachments = { "new": [], "changed": [], "deleted": [], } oldattachs = {x["id"]: x for x in self.diff["attachments"][0]} newattachs = {x["id"]: x for x in self.diff["attachments"][1]} for aid in set( tuple(oldattachs.keys()) + tuple(newattachs.keys())): if aid in oldattachs and aid in newattachs: if oldattachs[aid] != newattachs[aid]: change = { "filename": oldattachs[aid]["filename"], "changes": make_diff_from_dicts(oldattachs[aid], newattachs[aid]) } attachments["changed"].append(change) elif aid in oldattachs and aid not in newattachs: attachments["deleted"].append(oldattachs[aid]) elif aid not in oldattachs and aid in newattachs: attachments["new"].append(newattachs[aid]) if attachments["new"] or attachments["changed"] or attachments[ "deleted"]: value = attachments elif key in self.values: value = [resolve_value(key, x) for x in self.diff[key]] else: value = self.diff[key] if not value: continue result[key] = value return result