New wizard design
review167001
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
* New Wizard design
|
||||
* Add pyson.Id
|
||||
* Use XML-RPC struct for Decimal and Date
|
||||
* Remove change_default on fields
|
||||
|
||||
@@ -8,6 +8,7 @@ API Reference
|
||||
:maxdepth: 1
|
||||
|
||||
models/index
|
||||
wizard
|
||||
pyson
|
||||
transaction
|
||||
tools/singleton
|
||||
|
||||
@@ -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.
|
||||
@@ -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>`.
|
||||
@@ -15,3 +15,4 @@ from . import module
|
||||
from .cache import *
|
||||
from .date import *
|
||||
from .trigger import *
|
||||
from .session import *
|
||||
|
||||
+25
-17
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
'import_data.xml',
|
||||
'sequence.xml',
|
||||
'workflow.xml',
|
||||
'wizard.xml',
|
||||
],
|
||||
'translation': [
|
||||
],
|
||||
|
||||
+32
-15
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
@@ -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():
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user