Add history revisions
issue3678 review3551002
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
* Add history revisions
|
||||
* Add the multi selection widget
|
||||
* Add index to one2many's on_change
|
||||
* Remove auto-refresh on Action Window
|
||||
|
||||
@@ -401,6 +401,11 @@ Class methods:
|
||||
Could be overrided to use a custom SQL query instead of a table of the
|
||||
database. It should return a SQL FromItem.
|
||||
|
||||
.. classmethod:: ModelSQL.history_revisions(ids)
|
||||
|
||||
Return a sorted list of all revisions for ids. The list is composed of
|
||||
the date, id and username of the revision.
|
||||
|
||||
.. classmethod:: ModelStorage.search(domain[, offset[, limit[, order[, count[, query]]]]])
|
||||
|
||||
Return a list of records that match the :ref:`domain <topics-domain>` or
|
||||
|
||||
@@ -68,6 +68,7 @@ class Model(ModelSQL, ModelView):
|
||||
cls._order.insert(0, ('model', 'ASC'))
|
||||
cls.__rpc__.update({
|
||||
'list_models': RPC(),
|
||||
'list_history': RPC(),
|
||||
'global_search': RPC(),
|
||||
})
|
||||
|
||||
@@ -129,6 +130,12 @@ class Model(ModelSQL, ModelView):
|
||||
])
|
||||
return [m.model for m in models]
|
||||
|
||||
@classmethod
|
||||
def list_history(cls):
|
||||
'Return a list of all models with history'
|
||||
return [name for name, model in Pool().iterobject()
|
||||
if getattr(model, '_history', False)]
|
||||
|
||||
@classmethod
|
||||
def create(cls, vlist):
|
||||
pool = Pool()
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
import re
|
||||
import datetime
|
||||
from functools import reduce
|
||||
from itertools import islice, izip
|
||||
from itertools import islice, izip, chain
|
||||
|
||||
from sql import Table, Column, Literal, Desc, Asc, Expression, Flavor
|
||||
from sql.functions import Now, Extract
|
||||
@@ -10,7 +11,7 @@ from sql.conditionals import Coalesce
|
||||
from sql.operators import Or, And, Operator
|
||||
from sql.aggregate import Count, Max
|
||||
|
||||
from trytond.model import ModelStorage
|
||||
from trytond.model import ModelStorage, ModelView
|
||||
from trytond.model import fields
|
||||
from trytond import backend
|
||||
from trytond.tools import reduce_ids
|
||||
@@ -19,6 +20,7 @@ from trytond.transaction import Transaction
|
||||
from trytond.pool import Pool
|
||||
from trytond.cache import LRUDict
|
||||
from trytond.exceptions import ConcurrencyException
|
||||
from trytond.rpc import RPC
|
||||
_RE_UNIQUE = re.compile('UNIQUE\s*\((.*)\)', re.I)
|
||||
_RE_CHECK = re.compile('CHECK\s*\((.*)\)', re.I)
|
||||
|
||||
@@ -38,6 +40,10 @@ class ModelSQL(ModelStorage):
|
||||
cls._sql_constraints = []
|
||||
cls._order = [('id', 'ASC')]
|
||||
cls._sql_error_messages = {}
|
||||
if issubclass(cls, ModelView):
|
||||
cls.__rpc__.update({
|
||||
'history_revisions': RPC(),
|
||||
})
|
||||
|
||||
if not cls._table:
|
||||
cls._table = cls.__name__.replace('.', '_')
|
||||
@@ -228,6 +234,40 @@ class ModelSQL(ModelStorage):
|
||||
if name in exception[0]:
|
||||
cls.raise_user_error(error)
|
||||
|
||||
@classmethod
|
||||
def history_revisions(cls, ids):
|
||||
pool = Pool()
|
||||
ModelAccess = pool.get('ir.model.access')
|
||||
User = pool.get('res.user')
|
||||
cursor = Transaction().cursor
|
||||
|
||||
ModelAccess.check(cls.__name__, 'read')
|
||||
|
||||
table = cls.__table_history__()
|
||||
user = User.__table__()
|
||||
revisions = []
|
||||
in_max = cursor.IN_MAX
|
||||
for i in range(0, len(ids), in_max):
|
||||
sub_ids = ids[i:i + in_max]
|
||||
where = reduce_ids(table.id, sub_ids)
|
||||
cursor.execute(*table.join(user, 'LEFT',
|
||||
Coalesce(table.write_uid, table.create_uid) == user.id)
|
||||
.select(
|
||||
Coalesce(table.write_date, table.create_date),
|
||||
table.id,
|
||||
user.name,
|
||||
where=where))
|
||||
revisions.append(cursor.fetchall())
|
||||
revisions = list(chain(*revisions))
|
||||
revisions.sort(reverse=True)
|
||||
# SQLite uses char for COALESCE
|
||||
if revisions and isinstance(revisions[0][0], basestring):
|
||||
strptime = datetime.datetime.strptime
|
||||
format_ = '%Y-%m-%d %H:%M:%S.%f'
|
||||
revisions = [(strptime(timestamp, format_), id_, name)
|
||||
for timestamp, id_, name in revisions]
|
||||
return revisions
|
||||
|
||||
@classmethod
|
||||
def __insert_history(cls, ids, deleted=False):
|
||||
transaction = Transaction()
|
||||
|
||||
@@ -104,6 +104,45 @@ class HistoryTestCase(unittest.TestCase):
|
||||
history = History(history_id)
|
||||
self.assertEqual(history.value, value)
|
||||
|
||||
def test0030history_revisions(self):
|
||||
'Test history revisions'
|
||||
History = POOL.get('test.history')
|
||||
|
||||
with Transaction().start(DB_NAME, USER,
|
||||
context=CONTEXT) as transaction:
|
||||
history = History(value=1)
|
||||
history.save()
|
||||
history_id = history.id
|
||||
first = history.create_date
|
||||
|
||||
transaction.cursor.commit()
|
||||
|
||||
with Transaction().start(DB_NAME, USER,
|
||||
context=CONTEXT) as transaction:
|
||||
history = History(history_id)
|
||||
history.value = 2
|
||||
history.save()
|
||||
second = history.write_date
|
||||
|
||||
transaction.cursor.commit()
|
||||
|
||||
with Transaction().start(DB_NAME, USER,
|
||||
context=CONTEXT) as transaction:
|
||||
history = History(history_id)
|
||||
history.value = 3
|
||||
history.save()
|
||||
third = history.write_date
|
||||
|
||||
transaction.cursor.commit()
|
||||
|
||||
with Transaction().start(DB_NAME, USER, context=CONTEXT):
|
||||
revisions = History.history_revisions([history_id])
|
||||
self.assertEqual(revisions, [
|
||||
(third, history_id, u'Administrator'),
|
||||
(second, history_id, u'Administrator'),
|
||||
(first, history_id, u'Administrator'),
|
||||
])
|
||||
|
||||
|
||||
def suite():
|
||||
return unittest.TestLoader().loadTestsFromTestCase(HistoryTestCase)
|
||||
|
||||
Reference in New Issue
Block a user