[fix] Talk of installing addons, not modules
[odoo/odoo.git] / bin / 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
22 from operator import attrgetter
23
24 from osv import osv, fields
25 from tools.translate import _
26 import netsvc
27 import pooler
28
29 class res_config_configurable(osv.osv_memory):
30     ''' Base classes for new-style configuration items
31
32     Configuration items should inherit from this class, implement
33     the execute method (and optionally the cancel one) and have
34     their view inherit from the related res_config_view_base view.
35     '''
36     _name = 'res.config'
37     logger = netsvc.Logger()
38
39     def _progress(self, cr, uid, context=None):
40         total = self.pool.get('ir.actions.todo')\
41             .search_count(cr, uid, [], context)
42         open = self.pool.get('ir.actions.todo')\
43             .search_count(cr, uid, [('active','=',True),
44                                     ('state','<>','open')],
45                           context)
46         if total:
47             return round(open*100./total)
48         return 100.
49
50     _columns = dict(
51         progress=fields.float('Configuration Progress', readonly=True),
52         )
53     _defaults = dict(
54         progress=_progress
55         )
56
57     def _next_action(self, cr, uid):
58         todos = self.pool.get('ir.actions.todo')
59         self.logger.notifyChannel('actions', netsvc.LOG_INFO,
60                                   'getting next %s' % todos)
61         active_todos = todos.search(cr, uid, [('state','=','open'),
62                                               ('active','=',True)],
63                                     limit=1, context=None)
64         if active_todos:
65             return todos.browse(cr, uid, active_todos[0], context=None)
66         return None
67
68     def _next(self, cr, uid):
69         self.logger.notifyChannel('actions', netsvc.LOG_INFO,
70                                   'getting next operation')
71         next = self._next_action(cr, uid)
72         self.logger.notifyChannel('actions', netsvc.LOG_INFO,
73                                   'next action is %s' % next)
74         if next:
75             self.pool.get('ir.actions.todo').write(cr, uid, next.id, {
76                     'state':'done',
77                     }, context=None)
78             action = next.action_id
79             return {
80                 'view_mode': action.view_mode,
81                 'view_type': action.view_type,
82                 'view_id': action.view_id and [action.view_id.id] or False,
83                 'res_model': action.res_model,
84                 'type': action.type,
85                 'target': action.target,
86                 }
87         self.logger.notifyChannel(
88             'actions', netsvc.LOG_INFO,
89             'all configuration actions have been executed')
90
91         current_user_menu = self.pool.get('res.users')\
92             .browse(cr, uid, uid).menu_id
93         # return the action associated with the menu
94         return self.pool.get(current_user_menu.type)\
95             .read(cr, uid, current_user_menu.id)
96     def next(self, cr, uid, ids, context=None):
97         return self._next(cr, uid)
98
99     def execute(self, cr, uid, ids, context=None):
100         raise NotImplementedError(
101             'Configuration items need to implement execute')
102     def cancel(self, cr, uid, ids, context=None):
103         pass
104
105     def action_next(self, cr, uid, ids, context=None):
106         next = self.execute(cr, uid, ids, context=None)
107         if next: return next
108         return self.next(cr, uid, ids, context=context)
109
110     def action_skip(self, cr, uid, ids, context=None):
111         next = self.cancel(cr, uid, ids, context=None)
112         if next: return next
113         return self.next(cr, uid, ids, context=context)
114 res_config_configurable()
115
116 class res_config_installer(osv.osv_memory):
117     ''' New-style configuration base specialized for modules selection
118     and installation.
119     '''
120     _name = 'res.config.installer'
121     _inherit = 'res.config'
122
123     _install_if = {}
124
125     def _already_installed(self, cr, uid, context=None):
126         """ For each module (boolean fields in a res.config.installer),
127         check if it's already installed (neither uninstallable nor uninstalled)
128         and if it is, check it by default
129         """
130         modules = self.pool.get('ir.module.module')
131
132         selectable = [field for field in self._columns
133                       if type(self._columns[field]) is fields.boolean]
134         return modules.browse(
135             cr, uid,
136             modules.search(cr, uid,
137                            [('name','in',selectable),
138                             ('state','not in',['uninstallable', 'uninstalled'])],
139                            context=context),
140             context=context)
141
142
143     def _modules_to_install(self, cr, uid, ids, context=None):
144         """ selects all modules to install:
145
146         * checked boolean fields
147         * return values of hook methods. Hook methods are of the form
148           ``_if_%(addon_name)s``, and are called if the corresponding
149           addon is marked for installation. They take the arguments
150           cr, uid, ids and context, and return an iterable of addon
151           names
152         * additionals, additionals are setup through the ``_install_if``
153           class variable. ``_install_if`` is a dict of {iterable:iterable}
154           where key and value are iterables of addon names.
155
156           If all the addons in the key are selected for installation
157           (warning: addons added through hooks don't count), then the
158           addons in the value are added to the set of modules to install
159         * not already installed
160         """
161         base = set(module_name
162                    for installer in self.read(cr, uid, ids, context=context)
163                    for module_name, to_install in installer.iteritems()
164                    if module_name != 'id'
165                    if type(self._columns[module_name]) is fields.boolean
166                    if to_install)
167
168         hooks_results = set()
169         for module in base:
170             hook = getattr(self, '_if_%s'%(module), None)
171             if hook:
172                 hooks_results.update(hook(cr, uid, ids, context=None) or set())
173
174         additionals = set(
175             module for requirements, consequences \
176                        in self._install_if.iteritems()
177                    if base.issuperset(requirements)
178                    for module in consequences)
179
180         return (base | hooks_results | additionals) - set(
181             map(attrgetter('name'), self._already_installed(cr, uid, context)))
182
183     def default_get(self, cr, uid, fields_list, context=None):
184         ''' If an addon is already installed, check it by default
185         '''
186         defaults = super(res_config_installer, self).default_get(
187             cr, uid, fields_list, context=context)
188
189         return dict(defaults,
190                     **dict.fromkeys(
191                         map(attrgetter('name'),
192                             self._already_installed(cr, uid, context=context)),
193                         True))
194
195     def fields_get(self, cr, uid, fields=None, context=None, read_access=True):
196         """ If an addon is already installed, set it to readonly as
197         res.config.installer doesn't handle uninstallations of already
198         installed addons
199         """
200         fields = super(res_config_installer, self).fields_get(
201             cr, uid, fields, context, read_access)
202
203         for module in self._already_installed(cr, uid, context=context):
204             fields[module.name].update(
205                 readonly=True,
206                 help=fields[module.name].get('help', '') +
207                      '\n\nThis addon is already installed on your system')
208
209         return fields
210
211     def execute(self, cr, uid, ids, context=None):
212         modules = self.pool.get('ir.module.module')
213         to_install = list(self._modules_to_install(
214             cr, uid, ids, context=context))
215         self.logger.notifyChannel(
216             'installer', netsvc.LOG_INFO,
217             'Selecting addons %s to install'%to_install)
218         modules.state_update(
219             cr, uid,
220             modules.search(cr, uid, [('name','in',to_install)]),
221             'to install', ['uninstalled'], context=context)
222         cr.commit()
223
224         pooler.restart_pool(cr.dbname, update_module=True)
225 res_config_installer()
226
227 DEPRECATION_MESSAGE = 'You are using an addon using old-style configuration '\
228     'wizards (ir.actions.configuration.wizard). Old-style configuration '\
229     'wizards have been deprecated.\n'\
230     'The addon should be migrated to res.config objects.'
231 class ir_actions_configuration_wizard(osv.osv_memory):
232     ''' Compatibility configuration wizard
233
234     The old configuration wizard has been replaced by res.config, but in order
235     not to break existing but not-yet-migrated addons, the old wizard was
236     reintegrated and gutted.
237     '''
238     _name='ir.actions.configuration.wizard'
239     _inherit = 'res.config'
240
241     def _next_action_note(self, cr, uid, ids, context=None):
242         next = self._next_action(cr, uid)
243         if next:
244             # if the next one is also an old-style extension, you never know...
245             if next.note:
246                 return next.note
247             return "Click 'Continue' to configure the next addon..."
248         return "Your database is now fully configured.\n\n"\
249             "Click 'Continue' and enjoy your OpenERP experience..."
250
251     _columns = {
252         'note': fields.text('Next Wizard', readonly=True),
253         }
254     _defaults = {
255         'note': _next_action_note,
256         }
257
258     def execute(self, cr, uid, ids, context=None):
259         self.logger.notifyChannel(
260             'configuration', netsvc.LOG_WARNING, DEPRECATION_MESSAGE)
261
262 ir_actions_configuration_wizard()
263
264 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: