Allow to sync XML data
- Adds menu entry for ir.model.data - Remove write access on ir.model.data to administrator group - Add sync button on ir.model.data - Change storage format of values to be comparable issue3918 review8351002
This commit is contained in:
1 parent
3c53e43d25
commit
b955ab3070
7 files changed
+170
-25
No files matched your search
@@ -1,3 +1,4 @@
|
||||
* Allow to sync XML data
|
||||
* Remove on_change calls in Model.default_get
|
||||
* Add group call to on_change
|
||||
* Add UnionMixin
|
||||
|
||||
+8
-10
@@ -2,8 +2,6 @@
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
import time
|
||||
from xml import sax
|
||||
from decimal import Decimal
|
||||
import datetime
|
||||
import logging
|
||||
import traceback
|
||||
import sys
|
||||
@@ -604,10 +602,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
|
||||
if not old_values:
|
||||
old_values = {}
|
||||
else:
|
||||
old_values = safe_eval(old_values, {
|
||||
'Decimal': Decimal,
|
||||
'datetime': datetime,
|
||||
})
|
||||
old_values = self.ModelData.load_values(old_values)
|
||||
|
||||
for key in old_values:
|
||||
if isinstance(old_values[key], str):
|
||||
@@ -691,6 +686,9 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
|
||||
else:
|
||||
self.write_records(module, model, record, to_update,
|
||||
old_values, fs_id, mdata_id)
|
||||
self.grouped_model_data.extend(([self.ModelData(mdata_id)], {
|
||||
'fs_values': self.ModelData.dump_values(values),
|
||||
}))
|
||||
else:
|
||||
if self.grouped:
|
||||
self.grouped_creations[model][fs_id] = values
|
||||
@@ -713,7 +711,8 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
|
||||
'model': model,
|
||||
'module': self.module,
|
||||
'db_id': record.id,
|
||||
'values': str(values),
|
||||
'values': self.ModelData.dump_values(values),
|
||||
'fs_values': self.ModelData.dump_values(values),
|
||||
'noupdate': self.noupdate,
|
||||
})
|
||||
|
||||
@@ -725,7 +724,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
|
||||
'db_id': record.id,
|
||||
'model': model,
|
||||
'id': mdata.id,
|
||||
'values': str(values),
|
||||
'values': self.ModelData.dump_values(values),
|
||||
})
|
||||
self.fs2db.reset_browsercord(self.module, model,
|
||||
[r.id for r in records])
|
||||
@@ -774,8 +773,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
|
||||
'model': model,
|
||||
'module': module,
|
||||
'db_id': record.id,
|
||||
'values': str(values),
|
||||
'date_update': datetime.datetime.now(),
|
||||
'values': self.ModelData.dump_values(values),
|
||||
}))
|
||||
|
||||
# reset_browsercord to keep cache memory low
|
||||
|
||||
+67
-7
@@ -1,11 +1,14 @@
|
||||
#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 datetime
|
||||
import re
|
||||
import heapq
|
||||
from sql.aggregate import Max
|
||||
from sql.conditionals import Case
|
||||
from collections import defaultdict
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
from ..model import ModelView, ModelSQL, fields
|
||||
from ..report import Report
|
||||
@@ -16,6 +19,7 @@ from ..pool import Pool
|
||||
from ..pyson import Bool, Eval
|
||||
from ..rpc import RPC
|
||||
from .. import backend
|
||||
from ..protocols.jsonrpc import object_hook, JSONEncoder
|
||||
try:
|
||||
from ..tools.StringMatcher import StringMatcher
|
||||
except ImportError:
|
||||
@@ -813,10 +817,11 @@ class ModelData(ModelSQL, ModelView):
|
||||
db_id = fields.Integer('Resource ID',
|
||||
help="The id of the record in the database.", select=True,
|
||||
required=True)
|
||||
date_update = fields.DateTime('Update Date')
|
||||
date_init = fields.DateTime('Init Date')
|
||||
values = fields.Text('Values')
|
||||
fs_values = fields.Text('Values on File System')
|
||||
noupdate = fields.Boolean('No Update')
|
||||
out_of_sync = fields.Function(fields.Boolean('Out of Sync'),
|
||||
'get_out_of_sync', searcher='search_out_of_sync')
|
||||
_get_id_cache = Cache('ir_model_data.get_id', context=False)
|
||||
|
||||
@classmethod
|
||||
@@ -826,6 +831,11 @@ class ModelData(ModelSQL, ModelView):
|
||||
('fs_id_module_model_uniq', 'UNIQUE("fs_id", "module", "model")',
|
||||
'The triple (fs_id, module, model) must be unique!'),
|
||||
]
|
||||
cls._buttons.update({
|
||||
'sync': {
|
||||
'invisible': ~Eval('out_of_sync'),
|
||||
},
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def __register__(cls, module_name):
|
||||
@@ -843,14 +853,24 @@ class ModelData(ModelSQL, ModelView):
|
||||
where=model_data.inherit == True))
|
||||
table.drop_column('inherit', True)
|
||||
|
||||
@staticmethod
|
||||
def default_date_init():
|
||||
return datetime.datetime.now()
|
||||
|
||||
@staticmethod
|
||||
def default_noupdate():
|
||||
return False
|
||||
|
||||
def get_out_of_sync(self, name):
|
||||
return self.values != self.fs_values and self.fs_values is not None
|
||||
|
||||
@classmethod
|
||||
def search_out_of_sync(cls, name, clause):
|
||||
table = cls.__table__()
|
||||
name, operator, value = clause
|
||||
Operator = fields.SQL_OPERATORS[operator]
|
||||
query = table.select(table.id,
|
||||
where=Operator(
|
||||
(table.fs_values != table.values) & (table.fs_values != None),
|
||||
value))
|
||||
return [('id', 'in', query)]
|
||||
|
||||
@classmethod
|
||||
def write(cls, data, values, *args):
|
||||
super(ModelData, cls).write(data, values, *args)
|
||||
@@ -877,6 +897,46 @@ class ModelData(ModelSQL, ModelView):
|
||||
cls._get_id_cache.set(key, id_)
|
||||
return id_
|
||||
|
||||
@classmethod
|
||||
def dump_values(cls, values):
|
||||
return json.dumps(sorted(values.iteritems()), cls=JSONEncoder)
|
||||
|
||||
@classmethod
|
||||
def load_values(cls, values):
|
||||
try:
|
||||
return dict(json.loads(values, object_hook=object_hook))
|
||||
except ValueError:
|
||||
# Migration from 3.2
|
||||
from ..tools import safe_eval
|
||||
from decimal import Decimal
|
||||
import datetime
|
||||
return safe_eval(values, {
|
||||
'Decimal': Decimal,
|
||||
'datetime': datetime,
|
||||
})
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
def sync(cls, records):
|
||||
pool = Pool()
|
||||
to_write = []
|
||||
with Transaction().set_user(0):
|
||||
for data in records:
|
||||
Model = pool.get(data.model)
|
||||
values = cls.load_values(data.values)
|
||||
fs_values = cls.load_values(data.fs_values)
|
||||
# values could be the same once loaded
|
||||
# if they come from version < 3.2
|
||||
if values != fs_values:
|
||||
record = Model(data.db_id)
|
||||
Model.write([record], fs_values)
|
||||
values.update(fs_values)
|
||||
to_write.extend([[data], {
|
||||
'values': cls.dump_values(values),
|
||||
}])
|
||||
if to_write:
|
||||
cls.write(*to_write)
|
||||
|
||||
|
||||
class PrintModelGraphStart(ModelView):
|
||||
'Print Model Graph'
|
||||
|
||||
@@ -173,5 +173,49 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<menuitem parent="menu_model_access_form"
|
||||
action="act_model_button_form" id="menu_model_button_form"/>
|
||||
|
||||
<record model="ir.ui.view" id="model_data_view_list">
|
||||
<field name="model">ir.model.data</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">model_data_list</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="model_data_view_form">
|
||||
<field name="model">ir.model.data</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">model_data_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_model_data_form">
|
||||
<field name="name">Data</field>
|
||||
<field name="res_model">ir.model.data</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view"
|
||||
id="act_model_data_form_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="model_data_view_list"/>
|
||||
<field name="act_window" ref="act_model_data_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view"
|
||||
id="act_model_data_form_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="model_data_view_form"/>
|
||||
<field name="act_window" ref="act_model_data_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.domain"
|
||||
id="act_model_data_form_domain_out_of_sync">
|
||||
<field name="name">Out of Sync</field>
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="domain">[('out_of_sync', '=', True)]</field>
|
||||
<field name="act_window" ref="act_model_data_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.domain"
|
||||
id="act_model_data_form_domain_all">
|
||||
<field name="name">All</field>
|
||||
<field name="sequence" eval="9999"/>
|
||||
<field name="domain"></field>
|
||||
<field name="act_window" ref="act_model_data_form"/>
|
||||
</record>
|
||||
<menuitem parent="menu_model_form" action="act_model_data_form"
|
||||
id="menu_model_data_form"/>
|
||||
|
||||
</data>
|
||||
</tryton>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<form string="Model Data">
|
||||
<label name="module"/>
|
||||
<field name="module"/>
|
||||
<label name="model"/>
|
||||
<field name="model"/>
|
||||
<label name="fs_id"/>
|
||||
<field name="fs_id"/>
|
||||
<label name="db_id"/>
|
||||
<field name="db_id"/>
|
||||
<label name="noupdate"/>
|
||||
<field name="noupdate"/>
|
||||
<newline/>
|
||||
<label name="out_of_sync"/>
|
||||
<field name="out_of_sync"/>
|
||||
<button string="Sync" name="sync" colspan="2"/>
|
||||
<group id="values" colspan="2" yexpand="1" yfill="1" col="1">
|
||||
<separator name="values"/>
|
||||
<field name="values"/>
|
||||
</group>
|
||||
<group id="fs_values" colspan="2" yexpand="1" yfill="1" col="1">
|
||||
<separator name="fs_values"/>
|
||||
<field name="fs_values"/>
|
||||
</group>
|
||||
</form>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tree string="Model Data">
|
||||
<field name="module"/>
|
||||
<field name="model"/>
|
||||
<field name="fs_id"/>
|
||||
<field name="db_id"/>
|
||||
<field name="noupdate"/>
|
||||
<field name="out_of_sync"/>
|
||||
<button string="Sync" name="sync"/>
|
||||
</tree>
|
||||
|
||||
+10
-8
@@ -265,14 +265,6 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_ir_model_data_admin">
|
||||
<field name="model" search="[('model', '=', 'ir.model.data')]"/>
|
||||
<field name="group" ref="group_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_ir_cron">
|
||||
<field name="model" search="[('model', '=', 'ir.cron')]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
@@ -746,6 +738,16 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="group" ref="group_admin"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.button" id="model_data_sync_button">
|
||||
<field name="name">sync</field>
|
||||
<field name="model" search="[('model', '=', 'ir.model.data')]"/>
|
||||
</record>
|
||||
<record model="ir.model.button-res.group"
|
||||
id="model_data_sync_button_group_admin">
|
||||
<field name="button" ref="model_data_sync_button"/>
|
||||
<field name="group" ref="group_admin"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="sequence_type_view_form">
|
||||
<field name="model">ir.sequence.type</field>
|
||||
<field name="inherit" ref="ir.sequence_type_view_form"/>
|
||||
|
||||
Reference in new issue
Block a user