1050 lines
37 KiB
Python
1050 lines
37 KiB
Python
#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 base64
|
|
import os
|
|
from operator import itemgetter
|
|
from collections import defaultdict
|
|
|
|
from ..model import ModelView, ModelSQL, fields
|
|
from ..tools import file_open, safe_eval
|
|
from ..backend import TableHandler
|
|
from ..pyson import PYSONEncoder, CONTEXT, PYSON
|
|
from ..transaction import Transaction
|
|
from ..pool import Pool
|
|
from ..cache import Cache
|
|
from ..rpc import RPC
|
|
|
|
__all__ = [
|
|
'Action', 'ActionKeyword', 'ActionReport',
|
|
'ActionActWindow', 'ActionActWindowView', 'ActionActWindowDomain',
|
|
'ActionWizard', 'ActionURL',
|
|
]
|
|
|
|
EMAIL_REFKEYS = set(('cc', 'to', 'subject'))
|
|
|
|
|
|
class Action(ModelSQL, ModelView):
|
|
"Action"
|
|
__name__ = 'ir.action'
|
|
name = fields.Char('Name', required=True, translate=True)
|
|
type = fields.Char('Type', required=True, readonly=True)
|
|
usage = fields.Char('Usage')
|
|
keywords = fields.One2Many('ir.action.keyword', 'action',
|
|
'Keywords')
|
|
groups = fields.Many2Many('ir.action-res.group', 'action', 'group',
|
|
'Groups')
|
|
icon = fields.Many2One('ir.ui.icon', 'Icon')
|
|
active = fields.Boolean('Active', select=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Action, cls).__setup__()
|
|
cls.__rpc__.update({
|
|
'get_action_id': RPC(),
|
|
})
|
|
|
|
@staticmethod
|
|
def default_usage():
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_active():
|
|
return True
|
|
|
|
@classmethod
|
|
def write(cls, actions, values):
|
|
pool = Pool()
|
|
super(Action, cls).write(actions, values)
|
|
pool.get('ir.action.keyword')._get_keyword_cache.clear()
|
|
|
|
@classmethod
|
|
def get_action_id(cls, action_id):
|
|
pool = Pool()
|
|
with Transaction().set_context(active_test=False):
|
|
if cls.search([
|
|
('id', '=', action_id),
|
|
]):
|
|
return action_id
|
|
for action_type in (
|
|
'ir.action.report',
|
|
'ir.action.act_window',
|
|
'ir.action.wizard',
|
|
'ir.action.url',
|
|
):
|
|
Action = pool.get(action_type)
|
|
actions = Action.search([
|
|
('id', '=', action_id),
|
|
])
|
|
if actions:
|
|
action, = actions
|
|
return action.action.id
|
|
|
|
@classmethod
|
|
def get_action_values(cls, type_, action_ids):
|
|
Action = Pool().get(type_)
|
|
columns = set(Action._fields.keys() + Action._inherit_fields.keys())
|
|
columns.add('icon.rec_name')
|
|
to_remove = ()
|
|
if type_ == 'ir.action.report':
|
|
to_remove = ('report_content_custom', 'report_content',
|
|
'style_content')
|
|
elif type_ == 'ir.action.act_window':
|
|
to_remove = ('domain', 'context', 'search_value')
|
|
columns.difference_update(to_remove)
|
|
return Action.read(action_ids, list(columns))
|
|
|
|
|
|
class ActionKeyword(ModelSQL, ModelView):
|
|
"Action keyword"
|
|
__name__ = 'ir.action.keyword'
|
|
keyword = fields.Selection([
|
|
('tree_open', 'Open tree'),
|
|
('tree_action', 'Action tree'),
|
|
('form_print', 'Print form'),
|
|
('form_action', 'Action form'),
|
|
('form_relate', 'Form relate'),
|
|
('graph_open', 'Open Graph'),
|
|
], string='Keyword', required=True)
|
|
model = fields.Reference('Model', selection='models_get')
|
|
action = fields.Many2One('ir.action', 'Action',
|
|
ondelete='CASCADE', select=True)
|
|
groups = fields.Function(fields.One2Many('res.group', None, 'Groups'),
|
|
'get_groups', searcher='search_groups')
|
|
_get_keyword_cache = Cache('ir_action_keyword.get_keyword')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(ActionKeyword, cls).__setup__()
|
|
cls.__rpc__.update({'get_keyword': RPC()})
|
|
cls._error_messages.update({
|
|
'wrong_wizard_model': ('Wrong wizard model in keyword action '
|
|
'"%s".'),
|
|
})
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
super(ActionKeyword, cls).__register__(module_name)
|
|
|
|
table = TableHandler(Transaction().cursor, cls, module_name)
|
|
table.index_action(['keyword', 'model'], 'add')
|
|
|
|
def get_groups(self, name):
|
|
return [g.id for g in self.action.groups]
|
|
|
|
@classmethod
|
|
def search_groups(cls, name, clause):
|
|
return [('action.groups',) + tuple(clause[1:])]
|
|
|
|
@classmethod
|
|
def validate(cls, actions):
|
|
super(ActionKeyword, cls).validate(actions)
|
|
for action in actions:
|
|
action.check_wizard_model()
|
|
|
|
def check_wizard_model(self):
|
|
ActionWizard = Pool().get('ir.action.wizard')
|
|
if self.action.type == 'ir.action.wizard':
|
|
action_wizard, = ActionWizard.search([
|
|
('action', '=', self.action.id),
|
|
], limit=1)
|
|
if action_wizard.model:
|
|
if self.model.__name__ != action_wizard.model:
|
|
self.raise_user_error('wrong_wizard_model', (
|
|
action_wizard.rec_name,))
|
|
|
|
@staticmethod
|
|
def _convert_vals(vals):
|
|
vals = vals.copy()
|
|
pool = Pool()
|
|
Action = pool.get('ir.action')
|
|
if 'action' in vals:
|
|
vals['action'] = Action.get_action_id(vals['action'])
|
|
return vals
|
|
|
|
@staticmethod
|
|
def models_get():
|
|
pool = Pool()
|
|
Model = pool.get('ir.model')
|
|
models = Model.search([])
|
|
res = []
|
|
for model in models:
|
|
res.append([model.model, model.name])
|
|
return res
|
|
|
|
@classmethod
|
|
def delete(cls, keywords):
|
|
ModelView._fields_view_get_cache.clear()
|
|
ModelView._view_toolbar_get_cache.clear()
|
|
cls._get_keyword_cache.clear()
|
|
super(ActionKeyword, cls).delete(keywords)
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
ModelView._fields_view_get_cache.clear()
|
|
ModelView._view_toolbar_get_cache.clear()
|
|
cls._get_keyword_cache.clear()
|
|
new_vlist = []
|
|
for vals in vlist:
|
|
new_vlist.append(cls._convert_vals(vals))
|
|
return super(ActionKeyword, cls).create(new_vlist)
|
|
|
|
@classmethod
|
|
def write(cls, keywords, vals):
|
|
vals = cls._convert_vals(vals)
|
|
super(ActionKeyword, cls).write(keywords, vals)
|
|
ModelView._fields_view_get_cache.clear()
|
|
ModelView._view_toolbar_get_cache.clear()
|
|
cls._get_keyword_cache.clear()
|
|
|
|
@classmethod
|
|
def get_keyword(cls, keyword, value):
|
|
Action = Pool().get('ir.action')
|
|
key = (keyword, tuple(value))
|
|
keywords = cls._get_keyword_cache.get(key)
|
|
if keywords is not None:
|
|
return keywords
|
|
keywords = []
|
|
model, model_id = value
|
|
|
|
clause = [
|
|
('keyword', '=', keyword),
|
|
('model', '=', model + ',-1'),
|
|
]
|
|
if model_id >= 0:
|
|
clause = ['OR',
|
|
clause,
|
|
[
|
|
('keyword', '=', keyword),
|
|
('model', '=', model + ',' + str(model_id)),
|
|
],
|
|
]
|
|
action_keywords = cls.search(clause, order=[])
|
|
types = defaultdict(list)
|
|
for action_keyword in action_keywords:
|
|
type_ = action_keyword.action.type
|
|
types[type_].append(action_keyword.action.id)
|
|
for type_, action_ids in types.iteritems():
|
|
keywords.extend(Action.get_action_values(type_, action_ids))
|
|
keywords.sort(key=itemgetter('name'))
|
|
cls._get_keyword_cache.set(key, keywords)
|
|
return keywords
|
|
|
|
|
|
class ActionReport(ModelSQL, ModelView):
|
|
"Action report"
|
|
__name__ = 'ir.action.report'
|
|
_inherits = {'ir.action': 'action'}
|
|
model = fields.Char('Model')
|
|
report_name = fields.Char('Internal Name', required=True)
|
|
report = fields.Char('Path')
|
|
report_content_custom = fields.Binary('Content')
|
|
report_content = fields.Function(fields.Binary('Content'),
|
|
'get_report_content', setter='set_report_content')
|
|
action = fields.Many2One('ir.action', 'Action', required=True,
|
|
ondelete='CASCADE')
|
|
style = fields.Property(fields.Char('Style',
|
|
help='Define the style to apply on the report.'))
|
|
style_content = fields.Function(fields.Binary('Style'),
|
|
'get_style_content')
|
|
direct_print = fields.Boolean('Direct Print')
|
|
template_extension = fields.Selection([
|
|
('odt', 'OpenDocument Text'),
|
|
('odp', 'OpenDocument Presentation'),
|
|
('ods', 'OpenDocument Spreadsheet'),
|
|
('odg', 'OpenDocument Graphics'),
|
|
], string='Template Extension', required=True,
|
|
translate=False)
|
|
extension = fields.Selection([
|
|
('', ''),
|
|
('bib', 'BibTex'),
|
|
('bmp', 'Windows Bitmap'),
|
|
('csv', 'Text CSV'),
|
|
('dbf', 'dBase'),
|
|
('dif', 'Data Interchange Format'),
|
|
('doc', 'Microsoft Word 97/2000/XP'),
|
|
('doc6', 'Microsoft Word 6.0'),
|
|
('doc95', 'Microsoft Word 95'),
|
|
('docbook', 'DocBook'),
|
|
('emf', 'Enhanced Metafile'),
|
|
('eps', 'Encapsulated PostScript'),
|
|
('gif', 'Graphics Interchange Format'),
|
|
('html', 'HTML Document'),
|
|
('jpg', 'Joint Photographic Experts Group'),
|
|
('met', 'OS/2 Metafile'),
|
|
('ooxml', 'Microsoft Office Open XML'),
|
|
('pbm', 'Portable Bitmap'),
|
|
('pct', 'Mac Pict'),
|
|
('pdb', 'AportisDoc (Palm)'),
|
|
('pdf', 'Portable Document Format'),
|
|
('pgm', 'Portable Graymap'),
|
|
('png', 'Portable Network Graphic'),
|
|
('ppm', 'Portable Pixelmap'),
|
|
('ppt', 'Microsoft PowerPoint 97/2000/XP'),
|
|
('psw', 'Pocket Word'),
|
|
('pwp', 'PlaceWare'),
|
|
('pxl', 'Pocket Excel'),
|
|
('ras', 'Sun Raster Image'),
|
|
('rtf', 'Rich Text Format'),
|
|
('latex', 'LaTeX 2e'),
|
|
('sda', 'StarDraw 5.0 (OpenOffice.org Impress)'),
|
|
('sdc', 'StarCalc 5.0'),
|
|
('sdc4', 'StarCalc 4.0'),
|
|
('sdc3', 'StarCalc 3.0'),
|
|
('sdd', 'StarImpress 5.0'),
|
|
('sdd3', 'StarDraw 3.0 (OpenOffice.org Impress)'),
|
|
('sdd4', 'StarImpress 4.0'),
|
|
('sdw', 'StarWriter 5.0'),
|
|
('sdw4', 'StarWriter 4.0'),
|
|
('sdw3', 'StarWriter 3.0'),
|
|
('slk', 'SYLK'),
|
|
('svg', 'Scalable Vector Graphics'),
|
|
('svm', 'StarView Metafile'),
|
|
('swf', 'Macromedia Flash (SWF)'),
|
|
('sxc', 'OpenOffice.org 1.0 Spreadsheet'),
|
|
('sxi', 'OpenOffice.org 1.0 Presentation'),
|
|
('sxd', 'OpenOffice.org 1.0 Drawing'),
|
|
('sxd3', 'StarDraw 3.0'),
|
|
('sxd5', 'StarDraw 5.0'),
|
|
('sxw', 'Open Office.org 1.0 Text Document'),
|
|
('text', 'Text Encoded'),
|
|
('tiff', 'Tagged Image File Format'),
|
|
('txt', 'Plain Text'),
|
|
('wmf', 'Windows Metafile'),
|
|
('xhtml', 'XHTML Document'),
|
|
('xls', 'Microsoft Excel 97/2000/XP'),
|
|
('xls5', 'Microsoft Excel 5.0'),
|
|
('xls95', 'Microsoft Excel 95'),
|
|
('xpm', 'X PixMap'),
|
|
], translate=False,
|
|
string='Extension', help='Leave empty for the same as template, '
|
|
'see unoconv documentation for compatible format')
|
|
module = fields.Char('Module', readonly=True, select=True)
|
|
email = fields.Char('Email',
|
|
help='Python dictonary where keys define "to" "cc" "subject"\n'
|
|
"Example: {'to': 'test@example.com', 'cc': 'user@example.com'}")
|
|
pyson_email = fields.Function(fields.Char('PySON Email'), 'get_pyson')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(ActionReport, cls).__setup__()
|
|
cls._sql_constraints += [
|
|
('report_name_module_uniq', 'UNIQUE(report_name, module)',
|
|
'The internal name must be unique by module!'),
|
|
]
|
|
cls._error_messages.update({
|
|
'invalid_email': 'Invalid email definition on report "%s".',
|
|
})
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
super(ActionReport, cls).__register__(module_name)
|
|
|
|
cursor = Transaction().cursor
|
|
table = TableHandler(cursor, cls, module_name)
|
|
|
|
# Migration from 1.0 report_name_uniq has been removed
|
|
table.drop_constraint('report_name_uniq')
|
|
|
|
# Migration from 1.0 output_format (m2o) is now extension (selection)
|
|
if table.column_exist('output_format'):
|
|
cursor.execute(
|
|
'SELECT report.id FROM "' + cls._table + '" report '
|
|
'JOIN ir_action_report_outputformat of '
|
|
'ON (report.output_format = of.id) '
|
|
'WHERE of.format = \'pdf\'')
|
|
|
|
ids = [x[0] for x in cursor.fetchall()]
|
|
with Transaction().set_user(0):
|
|
cls.write(cls.browse(ids), {'extension': 'pdf'})
|
|
ids = cls.search([('id', 'not in', ids)])
|
|
cls.write(cls.browse(ids), {'extension': 'odt'})
|
|
|
|
table.drop_column("output_format")
|
|
TableHandler.dropTable(cursor, 'ir.action.report.outputformat',
|
|
'ir_action_report_outputformat')
|
|
|
|
# Migrate from 2.0 remove required on extension
|
|
table.not_null_action('extension', action='remove')
|
|
cursor.execute('UPDATE "' + cls._table + '" '
|
|
'SET extension = %s '
|
|
'WHERE extension = %s', ('', 'odt'))
|
|
|
|
# Migration from 2.0 report_content_data renamed into
|
|
# report_content_custom to remove base64 encoding
|
|
if (table.column_exist('report_content_data')
|
|
and table.column_exist('report_content_custom')):
|
|
limit = cursor.IN_MAX
|
|
cursor.execute('SELECT COUNT(id) '
|
|
'FROM "' + cls._table + '"')
|
|
report_count, = cursor.fetchone()
|
|
for offset in range(0, report_count, limit):
|
|
cursor.execute(cursor.limit_clause(
|
|
'SELECT id, report_content_data '
|
|
'FROM "' + cls._table + '"'
|
|
'ORDER BY id',
|
|
limit, offset))
|
|
for report_id, report in cursor.fetchall():
|
|
if report:
|
|
report = buffer(base64.decodestring(str(report)))
|
|
cursor.execute('UPDATE "' + cls._table + '" '
|
|
'SET report_content_custom = %s '
|
|
'WHERE id = %s', (report, report_id))
|
|
table.drop_column('report_content_data')
|
|
|
|
@staticmethod
|
|
def default_type():
|
|
return 'ir.action.report'
|
|
|
|
@staticmethod
|
|
def default_report_content():
|
|
return None
|
|
|
|
@staticmethod
|
|
def default_direct_print():
|
|
return False
|
|
|
|
@staticmethod
|
|
def default_template_extension():
|
|
return 'odt'
|
|
|
|
@staticmethod
|
|
def default_extension():
|
|
return ''
|
|
|
|
@staticmethod
|
|
def default_module():
|
|
return Transaction().context.get('module') or ''
|
|
|
|
@classmethod
|
|
def validate(cls, reports):
|
|
super(ActionReport, cls).validate(reports)
|
|
cls.check_email(reports)
|
|
|
|
@classmethod
|
|
def check_email(cls, reports):
|
|
"Check email"
|
|
for report in reports:
|
|
if report.email:
|
|
try:
|
|
value = safe_eval(report.email, CONTEXT)
|
|
except Exception:
|
|
value = None
|
|
if isinstance(value, dict):
|
|
inkeys = set(value)
|
|
if not inkeys <= EMAIL_REFKEYS:
|
|
cls.raise_user_error('invalid_email', (
|
|
report.rec_name,))
|
|
else:
|
|
cls.raise_user_error('invalid_email', (report.rec_name,))
|
|
|
|
@classmethod
|
|
def get_report_content(cls, reports, name):
|
|
contents = {}
|
|
converter = buffer
|
|
default = None
|
|
format_ = Transaction().context.pop('%s.%s'
|
|
% (cls.__name__, name), '')
|
|
if format_ == 'size':
|
|
converter = len
|
|
default = 0
|
|
for report in reports:
|
|
data = getattr(report, name + '_custom')
|
|
if not data and getattr(report, name[:-8]):
|
|
try:
|
|
with file_open(
|
|
getattr(report, name[:-8]).replace('/', os.sep),
|
|
mode='rb') as fp:
|
|
data = fp.read()
|
|
except Exception:
|
|
data = None
|
|
contents[report.id] = converter(data) if data else default
|
|
return contents
|
|
|
|
@classmethod
|
|
def set_report_content(cls, records, name, value):
|
|
cls.write(records, {'%s_custom' % name: value})
|
|
|
|
@classmethod
|
|
def get_style_content(cls, reports, name):
|
|
contents = {}
|
|
converter = buffer
|
|
default = None
|
|
format_ = Transaction().context.pop('%s.%s'
|
|
% (cls.__name__, name), '')
|
|
if format_ == 'size':
|
|
converter = len
|
|
default = 0
|
|
for report in reports:
|
|
try:
|
|
with file_open(report.style.replace('/', os.sep),
|
|
mode='rb') as fp:
|
|
data = fp.read()
|
|
except Exception:
|
|
data = None
|
|
contents[report.id] = converter(data) if data else default
|
|
return contents
|
|
|
|
@classmethod
|
|
def get_pyson(cls, reports, name):
|
|
pysons = {}
|
|
encoder = PYSONEncoder()
|
|
field = name[6:]
|
|
defaults = {
|
|
'email': '{}',
|
|
}
|
|
for report in reports:
|
|
pysons[report.id] = encoder.encode(safe_eval(getattr(report, field)
|
|
or defaults.get(field, 'None'), CONTEXT))
|
|
return pysons
|
|
|
|
@classmethod
|
|
def copy(cls, reports, default=None):
|
|
Action = Pool().get('ir.action')
|
|
|
|
if default is None:
|
|
default = {}
|
|
default = default.copy()
|
|
default.setdefault('module', None)
|
|
|
|
new_reports = []
|
|
for report in reports:
|
|
if report.report:
|
|
default['report_content'] = None
|
|
default['report_name'] = report.report_name
|
|
default['action'] = Action.copy([report.action])[0].id
|
|
new_reports.extend(super(ActionReport, cls).copy([report],
|
|
default=default))
|
|
return new_reports
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
new_vlist = []
|
|
for vals in vlist:
|
|
later = {}
|
|
vals = vals.copy()
|
|
for field in vals:
|
|
if (field in cls._fields
|
|
and hasattr(cls._fields[field], 'set')):
|
|
later[field] = vals[field]
|
|
for field in later:
|
|
del vals[field]
|
|
cursor = Transaction().cursor
|
|
if cursor.nextid(cls._table):
|
|
cursor.setnextid(cls._table, cursor.currid('ir_action'))
|
|
report, = super(ActionReport, cls).create([vals])
|
|
cursor.execute('SELECT action FROM "' + cls._table + '" '
|
|
'WHERE id = %s', (report.id,))
|
|
new_id, = cursor.fetchone()
|
|
cursor.execute('UPDATE "' + cls._table + '" SET id = %s '
|
|
'WHERE id = %s', (new_id, report.id))
|
|
cursor.update_auto_increment(cls._table, new_id)
|
|
report = cls(new_id)
|
|
new_vlist.append(report)
|
|
cls.write([report], later)
|
|
return new_vlist
|
|
|
|
@classmethod
|
|
def write(cls, reports, vals):
|
|
pool = Pool()
|
|
context = Transaction().context
|
|
if 'module' in context:
|
|
vals = vals.copy()
|
|
vals['module'] = context['module']
|
|
|
|
super(ActionReport, cls).write(reports, vals)
|
|
pool.get('ir.action.keyword')._get_keyword_cache.clear()
|
|
|
|
@classmethod
|
|
def delete(cls, reports):
|
|
Action = Pool().get('ir.action')
|
|
|
|
actions = [x.action for x in reports]
|
|
super(ActionReport, cls).delete(reports)
|
|
Action.delete(actions)
|
|
|
|
|
|
class ActionActWindow(ModelSQL, ModelView):
|
|
"Action act window"
|
|
__name__ = 'ir.action.act_window'
|
|
_inherits = {'ir.action': 'action'}
|
|
domain = fields.Char('Domain Value')
|
|
context = fields.Char('Context Value')
|
|
res_model = fields.Char('Model')
|
|
act_window_views = fields.One2Many('ir.action.act_window.view',
|
|
'act_window', 'Views')
|
|
views = fields.Function(fields.Binary('Views'), 'get_views')
|
|
act_window_domains = fields.One2Many('ir.action.act_window.domain',
|
|
'act_window', 'Domains')
|
|
domains = fields.Function(fields.Binary('Domains'), 'get_domains')
|
|
limit = fields.Integer('Limit', required=True,
|
|
help='Default limit for the list view')
|
|
auto_refresh = fields.Integer('Auto-Refresh', required=True,
|
|
help='Add an auto-refresh on the view')
|
|
action = fields.Many2One('ir.action', 'Action', required=True,
|
|
ondelete='CASCADE')
|
|
window_name = fields.Boolean('Window Name',
|
|
help='Use the action name as window name')
|
|
search_value = fields.Char('Search Criteria',
|
|
help='Default search criteria for the list view')
|
|
pyson_domain = fields.Function(fields.Char('PySON Domain'), 'get_pyson')
|
|
pyson_context = fields.Function(fields.Char('PySON Context'),
|
|
'get_pyson')
|
|
pyson_search_value = fields.Function(fields.Char(
|
|
'PySON Search Criteria'), 'get_pyson')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(ActionActWindow, cls).__setup__()
|
|
cls._error_messages.update({
|
|
'invalid_views': ('Invalid view "%(view)s" for action '
|
|
'"%(action)s".'),
|
|
'invalid_domain': ('Invalid domain or search criteria '
|
|
'"%(domain)s" on action "%(action)s".'),
|
|
'invalid_context': ('Invalid context "%(context)s" on action '
|
|
'"%(action)s".'),
|
|
})
|
|
cls.__rpc__.update({
|
|
'get': RPC(),
|
|
})
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
cursor = Transaction().cursor
|
|
super(ActionActWindow, cls).__register__(module_name)
|
|
|
|
# Migration from 2.0: new search_value format
|
|
cursor.execute('UPDATE "%s" '
|
|
'SET search_value = %%s '
|
|
'WHERE search_value = %%s' % cls._table,
|
|
('[]', '{}'))
|
|
|
|
@staticmethod
|
|
def default_type():
|
|
return 'ir.action.act_window'
|
|
|
|
@staticmethod
|
|
def default_context():
|
|
return '{}'
|
|
|
|
@staticmethod
|
|
def default_limit():
|
|
return 0
|
|
|
|
@staticmethod
|
|
def default_auto_refresh():
|
|
return 0
|
|
|
|
@staticmethod
|
|
def default_window_name():
|
|
return True
|
|
|
|
@staticmethod
|
|
def default_search_value():
|
|
return '[]'
|
|
|
|
@classmethod
|
|
def validate(cls, actions):
|
|
super(ActionActWindow, cls).validate(actions)
|
|
cls.check_views(actions)
|
|
cls.check_domain(actions)
|
|
cls.check_context(actions)
|
|
|
|
@classmethod
|
|
def check_views(cls, actions):
|
|
"Check views"
|
|
for action in actions:
|
|
if action.res_model:
|
|
for act_window_view in action.act_window_views:
|
|
view = act_window_view.view
|
|
if view.model != action.res_model:
|
|
cls.raise_user_error('invalid_views', {
|
|
'view': view.rec_name,
|
|
'action': action.rec_name,
|
|
})
|
|
if view.type == 'board':
|
|
cls.raise_user_error('invalid_views', {
|
|
'view': view.rec_name,
|
|
'action': action.rec_name,
|
|
})
|
|
else:
|
|
for act_window_view in action.act_window_views:
|
|
view = act_window_view.view
|
|
if view.model:
|
|
cls.raise_user_error('invalid_views', {
|
|
'view': view.rec_name,
|
|
'action': action.rec_name,
|
|
})
|
|
if view.type != 'board':
|
|
cls.raise_user_error('invalid_views', {
|
|
'view': view.rec_name,
|
|
'action': action.rec_name,
|
|
})
|
|
|
|
@classmethod
|
|
def check_domain(cls, actions):
|
|
"Check domain and search_value"
|
|
for action in actions:
|
|
for domain in (action.domain, action.search_value):
|
|
if not domain:
|
|
continue
|
|
try:
|
|
value = safe_eval(domain, CONTEXT)
|
|
except Exception:
|
|
cls.raise_user_error('invalid_domain', {
|
|
'domain': domain,
|
|
'action': action.rec_name,
|
|
})
|
|
if isinstance(value, PYSON):
|
|
if not value.types() == set([list]):
|
|
cls.raise_user_error('invalid_domain', {
|
|
'domain': domain,
|
|
'action': action.rec_name,
|
|
})
|
|
elif not isinstance(value, list):
|
|
cls.raise_user_error('invalid_domain', {
|
|
'domain': domain,
|
|
'action': action.rec_name,
|
|
})
|
|
else:
|
|
try:
|
|
fields.domain_validate(value)
|
|
except Exception:
|
|
cls.raise_user_error('invalid_domain', {
|
|
'domain': domain,
|
|
'action': action.rec_name,
|
|
})
|
|
|
|
@classmethod
|
|
def check_context(cls, actions):
|
|
"Check context"
|
|
for action in actions:
|
|
if action.context:
|
|
try:
|
|
value = safe_eval(action.context, CONTEXT)
|
|
except Exception:
|
|
cls.raise_user_error('invalid_context', {
|
|
'context': action.context,
|
|
'action': action.rec_name,
|
|
})
|
|
if isinstance(value, PYSON):
|
|
if not value.types() == set([dict]):
|
|
cls.raise_user_error('invalid_context', {
|
|
'context': action.context,
|
|
'action': action.rec_name,
|
|
})
|
|
elif not isinstance(value, dict):
|
|
cls.raise_user_error('invalid_context', {
|
|
'context': action.context,
|
|
'action': action.rec_name,
|
|
})
|
|
else:
|
|
try:
|
|
fields.context_validate(value)
|
|
except Exception:
|
|
cls.raise_user_error('invalid_context', {
|
|
'context': action.context,
|
|
'action': action.rec_name,
|
|
})
|
|
|
|
def get_views(self, name):
|
|
return [(view.view.id, view.view.type)
|
|
for view in self.act_window_views]
|
|
|
|
def get_domains(self, name):
|
|
encoder = PYSONEncoder()
|
|
return [(domain.name,
|
|
encoder.encode(safe_eval(domain.domain or '[]', CONTEXT)))
|
|
for domain in self.act_window_domains]
|
|
|
|
@classmethod
|
|
def get_pyson(cls, windows, name):
|
|
pysons = {}
|
|
encoder = PYSONEncoder()
|
|
field = name[6:]
|
|
defaults = {
|
|
'domain': '[]',
|
|
'context': '{}',
|
|
'search_value': '{}',
|
|
}
|
|
for window in windows:
|
|
pysons[window.id] = encoder.encode(safe_eval(getattr(window, field)
|
|
or defaults.get(field, 'None'), CONTEXT))
|
|
return pysons
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
new_vlist = []
|
|
for vals in vlist:
|
|
later = {}
|
|
vals = vals.copy()
|
|
for field in vals:
|
|
if (field in cls._fields
|
|
and hasattr(cls._fields[field], 'set')):
|
|
later[field] = vals[field]
|
|
for field in later:
|
|
del vals[field]
|
|
cursor = Transaction().cursor
|
|
if cursor.nextid(cls._table):
|
|
cursor.setnextid(cls._table, cursor.currid('ir_action'))
|
|
act_window, = super(ActionActWindow, cls).create([vals])
|
|
cursor.execute('SELECT action FROM "' + cls._table + '" '
|
|
'WHERE id = %s', (act_window.id,))
|
|
new_id, = cursor.fetchone()
|
|
cursor.execute('UPDATE "' + cls._table + '" SET id = %s '
|
|
'WHERE id = %s', (new_id, act_window.id))
|
|
cursor.update_auto_increment(cls._table, new_id)
|
|
act_window = cls(new_id)
|
|
new_vlist.append(act_window)
|
|
cls.write([act_window], later)
|
|
return new_vlist
|
|
|
|
@classmethod
|
|
def write(cls, act_windows, values):
|
|
pool = Pool()
|
|
super(ActionActWindow, cls).write(act_windows, values)
|
|
pool.get('ir.action.keyword')._get_keyword_cache.clear()
|
|
|
|
@classmethod
|
|
def delete(cls, act_windows):
|
|
Action = Pool().get('ir.action')
|
|
|
|
actions = [x.action for x in act_windows]
|
|
super(ActionActWindow, cls).delete(act_windows)
|
|
Action.delete(actions)
|
|
|
|
@classmethod
|
|
def get(cls, xml_id):
|
|
'Get values from XML id or id'
|
|
pool = Pool()
|
|
ModelData = pool.get('ir.model.data')
|
|
Action = pool.get('ir.action')
|
|
if '.' in xml_id:
|
|
action_id = ModelData.get_id(*xml_id.split('.'))
|
|
else:
|
|
action_id = int(xml_id)
|
|
return Action.get_action_values(cls.__name__, [action_id])[0]
|
|
|
|
@classmethod
|
|
def copy(cls, actions, default=None):
|
|
Action = Pool().get('ir.action')
|
|
if default is None:
|
|
default = {}
|
|
default = default.copy()
|
|
new_actions = []
|
|
for act_window in actions:
|
|
default['action'] = Action.copy([act_window.action])[0].id
|
|
new_actions.extend(super(ActionActWindow, cls).copy([act_window],
|
|
default=default))
|
|
return new_actions
|
|
|
|
|
|
class ActionActWindowView(ModelSQL, ModelView):
|
|
"Action act window view"
|
|
__name__ = 'ir.action.act_window.view'
|
|
_rec_name = 'view'
|
|
sequence = fields.Integer('Sequence', required=True)
|
|
view = fields.Many2One('ir.ui.view', 'View', required=True,
|
|
ondelete='CASCADE')
|
|
act_window = fields.Many2One('ir.action.act_window', 'Action',
|
|
ondelete='CASCADE')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(ActionActWindowView, cls).__setup__()
|
|
cls._order.insert(0, ('sequence', 'ASC'))
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
super(ActionActWindowView, cls).__register__(module_name)
|
|
table = TableHandler(Transaction().cursor, cls, module_name)
|
|
|
|
# Migration from 1.0 remove multi
|
|
table.drop_column('multi')
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
pool = Pool()
|
|
windows = super(ActionActWindowView, cls).create(vlist)
|
|
pool.get('ir.action.keyword')._get_keyword_cache.clear()
|
|
return windows
|
|
|
|
@classmethod
|
|
def write(cls, windows, values):
|
|
pool = Pool()
|
|
super(ActionActWindowView, cls).write(windows, values)
|
|
pool.get('ir.action.keyword')._get_keyword_cache.clear()
|
|
|
|
@classmethod
|
|
def delete(cls, windows):
|
|
pool = Pool()
|
|
super(ActionActWindowView, cls).delete(windows)
|
|
pool.get('ir.action.keyword')._get_keyword_cache.clear()
|
|
|
|
|
|
class ActionActWindowDomain(ModelSQL, ModelView):
|
|
"Action act window domain"
|
|
__name__ = 'ir.action.act_window.domain'
|
|
name = fields.Char('Name', translate=True)
|
|
sequence = fields.Integer('Sequence', required=True)
|
|
domain = fields.Char('Domain')
|
|
act_window = fields.Many2One('ir.action.act_window', 'Action',
|
|
select=True, required=True, ondelete='CASCADE')
|
|
active = fields.Boolean('Active')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(ActionActWindowDomain, cls).__setup__()
|
|
cls._order.insert(0, ('sequence', 'ASC'))
|
|
|
|
@staticmethod
|
|
def default_active():
|
|
return True
|
|
|
|
|
|
class ActionWizard(ModelSQL, ModelView):
|
|
"Action wizard"
|
|
__name__ = 'ir.action.wizard'
|
|
_inherits = {'ir.action': 'action'}
|
|
wiz_name = fields.Char('Wizard name', required=True)
|
|
action = fields.Many2One('ir.action', 'Action', required=True,
|
|
ondelete='CASCADE')
|
|
model = fields.Char('Model')
|
|
email = fields.Char('Email')
|
|
window = fields.Boolean('Window', help='Run wizard in a new window')
|
|
|
|
@staticmethod
|
|
def default_type():
|
|
return 'ir.action.wizard'
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
new_vlist = []
|
|
for vals in vlist:
|
|
later = {}
|
|
vals = vals.copy()
|
|
for field in vals:
|
|
if (field in cls._fields
|
|
and hasattr(cls._fields[field], 'set')):
|
|
later[field] = vals[field]
|
|
for field in later:
|
|
del vals[field]
|
|
cursor = Transaction().cursor
|
|
if cursor.nextid(cls._table):
|
|
cursor.setnextid(cls._table, cursor.currid('ir_action'))
|
|
wizard, = super(ActionWizard, cls).create([vals])
|
|
cursor.execute('SELECT action FROM "' + cls._table + '" '
|
|
'WHERE id = %s', (wizard.id,))
|
|
new_id, = cursor.fetchone()
|
|
cursor.execute('UPDATE "' + cls._table + '" SET id = %s '
|
|
'WHERE id = %s', (new_id, wizard.id))
|
|
cursor.update_auto_increment(cls._table, new_id)
|
|
wizard = cls(new_id)
|
|
new_vlist.append(wizard)
|
|
cls.write([wizard], later)
|
|
return new_vlist
|
|
|
|
@classmethod
|
|
def write(cls, wizards, values):
|
|
pool = Pool()
|
|
super(ActionWizard, cls).write(wizards, values)
|
|
pool.get('ir.action.keyword')._get_keyword_cache.clear()
|
|
|
|
@classmethod
|
|
def delete(cls, wizards):
|
|
pool = Pool()
|
|
Action = pool.get('ir.action')
|
|
|
|
actions = [x.action for x in wizards]
|
|
|
|
super(ActionWizard, cls).delete(wizards)
|
|
Action.delete(actions)
|
|
|
|
@classmethod
|
|
def copy(cls, wizards, default=None):
|
|
Action = Pool().get('ir.action')
|
|
|
|
if default is None:
|
|
default = {}
|
|
default = default.copy()
|
|
new_wizards = []
|
|
for wizard in wizards:
|
|
default['action'] = Action.copy([wizard.action])[0].id
|
|
new_wizards.extend(super(ActionWizard, cls).copy([wizard],
|
|
default=default))
|
|
return new_wizards
|
|
|
|
|
|
class ActionURL(ModelSQL, ModelView):
|
|
"Action URL"
|
|
__name__ = 'ir.action.url'
|
|
_inherits = {'ir.action': 'action'}
|
|
url = fields.Char('Action Url', required=True)
|
|
action = fields.Many2One('ir.action', 'Action', required=True,
|
|
ondelete='CASCADE')
|
|
|
|
@staticmethod
|
|
def default_type():
|
|
return 'ir.action.url'
|
|
|
|
@staticmethod
|
|
def default_target():
|
|
return 'new'
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
new_vlist = []
|
|
for vals in vlist:
|
|
later = {}
|
|
vals = vals.copy()
|
|
for field in vals:
|
|
if (field in cls._fields
|
|
and hasattr(cls._fields[field], 'set')):
|
|
later[field] = vals[field]
|
|
for field in later:
|
|
del vals[field]
|
|
cursor = Transaction().cursor
|
|
if cursor.nextid(cls._table):
|
|
cursor.setnextid(cls._table, cursor.currid('ir_action'))
|
|
url, = super(ActionURL, cls).create([vals])
|
|
cursor.execute('SELECT action FROM "' + cls._table + '" '
|
|
'WHERE id = %s', (url.id,))
|
|
new_id, = cursor.fetchone()
|
|
cls.write([url], {}) # simulate write to clear the cache
|
|
cursor.execute('UPDATE "' + cls._table + '" SET id = %s '
|
|
'WHERE id = %s', (new_id, url.id))
|
|
cursor.update_auto_increment(cls._table, new_id)
|
|
url = cls(new_id)
|
|
new_vlist.append(url)
|
|
cls.write([url], later)
|
|
return new_vlist
|
|
|
|
@classmethod
|
|
def write(cls, urls, values):
|
|
pool = Pool()
|
|
super(ActionURL, cls).write(urls, values)
|
|
pool.get('ir.action.keyword')._get_keyword_cache.clear()
|
|
|
|
@classmethod
|
|
def delete(cls, urls):
|
|
pool = Pool()
|
|
Action = pool.get('ir.action')
|
|
|
|
actions = [x.action for x in urls]
|
|
|
|
super(ActionURL, cls).delete(urls)
|
|
Action.delete(actions)
|
|
|
|
@classmethod
|
|
def copy(cls, urls, default=None):
|
|
Action = Pool().get('ir.action')
|
|
|
|
if default is None:
|
|
default = {}
|
|
default = default.copy()
|
|
new_urls = []
|
|
for url in urls:
|
|
default['action'] = Action.copy([url.action])[0].id
|
|
new_urls.extend(super(ActionURL, cls).copy([url],
|
|
default=default))
|
|
return new_urls
|