355 lines
12 KiB
Python
355 lines
12 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.
|
|
|
|
__all__ = ['Wizard', 'StateView', 'StateTransition', 'StateAction', 'Button',
|
|
'Session']
|
|
|
|
try:
|
|
import simplejson as json
|
|
except ImportError:
|
|
import json
|
|
|
|
from trytond.pool import Pool
|
|
from trytond.transaction import Transaction
|
|
from trytond.error import WarningErrorMixin
|
|
from trytond.url import URLMixin
|
|
from trytond.protocols.jsonrpc import object_hook, JSONEncoder
|
|
from trytond.model.fields import states_validate
|
|
from trytond.pyson import PYSONEncoder
|
|
|
|
|
|
class Button(object):
|
|
'''
|
|
Define a button on wizard.
|
|
'''
|
|
|
|
def __init__(self, string, state, icon='', default=False, states=None):
|
|
self.string = string
|
|
self.state = state
|
|
self.icon = icon
|
|
self.default = bool(default)
|
|
self.__states = None
|
|
self.states = states or {}
|
|
|
|
@property
|
|
def states(self):
|
|
return self.__states
|
|
|
|
@states.setter
|
|
def states(self, value):
|
|
states_validate(value)
|
|
self.__states = value
|
|
|
|
|
|
class State(object):
|
|
'''
|
|
A State of a wizard.
|
|
'''
|
|
|
|
|
|
class StateView(State):
|
|
'''
|
|
A view state of a wizard.
|
|
'''
|
|
|
|
def __init__(self, model_name, view, buttons):
|
|
'''
|
|
model_name is the name of the model
|
|
view is the xml id of the view
|
|
buttons is a list of Button
|
|
'''
|
|
self.model_name = model_name
|
|
self.view = view
|
|
self.buttons = buttons
|
|
assert len(self.buttons) == len(set(b.state for b in self.buttons))
|
|
assert len([b for b in self.buttons if b.default]) <= 1
|
|
|
|
def get_view(self):
|
|
'''
|
|
Returns the view definition
|
|
'''
|
|
model_obj = Pool().get(self.model_name)
|
|
model_data_obj = Pool().get('ir.model.data')
|
|
module, fs_id = self.view.split('.')
|
|
view_id = model_data_obj.get_id(module, fs_id)
|
|
return model_obj.fields_view_get(view_id=view_id, view_type='form')
|
|
|
|
def get_defaults(self, wizard, session, state_name, fields):
|
|
'''
|
|
Returns defaults values for the fields
|
|
'''
|
|
model_obj = Pool().get(self.model_name)
|
|
defaults = model_obj.default_get(fields)
|
|
default = getattr(wizard, 'default_%s' % state_name, None)
|
|
if default:
|
|
defaults.update(default(session, fields))
|
|
return defaults
|
|
|
|
def get_buttons(self, wizard, state_name):
|
|
'''
|
|
Returns button definitions translated
|
|
'''
|
|
translation_obj = Pool().get('ir.translation')
|
|
def translation_key(button):
|
|
return (','.join((wizard._name, state_name, button.state)),
|
|
'wizard_button', Transaction().language, button.string)
|
|
translation_keys = [translation_key(button) for button in self.buttons]
|
|
translations = translation_obj._get_sources(translation_keys)
|
|
encoder = PYSONEncoder()
|
|
result = []
|
|
for button in self.buttons:
|
|
result.append({
|
|
'state': button.state,
|
|
'icon': button.icon,
|
|
'default': button.default,
|
|
'string': (translations.get(translation_key(button))
|
|
or button.string),
|
|
'states': encoder.encode(button.states),
|
|
})
|
|
return result
|
|
|
|
|
|
class StateTransition(State):
|
|
'''
|
|
A transition state of a wizard.
|
|
'''
|
|
|
|
class StateAction(StateTransition):
|
|
'''
|
|
An action state of a wizard.
|
|
'''
|
|
|
|
def __init__(self, action_id):
|
|
'''
|
|
action_id is a string containing ``module.xml_id``
|
|
'''
|
|
super(StateAction, self).__init__()
|
|
self.action_id = action_id
|
|
|
|
def get_action(self):
|
|
"Returns action definition"
|
|
pool = Pool()
|
|
model_data_obj = pool.get('ir.model.data')
|
|
action_obj = pool.get('ir.action')
|
|
module, fs_id = self.action_id.split('.')
|
|
action_id = action_obj.get_action_id(
|
|
model_data_obj.get_id(module, fs_id))
|
|
action = action_obj.browse(action_id)
|
|
return action_obj.get_action_values(action.type, action.id)
|
|
|
|
|
|
class _SessionRecord(object):
|
|
'''
|
|
A record of a wizard form.
|
|
'''
|
|
# Declared in class to prevent:
|
|
# 'maximum recursion depth exceeded in __subclasscheck__'
|
|
_model = None
|
|
_data = None
|
|
__cache = None
|
|
|
|
def __init__(self, model, data):
|
|
self._model = model
|
|
self._data = data
|
|
self.__cache = {}
|
|
|
|
def __getattr__(self, name):
|
|
if name in self.__cache:
|
|
return self.__cache[name]
|
|
field = self._model._columns[name]
|
|
data = self._data.get(name, False)
|
|
if data:
|
|
target_obj = None
|
|
if hasattr(field, 'model_name'):
|
|
target_obj = Pool().get(field.model_name)
|
|
elif hasattr(field, 'get_target'):
|
|
target_obj = field.get_target()
|
|
if target_obj:
|
|
def instance(data):
|
|
if isinstance(data, dict):
|
|
return _SessionRecord(target_obj, data)
|
|
return target_obj.browse(data)
|
|
if isinstance(data, list):
|
|
data = [instance(x) for x in data]
|
|
else:
|
|
data = instance(data)
|
|
self.__cache[name] = data
|
|
return data
|
|
|
|
def __setattr__(self, name, value):
|
|
if (self._model is not None
|
|
and (name in self._model._columns
|
|
or name in self._model._inherit_fields)):
|
|
self.__cache.pop(name, None)
|
|
self._data[name] = value
|
|
else:
|
|
super(_SessionRecord, self).__setattr__(name, value)
|
|
|
|
|
|
class Session(object):
|
|
'''
|
|
A wizard session.
|
|
'''
|
|
|
|
def __init__(self, wizard, session_id):
|
|
pool = Pool()
|
|
session_obj = pool.get('ir.session.wizard')
|
|
self._session = session_obj.browse(session_id)
|
|
self.data = json.loads(self._session.data.encode('utf-8'),
|
|
object_hook=object_hook)
|
|
for state_name, state in wizard.states.iteritems():
|
|
if isinstance(state, StateView):
|
|
model = pool.get(state.model_name)
|
|
self.data.setdefault(state_name, {})
|
|
setattr(self, state_name,
|
|
_SessionRecord(model, self.data[state_name]))
|
|
|
|
def save(self):
|
|
"Save the session in database"
|
|
session_obj = Pool().get('ir.session.wizard')
|
|
session_obj.write(self._session.id, {
|
|
'data': json.dumps(self.data, cls=JSONEncoder),
|
|
})
|
|
|
|
|
|
class Wizard(WarningErrorMixin, URLMixin):
|
|
_name = ""
|
|
start_state = 'start'
|
|
end_state = 'end'
|
|
|
|
def __new__(cls):
|
|
Pool.register(cls, type='wizard')
|
|
|
|
def __init__(self):
|
|
super(Wizard, self).__init__()
|
|
self._rpc = {
|
|
'create': True,
|
|
'delete': True,
|
|
'execute': True,
|
|
}
|
|
self._error_messages = {}
|
|
|
|
@property
|
|
def states(self):
|
|
result = {}
|
|
for attr in dir(self):
|
|
if attr == 'states':
|
|
continue
|
|
if isinstance(getattr(self, attr), State):
|
|
result[attr] = getattr(self, attr)
|
|
return result
|
|
|
|
def init(self, module_name):
|
|
pool = Pool()
|
|
translation_obj = pool.get('ir.translation')
|
|
cursor = Transaction().cursor
|
|
for state_name, state in self.states.iteritems():
|
|
if isinstance(state, StateView):
|
|
for button in state.buttons:
|
|
cursor.execute('SELECT id, name, src '
|
|
'FROM ir_translation '
|
|
'WHERE module = %s '
|
|
'AND lang = %s '
|
|
'AND type = %s '
|
|
'AND name = %s',
|
|
(module_name, 'en_US', 'wizard_button',
|
|
self._name + ',' + state_name + ',' +
|
|
button.state))
|
|
res = cursor.dictfetchall()
|
|
src_md5 = translation_obj.get_src_md5(button.string)
|
|
if not res:
|
|
cursor.execute('INSERT INTO ir_translation '
|
|
'(name, lang, type, src, src_md5, value, module, '
|
|
'fuzzy) '
|
|
'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)',
|
|
(self._name + ',' + state_name + ',' +
|
|
button.state,
|
|
'en_US', 'wizard_button', button.string,
|
|
src_md5, '', module_name, False))
|
|
elif res[0]['src'] != button.string:
|
|
cursor.execute('UPDATE ir_translation '
|
|
'SET src = %s, src_md5 = %s '
|
|
'WHERE id = %s',
|
|
(button.string, src_md5, res[0]['id']))
|
|
|
|
cursor.execute('SELECT id, src FROM ir_translation '
|
|
'WHERE lang = %s '
|
|
'AND type = %s '
|
|
'AND name = %s',
|
|
('en_US', 'error', self._name))
|
|
trans_error = {}
|
|
for trans in cursor.dictfetchall():
|
|
trans_error[trans['src']] = trans
|
|
|
|
for error in self._error_messages.values():
|
|
if error not in trans_error:
|
|
error_md5 = translation_obj.get_src_md5(error)
|
|
cursor.execute('INSERT INTO ir_translation '
|
|
'(name, lang, type, src, src_md5, value, module, fuzzy) '
|
|
'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)',
|
|
(self._name, 'en_US', 'error', error, error_md5, '',
|
|
module_name, False))
|
|
|
|
def create(self):
|
|
"Create a session"
|
|
session_obj = Pool().get('ir.session.wizard')
|
|
return (session_obj.create({}), self.start_state, self.end_state)
|
|
|
|
def delete(self, session_id):
|
|
"Delete the session"
|
|
session_obj = Pool().get('ir.session.wizard')
|
|
session_obj.delete(session_id)
|
|
|
|
def execute(self, session, data, state_name):
|
|
'''
|
|
Execute the wizard state.
|
|
|
|
session is a Session or a Session id
|
|
data is a dictionary with the session data to update
|
|
state_name is the name of state to execute
|
|
|
|
Returns a dictionary with:
|
|
- ``actions``: a list of Action to execute
|
|
- ``view``: a dictionary with:
|
|
- ``fields_view``: a fields/view definition
|
|
- ``defaults``: a dictionary with default values
|
|
- ``buttons``: a list of buttons
|
|
'''
|
|
if state_name == self.end_state:
|
|
return {}
|
|
if isinstance(session, (int, long)):
|
|
session = Session(self, session)
|
|
for key, value in data.iteritems():
|
|
session.data[key].update(value)
|
|
|
|
state = self.states[state_name]
|
|
result = {}
|
|
|
|
if isinstance(state, StateView):
|
|
view = state.get_view()
|
|
defaults = state.get_defaults(self, session, state_name,
|
|
view['fields'].keys())
|
|
buttons = state.get_buttons(self, state_name)
|
|
result['view'] = {
|
|
'fields_view': view,
|
|
'defaults': defaults,
|
|
'buttons': buttons,
|
|
'state': state_name,
|
|
}
|
|
elif isinstance(state, StateTransition):
|
|
do_result = None
|
|
if isinstance(state, StateAction):
|
|
action = state.get_action()
|
|
do = getattr(self, 'do_%s' % state_name, None)
|
|
if do:
|
|
do_result = do(session, action)
|
|
else:
|
|
do_result = action, {}
|
|
transition = getattr(self, 'transition_%s' % state_name, None)
|
|
if transition:
|
|
result = self.execute(session, {}, transition(session))
|
|
if do_result:
|
|
result.setdefault('actions', []).append(do_result)
|
|
session.save()
|
|
return result
|