New wizard design

review167001
This commit is contained in:
Cédric Krier
2012-01-02 17:48:17 +01:00
parent 1712b0ffe8
commit 274ea7bee2
23 changed files with 1145 additions and 633 deletions
+1
View File
@@ -1,3 +1,4 @@
* New Wizard design
* Add pyson.Id
* Use XML-RPC struct for Decimal and Date
* Remove change_default on fields
+1
View File
@@ -8,6 +8,7 @@ API Reference
:maxdepth: 1
models/index
wizard
pyson
transaction
tools/singleton
+200
View File
@@ -0,0 +1,200 @@
.. _ref-wizard:
.. module:: trytond.wizard
======
Wizard
======
A wizard is a `finite state machine`_.
There is also a more :ref:`practical introduction into wizards
<topics-wizard>`.
.. _`finite state machine`: http://en.wikipedia.org/wiki/Finite-state_machine
.. class:: Wizard()
This is the base for any wizard. It contains the engine for the finite
state machine. A wizard must have some :class:`State` instance attributes
that the engine will use.
Class attributes are:
.. attribute:: Wizard._name
It contains the unique name to reference the wizard throughout the
platform.
.. attribute:: Wizard.start_state
It contains the name of the starting state.
.. attribute:: Wizard.end_state
It contains the name of the ending state.
.. attribute:: Wizard._rpc
Same as :attribute:`trytond.model.Model._rpc`.
.. attribute:: Wizard.states
It contains a dictionary with state name as key and :class:`State` as
value.
Instance methods are:
.. method:: Wizard.init(module_name)
Register the wizard.
.. method:: Wizard.create()
Create a session for the wizard and returns a tuple containing the session
id, the starting and ending state.
.. method:: Wizard.delete(session_id)
Delete the session.
.. method:: Wizard.execute(session, data, state_name)
Execute the wizard for the state.
`session` can be an instance of :class:`Session` or a session id.
`data` is a dictionary with the session data to update.
`active_id`, `active_ids` and `active_model` must be set in the context
according to the records on which the wizard is run.
=======
Session
=======
.. class:: Session(wizard, session_id)
A wizard session contains values of each :class:`StateView` associated to
the wizard.
Instance attributes are:
.. attribute:: Session.data
Raw storage of session data.
Instance methods are:
.. method:: Session.save()
Save the session in database.
=====
State
=====
.. class:: State()
This is the base for any wizard state.
=========
StateView
=========
.. class:: StateView(model_name, view, buttons)
A :class:`StateView` is a state that will display a form in the client.
The form is defined by the :class:`~trytond.model.ModelView` with the name
`model_name`, the `XML` id in `view` and the `buttons`.
Instance attributes are:
.. attribute:: StateView.model_name
The name of the :class:`~trytond.model.ModelView`.
.. attribute:: StateView.view
The `XML` id of the form view.
.. attribute:: StateView.buttons
The list of :class:`Button` instances to display on the form.
Instance methods are:
.. method:: StateView.get_view
Returns the view definition like
:method:`~trytond.model.ModelView.fields_view_get`.
.. method:: StateView.get_defaults(wizard, session, state_name, fields)
Return default values for the fields.
* wizard is a :class:`Wizard` instance
* session is a :class:`Session` instance
* state_name is the name of the :class:`State`
* fields is the list of field names
.. method:: StateView.get_buttons(wizard, state_name)
Returns button definitions of the wizard.
* wizard is a :class:`Wizard` instance
* state_name is the name of the :class:`StateView` instance
===============
StateTransition
===============
.. class:: StateTransition()
A :class:`StateTransition` brings the wizard to the `state` returned by the
method having the same name as the state but starting with `transition_`.
===========
StateAction
===========
.. class:: StateAction(action_id)
A :class:`StateAction` is a :class:`StateTransition` which let the client
launch an `ir.action`. This action definition can be customized with a
method on wizard having the same name as the state but starting with `do_`.
Instance attributes are:
.. attribute:: StateAction.action_id
The `XML` id of the `ir.action`.
Instance methods are:
.. method:: StateAction.get_action()
Returns the `ir.action` definition.
======
Button
======
.. class:: Button(string, state[, icon[, default]])
A :class:`Button` is a single object containing the definition of a wizard
button.
Instance attributes are:
.. attribute:: Button.string
The label display on the button.
.. attribute:: Button.state
The next state to reach if button is clicked.
.. attribute:: Button.icon
The name of the icon to display on the button.
.. attribute:: Button.default
A boolean to set it as default on the form.
+57
View File
@@ -0,0 +1,57 @@
.. _topics-wizard:
======
Wizard
======
A wizard describes a series of steps defined as :class:`trytond.wizard.State`.
The wizard uses a :class:`trytond.wizard.Session` to store data between states.
The basics:
* Each wizard is a Python class that subclasses
:class:`trytond.wizard.Wizard`.
* The states of the wizard are attributes that are instances of
:class:`trytond.wizard.State`.
Example
=======
This example defines a wizard which export translations::
from trytond.wizard import Wizard, StateView, StateTransition, Button
class TranslationExport(Wizard):
"Export translation"
_name = "ir.translation.export"
start = StateView('ir.translation.export.start',
'ir.translation_export_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Export', 'export', 'tryton-ok', default=True),
])
export = StateTransition()
result = StateView('ir.translation.export.result',
'ir.translation_export_result_view_form', [
Button('Close', 'end', 'tryton-close'),
])
def transition_export(self, session):
pool = Pool()
translation_obj = pool.get('ir.translation')
file_data = translation_obj.translation_export(
session.start.language.code, session.start.module.name)
session.result.file = buffer(file_data)
return 'result'
def default_result(self, session, fields):
return {
'file': session.result.file,
}
TranslationExport()
Instantiating the class registers the wizard class in the framework. Later the
class will be instantiated once per database and stored in the
:ref:`Pool <topics-pool>`.
+1
View File
@@ -15,3 +15,4 @@ from . import module
from .cache import *
from .date import *
from .trigger import *
from .session import *
+25 -17
View File
@@ -60,6 +60,25 @@ class Action(ModelSQL, ModelView):
return action.action.id
return False
def get_action_values(self, type_, action_id):
action_obj = Pool().get(type_)
action_id = action_obj.search([
('action', '=', action_id),
])
if not action_id:
return
columns = set(action_obj._columns.keys()
+ action_obj._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_obj.read(action_id[0], list(columns))
Action()
@@ -170,6 +189,7 @@ class ActionKeyword(ModelSQL, ModelView):
def get_keyword(self, keyword, value):
pool = Pool()
action_obj = pool.get('ir.action')
res = []
model, model_id = value
@@ -186,25 +206,13 @@ class ActionKeyword(ModelSQL, ModelView):
for action_keyword_id in action_keyword_ids:
action_keyword = self.browse(action_keyword_id)
try:
action_obj = pool.get(action_keyword.action.type)
type_ = action_keyword.action.type
except UserError:
continue
action_id = action_obj.search([
('action', '=', action_keyword.action.id),
])
if action_id:
columns = set(action_obj._columns.keys()
+ action_obj._inherit_fields.keys())
columns.add('icon.rec_name')
if action_keyword.action.type == 'ir.action.report':
to_remove = ('report_content_custom', 'report_content',
'style_content')
elif action_keyword.action.type == 'ir.action.act_window':
to_remove = ('domain', 'context', 'search_value')
else:
to_remove = set()
columns.difference_update(to_remove)
res.append(action_obj.read(action_id[0], list(columns)))
values = action_obj.get_action_values(type_,
action_keyword.action.id)
if values:
res.append(values)
return res
ActionKeyword()
+27 -29
View File
@@ -4,7 +4,7 @@ import datetime
import re
from ..model import ModelView, ModelSQL, fields
from ..report import Report
from ..wizard import Wizard
from ..wizard import Wizard, StateView, StateAction, Button
from ..transaction import Transaction
from ..cache import Cache
from ..pool import Pool
@@ -506,9 +506,9 @@ class ModelData(ModelSQL, ModelView):
ModelData()
class PrintModelGraphInit(ModelView):
'Print Model Graph Init'
_name = 'ir.model.print_model_graph.init'
class PrintModelGraphStart(ModelView):
'Print Model Graph'
_name = 'ir.model.print_model_graph.start'
_description = __doc__
level = fields.Integer('Level')
filter = fields.Text('Filter', help="Entering a Python "
@@ -517,30 +517,29 @@ class PrintModelGraphInit(ModelView):
def default_level(self):
return 1
PrintModelGraphInit()
PrintModelGraphStart()
class PrintModelGraph(Wizard):
_name = 'ir.model.print_model_graph'
states = {
'init': {
'result': {
'type': 'form',
'object': 'ir.model.print_model_graph.init',
'state': [
('end', 'Cancel', 'tryton-cancel'),
('print', 'Print', 'tryton-ok', True),
],
},
},
'print': {
'result': {
'type': 'print',
'report': 'ir.model.graph',
'state': 'end',
},
},
}
start = StateView('ir.model.print_model_graph.start',
'ir.print_model_graph_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateAction('ir.report_model_graph')
def transition_print_(self, session):
return 'end'
def do_print_(self, session, action):
return action, {
'id': Transaction().context.get('active_id'),
'ids': Transaction().context.get('active_ids'),
'level': session.start.level,
'filter': session.start.filter,
}
PrintModelGraph()
@@ -548,16 +547,16 @@ PrintModelGraph()
class ModelGraph(Report):
_name = 'ir.model.graph'
def execute(self, ids, datas):
def execute(self, ids, data):
import pydot
pool = Pool()
model_obj = pool.get('ir.model')
action_report_obj = pool.get('ir.action.report')
if not datas['form']['filter']:
if not data['filter']:
filter = None
else:
filter = re.compile(datas['form']['filter'], re.VERBOSE)
filter = re.compile(data['filter'], re.VERBOSE)
action_report_ids = action_report_obj.search([
('report_name', '=', self._name)
])
@@ -570,8 +569,7 @@ class ModelGraph(Report):
graph = pydot.Dot(fontsize="8")
graph.set('center', '1')
graph.set('ratio', 'auto')
self.fill_graph(models, graph, level=datas['form']['level'],
filter=filter)
self.fill_graph(models, graph, level=data['level'], filter=filter)
data = graph.create(prog='dot', format='png')
return ('png', buffer(data), False, action_report.name)
+2 -2
View File
@@ -249,8 +249,8 @@ this repository contains the full copyright notices and license terms. -->
<field name="report_name">ir.model.graph</field>
</record>
<record model="ir.ui.view" id="print_model_graph_init_view_form">
<field name="model">ir.model.print_model_graph.init</field>
<record model="ir.ui.view" id="print_model_graph_start_view_form">
<field name="model">ir.model.print_model_graph.start</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
+100 -125
View File
@@ -4,7 +4,9 @@ import os
from trytond.model import ModelView, ModelSQL, fields
import trytond.tools as tools
from trytond.modules import create_graph, get_module_list
from trytond.wizard import Wizard
from trytond.wizard import Wizard, StateView, Button, StateTransition, \
StateAction
from trytond.backend import Database, TableHandler
from trytond.pool import Pool
from trytond.transaction import Transaction
@@ -367,7 +369,9 @@ class ModuleConfigWizardItem(ModelSQL, ModelView):
"Config wizard to run after installing module"
_name = 'ir.module.module.config_wizard.item'
_description = __doc__
name = fields.Char('Name', required=True, readonly=True)
_rec_name = 'action'
action = fields.Many2One('ir.action', 'Action', required=True,
readonly=True)
sequence= fields.Integer('Sequence')
state = fields.Selection([
('open', 'Open'),
@@ -378,6 +382,14 @@ class ModuleConfigWizardItem(ModelSQL, ModelView):
super(ModuleConfigWizardItem, self).__init__()
self._order.insert(0, ('sequence', 'ASC'))
def init(self, module_name):
table = TableHandler(Transaction().cursor, self, module_name)
# Migrate from 2.2 remove name
table.drop_column('name')
super(ModuleConfigWizardItem, self).init(module_name)
def default_state(self):
return 'open'
@@ -395,99 +407,116 @@ class ModuleConfigWizardFirst(ModelView):
ModuleConfigWizardFirst()
class ModuleConfigWizardOther(ModelView):
'Module Config Wizard Other'
_name = 'ir.module.module.config_wizard.other'
_description = __doc__
percentage = fields.Float('Percentage', readonly=True)
def default_percentage(self):
pool = Pool()
item_obj = pool.get('ir.module.module.config_wizard.item')
done = item_obj.search([
('state', '=', 'open'),
], count=True)
all = item_obj.search([], count=True)
return 100.0 * done/all
ModuleConfigWizardOther()
class ModuleConfigWizard(Wizard):
'Run config wizards'
_name = 'ir.module.module.config_wizard'
states = {
'init': {
'result': {
'type': 'choice',
'next_state': '_first',
},
},
'first': {
'result': {
'type': 'form',
'object': 'ir.module.module.config_wizard.first',
'state': [
('end', 'Cancel', 'tryton-cancel'),
('wizard', 'Ok', 'tryton-ok', True),
],
},
},
'wizard': {
'result': {
'type': 'action',
'action': '_action_wizard',
'state': 'next',
},
},
'next': {
'result': {
'type': 'choice',
'next_state': '_next',
},
},
}
def _first(self, data):
res = self._next(data)
if res == 'wizard':
class ConfigStateAction(StateAction):
def __init__(self):
StateAction.__init__(self, None)
def get_action(self):
pool = Pool()
item_obj = pool.get('ir.module.module.config_wizard.item')
action_obj = pool.get('ir.action')
item_ids = item_obj.search([
('state', '=', 'open'),
], limit=1)
if item_ids:
item = item_obj.browse(item_ids[0])
item_obj.write(item.id, {
'state': 'done',
})
return action_obj.get_action_values(item.action.type,
item.action.id)
start = StateTransition()
first = StateView('ir.module.module.config_wizard.first',
'ir.module_config_wizard_first_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Ok', 'action', 'tryton-ok', default=True),
])
other = StateView('ir.module.module.config_wizard.other',
'ir.module_config_wizard_other_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Next', 'action', 'tryton-go-next', default=True),
])
action = ConfigStateAction()
def transition_start(self, session):
res = self.transition_action(session)
if res == 'other':
return 'first'
return res
def _action_wizard(self, data):
pool = Pool()
item_obj = pool.get('ir.module.module.config_wizard.item')
item_ids = item_obj.search([
('state', '=', 'open'),
], limit=1)
if item_ids:
item = item_obj.browse(item_ids[0])
item_obj.write(item.id, {
'state': 'done',
})
return {
'type': 'ir.action.wizard',
'wiz_name': item.name,
}
return {}
def _next(self, data):
def transition_action(self, session):
pool = Pool()
item_obj = pool.get('ir.module.module.config_wizard.item')
item_ids = item_obj.search([
('state', '=', 'open'),
])
if item_ids:
return 'wizard'
return 'other'
return 'end'
ModuleConfigWizard()
class ModuleInstallUpgradeInit(ModelView):
'Module Install Upgrade Init'
_name = 'ir.module.module.install_upgrade.init'
_description = __doc__
module_info = fields.Text('Modules to update', readonly=True)
ModuleInstallUpgradeInit()
class ModuleInstallUpgradeStart(ModelView):
'Module Install Upgrade Start'
_name = 'ir.module.module.install_upgrade.start'
_description = __doc__
module_info = fields.Text('Modules to update', readonly=True)
ModuleInstallUpgradeStart()
class ModuleInstallUpgradeDone(ModelView):
'Module Install Upgrade Done'
_name = 'ir.module.module.install_upgrade.done'
_description = __doc__
ModuleInstallUpgradeDone()
class ModuleInstallUpgrade(Wizard):
"Install / Upgrade modules"
_name = 'ir.module.module.install_upgrade'
def _get_install(self, data):
start = StateView('ir.module.module.install_upgrade.start',
'ir.module_install_upgrade_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Start Upgrade', 'upgrade', 'tryton-ok', default=True),
])
upgrade = StateTransition()
done = StateView('ir.module.module.install_upgrade.done',
'ir.module_install_upgrade_done_view_form', [
Button('Ok', 'config', 'tryton-ok', default=True),
])
config = StateAction('ir.act_module_config_wizard')
def default_start(self, session, fields):
pool = Pool()
module_obj = pool.get('ir.module.module')
module_ids = module_obj.search([
@@ -499,7 +528,7 @@ class ModuleInstallUpgrade(Wizard):
for x in modules),
}
def _upgrade_module(self, data):
def transition_upgrade(self, session):
pool = Pool()
module_obj = pool.get('ir.module.module')
lang_obj = pool.get('ir.lang')
@@ -514,49 +543,7 @@ class ModuleInstallUpgrade(Wizard):
transaction.cursor.commit()
if module_ids:
pool.init(update=True, lang=lang)
new_wizard = pool.get('ir.module.module.install_upgrade',
type='wizard')
new_wizard._lock.acquire()
new_wizard._datas[data['_wiz_id']] = self._datas[data['_wiz_id']]
new_wizard._lock.release()
return {}
states = {
'init': {
'actions': ['_get_install'],
'result': {
'type': 'form',
'object': 'ir.module.module.install_upgrade.init',
'state': [
('end', 'Cancel', 'tryton-cancel'),
('start', 'Start Upgrade', 'tryton-ok', True),
],
},
},
'start': {
'actions': ['_upgrade_module'],
'result': {
'type': 'form',
'object': 'ir.module.module.install_upgrade.start',
'state': [
('config', 'Ok', 'tryton-ok', True),
],
},
},
'config': {
'result': {
'type': 'action',
'action': '_config',
'state': 'end',
},
},
}
def _config(self, data):
return {
'type': 'ir.action.wizard',
'wiz_name': 'ir.module.module.config_wizard',
}
return 'done'
ModuleInstallUpgrade()
@@ -564,22 +551,10 @@ ModuleInstallUpgrade()
class ModuleConfig(Wizard):
'Configure Modules'
_name = 'ir.module.module.config'
states = {
'init': {
'result': {
'type': 'action',
'action': '_action_open',
'state': 'end',
},
},
}
def _action_open(self, datas):
pool = Pool()
model_data_obj = pool.get('ir.model.data')
act_window_obj = pool.get('ir.action.act_window')
act_window_id = model_data_obj.get_id('ir', 'act_module_form')
res = act_window_obj.read(act_window_id)
return res
start = StateAction('ir.act_module_form')
def transition_start(self, session):
return 'end'
ModuleConfig()
+26 -7
View File
@@ -99,7 +99,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="arch" type="xml">
<![CDATA[
<tree string="Config Wizard Items" editable="bottom">
<field name="name"/>
<field name="action"/>
<field name="sequence"/>
<field name="state"/>
</tree>
@@ -147,6 +147,20 @@ this repository contains the full copyright notices and license terms. -->
</field>
</record>
<record model="ir.ui.view" id="module_config_wizard_other_view_form">
<field name="model">ir.module.module.config_wizard.other</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
<form string="Configuration Wizard Next Step!" col="2">
<image name="tryton-dialog-information" xexpand="0"
xfill="0"/>
<field name="percentage" widget="progressbar"/>
</form>
]]>
</field>
</record>
<record model="ir.action.wizard" id="act_module_install_upgrade">
<field name="name">Perform Pending Installation/Upgrade</field>
<field name="wiz_name">ir.module.module.install_upgrade</field>
@@ -157,8 +171,8 @@ this repository contains the full copyright notices and license terms. -->
<field name="model">ir.module.module,-1</field>
</record>
<record model="ir.ui.view" id="module_install_upgrade_init_view_form">
<field name="model">ir.module.module.install_upgrade.init</field>
<record model="ir.ui.view" id="module_install_upgrade_start_view_form">
<field name="model">ir.module.module.install_upgrade.start</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
@@ -179,12 +193,12 @@ this repository contains the full copyright notices and license terms. -->
</field>
</record>
<record model="ir.ui.view" id="module_install_upgrade_start_view_form">
<field name="model">ir.module.module.install_upgrade.start</field>
<record model="ir.ui.view" id="module_install_upgrade_done_view_form">
<field name="model">ir.module.module.install_upgrade.done</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
<form string="System upgrade done" col="2">
<form string="System Upgrade Done" col="2">
<image name="tryton-dialog-information" xexpand="0" xfill="0"/>
<label string="The modules have been upgraded / installed !"
id="modules"
@@ -199,9 +213,14 @@ this repository contains the full copyright notices and license terms. -->
action="act_module_install_upgrade"
id="menu_module_install_upgrade"/>
<record model="ir.action.wizard" id="act_module_config">
<field name="name">Configure Modules</field>
<field name="wiz_name">ir.module.module.config</field>
</record>
<record model="ir.module.module.config_wizard.item"
id="config_wizard_item_module">
<field name="name">ir.module.module.config</field>
<field name="action" ref="act_module_config"/>
<field name="sequence" eval="10000"/>
</record>
</data>
+22
View File
@@ -0,0 +1,22 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
try:
import simplejson as json
except ImportError:
import json
from trytond.model import ModelSQL, fields
class SessionWizard(ModelSQL):
"Session Wizard"
_name = 'ir.session.wizard'
_description = __doc__
data = fields.Text('Data')
# prevent read/write/delete from _rpc
def default_data(self):
return json.dumps({})
SessionWizard()
+134 -199
View File
@@ -17,7 +17,8 @@ except ImportError:
from functools import reduce
from ..model import ModelView, ModelSQL, fields
from ..model.cacheable import Cacheable
from ..wizard import Wizard
from ..wizard import Wizard, StateView, StateTransition, StateAction, \
Button
from ..tools import file_open, reduce_ids
from ..backend import TableHandler, FIELDS
from ..pyson import PYSONEncoder
@@ -566,14 +567,6 @@ class Translation(ModelSQL, ModelView, Cacheable):
Translation()
class ReportTranslationSetInit(ModelView):
"Update Report Translation"
_name = 'ir.translation.set_report.init'
_description = __doc__
ReportTranslationSetInit()
class ReportTranslationSetStart(ModelView):
"Update Report Translation"
_name = 'ir.translation.set_report.start'
@@ -582,33 +575,29 @@ class ReportTranslationSetStart(ModelView):
ReportTranslationSetStart()
class ReportTranslationSetSucceed(ModelView):
"Update Report Translation"
_name = 'ir.translation.set_report.succeed'
_description = __doc__
ReportTranslationSetSucceed()
class ReportTranslationSet(Wizard):
"Update report translation"
_name = "ir.translation.set_report"
states = {
'init': {
'actions': [],
'result': {
'type': 'form',
'object': 'ir.translation.set_report.init',
'state': [
('end', 'Cancel', 'tryton-cancel'),
('start', 'Start Update', 'tryton-ok', True),
],
},
},
'start': {
'actions': ['_set_report_translation'],
'result': {
'type': 'form',
'object': 'ir.translation.set_report.start',
'state': [
('end', 'Ok', 'tryton-ok', True),
],
},
},
}
start = StateView('ir.translation.set_report.start',
'ir.translation_set_report_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Start Update', 'set_report', 'tryton-ok',
default=True),
])
set_report = StateTransition()
succeed = StateView('ir.translation.set_report.succeed',
'ir.translation_set_report_succeed_view_form', [
Button('Ok', 'end', 'tryton-ok', default=True),
])
def _translate_report(self, node):
strings = []
@@ -628,7 +617,7 @@ class ReportTranslationSet(Wizard):
strings.extend(self._translate_report(child))
return strings
def _set_report_translation(self, data):
def transition_set_report(self, session):
pool = Pool()
report_obj = pool.get('ir.action.report')
translation_obj = pool.get('ir.translation')
@@ -731,54 +720,41 @@ class ReportTranslationSet(Wizard):
'(' + ','.join(('%s',) * len(strings)) + ')',
(report.report_name, 'odt', report.module) + \
tuple(strings))
return {}
return 'succeed'
ReportTranslationSet()
class TranslationCleanInit(ModelView):
'Clean translation init'
_name = 'ir.translation.clean.init'
_description = __doc__
TranslationCleanInit()
class TranslationCleanStart(ModelView):
'Clean translation start'
'Clean translation'
_name = 'ir.translation.clean.start'
_description = __doc__
TranslationCleanStart()
class TranslationCleanSucceed(ModelView):
'Clean translation'
_name = 'ir.translation.clean.succeed'
_description = __doc__
TranslationCleanSucceed()
class TranslationClean(Wizard):
"Clean translation"
_name = 'ir.translation.clean'
states = {
'init': {
'actions': [],
'result': {
'type': 'form',
'object': 'ir.translation.clean.init',
'state': [
('end', 'Cancel', 'tryton-cancel'),
('start', 'Start', 'tryton-ok', True),
],
},
},
'start': {
'actions': ['_clean_translation'],
'result': {
'type': 'form',
'object': 'ir.translation.clean.start',
'state': [
('end', 'Ok', 'tryton-ok', True),
],
},
},
}
start = StateView('ir.translation.clean.start',
'ir.translation_clean_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Clean', 'clean', 'tryton-ok', default=True),
])
clean = StateTransition()
succeed = StateView('ir.translation.clean.succeed',
'ir.translation_clean_succeed_view_form', [
Button('Ok', 'end', 'tryton-ok', default=True),
])
def _clean_field(self, translation):
pool = Pool()
@@ -862,9 +838,8 @@ class TranslationClean(Wizard):
state = wizard.states.get(state_name)
if not state:
return True
for but in state['result']['state']:
if but[0] == button_name:
return False
if button_name in [b.state for b in state.buttons]:
return False
return True
def _clean_help(self, translation):
@@ -923,7 +898,7 @@ class TranslationClean(Wizard):
else:
return True
def _clean_translation(self, data):
def transition_clean(self, session):
pool = Pool()
translation_obj = pool.get('ir.translation')
model_data_obj = pool.get('ir.model.data')
@@ -952,40 +927,53 @@ class TranslationClean(Wizard):
to_delete.remove(mdata.db_id)
translation_obj.delete(to_delete)
return {}
return 'succeed'
TranslationClean()
class TranslationUpdateInit(ModelView):
"Update translation - language"
_name = 'ir.translation.update.init'
class TranslationUpdateStart(ModelView):
"Update translation"
_name = 'ir.translation.update.start'
_description = __doc__
lang = fields.Selection('get_language', string='Language',
required=True)
def default_lang(self):
return Transaction().context.get('language', False)
language = fields.Many2One('ir.lang', 'Language', required=True,
domain=[('translatable', '=', True)])
def get_language(self):
pool = Pool()
lang_obj = pool.get('ir.lang')
lang_ids = lang_obj.search([('translatable', '=', True)])
langs = lang_obj.browse(lang_ids)
res = [(lang.code, lang.name) for lang in langs if lang.code != 'en_US']
return res
def default_language(self):
lang_obj = Pool().get('ir.lang')
code = Transaction().context.get('language', False)
try:
lang_id, = lang_obj.search([
('code', '=', code),
('translatable', '=', True),
], limit=1)
return lang_id
except ValueError:
return False
TranslationUpdateInit()
TranslationUpdateStart()
class TranslationUpdate(Wizard):
"Update translation"
_name = "ir.translation.update"
def _update_translation(self, data):
start = StateView('ir.translation.update.start',
'ir.translation_update_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Update', 'update', 'tryton-ok', default=True),
])
update = StateAction('ir.act_translation_form')
def transition_update(self, session):
return 'end'
def do_update(self, session, action):
pool = Pool()
translation_obj = pool.get('ir.translation')
cursor = Transaction().cursor
lang = session.start.language.code
cursor.execute('SELECT name, res_id, type, src, module ' \
'FROM ir_translation ' \
'WHERE lang=\'en_US\' ' \
@@ -996,13 +984,13 @@ class TranslationUpdate(Wizard):
'WHERE lang=%s ' \
'AND type in (\'odt\', \'view\', \'wizard_button\', ' \
' \'selection\', \'error\')',
(data['form']['lang'],))
(lang,))
for row in cursor.dictfetchall():
with Transaction().set_user(0):
translation_obj.create({
'name': row['name'],
'res_id': row['res_id'],
'lang': data['form']['lang'],
'lang': lang,
'type': row['type'],
'src': row['src'],
'module': row['module'],
@@ -1015,13 +1003,13 @@ class TranslationUpdate(Wizard):
'FROM ir_translation ' \
'WHERE lang=%s ' \
'AND type in (\'field\', \'model\', \'help\')',
(data['form']['lang'],))
(lang,))
for row in cursor.dictfetchall():
with Transaction().set_user(0):
translation_obj.create({
'name': row['name'],
'res_id': row['res_id'],
'lang': data['form']['lang'],
'lang': lang,
'type': row['type'],
'module': row['module'],
})
@@ -1035,7 +1023,7 @@ class TranslationUpdate(Wizard):
'WHERE lang=%s ' \
'AND type in (\'field\', \'model\', \'selection\', ' \
'\'help\')',
(data['form']['lang'],))
(lang,))
for row in cursor.dictfetchall():
cursor.execute('UPDATE ir_translation ' \
'SET fuzzy = %s, ' \
@@ -1045,7 +1033,7 @@ class TranslationUpdate(Wizard):
'AND type = %s ' \
'AND lang = %s',
(True, row['src'], row['name'], row['res_id'], row['type'],
data['form']['lang']))
lang))
cursor.execute('SELECT src, MAX(value) AS value FROM ir_translation ' \
'WHERE lang = %s ' \
@@ -1054,7 +1042,7 @@ class TranslationUpdate(Wizard):
'WHERE (value = \'\' OR value IS NULL) ' \
'AND lang = %s) ' \
'AND value != \'\' AND value IS NOT NULL ' \
'GROUP BY src', (data['form']['lang'], data['form']['lang']))
'GROUP BY src', (lang, lang))
for row in cursor.dictfetchall():
cursor.execute('UPDATE ir_translation ' \
@@ -1063,136 +1051,83 @@ class TranslationUpdate(Wizard):
'WHERE src = %s ' \
'AND (value = \'\' OR value IS NULL) ' \
'AND lang = %s', (True, row['value'], row['src'],
data['form']['lang']))
lang))
cursor.execute('UPDATE ir_translation ' \
'SET fuzzy = %s ' \
'WHERE (value = \'\' OR value IS NULL) ' \
'AND lang = %s', (False, data['form']['lang'],))
return {}
'AND lang = %s', (False, lang,))
def _action_translation_open(self, data):
pool = Pool()
model_data_obj = pool.get('ir.model.data')
act_window_obj = pool.get('ir.action.act_window')
model_data_ids = model_data_obj.search([
('fs_id', '=', 'act_translation_form'),
('module', '=', 'ir'),
('inherit', '=', False),
], limit=1)
model_data = model_data_obj.browse(model_data_ids[0])
res = act_window_obj.read(model_data.db_id)
res['pyson_domain'] = PYSONEncoder().encode([
action['pyson_domain'] = PYSONEncoder().encode([
('module', '!=', False),
('lang', '=', data['form']['lang']),
('lang', '=', lang),
])
return res
states = {
'init': {
'actions': [],
'result': {
'type': 'form',
'object': 'ir.translation.update.init',
'state': [
('end', 'Cancel', 'tryton-cancel'),
('start', 'Start Update','tryton-ok', True),
],
},
},
'start': {
'actions': ['_update_translation'],
'result': {
'type': 'action',
'action': '_action_translation_open',
'state': 'end',
},
},
}
return action, {}
TranslationUpdate()
class TranslationExportInit(ModelView):
"Export translation - language and module"
_name = 'ir.translation.export.init'
class TranslationExportStart(ModelView):
"Export translation"
_name = 'ir.translation.export.start'
_description = __doc__
lang = fields.Selection('get_language', string='Language',
required=True)
module = fields.Selection('get_module', string='Module',
required=True)
def default_lang(self):
return Transaction().context.get('language', False)
def get_language(self):
pool = Pool()
lang_obj = pool.get('ir.lang')
lang_ids = lang_obj.search([
('translatable', '=', True),
])
langs = lang_obj.browse(lang_ids)
res = [(lang.code, lang.name) for lang in langs]
return res
def get_module(self):
pool = Pool()
module_obj = pool.get('ir.module.module')
module_ids = module_obj.search([
language = fields.Many2One('ir.lang', 'Language', required=True,
domain=[('translatable', '=', True)])
module = fields.Many2One('ir.module.module', 'Module', required=True,
domain=[
('state', 'in', ['installed', 'to upgrade', 'to remove']),
])
modules = module_obj.browse(module_ids)
res = [(module.name, module.name) for module in modules]
return res
TranslationExportInit()
class TranslationExportStart(ModelView):
"Export translation - file"
_description = __doc__
_name = 'ir.translation.export.start'
file = fields.Binary('File', readonly=True)
def default_language(self):
lang_obj = Pool().get('ir.lang')
code = Transaction().context.get('language', False)
try:
lang_id, = lang_obj.search([
('code', '=', code),
('translatable', '=', True),
], limit=1)
return lang_id
except ValueError:
return False
TranslationExportStart()
class TranslationExportResult(ModelView):
"Export translation"
_description = __doc__
_name = 'ir.translation.export.result'
file = fields.Binary('File', readonly=True)
TranslationExportResult()
class TranslationExport(Wizard):
"Export translation"
_name = "ir.translation.export"
def _export_translation(self, data):
start = StateView('ir.translation.export.start',
'ir.translation_export_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Export', 'export', 'tryton-ok', default=True),
])
export = StateTransition()
result = StateView('ir.translation.export.result',
'ir.translation_export_result_view_form', [
Button('Close', 'end', 'tryton-close'),
])
def transition_export(self, session):
pool = Pool()
translation_obj = pool.get('ir.translation')
file_data = translation_obj.translation_export(data['form']['lang'],
data['form']['module'])
return {
'file': buffer(file_data),
}
file_data = translation_obj.translation_export(
session.start.language.code, session.start.module.name)
session.result.file = buffer(file_data)
return 'result'
states = {
'init': {
'actions': [],
'result': {
'type': 'form',
'object': 'ir.translation.export.init',
'state': [
('end', 'Cancel', 'tryton-cancel'),
('start', 'Start Export', 'tryton-ok', True),
],
},
},
'start': {
'actions': ['_export_translation'],
'result': {
'type': 'form',
'object': 'ir.translation.export.start',
'state': [
('end', 'Close', 'tryton-close'),
],
},
},
}
def default_result(self, session, fields):
return {
'file': session.result.file,
}
TranslationExport()
+19 -19
View File
@@ -256,8 +256,8 @@ this repository contains the full copyright notices and license terms. -->
<field name="fuzzy" eval="False"/>
</record>
<record model="ir.ui.view" id="act_translation_set_report_init_view_form">
<field name="model">ir.translation.set_report.init</field>
<record model="ir.ui.view" id="translation_set_report_start_view_form">
<field name="model">ir.translation.set_report.start</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
@@ -270,8 +270,8 @@ this repository contains the full copyright notices and license terms. -->
</field>
</record>
<record model="ir.ui.view" id="act_translation_set_report_start_view_form">
<field name="model">ir.translation.set_report.start</field>
<record model="ir.ui.view" id="translation_set_report_succeed_view_form">
<field name="model">ir.translation.set_report.succeed</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
@@ -294,8 +294,8 @@ this repository contains the full copyright notices and license terms. -->
id="menu_translation_set_report"
sequence="10"/>
<record model="ir.ui.view" id="act_translation_clean_init_view_form">
<field name="model">ir.translation.clean.init</field>
<record model="ir.ui.view" id="translation_clean_start_view_form">
<field name="model">ir.translation.clean.start</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
@@ -308,8 +308,8 @@ this repository contains the full copyright notices and license terms. -->
</field>
</record>
<record model="ir.ui.view" id="act_translation_clean_start_view_form">
<field name="model">ir.translation.clean.start</field>
<record model="ir.ui.view" id="translation_clean_succeed_view_form">
<field name="model">ir.translation.clean.succeed</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
@@ -332,14 +332,14 @@ this repository contains the full copyright notices and license terms. -->
id="menu_translation_clean"
sequence="20"/>
<record model="ir.ui.view" id="act_translation_update_init_view_form">
<field name="model">ir.translation.update.init</field>
<record model="ir.ui.view" id="translation_update_start_view_form">
<field name="model">ir.translation.update.start</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
<form string="Synchronize Translations">
<label name="lang"/>
<field name="lang"/>
<label name="language"/>
<field name="language" widget="selection"/>
</form>
]]>
</field>
@@ -355,23 +355,23 @@ this repository contains the full copyright notices and license terms. -->
id="menu_translation_update"
sequence="30"/>
<record model="ir.ui.view" id="act_translation_export_init_view_form">
<field name="model">ir.translation.export.init</field>
<record model="ir.ui.view" id="translation_export_start_view_form">
<field name="model">ir.translation.export.start</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
<form string="Export Translation">
<label name="lang"/>
<field name="lang"/>
<label name="language"/>
<field name="language" widget="selection"/>
<label name="module"/>
<field name="module"/>
<field name="module" widget="selection"/>
</form>
]]>
</field>
</record>
<record model="ir.ui.view" id="act_translation_export_start_view_form">
<field name="model">ir.translation.export.start</field>
<record model="ir.ui.view" id="translation_export_result_view_form">
<field name="model">ir.translation.export.result</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
+50 -67
View File
@@ -9,7 +9,8 @@ from trytond.backend import TableHandler
from trytond.pyson import CONTEXT, Eval
from trytond.tools import safe_eval
from trytond.transaction import Transaction
from trytond.wizard import Wizard
from trytond.wizard import Wizard, StateView, StateAction, StateTransition, \
Button
from trytond.pool import Pool
@@ -233,51 +234,39 @@ class View(ModelSQL, ModelView):
View()
class ShowViewInit(ModelView):
'Show view init'
_name = 'ir.ui.view.show.init'
class ShowViewStart(ModelView):
'Show view'
_name = 'ir.ui.view.show.start'
ShowViewInit()
ShowViewStart()
class ShowView(Wizard):
'Show view'
_name = 'ir.ui.view.show'
states = {
'init': {
'result': {
'type': 'form',
'object': 'ir.ui.view.show.init',
'state': [
('end', 'Close', 'tryton-cancel', True),
],
},
},
}
def __init__(self):
super(ShowView, self).__init__()
self._error_messages.update({
'view_type': 'Only "form" view can be shown!',
})
class ShowStateView(StateView):
def execute(self, wiz_id, data, state='init'):
pool = Pool()
view_obj = pool.get('ir.ui.view')
result = super(ShowView, self).execute(wiz_id, data, state=state)
view = view_obj.browse(data['id'])
view_type = view.type
if view.inherit:
view_type = view.inherit.type
if view_type != 'form':
self.raise_user_error('view_type')
model_obj = pool.get(view.model)
fields_view = model_obj.fields_view_get(view_id=view.id)
result['fields'] = fields_view['fields']
result['arch'] = fields_view['arch']
result['object'] = view.model
return result
def __init__(self, model_name, buttons):
StateView.__init__(self, model_name, None, buttons)
def get_view(self):
pool = Pool()
view_obj = pool.get('ir.ui.view')
view_id = Transaction().context.get('active_id')
view = view_obj.browse(view_id)
model_obj = pool.get(view.model)
if view.type != 'form':
return model_obj.fields_view_get(view_type='form')
return model_obj.fields_view_get(view_id=view_id)
def get_defaults(self, wizard, session, state_name, fields):
return {}
start = ShowStateView('ir.ui.view.show.start', [
Button('Close', 'end', 'tryton-close', default=True),
])
ShowView()
@@ -322,17 +311,10 @@ class AddShortcut(Wizard):
'Add shortcut'
_name = 'ir.ui.view_sc.add'
states = {
'init': {
'result': {
'type': 'action',
'action': '_add_shortcut',
'state': 'end',
},
},
}
start_state = 'add'
add = StateTransition()
def _add_shortcut(self, data):
def transition_add(self, session):
pool = Pool()
view_sc_obj = pool.get('ir.ui.view_sc')
model_obj = pool.get(data['model'])
@@ -344,7 +326,7 @@ class AddShortcut(Wizard):
'user_id': Transaction().user,
'resource': model_obj._name,
})
return {}
return 'end'
AddShortcut()
@@ -353,22 +335,28 @@ class OpenShortcut(Wizard):
'Open a shortcut'
_name = 'ir.ui.view_sc.open'
states = {
'init': {
'result': {
'type': 'action',
'action': '_open',
'state': 'end',
}
}
}
start_state = 'open_'
def _open(self, data):
class OpenStateAction(StateAction):
def __init__(self):
StateAction.__init__(self, None)
def get_action(self):
pass
open_ = OpenStateAction()
def transition_open_(self, session):
return 'end'
def do_open_(self, session, action):
pool = Pool()
view_sc_obj = pool.get('ir.ui.view_sc')
action_keyword_obj = pool.get('ir.action.keyword')
action_obj = pool.get('ir.action')
view_sc = view_sc_obj.browse(data['id'])
view_sc = view_sc_obj.browse(Transaction().context.get('active_id'))
models = (
'%s,%d' % (view_sc.resource, view_sc.res_id),
'%s,0' % (view_sc.resource),
@@ -382,15 +370,10 @@ class OpenShortcut(Wizard):
if action_keyword_ids:
break
if not action_keyword_ids:
return {}
return {}, {}
action_keyword = action_keyword_obj.browse(action_keyword_ids[0])
action_obj = pool.get(action_keyword.action.type)
action_ids = action_obj.search([
('action.id', '=', action_keyword.action.id),
])
if not action_ids:
return {}
return action_obj.read(action_ids[0])
return action_obj.get_action_values(action_keyword.action.type,
action_keyword.action.id), {}
OpenShortcut()
+13
View File
@@ -738,5 +738,18 @@ this repository contains the full copyright notices and license terms. -->
<field name="perm_delete" eval="True" />
</record>
<record model="ir.rule.group" id="rule_group_session_wizard">
<field name="model"
search="[('model', '=', 'ir.session.wizard')]"/>
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_session_wizard1">
<field name="field"
search="[('name', '=', 'create_uid'), ('model.model', '=', 'ir.session.wizard')]"/>
<field name="operator">=</field>
<field name="operand">User</field>
<field name="rule_group" ref="rule_group_session_wizard"/>
</record>
</data>
</tryton>
+18 -39
View File
@@ -10,7 +10,7 @@ except ImportError:
hashlib = None
import sha
from ..model import ModelView, ModelSQL, fields
from ..wizard import Wizard
from ..wizard import Wizard, StateView, Button, StateTransition
from ..tools import safe_eval
from ..backend import TableHandler
from ..security import get_connections
@@ -456,55 +456,34 @@ class Warning(ModelSQL, ModelView):
Warning()
class UserConfigInit(ModelView):
class UserConfigStart(ModelView):
'User Config Init'
_name = 'res.user.config.init'
_name = 'res.user.config.start'
_description = __doc__
UserConfigInit()
UserConfigStart()
class UserConfig(Wizard):
'Configure users'
_name = 'res.user.config'
states = {
'init': {
'result': {
'type': 'form',
'object': 'res.user.config.init',
'state': [
('end', 'Cancel', 'tryton-cancel'),
('user', 'Ok', 'tryton-ok', True),
],
},
},
'user': {
'actions': ['_reset'],
'result': {
'type': 'form',
'object': 'res.user',
'state': [
('end', 'End', 'tryton-cancel'),
('add', 'Add', 'tryton-ok', True),
],
},
},
'add': {
'actions': ['_add'],
'result': {
'type': 'state',
'state': 'user',
},
},
}
def _reset(self, data):
return {}
start = StateView('res.user.config.start',
'res.user_config_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Ok', 'user', 'tryton-ok', default=True),
])
user = StateView('res.user',
'res.user_view_form', [
Button('End', 'end', 'tryton-cancel'),
Button('Add', 'add', 'tryton-ok'),
])
add = StateTransition()
def _add(self, data):
def transition_add(self, session):
pool = Pool()
res_obj = pool.get('res.user')
res_obj.create(data['form'])
return {}
res_obj.create(session.data['user'])
return 'user'
UserConfig()
+9 -3
View File
@@ -144,8 +144,8 @@ this repository contains the full copyright notices and license terms. -->
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.ui.view" id="user_config_init_view_form">
<field name="model">res.user.config.init</field>
<record model="ir.ui.view" id="user_config_start_view_form">
<field name="model">res.user.config.start</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
@@ -163,9 +163,15 @@ this repository contains the full copyright notices and license terms. -->
]]>
</field>
</record>
<record model="ir.action.wizard" id="act_user_config">
<field name="name">Configure Users</field>
<field name="wiz_name">res.user.config</field>
</record>
<record model="ir.module.module.config_wizard.item"
id="config_wizard_item_user">
<field name="name">res.user.config</field>
<field name="action" ref="act_user_config"/>
</record>
<record model="ir.ui.view" id="user_warning_view_form">
+1
View File
@@ -10,6 +10,7 @@
'import_data.xml',
'sequence.xml',
'workflow.xml',
'wizard.xml',
],
'translation': [
],
+32 -15
View File
@@ -1,26 +1,43 @@
from trytond.model import ModelView, fields
from trytond.wizard import Wizard
from trytond.wizard import Wizard, StateView, StateTransition, StateAction, Button
from trytond.transaction import Transaction
class TestWizardInit(ModelView):
_name = 'test.test_wizard.init'
class TestWizardStart(ModelView):
_name = 'test.test_wizard.start'
name = fields.Char('Test me')
user = fields.Many2One('res.user', 'User')
groups = fields.One2Many('res.group', None, 'Groups')
TestWizardInit()
def default_user(self):
return Transaction().user
TestWizardStart()
class TestWizard(Wizard):
_name = 'test.test_wizard'
states = {
'init': {
'result': {
'type': 'form',
'object': 'test.test_wizard.init',
'state': [
('end', 'Ok', 'tryton-ok', True),
],
},
},
}
start = StateView('test.test_wizard.start',
'test.test_wizard_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Next', 'next_', 'tryton-next', default=True),
])
next_ = StateTransition()
action = StateAction('ir.act_menu_tree')
def default_start(self, session, fields):
return {
'name': 'Test wizard',
}
def transition_next_(self, session):
return 'action'
def do_action(self, session, action):
return action, {}
def transition_action(self, session):
return 'end'
TestWizard()
+19
View File
@@ -0,0 +1,19 @@
<?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. -->
<tryton>
<data>
<record model="ir.ui.view" id="test_wizard_start_view_form">
<field name="model">test.test_wizard.start</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
<form string="Test Wizard">
<label name="name"/>
<field name="name"/>
</form>
]]>
</field>
</record>
</data>
</tryton>
+4 -2
View File
@@ -148,9 +148,9 @@ def install_module(name):
install_upgrade_obj = POOL.get('ir.module.module.install_upgrade',
type='wizard')
wiz_id = install_upgrade_obj.create()
wiz_id, _, _ = install_upgrade_obj.create()
transaction.cursor.commit()
install_upgrade_obj.execute(wiz_id, {}, 'start')
install_upgrade_obj.execute(wiz_id, {}, 'upgrade')
transaction.cursor.commit()
install_upgrade_obj.delete(wiz_id)
transaction.cursor.commit()
@@ -259,6 +259,8 @@ def all_suite():
suite_.addTests(test_mixins.suite())
import trytond.tests.test_workflow as test_workflow
suite_.addTests(test_workflow.suite())
import trytond.tests.test_wizard as test_wizard
suite_.addTests(test_wizard.suite())
return suite_
def modules_suite():
+87
View File
@@ -0,0 +1,87 @@
# 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 unittest
from trytond.tests.test_tryton import (POOL, DB_NAME, USER, CONTEXT,
install_module)
from trytond.transaction import Transaction
from trytond.wizard import Session
class WizardTestCase(unittest.TestCase):
def setUp(self):
install_module('test')
self.wizard = POOL.get('test.test_wizard', type='wizard')
self.session = POOL.get('ir.session.wizard')
def test0010create(self):
'Create Session Wizard'
with Transaction().start(DB_NAME, USER, CONTEXT):
session_id, start_state, end_state = self.wizard.create()
self.assertEqual(start_state, 'start')
self.assertEqual(end_state, 'end')
self.assert_(session_id)
def test0020delete(self):
'Delete Session Wizard'
with Transaction().start(DB_NAME, USER, CONTEXT):
session_id, _, _ = self.wizard.create()
self.wizard.delete(session_id)
def test0030session(self):
'Session Wizard'
with Transaction().start(DB_NAME, USER, CONTEXT):
session_id = self.session.create({})
session = Session(self.wizard, session_id)
self.assertEqual(session.start.name, False)
session.start.name = 'Test'
session.start.user = USER
session.start.groups = [
{'name': 'Group A'},
{'name': 'Group B'},
]
session.save()
session = Session(self.wizard, session_id)
self.assertEqual(session.start.name, 'Test')
self.assertEqual(session.start.user.id, USER)
self.assertEqual(session.start.user.login, 'admin')
group_a, group_b = session.start.groups
self.assertEqual(group_a.name, 'Group A')
self.assertEqual(group_b.name, 'Group B')
def test0040execute(self):
'Execute Wizard'
with Transaction().start(DB_NAME, USER, CONTEXT):
session_id, start_state, end_state = self.wizard.create()
result = self.wizard.execute(session_id, {}, start_state)
self.assertEqual(result.keys(), ['view'])
self.assertEqual(result['view']['defaults'], {
'name': 'Test wizard',
})
self.assertEqual(result['view']['buttons'], [
{
'state': 'end',
'states': '{}',
'icon': 'tryton-cancel',
'default': False,
'string': 'Cancel',
},
{
'state': 'next_',
'states': '{}',
'icon': 'tryton-next',
'default': True,
'string': 'Next',
},
])
result = self.wizard.execute(session_id, {
start_state: {
'name': 'Test Update',
}}, 'next_')
self.assertEqual(len(result['actions']), 1)
def suite():
return unittest.TestLoader().loadTestsFromTestCase(WizardTestCase)
if __name__ == '__main__':
unittest.TextTestRunner(verbosity=2).run(suite())
+297 -109
View File
@@ -1,18 +1,221 @@
#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 copy
from threading import Lock
from random import randint
from xmlrpclib import MAXINT
__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 = ""
states = {}
start_state = 'start'
end_state = 'end'
def __new__(cls):
Pool.register(cls, type='wizard')
@@ -25,48 +228,55 @@ class Wizard(WarningErrorMixin, URLMixin):
'execute': True,
}
self._error_messages = {}
self._lock = Lock()
self._datas = {}
@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 in self.states.keys():
if self.states[state]['result']['type'] == 'form':
for button in \
self.states[state]['result']['state']:
button_name = button[0]
button_value = button[1]
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 + ',' + button_name))
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()
value_md5 = translation_obj.get_src_md5(button_value)
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 + ',' + button_name,
'en_US', 'wizard_button', button_value,
value_md5, '', module_name, False))
elif res[0]['src'] != button_value:
(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_value, value_md5, res[0]['id']))
(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))
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
@@ -81,86 +291,64 @@ class Wizard(WarningErrorMixin, URLMixin):
module_name, False))
def create(self):
self._lock.acquire()
wiz_id = 0
while True:
wiz_id = randint(0, MAXINT)
if wiz_id not in self._datas:
break
self._datas[wiz_id] = {'user': Transaction().user, '_wiz_id': wiz_id}
self._lock.release()
return wiz_id
"Create a session"
session_obj = Pool().get('ir.session.wizard')
return (session_obj.create({}), self.start_state, self.end_state)
def delete(self, wiz_id):
if wiz_id not in self._datas:
return
if self._datas[wiz_id]['user'] != Transaction().user:
raise Exception('AccessDenied')
self._lock.acquire()
del self._datas[wiz_id]
self._lock.release()
def delete(self, session_id):
"Delete the session"
session_obj = Pool().get('ir.session.wizard')
session_obj.delete(session_id)
def execute(self, wiz_id, data, state='init'):
pool = Pool()
translation_obj = pool.get('ir.translation')
res = {}
def execute(self, session, data, state_name):
'''
Execute the wizard state.
if self._datas.get(wiz_id, {}).get('user') != Transaction().user:
raise Exception('AccessDenied')
self._datas[wiz_id].update(data)
data = self._datas[wiz_id]
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
state_def = self.states[state]
result_def = state_def.get('result', {})
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)
actions_res = {}
# iterate through the list of actions defined for this state
for action in state_def.get('actions', []):
# execute them
action_res = getattr(self, action)(data)
assert isinstance(action_res, dict), \
'The return value of wizard actions ' \
'should be a dictionary'
actions_res.update(action_res)
state = self.states[state_name]
result = {}
res = copy.copy(result_def)
if state_def.get('actions'):
res['datas'] = actions_res
if result_def['type'] == 'action':
res['action'] = getattr(self, result_def['action'])(data)
elif result_def['type'] == 'form':
obj = pool.get(result_def['object'])
view = obj.fields_view_get(view_type='form')
fields = view['fields']
arch = view['arch']
button_list = copy.copy(result_def['state'])
default_values = obj.default_get(fields.keys())
for field in default_values.keys():
if '.' in field:
continue
fields[field]['value'] = default_values[field]
# translate buttons
for i, button in enumerate(button_list):
button_name = button[0]
res_trans = translation_obj._get_source(
self._name + ',' + state + ',' + button_name,
'wizard_button', Transaction().language)
if res_trans:
button = list(button)
button[1] = res_trans
button_list[i] = tuple(button)
res['fields'] = fields
res['arch'] = arch
res['state'] = button_list
elif result_def['type'] == 'choice':
next_state = getattr(self, result_def['next_state'])(data)
if next_state == 'end':
return {'type': 'state', 'state': 'end'}
return self.execute(wiz_id, data, next_state)
return res
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