Files
tryton/trytond/wizard/wizard.py
T
Cédric Krier 75931c4bf8 Set session state dirty if updated
When updating session with data from the client, the states must be mark as
dirty to be saved in the database at the end of the process.

issue2421
2012-02-07 17:35:21 +01:00

366 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 = {}
self.dirty = False
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
self.dirty = True
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]))
@property
def dirty(self):
return any(getattr(self, state_name).dirty
for state_name in self.data)
def save(self):
"Save the session in database"
session_obj = Pool().get('ir.session.wizard')
if self.dirty:
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():
prev_data = session.data[key].copy()
session.data[key].update(value)
if prev_data != session.data[key]:
getattr(session, key).dirty = True
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