[FIX] Remove unused imports and fix some imports that doesn't use the new namespace
[odoo/odoo.git] / openerp / addons / base / res / res_config.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21 import logging
22 from operator import attrgetter
23
24 from openerp import pooler
25 from openerp.osv import osv, fields
26 from openerp.tools import ustr
27 from openerp.tools.translate import _
28
29 _logger = logging.getLogger(__name__)
30
31 class res_config_configurable(osv.osv_memory):
32     ''' Base classes for new-style configuration items
33
34     Configuration items should inherit from this class, implement
35     the execute method (and optionally the cancel one) and have
36     their view inherit from the related res_config_view_base view.
37     '''
38     _name = 'res.config'
39
40     def _next_action(self, cr, uid, context=None):
41         Todos = self.pool['ir.actions.todo']
42         _logger.info('getting next %s', Todos)
43
44         active_todos = Todos.browse(cr, uid,
45             Todos.search(cr, uid, ['&', ('type', '=', 'automatic'), ('state','=','open')]),
46                                     context=context)
47
48         user_groups = set(map(
49             lambda g: g.id,
50             self.pool['res.users'].browse(cr, uid, [uid], context=context)[0].groups_id))
51
52         valid_todos_for_user = [
53             todo for todo in active_todos
54             if not todo.groups_id or bool(user_groups.intersection((
55                 group.id for group in todo.groups_id)))
56         ]
57
58         if valid_todos_for_user:
59             return valid_todos_for_user[0]
60
61         return None
62
63     def _next(self, cr, uid, context=None):
64         _logger.info('getting next operation')
65         next = self._next_action(cr, uid, context=context)
66         _logger.info('next action is %s', next)
67         if next:
68             res = next.action_launch(context=context)
69             res['nodestroy'] = False
70             return res
71         # reload the client; open the first available root menu
72         menu_obj = self.pool.get('ir.ui.menu')
73         menu_ids = menu_obj.search(cr, uid, [('parent_id', '=', False)], context=context)
74         return {
75             'type': 'ir.actions.client',
76             'tag': 'reload',
77             'params': {'menu_id': menu_ids and menu_ids[0] or False},
78         }
79
80     def start(self, cr, uid, ids, context=None):
81         return self.next(cr, uid, ids, context)
82
83     def next(self, cr, uid, ids, context=None):
84         """ Returns the next todo action to execute (using the default
85         sort order)
86         """
87         return self._next(cr, uid, context=context)
88
89     def execute(self, cr, uid, ids, context=None):
90         """ Method called when the user clicks on the ``Next`` button.
91
92         Execute *must* be overloaded unless ``action_next`` is overloaded
93         (which is something you generally don't need to do).
94
95         If ``execute`` returns an action dictionary, that action is executed
96         rather than just going to the next configuration item.
97         """
98         raise NotImplementedError(
99             'Configuration items need to implement execute')
100     def cancel(self, cr, uid, ids, context=None):
101         """ Method called when the user click on the ``Skip`` button.
102
103         ``cancel`` should be overloaded instead of ``action_skip``. As with
104         ``execute``, if it returns an action dictionary that action is
105         executed in stead of the default (going to the next configuration item)
106
107         The default implementation is a NOOP.
108
109         ``cancel`` is also called by the default implementation of
110         ``action_cancel``.
111         """
112         pass
113
114     def action_next(self, cr, uid, ids, context=None):
115         """ Action handler for the ``next`` event.
116
117         Sets the status of the todo the event was sent from to
118         ``done``, calls ``execute`` and -- unless ``execute`` returned
119         an action dictionary -- executes the action provided by calling
120         ``next``.
121         """
122         next = self.execute(cr, uid, ids, context=context)
123         if next: return next
124         return self.next(cr, uid, ids, context=context)
125
126     def action_skip(self, cr, uid, ids, context=None):
127         """ Action handler for the ``skip`` event.
128
129         Sets the status of the todo the event was sent from to
130         ``skip``, calls ``cancel`` and -- unless ``cancel`` returned
131         an action dictionary -- executes the action provided by calling
132         ``next``.
133         """
134         next = self.cancel(cr, uid, ids, context=context)
135         if next: return next
136         return self.next(cr, uid, ids, context=context)
137
138     def action_cancel(self, cr, uid, ids, context=None):
139         """ Action handler for the ``cancel`` event. That event isn't
140         generated by the res.config.view.base inheritable view, the
141         inherited view has to overload one of the buttons (or add one
142         more).
143
144         Sets the status of the todo the event was sent from to
145         ``cancel``, calls ``cancel`` and -- unless ``cancel`` returned
146         an action dictionary -- executes the action provided by calling
147         ``next``.
148         """
149         next = self.cancel(cr, uid, ids, context=context)
150         if next: return next
151         return self.next(cr, uid, ids, context=context)
152
153 res_config_configurable()
154
155 class res_config_installer(osv.osv_memory):
156     """ New-style configuration base specialized for addons selection
157     and installation.
158
159     Basic usage
160     -----------
161
162     Subclasses can simply define a number of _columns as
163     fields.boolean objects. The keys (column names) should be the
164     names of the addons to install (when selected). Upon action
165     execution, selected boolean fields (and those only) will be
166     interpreted as addons to install, and batch-installed.
167
168     Additional addons
169     -----------------
170
171     It is also possible to require the installation of an additional
172     addon set when a specific preset of addons has been marked for
173     installation (in the basic usage only, additionals can't depend on
174     one another).
175
176     These additionals are defined through the ``_install_if``
177     property. This property is a mapping of a collection of addons (by
178     name) to a collection of addons (by name) [#]_, and if all the *key*
179     addons are selected for installation, then the *value* ones will
180     be selected as well. For example::
181
182         _install_if = {
183             ('sale','crm'): ['sale_crm'],
184         }
185
186     This will install the ``sale_crm`` addon if and only if both the
187     ``sale`` and ``crm`` addons are selected for installation.
188
189     You can define as many additionals as you wish, and additionals
190     can overlap in key and value. For instance::
191
192         _install_if = {
193             ('sale','crm'): ['sale_crm'],
194             ('sale','project'): ['project_mrp'],
195         }
196
197     will install both ``sale_crm`` and ``project_mrp`` if all of
198     ``sale``, ``crm`` and ``project`` are selected for installation.
199
200     Hook methods
201     ------------
202
203     Subclasses might also need to express dependencies more complex
204     than that provided by additionals. In this case, it's possible to
205     define methods of the form ``_if_%(name)s`` where ``name`` is the
206     name of a boolean field. If the field is selected, then the
207     corresponding module will be marked for installation *and* the
208     hook method will be executed.
209
210     Hook methods take the usual set of parameters (cr, uid, ids,
211     context) and can return a collection of additional addons to
212     install (if they return anything, otherwise they should not return
213     anything, though returning any "falsy" value such as None or an
214     empty collection will have the same effect).
215
216     Complete control
217     ----------------
218
219     The last hook is to simply overload the ``modules_to_install``
220     method, which implements all the mechanisms above. This method
221     takes the usual set of parameters (cr, uid, ids, context) and
222     returns a ``set`` of addons to install (addons selected by the
223     above methods minus addons from the *basic* set which are already
224     installed) [#]_ so an overloader can simply manipulate the ``set``
225     returned by ``res_config_installer.modules_to_install`` to add or
226     remove addons.
227
228     Skipping the installer
229     ----------------------
230
231     Unless it is removed from the view, installers have a *skip*
232     button which invokes ``action_skip`` (and the ``cancel`` hook from
233     ``res.config``). Hooks and additionals *are not run* when skipping
234     installation, even for already installed addons.
235
236     Again, setup your hooks accordingly.
237
238     .. [#] note that since a mapping key needs to be hashable, it's
239            possible to use a tuple or a frozenset, but not a list or a
240            regular set
241
242     .. [#] because the already-installed modules are only pruned at
243            the very end of ``modules_to_install``, additionals and
244            hooks depending on them *are guaranteed to execute*. Setup
245            your hooks accordingly.
246     """
247     _name = 'res.config.installer'
248     _inherit = 'res.config'
249
250     _install_if = {}
251
252     def already_installed(self, cr, uid, context=None):
253         """ For each module, check if it's already installed and if it
254         is return its name
255
256         :returns: a list of the already installed modules in this
257                   installer
258         :rtype: [str]
259         """
260         return map(attrgetter('name'),
261                    self._already_installed(cr, uid, context=context))
262
263     def _already_installed(self, cr, uid, context=None):
264         """ For each module (boolean fields in a res.config.installer),
265         check if it's already installed (either 'to install', 'to upgrade'
266         or 'installed') and if it is return the module's browse_record
267
268         :returns: a list of all installed modules in this installer
269         :rtype: [browse_record]
270         """
271         modules = self.pool.get('ir.module.module')
272
273         selectable = [field for field in self._columns
274                       if type(self._columns[field]) is fields.boolean]
275         return modules.browse(
276             cr, uid,
277             modules.search(cr, uid,
278                            [('name','in',selectable),
279                             ('state','in',['to install', 'installed', 'to upgrade'])],
280                            context=context),
281             context=context)
282
283
284     def modules_to_install(self, cr, uid, ids, context=None):
285         """ selects all modules to install:
286
287         * checked boolean fields
288         * return values of hook methods. Hook methods are of the form
289           ``_if_%(addon_name)s``, and are called if the corresponding
290           addon is marked for installation. They take the arguments
291           cr, uid, ids and context, and return an iterable of addon
292           names
293         * additionals, additionals are setup through the ``_install_if``
294           class variable. ``_install_if`` is a dict of {iterable:iterable}
295           where key and value are iterables of addon names.
296
297           If all the addons in the key are selected for installation
298           (warning: addons added through hooks don't count), then the
299           addons in the value are added to the set of modules to install
300         * not already installed
301         """
302         base = set(module_name
303                    for installer in self.read(cr, uid, ids, context=context)
304                    for module_name, to_install in installer.iteritems()
305                    if module_name != 'id'
306                    if type(self._columns[module_name]) is fields.boolean
307                    if to_install)
308
309         hooks_results = set()
310         for module in base:
311             hook = getattr(self, '_if_%s'%(module), None)
312             if hook:
313                 hooks_results.update(hook(cr, uid, ids, context=None) or set())
314
315         additionals = set(
316             module for requirements, consequences \
317                        in self._install_if.iteritems()
318                    if base.issuperset(requirements)
319                    for module in consequences)
320
321         return (base | hooks_results | additionals).difference(
322                     self.already_installed(cr, uid, context))
323
324     def default_get(self, cr, uid, fields_list, context=None):
325         ''' If an addon is already installed, check it by default
326         '''
327         defaults = super(res_config_installer, self).default_get(
328             cr, uid, fields_list, context=context)
329
330         return dict(defaults,
331                     **dict.fromkeys(
332                         self.already_installed(cr, uid, context=context),
333                         True))
334
335     def fields_get(self, cr, uid, fields=None, context=None, write_access=True):
336         """ If an addon is already installed, set it to readonly as
337         res.config.installer doesn't handle uninstallations of already
338         installed addons
339         """
340         fields = super(res_config_installer, self).fields_get(
341             cr, uid, fields, context, write_access)
342
343         for name in self.already_installed(cr, uid, context=context):
344             if name not in fields:
345                 continue
346             fields[name].update(
347                 readonly=True,
348                 help= ustr(fields[name].get('help', '')) +
349                      _('\n\nThis addon is already installed on your system'))
350         return fields
351
352     def execute(self, cr, uid, ids, context=None):
353         modules = self.pool.get('ir.module.module')
354         to_install = list(self.modules_to_install(
355             cr, uid, ids, context=context))
356         _logger.info('Selecting addons %s to install', to_install)
357         modules.state_update(
358             cr, uid,
359             modules.search(cr, uid, [('name','in',to_install)]),
360             'to install', ['uninstalled'], context=context)
361         cr.commit() #TOFIX: after remove this statement, installation wizard is fail
362         new_db, self.pool = pooler.restart_pool(cr.dbname, update_module=True)
363
364 res_config_installer()
365
366 DEPRECATION_MESSAGE = 'You are using an addon using old-style configuration '\
367     'wizards (ir.actions.configuration.wizard). Old-style configuration '\
368     'wizards have been deprecated.\n'\
369     'The addon should be migrated to res.config objects.'
370 class ir_actions_configuration_wizard(osv.osv_memory):
371     ''' Compatibility configuration wizard
372
373     The old configuration wizard has been replaced by res.config, but in order
374     not to break existing but not-yet-migrated addons, the old wizard was
375     reintegrated and gutted.
376     '''
377     _name='ir.actions.configuration.wizard'
378     _inherit = 'res.config'
379
380     def _next_action_note(self, cr, uid, ids, context=None):
381         next = self._next_action(cr, uid)
382         if next:
383             # if the next one is also an old-style extension, you never know...
384             if next.note:
385                 return next.note
386             return _("Click 'Continue' to configure the next addon...")
387         return _("Your database is now fully configured.\n\n"\
388             "Click 'Continue' and enjoy your OpenERP experience...")
389
390     _columns = {
391         'note': fields.text('Next Wizard', readonly=True),
392         }
393     _defaults = {
394         'note': _next_action_note,
395         }
396
397     def execute(self, cr, uid, ids, context=None):
398         _logger.warning(DEPRECATION_MESSAGE)
399
400 ir_actions_configuration_wizard()
401
402
403
404 class res_config_settings(osv.osv_memory):
405     """ Base configuration wizard for application settings.  It provides support for setting
406         default values, assigning groups to employee users, and installing modules.
407         To make such a 'settings' wizard, define a model like::
408
409             class my_config_wizard(osv.osv_memory):
410                 _name = 'my.settings'
411                 _inherit = 'res.config.settings'
412                 _columns = {
413                     'default_foo': fields.type(..., default_model='my.model'),
414                     'group_bar': fields.boolean(..., group='base.group_user', implied_group='my.group'),
415                     'module_baz': fields.boolean(...),
416                     'other_field': fields.type(...),
417                 }
418
419         The method ``execute`` provides some support based on a naming convention:
420
421         *   For a field like 'default_XXX', ``execute`` sets the (global) default value of
422             the field 'XXX' in the model named by ``default_model`` to the field's value.
423
424         *   For a boolean field like 'group_XXX', ``execute`` adds/removes 'implied_group'
425             to/from the implied groups of 'group', depending on the field's value.
426             By default 'group' is the group Employee.  Groups are given by their xml id.
427
428         *   For a boolean field like 'module_XXX', ``execute`` triggers the immediate
429             installation of the module named 'XXX' if the field has value ``True``.
430
431         *   For the other fields, the method ``execute`` invokes all methods with a name
432             that starts with 'set_'; such methods can be defined to implement the effect
433             of those fields.
434
435         The method ``default_get`` retrieves values that reflect the current status of the
436         fields like 'default_XXX', 'group_XXX' and 'module_XXX'.  It also invokes all methods
437         with a name that starts with 'get_default_'; such methods can be defined to provide
438         current values for other fields.
439     """
440     _name = 'res.config.settings'
441
442     def copy(self, cr, uid, id, values, context=None):
443         raise osv.except_osv(_("Cannot duplicate configuration!"), "")
444
445     def _get_classified_fields(self, cr, uid, context=None):
446         """ return a dictionary with the fields classified by category::
447
448                 {   'default': [('default_foo', 'model', 'foo'), ...],
449                     'group':   [('group_bar', browse_group, browse_implied_group), ...],
450                     'module':  [('module_baz', browse_module), ...],
451                     'other':   ['other_field', ...],
452                 }
453         """
454         ir_model_data = self.pool.get('ir.model.data')
455         ir_module = self.pool.get('ir.module.module')
456         def ref(xml_id):
457             mod, xml = xml_id.split('.', 1)
458             return ir_model_data.get_object(cr, uid, mod, xml, context)
459
460         defaults, groups, modules, others = [], [], [], []
461         for name, field in self._columns.items():
462             if name.startswith('default_') and hasattr(field, 'default_model'):
463                 defaults.append((name, field.default_model, name[8:]))
464             elif name.startswith('group_') and isinstance(field, fields.boolean) and hasattr(field, 'implied_group'):
465                 field_group = getattr(field, 'group', 'base.group_user')
466                 groups.append((name, ref(field_group), ref(field.implied_group)))
467             elif name.startswith('module_') and isinstance(field, fields.boolean):
468                 mod_ids = ir_module.search(cr, uid, [('name', '=', name[7:])])
469                 modules.append((name, ir_module.browse(cr, uid, mod_ids[0], context)))
470             else:
471                 others.append(name)
472
473         return {'default': defaults, 'group': groups, 'module': modules, 'other': others}
474
475     def default_get(self, cr, uid, fields, context=None):
476         ir_values = self.pool.get('ir.values')
477         classified = self._get_classified_fields(cr, uid, context)
478
479         res = super(res_config_settings, self).default_get(cr, uid, fields, context)
480
481         # defaults: take the corresponding default value they set
482         for name, model, field in classified['default']:
483             value = ir_values.get_default(cr, uid, model, field)
484             if value is not None:
485                 res[name] = value
486
487         # groups: which groups are implied by the group Employee
488         for name, group, implied_group in classified['group']:
489             res[name] = implied_group in group.implied_ids
490
491         # modules: which modules are installed/to install
492         for name, module in classified['module']:
493             res[name] = module.state in ('installed', 'to install', 'to upgrade')
494
495         # other fields: call all methods that start with 'get_default_'
496         for method in dir(self):
497             if method.startswith('get_default_'):
498                 res.update(getattr(self, method)(cr, uid, fields, context))
499
500         return res
501
502     def execute(self, cr, uid, ids, context=None):
503         ir_values = self.pool.get('ir.values')
504         ir_model_data = self.pool.get('ir.model.data')
505         ir_module = self.pool.get('ir.module.module')
506         res_groups = self.pool.get('res.groups')
507         classified = self._get_classified_fields(cr, uid, context)
508
509         config = self.browse(cr, uid, ids[0], context)
510
511         # default values fields
512         for name, model, field in classified['default']:
513             ir_values.set_default(cr, uid, model, field, config[name])
514
515         # group fields: modify group / implied groups
516         for name, group, implied_group in classified['group']:
517             if config[name]:
518                 group.write({'implied_ids': [(4, implied_group.id)]})
519             else:
520                 group.write({'implied_ids': [(3, implied_group.id)]})
521                 implied_group.write({'users': [(3, u.id) for u in group.users]})
522
523         # other fields: execute all methods that start with 'set_'
524         for method in dir(self):
525             if method.startswith('set_'):
526                 getattr(self, method)(cr, uid, ids, context)
527
528         # module fields: install/uninstall the selected modules
529         to_install_ids = []
530         to_uninstall_ids = []
531         for name, module in classified['module']:
532             if config[name]:
533                 if module.state == 'uninstalled': to_install_ids.append(module.id)
534             else:
535                 if module.state in ('installed','upgrade'): to_uninstall_ids.append(module.id)
536
537         if to_install_ids or to_uninstall_ids:
538             ir_module.button_uninstall(cr, uid, to_uninstall_ids, context=context)
539             ir_module.button_immediate_install(cr, uid, to_install_ids, context=context)
540
541         config = self.pool.get('res.config').next(cr, uid, [], context=context) or {}
542         if config.get('type') not in ('ir.actions.act_window_close',):
543             return config
544
545         # force client-side reload (update user menu and current view)
546         return {
547             'type': 'ir.actions.client',
548             'tag': 'reload',
549         }
550
551     def cancel(self, cr, uid, ids, context=None):
552         # ignore the current record, and send the action to reopen the view
553         act_window = self.pool.get('ir.actions.act_window')
554         action_ids = act_window.search(cr, uid, [('res_model', '=', self._name)])
555         if action_ids:
556             return act_window.read(cr, uid, action_ids[0], [], context=context)
557         return {}
558     
559     def name_get(self, cr, uid, ids, context=None):
560         """ Override name_get method to return an appropriate configuration wizard
561         name, and not the generated name."""
562         
563         if not ids:
564             return []
565         # name_get may receive int id instead of an id list
566         if isinstance(ids, (int, long)):
567             ids = [ids]
568             
569         act_window = self.pool.get('ir.actions.act_window')
570         action_ids = act_window.search(cr, uid, [('res_model', '=', self._name)], context=context)
571         name = self._name
572         if action_ids:
573             name = act_window.read(cr, uid, action_ids[0], ['name'], context=context)['name']
574         return [(record.id, name) for record in self.browse(cr, uid , ids, context=context)]
575
576 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: