diff --git a/ir/model.py b/ir/model.py
index 2fea691..2287a4a 100755
--- a/ir/model.py
+++ b/ir/model.py
@@ -1538,6 +1538,10 @@ class Log(ResourceAccessMixin, ModelSQL, ModelView):
})
action = fields.Function(
fields.Char("Action"), 'get_action', searcher='search_action')
+ old_values = fields.Text("Old Values", readonly=True)
+ new_values = fields.Text("New Values", readonly=True)
+ change_summary = fields.Function(
+ fields.Text("Changes"), 'get_change_summary')
@classmethod
def __setup__(cls):
@@ -1583,6 +1587,53 @@ class Log(ResourceAccessMixin, ModelSQL, ModelView):
return f'{field_name} : {state}'
return self.target
+ @staticmethod
+ def _load_change_values(value):
+ if not value:
+ return {}
+ try:
+ return json.loads(value)
+ except (TypeError, ValueError):
+ return {}
+
+ @staticmethod
+ def _format_change_value(value):
+ if isinstance(value, list):
+ if len(value) > 6:
+ return '[%s items]' % len(value)
+ return ', '.join(Log._format_change_value(v) for v in value)
+ if isinstance(value, dict):
+ return json.dumps(value, sort_keys=True, ensure_ascii=False)
+ if value in (None, ''):
+ return ''
+ return str(value)
+
+ def get_change_summary(self, name):
+ if self.event != 'write':
+ return ''
+ old_values = self._load_change_values(self.old_values)
+ new_values = self._load_change_values(self.new_values)
+ if not old_values and not new_values:
+ return ''
+ pool = Pool()
+ Field = pool.get('ir.model.field')
+ model = self.resource.__name__ if self.resource else None
+ lines = []
+ for field_name in sorted(set(old_values) | set(new_values)):
+ old_value = old_values.get(field_name)
+ new_value = new_values.get(field_name)
+ if old_value == new_value:
+ continue
+ if model:
+ field_label = Field.get_name(model, field_name)
+ else:
+ field_label = field_name
+ lines.append('%s: %s -> %s' % (
+ field_label,
+ self._format_change_value(old_value),
+ self._format_change_value(new_value)))
+ return '\n'.join(lines)
+
@classmethod
def search_action(cls, name, clause):
return [('target', *clause[1:])]
diff --git a/ir/view/model_log_form.xml b/ir/view/model_log_form.xml
index f404813..0916088 100755
--- a/ir/view/model_log_form.xml
+++ b/ir/view/model_log_form.xml
@@ -13,4 +13,10 @@ this repository contains the full copyright notices and license terms. -->
+
+
+
+
+
+
diff --git a/ir/view/model_log_list.xml b/ir/view/model_log_list.xml
index fa27f8f..dc193be 100755
--- a/ir/view/model_log_list.xml
+++ b/ir/view/model_log_list.xml
@@ -8,4 +8,5 @@ this repository contains the full copyright notices and license terms. -->
+
diff --git a/model/modelstorage.py b/model/modelstorage.py
index 89c7874..859ce8e 100755
--- a/model/modelstorage.py
+++ b/model/modelstorage.py
@@ -5,6 +5,7 @@ import base64
import csv
import datetime
import decimal
+import json
import random
import time
import warnings
@@ -193,6 +194,33 @@ class ModelStorage(Model):
resource=record, event=event, target=target, user=user,
**extra))
+ @staticmethod
+ def _json_log_value(value):
+ if isinstance(value, (datetime.date, datetime.datetime, datetime.time)):
+ return value.isoformat()
+ if isinstance(value, decimal.Decimal):
+ return str(value)
+ if isinstance(value, bytes):
+ return ''
+ if isinstance(value, Model):
+ return str(value)
+ if isinstance(value, (list, tuple)):
+ return [ModelStorage._json_log_value(v) for v in value]
+ if isinstance(value, dict):
+ return {
+ str(k): ModelStorage._json_log_value(v)
+ for k, v in value.items()
+ }
+ return value
+
+ @classmethod
+ def _log_value_text(cls, values):
+ values = {
+ name: cls._json_log_value(value)
+ for name, value in values.items()
+ }
+ return json.dumps(values, sort_keys=True, ensure_ascii=False)
+
@classmethod
def create(cls, vlist):
'''
@@ -267,7 +295,20 @@ class ModelStorage(Model):
all_records += records
all_fields.update(values.keys())
if transaction.check_access and values:
- cls.log(records, 'write', ','.join(sorted(values.keys())))
+ fields_names = list(sorted(values.keys()))
+ with without_check_access():
+ old_values = {
+ row['id']: {
+ name: row.get(name)
+ for name in fields_names
+ }
+ for row in cls.read(
+ [r.id for r in records], fields_names)}
+ for record in records:
+ cls.log([record], 'write', ','.join(fields_names),
+ old_values=cls._log_value_text(
+ old_values.get(record.id, {})),
+ new_values=cls._log_value_text(values))
ModelAccess.check(cls.__name__, 'write')
ModelFieldAccess.check(cls.__name__, all_fields, 'write')
diff --git a/modules/purchase_trade/purchase.py b/modules/purchase_trade/purchase.py
index 0a97595..6aefdb1 100755
--- a/modules/purchase_trade/purchase.py
+++ b/modules/purchase_trade/purchase.py
@@ -1198,7 +1198,9 @@ class Purchase(metaclass=PoolMeta):
for log in logs:
date = getattr(log, 'create_date', None)
user = getattr(getattr(log, 'user', None), 'rec_name', None) or ''
- text = getattr(log, 'action', None) or getattr(log, 'target', None)
+ text = getattr(log, 'change_summary', None)
+ text = text or getattr(log, 'action', None) or getattr(
+ log, 'target', None)
if not text:
text = getattr(log, 'event_string', None) or log.event
activity.append({