[REM] useless intermediate lambdas in default values
[odoo/odoo.git] / openerp / addons / base / res / res_lang.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 import locale
23 import logging
24 import re
25
26 from osv import fields, osv
27 from locale import localeconv
28 import tools
29 from tools.safe_eval import safe_eval as eval
30 from tools.translate import _
31
32 _logger = logging.getLogger(__name__)
33
34 class lang(osv.osv):
35     _name = "res.lang"
36     _description = "Languages"
37
38     _disallowed_datetime_patterns = tools.DATETIME_FORMATS_MAP.keys()
39     _disallowed_datetime_patterns.remove('%y') # this one is in fact allowed, just not good practice
40
41     def install_lang(self, cr, uid, **args):
42         """
43
44         This method is called from openerp/addons/base/base_data.xml to load
45         some language and set it as the default for every partners. The
46         language is set via tools.config by the RPC 'create' method on the
47         'db' object. This is a fragile solution and something else should be
48         found.
49
50         """
51         lang = tools.config.get('lang')
52         if not lang:
53             return False
54         lang_ids = self.search(cr, uid, [('code','=', lang)])
55         if not lang_ids:
56             self.load_lang(cr, uid, lang)
57         ir_values_obj = self.pool.get('ir.values')
58         default_value = ir_values_obj.get(cr, uid, 'default', False, ['res.partner'])
59         if not default_value:
60             ir_values_obj.set(cr, uid, 'default', False, 'lang', ['res.partner'], lang)
61         return True
62
63     def load_lang(self, cr, uid, lang, lang_name=None):
64         # create the language with locale information
65         fail = True
66         iso_lang = tools.get_iso_codes(lang)
67         for ln in tools.get_locales(lang):
68             try:
69                 locale.setlocale(locale.LC_ALL, str(ln))
70                 fail = False
71                 break
72             except locale.Error:
73                 continue
74         if fail:
75             lc = locale.getdefaultlocale()[0]
76             msg = 'Unable to get information for locale %s. Information from the default locale (%s) have been used.'
77             _logger.warning(msg, lang, lc)
78
79         if not lang_name:
80             lang_name = tools.ALL_LANGUAGES.get(lang, lang)
81
82
83         def fix_xa0(s):
84             """Fix badly-encoded non-breaking space Unicode character from locale.localeconv(),
85                coercing to utf-8, as some platform seem to output localeconv() in their system
86                encoding, e.g. Windows-1252"""
87             if s == '\xa0':
88                 return '\xc2\xa0'
89             return s
90
91         def fix_datetime_format(format):
92             """Python's strftime supports only the format directives
93                that are available on the platform's libc, so in order to
94                be 100% cross-platform we map to the directives required by
95                the C standard (1989 version), always available on platforms
96                with a C standard implementation."""
97             for pattern, replacement in tools.DATETIME_FORMATS_MAP.iteritems():
98                 format = format.replace(pattern, replacement)
99             return str(format)
100
101         lang_info = {
102             'code': lang,
103             'iso_code': iso_lang,
104             'name': lang_name,
105             'translatable': 1,
106             'date_format' : fix_datetime_format(locale.nl_langinfo(locale.D_FMT)),
107             'time_format' : fix_datetime_format(locale.nl_langinfo(locale.T_FMT)),
108             'decimal_point' : fix_xa0(str(locale.localeconv()['decimal_point'])),
109             'thousands_sep' : fix_xa0(str(locale.localeconv()['thousands_sep'])),
110         }
111         lang_id = False
112         try:
113             lang_id = self.create(cr, uid, lang_info)
114         finally:
115             tools.resetlocale()
116         return lang_id
117
118     def _check_format(self, cr, uid, ids, context=None):
119         for lang in self.browse(cr, uid, ids, context=context):
120             for pattern in self._disallowed_datetime_patterns:
121                 if (lang.time_format and pattern in lang.time_format)\
122                     or (lang.date_format and pattern in lang.date_format):
123                     return False
124         return True
125
126     def _get_default_date_format(self, cursor, user, context=None):
127         return '%m/%d/%Y'
128
129     def _get_default_time_format(self, cursor, user, context=None):
130         return '%H:%M:%S'
131
132     _columns = {
133         'name': fields.char('Name', size=64, required=True),
134         'code': fields.char('Locale Code', size=16, required=True, help='This field is used to set/get locales for user'),
135         'iso_code': fields.char('ISO code', size=16, required=False, help='This ISO code is the name of po files to use for translations'),
136         'translatable': fields.boolean('Translatable'),
137         'active': fields.boolean('Active'),
138         'direction': fields.selection([('ltr', 'Left-to-Right'), ('rtl', 'Right-to-Left')], 'Direction',required=True),
139         'date_format':fields.char('Date Format',size=64,required=True),
140         'time_format':fields.char('Time Format',size=64,required=True),
141         'grouping':fields.char('Separator Format',size=64,required=True,help="The Separator Format should be like [,n] where 0 < n :starting from Unit digit.-1 will end the separation. e.g. [3,2,-1] will represent 106500 to be 1,06,500;[1,2,-1] will represent it to be 106,50,0;[3] will represent it as 106,500. Provided ',' as the thousand separator in each case."),
142         'decimal_point':fields.char('Decimal Separator', size=64,required=True),
143         'thousands_sep':fields.char('Thousands Separator',size=64),
144     }
145     _defaults = {
146         'active': 1,
147         'translatable': 0,
148         'direction': 'ltr',
149         'date_format':_get_default_date_format,
150         'time_format':_get_default_time_format,
151         'grouping': '[]',
152         'decimal_point': '.',
153         'thousands_sep': ',',
154     }
155     _sql_constraints = [
156         ('name_uniq', 'unique (name)', 'The name of the language must be unique !'),
157         ('code_uniq', 'unique (code)', 'The code of the language must be unique !'),
158     ]
159
160     _constraints = [
161         (_check_format, 'Invalid date/time format directive specified. Please refer to the list of allowed directives, displayed when you edit a language.', ['time_format', 'date_format'])
162     ]
163
164     @tools.ormcache(skiparg=3)
165     def _lang_data_get(self, cr, uid, lang_id, monetary=False):
166         conv = localeconv()
167         lang_obj = self.browse(cr, uid, lang_id)
168         thousands_sep = lang_obj.thousands_sep or conv[monetary and 'mon_thousands_sep' or 'thousands_sep']
169         decimal_point = lang_obj.decimal_point
170         grouping = lang_obj.grouping
171         return (grouping, thousands_sep, decimal_point)
172
173     def write(self, cr, uid, ids, vals, context=None):
174         for lang_id in ids :
175             self._lang_data_get.clear_cache(self)
176         return super(lang, self).write(cr, uid, ids, vals, context)
177
178     def unlink(self, cr, uid, ids, context=None):
179         if context is None:
180             context = {}
181         languages = self.read(cr, uid, ids, ['code','active'], context=context)
182         for language in languages:
183             ctx_lang = context.get('lang')
184             if language['code']=='en_US':
185                 raise osv.except_osv(_('User Error'), _("Base Language 'en_US' can not be deleted !"))
186             if ctx_lang and (language['code']==ctx_lang):
187                 raise osv.except_osv(_('User Error'), _("You cannot delete the language which is User's Preferred Language !"))
188             if language['active']:
189                 raise osv.except_osv(_('User Error'), _("You cannot delete the language which is Active !\nPlease de-activate the language first."))
190             trans_obj = self.pool.get('ir.translation')
191             trans_ids = trans_obj.search(cr, uid, [('lang','=',language['code'])], context=context)
192             trans_obj.unlink(cr, uid, trans_ids, context=context)
193         return super(lang, self).unlink(cr, uid, ids, context=context)
194
195     def format(self, cr, uid, ids, percent, value, grouping=False, monetary=False, context=None):
196         """ Format() will return the language-specific output for float values"""
197
198         if percent[0] != '%':
199             raise ValueError("format() must be given exactly one %char format specifier")
200
201         lang_grouping, thousands_sep, decimal_point = self._lang_data_get(cr, uid, ids[0], monetary)
202         eval_lang_grouping = eval(lang_grouping)
203
204         formatted = percent % value
205         # floats and decimal ints need special action!
206         if percent[-1] in 'eEfFgG':
207             seps = 0
208             parts = formatted.split('.')
209
210             if grouping:
211                 parts[0], seps = intersperse(parts[0], eval_lang_grouping, thousands_sep)
212
213             formatted = decimal_point.join(parts)
214             while seps:
215                 sp = formatted.find(' ')
216                 if sp == -1: break
217                 formatted = formatted[:sp] + formatted[sp+1:]
218                 seps -= 1
219         elif percent[-1] in 'diu':
220             if grouping:
221                 formatted = intersperse(formatted, eval_lang_grouping, thousands_sep)[0]
222
223         return formatted
224
225 #    import re, operator
226 #    _percent_re = re.compile(r'%(?:\((?P<key>.*?)\))?'
227 #                             r'(?P<modifiers>[-#0-9 +*.hlL]*?)[eEfFgGdiouxXcrs%]')
228
229 lang()
230
231 def original_group(s, grouping, thousands_sep=''):
232
233     if not grouping:
234         return (s, 0)
235
236     result = ""
237     seps = 0
238     spaces = ""
239
240     if s[-1] == ' ':
241         sp = s.find(' ')
242         spaces = s[sp:]
243         s = s[:sp]
244
245     while s and grouping:
246         # if grouping is -1, we are done
247         if grouping[0] == -1:
248             break
249         # 0: re-use last group ad infinitum
250         elif grouping[0] != 0:
251             #process last group
252             group = grouping[0]
253             grouping = grouping[1:]
254         if result:
255             result = s[-group:] + thousands_sep + result
256             seps += 1
257         else:
258             result = s[-group:]
259         s = s[:-group]
260         if s and s[-1] not in "0123456789":
261             # the leading string is only spaces and signs
262             return s + result + spaces, seps
263     if not result:
264         return s + spaces, seps
265     if s:
266         result = s + thousands_sep + result
267         seps += 1
268     return result + spaces, seps
269
270 def split(l, counts):
271     """
272
273     >>> split("hello world", [])
274     ['hello world']
275     >>> split("hello world", [1])
276     ['h', 'ello world']
277     >>> split("hello world", [2])
278     ['he', 'llo world']
279     >>> split("hello world", [2,3])
280     ['he', 'llo', ' world']
281     >>> split("hello world", [2,3,0])
282     ['he', 'llo', ' wo', 'rld']
283     >>> split("hello world", [2,-1,3])
284     ['he', 'llo world']
285
286     """
287     res = []
288     saved_count = len(l) # count to use when encoutering a zero
289     for count in counts:
290         if not l:
291             break
292         if count == -1:
293             break
294         if count == 0:
295             while l:
296                 res.append(l[:saved_count])
297                 l = l[saved_count:]
298             break
299         res.append(l[:count])
300         l = l[count:]
301         saved_count = count
302     if l:
303         res.append(l)
304     return res
305
306 intersperse_pat = re.compile('([^0-9]*)([^ ]*)(.*)')
307
308 def intersperse(string, counts, separator=''):
309     """
310
311     See the asserts below for examples.
312
313     """
314     left, rest, right = intersperse_pat.match(string).groups()
315     def reverse(s): return s[::-1]
316     splits = split(reverse(rest), counts)
317     res = separator.join(map(reverse, reverse(splits)))
318     return left + res + right, len(splits) > 0 and len(splits) -1 or 0
319
320 # TODO rewrite this with a unit test library
321 def _group_examples():
322     for g in [original_group, intersperse]:
323         # print "asserts on", g.func_name
324         assert g("", []) == ("", 0)
325         assert g("0", []) == ("0", 0)
326         assert g("012", []) == ("012", 0)
327         assert g("1", []) == ("1", 0)
328         assert g("12", []) == ("12", 0)
329         assert g("123", []) == ("123", 0)
330         assert g("1234", []) == ("1234", 0)
331         assert g("123456789", []) == ("123456789", 0)
332         assert g("&ab%#@1", []) == ("&ab%#@1", 0)
333
334         assert g("0", []) == ("0", 0)
335         assert g("0", [1]) == ("0", 0)
336         assert g("0", [2]) == ("0", 0)
337         assert g("0", [200]) == ("0", 0)
338
339         # breaks original_group:
340         if g.func_name == 'intersperse':
341             assert g("12345678", [0], '.') == ('12345678', 0)
342             assert g("", [1], '.') == ('', 0)
343         assert g("12345678", [1], '.') == ('1234567.8', 1)
344         assert g("12345678", [1], '.') == ('1234567.8', 1)
345         assert g("12345678", [2], '.') == ('123456.78', 1)
346         assert g("12345678", [2,1], '.') == ('12345.6.78', 2)
347         assert g("12345678", [2,0], '.') == ('12.34.56.78', 3)
348         assert g("12345678", [-1,2], '.') == ('12345678', 0)
349         assert g("12345678", [2,-1], '.') == ('123456.78', 1)
350         assert g("12345678", [2,0,1], '.') == ('12.34.56.78', 3)
351         assert g("12345678", [2,0,0], '.') == ('12.34.56.78', 3)
352         assert g("12345678", [2,0,-1], '.') == ('12.34.56.78', 3)
353         assert g("12345678", [3,3,3,3], '.') == ('12.345.678', 2)
354
355     assert original_group("abc1234567xy", [2], '.') == ('abc1234567.xy', 1)
356     assert original_group("abc1234567xy8", [2], '.') == ('abc1234567xy8', 0) # difference here...
357     assert original_group("abc12", [3], '.') == ('abc12', 0)
358     assert original_group("abc12", [2], '.') == ('abc12', 0)
359     assert original_group("abc12", [1], '.') == ('abc1.2', 1)
360
361     assert intersperse("abc1234567xy", [2], '.') == ('abc1234567.xy', 1)
362     assert intersperse("abc1234567xy8", [2], '.') == ('abc1234567x.y8', 1) # ... w.r.t. here.
363     assert intersperse("abc12", [3], '.') == ('abc12', 0)
364     assert intersperse("abc12", [2], '.') == ('abc12', 0)
365     assert intersperse("abc12", [1], '.') == ('abc1.2', 1)
366
367
368 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: