[FIX] Gengo - Update modoel ir_translation for gengo and make it working. Works with...
[odoo/odoo.git] / addons / base_gengo / wizard / base_gengo_translations.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Business Applications
5 #    Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
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 uuid
23 import logging
24 import re
25 import time
26
27 from openerp.osv import osv, fields
28 from openerp import tools, SUPERUSER_ID
29 from openerp.tools.translate import _
30
31 _logger = logging.getLogger(__name__)
32
33 try:
34     from gengo import Gengo
35 except ImportError:
36     _logger.warning('Gengo library not found, Gengo features disabled. If you plan to use it, please install the gengo library from http://pypi.python.org/pypi/gengo')
37
38 GENGO_DEFAULT_LIMIT = 20
39
40
41 class base_gengo_translations(osv.osv_memory):
42     GENGO_KEY = "Gengo.UUID"
43
44     _name = 'base.gengo.translations'
45     _columns = {
46         'sync_type': fields.selection([('send', 'Send New Terms'),
47                                        ('receive', 'Receive Translation'),
48                                        ('both', 'Both')], "Sync Type"),
49         'lang_id': fields.many2one('res.lang', 'Language', required=True),
50         'sync_limit': fields.integer("No. of terms to sync"),
51     }
52     _defaults = {
53         'sync_type': 'both',
54         'sync_limit': 20
55     }
56
57     def init(self, cr):
58         icp = self.pool['ir.config_parameter']
59         if not icp.get_param(cr, SUPERUSER_ID, self.GENGO_KEY, default=None):
60             icp.set_param(cr, SUPERUSER_ID, self.GENGO_KEY, str(uuid.uuid4()))
61
62     def get_gengo_key(self, cr):
63         icp = self.pool['ir.config_parameter']
64         return icp.get_param(cr, SUPERUSER_ID, self.GENGO_KEY, default="Undefined")
65
66     def gengo_authentication(self, cr, uid, context=None):
67         '''
68         This method tries to open a connection with Gengo. For that, it uses the Public and Private
69         keys that are linked to the company (given by Gengo on subscription). It returns a tuple with
70          * as first element: a boolean depicting if the authentication was a success or not
71          * as second element: the connection, if it was a success, or the error message returned by
72             Gengo when the connection failed.
73             This error message can either be displayed in the server logs (if the authentication was called
74             by the cron) or in a dialog box (if requested by the user), thus it's important to return it
75             translated.
76         '''
77         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
78         if not user.company_id.gengo_public_key or not user.company_id.gengo_private_key:
79             return (False, _("Gengo `Public Key` or `Private Key` are missing. Enter your Gengo authentication parameters under `Settings > Companies > Gengo Parameters`."))
80         try:
81             gengo = Gengo(
82                 public_key=user.company_id.gengo_public_key.encode('ascii'),
83                 private_key=user.company_id.gengo_private_key.encode('ascii'),
84                 sandbox=user.company_id.gengo_sandbox,
85             )
86             gengo.getAccountStats()
87             return (True, gengo)
88         except Exception, e:
89             _logger.exception('Gengo connection failed')
90             return (False, _("Gengo connection failed with this message:\n``%s``") % e)
91
92     def act_update(self, cr, uid, ids, context=None):
93         '''
94         Function called by the wizard.
95         '''
96         if context is None:
97             context = {}
98
99         flag, gengo = self.gengo_authentication(cr, uid, context=context)
100         if not flag:
101             raise osv.except_osv(_('Gengo Authentication Error'), gengo)
102         for wizard in self.browse(cr, uid, ids, context=context):
103             supported_langs = self.pool.get('ir.translation')._get_all_supported_languages(cr, uid, context=context)
104             language = self.pool.get('ir.translation')._get_gengo_corresponding_language(wizard.lang_id.code)
105             if language not in supported_langs:
106                 raise osv.except_osv(_("Warning"), _('This language is not supported by the Gengo translation services.'))
107
108             ctx = context.copy()
109             ctx['gengo_language'] = wizard.lang_id.id
110             if wizard.sync_limit > 200 or wizard.sync_limit < 1:
111                 raise osv.except_osv(_("Warning"), _('Sync limit should between 1 to 200 for Gengo translation services.'))
112             if wizard.sync_type in ['send', 'both']:
113                 self._sync_request(cr, uid, wizard.sync_limit, context=ctx)
114             if wizard.sync_type in ['receive', 'both']:
115                 self._sync_response(cr, uid, wizard.sync_limit, context=ctx)
116         return {'type': 'ir.actions.act_window_close'}
117
118     def _sync_response(self, cr, uid, limit=GENGO_DEFAULT_LIMIT, context=None):
119         """
120         This method will be called by cron services to get translations from
121         Gengo. It will read translated terms and comments from Gengo and will
122         update respective ir.translation in openerp.
123         """
124         translation_pool = self.pool.get('ir.translation')
125         flag, gengo = self.gengo_authentication(cr, uid, context=context)
126         if not flag:
127             _logger.warning("%s", gengo)
128         else:
129             offset = 0
130             all_translation_ids = translation_pool.search(cr, uid, [('state', '=', 'inprogress'), ('gengo_translation', 'in', ('machine', 'standard', 'pro', 'ultra')), ('order_id', "!=", False)], context=context)
131             while True:
132                 translation_ids = all_translation_ids[offset:offset + limit]
133                 offset += limit
134                 if not translation_ids:
135                     break
136
137                 terms_progress = {
138                     'gengo_order_ids': set(),
139                     'ir_translation_ids': set(),
140                 }
141                 translation_terms = translation_pool.browse(cr, uid, translation_ids, context=context)
142                 for term in translation_terms:
143                     terms_progress['gengo_order_ids'].add(term.order_id)
144                     terms_progress['ir_translation_ids'].add(tools.ustr(term.id))
145
146                 for order_id in terms_progress['gengo_order_ids']:
147                     order_response = gengo.getTranslationOrderJobs(id=order_id)
148                     jobs_approved = order_response.get('response', []).get('order', []).get('jobs_approved', [])
149                     gengo_ids = ','.join(jobs_approved)
150
151                 if gengo_ids:  # Need to check, because getTranslationJobBatch don't catch this case and so call the getTranslationJobs because no ids in url
152                     try:
153                         job_response = gengo.getTranslationJobBatch(id=gengo_ids)
154                     except:
155                         continue
156                     if job_response['opstat'] == 'ok':
157                         for job in job_response['response'].get('jobs', []):
158                             if job.get('custom_data') in terms_progress['ir_translation_ids']:
159                                 self._update_terms_job(cr, uid, job, context=context)
160         return True
161
162     def _update_terms_job(self, cr, uid, job, context=None):
163         translation_pool = self.pool.get('ir.translation')
164         tid = int(job['custom_data'])
165         vals = {}
166         if job.get('status', False) in ('queued', 'available', 'pending', 'reviewable'):
167             vals['state'] = 'inprogress'
168         if job.get('body_tgt', False) and job.get('status', False) == 'approved':
169             vals['value'] = job['body_tgt']
170         if job.get('status', False) in ('approved', 'canceled'):
171             vals['state'] = 'translated'
172         if vals:
173             translation_pool.write(cr, uid, [tid], vals, context=context)
174
175     def _update_terms(self, cr, uid, response, term_ids, context=None):
176         """
177         Update the terms after their translation were requested to Gengo
178         """
179         translation_pool = self.pool.get('ir.translation')
180
181         vals = {
182             'order_id': response.get('order_id', ''),
183             'state': 'inprogress'
184         }
185
186         translation_pool.write(cr, uid, term_ids, vals, context=context)
187         jobs = response.get('jobs', [])
188         if jobs:
189             for t_id, res in jobs.items():
190                 self._update_terms_job(cr, uid, res, context=context)
191
192         return
193
194     def pack_jobs_request(self, cr, uid, term_ids, context=None):
195         ''' prepare the terms that will be requested to gengo and returns them in a dictionary with following format
196             {'jobs': {
197                 'term1.id': {...}
198                 'term2.id': {...}
199                 }
200             }'''
201
202         translation_pool = self.pool.get('ir.translation')
203         jobs = {}
204         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
205         auto_approve = 1 if user.company_id.gengo_auto_approve else 0
206         for term in translation_pool.browse(cr, uid, term_ids, context=context):
207             if re.search(r"\w", term.src or ""):
208                 comment = user.company_id.gengo_comment or ''
209                 if term.gengo_comment:
210                     comment += '\n' + term.gengo_comment
211                 jobs[time.strftime('%Y%m%d%H%M%S') + '-' + str(term.id)] = {
212                     'type': 'text',
213                     'slug': 'Single :: English to ' + term.lang,
214                     'tier': tools.ustr(term.gengo_translation),
215                     'custom_data': str(term.id),
216                     'body_src': term.src,
217                     'lc_src': 'en',
218                     'lc_tgt': translation_pool._get_gengo_corresponding_language(term.lang),
219                     'auto_approve': auto_approve,
220                     'comment': comment,
221                     'callback_url': self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url') + '/website/gengo_callback?pgk=' + self.get_gengo_key(cr)
222                 }
223         return {'jobs': jobs, 'as_group': 0}
224
225     def _send_translation_terms(self, cr, uid, term_ids, context=None):
226         """
227         Send a request to Gengo with all the term_ids in a different job, get the response and update the terms in
228         database accordingly.
229         """
230         flag, gengo = self.gengo_authentication(cr, uid, context=context)
231         if flag:
232             request = self.pack_jobs_request(cr, uid, term_ids, context=context)
233             if request['jobs']:
234                 result = gengo.postTranslationJobs(jobs=request)
235                 if result['opstat'] == 'ok':
236                     self._update_terms(cr, uid, result['response'], term_ids, context=context)
237         else:
238             _logger.error(gengo)
239         return True
240
241     def _sync_request(self, cr, uid, limit=GENGO_DEFAULT_LIMIT, context=None):
242         """
243         This scheduler will send a job request to the gengo , which terms are
244         waiing to be translated and for which gengo_translation is enabled.
245
246         A special key 'gengo_language' can be passed in the context in order to
247         request only translations of that language only. Its value is the language
248         ID in openerp.
249         """
250         if context is None:
251             context = {}
252         language_pool = self.pool.get('res.lang')
253         translation_pool = self.pool.get('ir.translation')
254         domain = [('state', '=', 'to_translate'), ('gengo_translation', 'in', ('machine', 'standard', 'pro', 'ultra')), ('order_id', "=", False)]
255         if context.get('gengo_language', False):
256             lc = language_pool.browse(cr, uid, context['gengo_language'], context=context).code
257             domain.append(('lang', '=', lc))
258
259         all_term_ids = translation_pool.search(cr, uid, domain, context=context)
260         try:
261             offset = 0
262             while True:
263                 #search for the n first terms to translate
264                 term_ids = all_term_ids[offset:offset + limit]
265                 if term_ids:
266                     offset += limit
267                     self._send_translation_terms(cr, uid, term_ids, context=context)
268                     _logger.info("%s Translation terms have been posted to Gengo successfully", len(term_ids))
269                 if not len(term_ids) == limit:
270                     break
271         except Exception, e:
272             _logger.error("%s", e)
273
274 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: