[IMP] ir_translation: added help messages and comments.
[odoo/odoo.git] / bin / addons / base / ir / ir_translation.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 osv import fields, osv
23 import tools
24
25 TRANSLATION_TYPE = [
26     ('field', 'Field'),
27     ('model', 'Object'),
28     ('rml', 'RML  (deprecated - use Report)'), # Pending deprecation - to be replaced by report!
29     ('report', 'Report/Template'),
30     ('selection', 'Selection'),
31     ('view', 'View'),
32     ('wizard_button', 'Wizard Button'),
33     ('wizard_field', 'Wizard Field'),
34     ('wizard_view', 'Wizard View'),
35     ('xsl', 'XSL'),
36     ('help', 'Help'),
37     ('code', 'Code'),
38     ('constraint', 'Constraint'),
39     ('sql_constraint', 'SQL Constraint')
40 ]
41
42 class ir_translation(osv.osv):
43     _name = "ir.translation"
44     _log_access = False
45
46     def _get_language(self, cr, uid, context):
47         lang_obj = self.pool.get('res.lang')
48         lang_ids = lang_obj.search(cr, uid, [('translatable', '=', True)],
49                 context=context)
50         langs = lang_obj.browse(cr, uid, lang_ids, context=context)
51         res = [(lang.code, lang.name) for lang in langs]
52         for lang_dict in tools.scan_languages():
53             if lang_dict not in res:
54                 res.append(lang_dict)
55         return res
56
57     _columns = {
58         'name': fields.char('Field Name', size=128, required=True),
59         'res_id': fields.integer('Resource ID', select=True),
60         'lang': fields.selection(_get_language, string='Language', size=16),
61         'type': fields.selection(TRANSLATION_TYPE, string='Type', size=16, select=True),
62         'src': fields.text('Source'),
63         'value': fields.text('Translation Value'),
64         # These two columns map to ir_model_data.module and ir_model_data.name.
65         # They are used to resolve the res_id above after loading is done.
66         'module': fields.char('Module', size=64, help='Maps to the ir_model_data for which this translation is provided.'),
67         'xml_id': fields.char('XML Id', size=128, help='Maps to the ir_model_data for which this translation is provided.'),
68     }
69
70     def _auto_init(self, cr, context={}):
71         super(ir_translation, self)._auto_init(cr, context)
72
73         # FIXME: there is a size limit on btree indexed values so we can't index src column with normal btree. 
74         cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_translation_ltns',))
75         if cr.fetchone():
76             #temporarily removed: cr.execute('CREATE INDEX ir_translation_ltns ON ir_translation (name, lang, type, src)')
77             cr.execute('DROP INDEX ir_translation_ltns')
78             cr.commit()
79         cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_translation_lts',))
80         if cr.fetchone():
81             #temporarily removed: cr.execute('CREATE INDEX ir_translation_lts ON ir_translation (lang, type, src)')
82             cr.execute('DROP INDEX ir_translation_lts')
83             cr.commit()
84
85         # add separate hash index on src (no size limit on values), as postgres 8.1+ is able to combine separate indexes
86         cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_translation_src_hash_idx',))
87         if not cr.fetchone():
88             cr.execute('CREATE INDEX ir_translation_src_hash_idx ON ir_translation using hash (src)')
89
90         cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_translation_ltn',))
91         if not cr.fetchone():
92             cr.execute('CREATE INDEX ir_translation_ltn ON ir_translation (name, lang, type)')
93             cr.commit()
94
95         cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_translation_mx',))
96         if not cr.fetchone():
97             cr.execute('CREATE INDEX ir_translation_mx ON ir_translation (module, xml_id)')
98             cr.commit()
99
100     @tools.cache(skiparg=3, multi='ids')
101     def _get_ids(self, cr, uid, name, tt, lang, ids):
102         translations = dict.fromkeys(ids, False)
103         if ids:
104             cr.execute('select res_id,value ' \
105                     'from ir_translation ' \
106                     'where lang=%s ' \
107                         'and type=%s ' \
108                         'and name=%s ' \
109                         'and res_id IN %s',
110                     (lang,tt,name,tuple(ids)))
111             for res_id, value in cr.fetchall():
112                 translations[res_id] = value
113         return translations
114
115     def _set_ids(self, cr, uid, name, tt, lang, ids, value, src=None):
116         # clear the caches
117         tr = self._get_ids(cr, uid, name, tt, lang, ids)
118         for res_id in tr:
119             if tr[res_id]:
120                 self._get_source.clear_cache(cr.dbname, uid, name, tt, lang, tr[res_id])
121         self._get_source.clear_cache(cr.dbname, uid, name, tt, lang)
122         self._get_ids.clear_cache(cr.dbname, uid, name, tt, lang, ids)
123
124         cr.execute('delete from ir_translation ' \
125                 'where lang=%s ' \
126                     'and type=%s ' \
127                     'and name=%s ' \
128                     'and res_id IN %s',
129                 (lang,tt,name,tuple(ids),))
130         for id in ids:
131             self.create(cr, uid, {
132                 'lang':lang,
133                 'type':tt,
134                 'name':name,
135                 'res_id':id,
136                 'value':value,
137                 'src':src,
138                 })
139         return len(ids)
140
141     @tools.cache(skiparg=3)
142     def _get_source(self, cr, uid, name, types, lang, source=None):
143         """
144         Returns the translation for the given combination of name, type, language
145         and source. All values passed to this method should be unicode (not byte strings),
146         especially ``source``.
147
148         :param name: identification of the term to translate, such as field name (optional if source is passed)
149         :param types: single string defining type of term to translate (see ``type`` field on ir.translation), or sequence of allowed types (strings)
150         :param lang: language code of the desired translation
151         :param source: optional source term to translate (should be unicode)
152         :rtype: unicode
153         :return: the request translation, or an empty unicode string if no translation was
154                  found and `source` was not passed
155         """
156         # FIXME: should assert that `source` is unicode and fix all callers to always pass unicode
157         # so we can remove the string encoding/decoding.
158         if not lang:
159             return u''
160         if isinstance(types, basestring):
161             types = (types,)
162         if source:
163             query = """SELECT value 
164                        FROM ir_translation 
165                        WHERE lang=%s 
166                         AND type in %s 
167                         AND src=%s"""
168             params = (lang or '', types, tools.ustr(source))
169             if name:
170                 query += " AND name=%s"
171                 params += (tools.ustr(name),)
172             cr.execute(query, params)
173         else:
174             cr.execute("""SELECT value
175                           FROM ir_translation
176                           WHERE lang=%s
177                            AND type in %s
178                            AND name=%s""",
179                     (lang or '', types, tools.ustr(name)))
180         res = cr.fetchone()
181         trad = res and res[0] or u''
182         if source and not trad:
183             return tools.ustr(source)
184         return trad
185
186     def create(self, cursor, user, vals, context=None):
187         if not context:
188             context = {}
189         ids = super(ir_translation, self).create(cursor, user, vals, context=context)
190         for trans_obj in self.read(cursor, user, [ids], ['name','type','res_id','src','lang'], context=context):
191             self._get_source.clear_cache(cursor.dbname, user, trans_obj['name'], trans_obj['type'], trans_obj['lang'], source=trans_obj['src'])
192             self._get_ids.clear_cache(cursor.dbname, user, trans_obj['name'], trans_obj['type'], trans_obj['lang'], [trans_obj['res_id']])
193         return ids
194
195     def write(self, cursor, user, ids, vals, context=None):
196         if not context:
197             context = {}
198         if isinstance(ids, (int, long)):
199             ids = [ids]
200         result = super(ir_translation, self).write(cursor, user, ids, vals, context=context)
201         for trans_obj in self.read(cursor, user, ids, ['name','type','res_id','src','lang'], context=context):
202             self._get_source.clear_cache(cursor.dbname, user, trans_obj['name'], trans_obj['type'], trans_obj['lang'], source=trans_obj['src'])
203             self._get_ids.clear_cache(cursor.dbname, user, trans_obj['name'], trans_obj['type'], trans_obj['lang'], [trans_obj['res_id']])
204         return result
205
206     def unlink(self, cursor, user, ids, context=None):
207         if not context:
208             context = {}
209         if isinstance(ids, (int, long)):
210             ids = [ids]
211         for trans_obj in self.read(cursor, user, ids, ['name','type','res_id','src','lang'], context=context):
212             self._get_source.clear_cache(cursor.dbname, user, trans_obj['name'], trans_obj['type'], trans_obj['lang'], source=trans_obj['src'])
213             self._get_ids.clear_cache(cursor.dbname, user, trans_obj['name'], trans_obj['type'], trans_obj['lang'], [trans_obj['res_id']])
214         result = super(ir_translation, self).unlink(cursor, user, ids, context=context)
215         return result
216
217 ir_translation()
218
219 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
220