diff --git a/addons/account/account.py b/addons/account/account.py index 01a4d851de8..2fbc32b1bfd 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -3187,11 +3187,14 @@ class wizard_multi_charts_accounts(osv.osv_memory): def _get_analytic_journal(journal_type): # Get the analytic journal data = False - if journal_type in ('sale', 'sale_refund'): - data = obj_data.get_object_reference(cr, uid, 'account', 'analytic_journal_sale') - elif journal_type in ('purchase', 'purchase_refund'): - pass - elif journal_type == 'general': + try: + if journal_type in ('sale', 'sale_refund'): + data = obj_data.get_object_reference(cr, uid, 'account', 'analytic_journal_sale') + elif journal_type in ('purchase', 'purchase_refund'): + data = obj_data.get_object_reference(cr, uid, 'account', 'exp') + elif journal_type == 'general': + pass + except ValueError: pass return data and data[1] or False diff --git a/addons/account/wizard/account_reconcile.py b/addons/account/wizard/account_reconcile.py index 0d5a3525af4..fb3b6f18251 100644 --- a/addons/account/wizard/account_reconcile.py +++ b/addons/account/wizard/account_reconcile.py @@ -23,6 +23,7 @@ import time from openerp.osv import fields, osv from openerp.tools.translate import _ +from openerp.tools.float_utils import float_round import openerp.addons.decimal_precision as dp class account_move_line_reconcile(osv.osv_memory): @@ -64,7 +65,11 @@ class account_move_line_reconcile(osv.osv_memory): credit += line.credit debit += line.debit account_id = line.account_id.id - return {'trans_nbr': count, 'account_id': account_id, 'credit': credit, 'debit': debit, 'writeoff': debit - credit} + precision = self.pool['decimal.precision'].precision_get(cr, uid, 'Account') + writeoff = float_round(debit-credit, precision_digits=precision) + credit = float_round(credit, precision_digits=precision) + debit = float_round(debit, precision_digits=precision) + return {'trans_nbr': count, 'account_id': account_id, 'credit': credit, 'debit': debit, 'writeoff': writeoff} def trans_rec_addendum_writeoff(self, cr, uid, ids, context=None): return self.pool.get('account.move.line.reconcile.writeoff').trans_rec_addendum(cr, uid, ids, context) diff --git a/addons/account/wizard/account_reconcile_view.xml b/addons/account/wizard/account_reconcile_view.xml index 184ce1aaab0..9d4d2684a8e 100644 --- a/addons/account/wizard/account_reconcile_view.xml +++ b/addons/account/wizard/account_reconcile_view.xml @@ -17,8 +17,8 @@ diff --git a/addons/account_analytic_analysis/account_analytic_analysis_view.xml b/addons/account_analytic_analysis/account_analytic_analysis_view.xml index 8e08f90eb02..f5b89db7089 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis_view.xml +++ b/addons/account_analytic_analysis/account_analytic_analysis_view.xml @@ -219,7 +219,7 @@ - + - [[ format(get_text(o,data['form']['followup_id'])) ]] +
[[ format(get_text(o,data['form']['followup_id'])) ]]
diff --git a/addons/auth_ldap/users_ldap.py b/addons/auth_ldap/users_ldap.py index 1e957a184c3..7b3d88af154 100644 --- a/addons/auth_ldap/users_ldap.py +++ b/addons/auth_ldap/users_ldap.py @@ -99,6 +99,9 @@ class CompanyLDAP(osv.osv): filter = filter_format(conf['ldap_filter'], (login,)) try: results = self.query(conf, filter) + + # Get rid of (None, attrs) for searchResultReference replies + results = [i for i in results if i[0]] if results and len(results) == 1: dn = results[0][0] conn = self.connect(conf) diff --git a/addons/auth_oauth/res_users.py b/addons/auth_oauth/res_users.py index b1c7e214f12..f58473b24a4 100644 --- a/addons/auth_oauth/res_users.py +++ b/addons/auth_oauth/res_users.py @@ -1,6 +1,6 @@ import logging -import urllib +import werkzeug.urls import urlparse import urllib2 import simplejson @@ -25,7 +25,7 @@ class res_users(osv.Model): ] def _auth_oauth_rpc(self, cr, uid, endpoint, access_token, context=None): - params = urllib.urlencode({'access_token': access_token}) + params = werkzeug.url_encode({'access_token': access_token}) if urlparse.urlparse(endpoint)[4]: url = endpoint + '&' + params else: diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py index f0862601611..a8efc343420 100644 --- a/addons/calendar/calendar.py +++ b/addons/calendar/calendar.py @@ -1330,13 +1330,13 @@ class calendar_event(osv.Model): new_id = get_real_ids(arg[2]) new_arg = (arg[0], arg[1], new_id) new_args.append(new_arg) - #offset, limit and count must be treated separately as we may need to deal with virtual ids - if context.get('virtual_id', True): - res = super(calendar_event, self).search(cr, uid, new_args, offset=0, limit=0, order=None, context=context, count=False) - res = self.get_recurrent_ids(cr, uid, res, args, order=order, context=context) - else: - res = super(calendar_event, self).search(cr, uid, new_args, offset=0, limit=0, order=order, context=context, count=False) + if not context.get('virtual_id', True): + return super(calendar_event, self).search(cr, uid, new_args, offset=offset, limit=limit, order=order, context=context, count=count) + + # offset, limit, order and count must be treated separately as we may need to deal with virtual ids + res = super(calendar_event, self).search(cr, uid, new_args, offset=0, limit=0, order=None, context=context, count=False) + res = self.get_recurrent_ids(cr, uid, res, args, order=order, context=context) if count: return len(res) elif limit: diff --git a/addons/calendar/calendar_view.xml b/addons/calendar/calendar_view.xml index 93649d046a1..01c4b596592 100644 --- a/addons/calendar/calendar_view.xml +++ b/addons/calendar/calendar_view.xml @@ -313,7 +313,7 @@ Meetings calendar.event form,calendar,tree,gantt - + diff --git a/addons/decimal_precision/decimal_precision.py b/addons/decimal_precision/decimal_precision.py index 27ce5641559..9af09704b61 100644 --- a/addons/decimal_precision/decimal_precision.py +++ b/addons/decimal_precision/decimal_precision.py @@ -50,9 +50,9 @@ class decimal_precision(orm.Model): self.precision_get.clear_cache(self) for obj in self.pool.obj_list(): for colname, col in self.pool.get(obj)._columns.items(): - if isinstance(col, (fields.float, fields.function)): + if hasattr(col, 'digits_change'): col.digits_change(cr) - RegistryManager.signal_registry_change(cr.dbname) + RegistryManager.signal_caches_change(cr.dbname) def create(self, cr, uid, data, context=None): res = super(decimal_precision, self).create(cr, uid, data, context=context) diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 4a2d6e5e1ec..514ba10f2f6 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -262,6 +262,7 @@ class abstracted_fs(object): if path == '/' and mode in ('list', 'cwd'): return (None, None, None ) + if path == '..': path = self.cwd + '/..' path = _to_unicode(os.path.normpath(path)) # again, for '/db/../ss' if path == '.': path = '' diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index 29d1cb3a66d..fcfe869da90 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -417,8 +417,8 @@ class email_template(osv.osv): # create a mail_mail based on values, without attachments values = self.generate_email(cr, uid, template_id, res_id, context=context) - assert values.get('email_from'), 'email_from is missing or empty after template rendering, send_mail() cannot proceed' - + if not values.get('email_from'): + raise osv.except_osv(_('Warning!'),_("Sender email is missing or empty after template rendering. Specify one to deliver your message")) # process partner_to field that is a comma separated list of partner_ids -> recipient_ids # NOTE: only usable if force_send is True, because otherwise the value is # not stored on the mail_mail, and therefore lost -> fixed in v8 diff --git a/addons/google_account/google_account.py b/addons/google_account/google_account.py index 0ecade3554e..d42b0b67e59 100644 --- a/addons/google_account/google_account.py +++ b/addons/google_account/google_account.py @@ -24,7 +24,7 @@ from openerp import SUPERUSER_ID from openerp.tools.translate import _ from openerp.addons.web.http import request -import urllib +import werkzeug.urls import urllib2 import simplejson @@ -43,7 +43,7 @@ class google_service(osv.osv_memory): #Get the Refresh Token From Google And store it in ir.config_parameter headers = {"Content-type": "application/x-www-form-urlencoded"} data = dict(code=authorization_code, client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, grant_type="authorization_code") - data = urllib.urlencode(data) + data = werkzeug.url_encode(data) try: req = urllib2.Request("https://accounts.google.com/o/oauth2/token", data, headers) content = urllib2.urlopen(req).read() @@ -62,7 +62,7 @@ class google_service(osv.osv_memory): 'response_type': 'code', 'client_id': ir_config.get_param(cr, SUPERUSER_ID, 'google_%s_client_id' % service), } - uri = 'https://accounts.google.com/o/oauth2/auth?%s' % urllib.urlencode(params) + uri = 'https://accounts.google.com/o/oauth2/auth?%s' % werkzeug.url_encode(params) return uri #If no scope is passed, we use service by default to get a default scope @@ -83,7 +83,7 @@ class google_service(osv.osv_memory): 'access_type':'offline' } - uri = self.get_uri_oauth(a='auth') + "?%s" % urllib.urlencode(params) + uri = self.get_uri_oauth(a='auth') + "?%s" % werkzeug.url_encode(params) return uri def _get_google_token_json(self, cr, uid, authorize_code, service, context=None): @@ -103,7 +103,7 @@ class google_service(osv.osv_memory): headers = {"content-type": "application/x-www-form-urlencoded"} try: - data = urllib.urlencode(params) + data = werkzeug.url_encode(params) req = urllib2.Request(self.get_uri_oauth(a='token'), data, headers) content = urllib2.urlopen(req).read() @@ -128,7 +128,7 @@ class google_service(osv.osv_memory): headers = {"content-type": "application/x-www-form-urlencoded"} try: - data = urllib.urlencode(params) + data = werkzeug.url_encode(params) req = urllib2.Request(self.get_uri_oauth(a='token'), data, headers) content = urllib2.urlopen(req).read() res = simplejson.loads(content) @@ -139,12 +139,12 @@ class google_service(osv.osv_memory): def _do_request(self,cr,uid,uri,params={},headers={},type='POST', context=None): - _logger.debug("Uri: %s - Type : %s - Headers: %s - Params : %s !" % (uri,type,headers,urllib.urlencode(params) if type =='GET' else params)) + _logger.debug("Uri: %s - Type : %s - Headers: %s - Params : %s !" % (uri,type,headers,werkzeug.url_encode(params) if type =='GET' else params)) res = False try: if type.upper() == 'GET' or type.upper() == 'DELETE': - data = urllib.urlencode(params) + data = werkzeug.url_encode(params) req = urllib2.Request(self.get_uri_api() + uri + "?" + data) elif type.upper() == 'POST' or type.upper() == 'PATCH' or type.upper() == 'PUT': req = urllib2.Request(self.get_uri_api() + uri, params, headers) diff --git a/addons/google_drive/google_drive.py b/addons/google_drive/google_drive.py index f3607094040..81e1a49b785 100644 --- a/addons/google_drive/google_drive.py +++ b/addons/google_drive/google_drive.py @@ -23,7 +23,7 @@ from openerp import SUPERUSER_ID from openerp.osv import fields, osv from openerp.tools.translate import _ -import urllib +import werkzeug.urls import urllib2 import json import re @@ -70,7 +70,7 @@ class config(osv.Model): google_drive_client_secret = ir_config.get_param(cr, SUPERUSER_ID, 'google_drive_client_secret') #For Getting New Access Token With help of old Refresh Token - data = urllib.urlencode(dict(client_id=google_drive_client_id, + data = werkzeug.url_encode(dict(client_id=google_drive_client_id, refresh_token=google_drive_refresh_token, client_secret=google_drive_client_secret, grant_type="refresh_token", diff --git a/addons/google_spreadsheet/google_spreadsheet.py b/addons/google_spreadsheet/google_spreadsheet.py index 81ec536b6f2..4c24a46a404 100644 --- a/addons/google_spreadsheet/google_spreadsheet.py +++ b/addons/google_spreadsheet/google_spreadsheet.py @@ -23,7 +23,7 @@ import simplejson import logging from lxml import etree import re -import urllib +import werkzeug.urls import urllib2 from openerp.osv import osv @@ -89,7 +89,7 @@ class config(osv.osv): try: req = urllib2.Request( - 'https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/batch?%s' % (spreadsheet_key, urllib.urlencode({'v': 3, 'access_token': access_token})), + 'https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/batch?%s' % (spreadsheet_key, werkzeug.url_encode({'v': 3, 'access_token': access_token})), data=request, headers={'content-type': 'application/atom+xml', 'If-Match': '*'}) urllib2.urlopen(req) diff --git a/addons/hw_escpos/controllers/main.py b/addons/hw_escpos/controllers/main.py index 85309ea31c9..16df99a5f77 100644 --- a/addons/hw_escpos/controllers/main.py +++ b/addons/hw_escpos/controllers/main.py @@ -21,6 +21,7 @@ except ImportError: try: from .. import escpos from ..escpos import printer + from ..escpos import supported_devices except ImportError: escpos = printer = None @@ -39,21 +40,16 @@ class EscposDriver(Thread): self.queue = Queue() self.status = {'status':'connecting', 'messages':[]} - self.supported_printers = [ - { 'vendor' : 0x04b8, 'product' : 0x0e03, 'name' : 'Epson TM-T20' }, - { 'vendor' : 0x04b8, 'product' : 0x0202, 'name' : 'Epson TM-T70' }, - ] - - def connected_usb_devices(self,devices): + def connected_usb_devices(self): connected = [] - for device in devices: + for device in supported_devices.device_list: if usb.core.find(idVendor=device['vendor'], idProduct=device['product']) != None: connected.append(device) return connected def get_escpos_printer(self): try: - printers = self.connected_usb_devices(self.supported_printers) + printers = self.connected_usb_devices() if len(printers) > 0: self.set_status('connected','Connected to '+printers[0]['name']) return escpos.printer.Usb(printers[0]['vendor'], printers[0]['product']) @@ -267,12 +263,12 @@ hw_proxy.drivers['escpos'] = driver class EscposProxy(hw_proxy.Proxy): - @http.route('/hw_proxy/open_cashbox', type='json', auth='admin') + @http.route('/hw_proxy/open_cashbox', type='json', auth='none') def open_cashbox(self): _logger.info('ESC/POS: OPEN CASHBOX') driver.push_task('cashbox') - @http.route('/hw_proxy/print_receipt', type='json', auth='admin') + @http.route('/hw_proxy/print_receipt', type='json', auth='none') def print_receipt(self, receipt): _logger.info('ESC/POS: PRINT RECEIPT') driver.push_task('receipt',receipt) diff --git a/addons/hw_escpos/escpos/__init__.py b/addons/hw_escpos/escpos/__init__.py index 22a5af61029..3fdeddee28b 100644 --- a/addons/hw_escpos/escpos/__init__.py +++ b/addons/hw_escpos/escpos/__init__.py @@ -1 +1 @@ -__all__ = ["constants","escpos","exceptions","printer"] +__all__ = ["constants","escpos","exceptions","printer","supported_devices"] diff --git a/addons/hw_escpos/escpos/supported_devices.py b/addons/hw_escpos/escpos/supported_devices.py new file mode 100644 index 00000000000..2c6a6be2d07 --- /dev/null +++ b/addons/hw_escpos/escpos/supported_devices.py @@ -0,0 +1,10 @@ +#!/usr/bin/python + +# This is a list of esc/pos compatible usb printers. The vendor and product ids can be found by +# typing lsusb in a linux terminal, this will give you the ids in the form ID VENDOR:PRODUCT + +device_list = [ + { 'vendor' : 0x04b8, 'product' : 0x0e03, 'name' : 'Epson TM-T20' }, + { 'vendor' : 0x04b8, 'product' : 0x0202, 'name' : 'Epson TM-T70' }, +] + diff --git a/addons/hw_proxy/controllers/main.py b/addons/hw_proxy/controllers/main.py index 3ea2de4424b..adb59a072b3 100644 --- a/addons/hw_proxy/controllers/main.py +++ b/addons/hw_proxy/controllers/main.py @@ -33,7 +33,7 @@ class Proxy(http.Controller): statuses[driver] = drivers[driver].get_status() return statuses - @http.route('/hw_proxy/hello', type='http', auth='admin') + @http.route('/hw_proxy/hello', type='http', auth='none') def hello(self): return request.make_response('ping', { 'Cache-Control': 'no-cache', @@ -42,11 +42,11 @@ class Proxy(http.Controller): 'Access-Control-Allow-Methods': 'GET', }) - @http.route('/hw_proxy/handshake', type='json', auth='admin') + @http.route('/hw_proxy/handshake', type='json', auth='none') def handshake(self): return True - @http.route('/hw_proxy/status', type='http', auth='admin') + @http.route('/hw_proxy/status', type='http', auth='none') def status_http(self): resp = '\n\n

Hardware Proxy Status

\n' statuses = self.get_status() @@ -75,39 +75,39 @@ class Proxy(http.Controller): 'Access-Control-Allow-Methods': 'GET', }) - @http.route('/hw_proxy/status_json', type='json', auth='admin') + @http.route('/hw_proxy/status_json', type='json', auth='none') def status_json(self): return self.get_status() - @http.route('/hw_proxy/scan_item_success', type='json', auth='admin') + @http.route('/hw_proxy/scan_item_success', type='json', auth='none') def scan_item_success(self, ean): """ A product has been scanned with success """ print 'scan_item_success: ' + str(ean) - @http.route('/hw_proxy/scan_item_error_unrecognized', type='json', auth='admin') + @http.route('/hw_proxy/scan_item_error_unrecognized', type='json', auth='none') def scan_item_error_unrecognized(self, ean): """ A product has been scanned without success """ print 'scan_item_error_unrecognized: ' + str(ean) - @http.route('/hw_proxy/help_needed', type='json', auth='admin') + @http.route('/hw_proxy/help_needed', type='json', auth='none') def help_needed(self): """ The user wants an help (ex: light is on) """ print "help_needed" - @http.route('/hw_proxy/help_canceled', type='json', auth='admin') + @http.route('/hw_proxy/help_canceled', type='json', auth='none') def help_canceled(self): """ The user stops the help request """ print "help_canceled" - @http.route('/hw_proxy/weighting_start', type='json', auth='admin') + @http.route('/hw_proxy/weighting_start', type='json', auth='none') def weighting_start(self): if self.scale == 'closed': print "Opening (Fake) Connection to Scale..." @@ -118,7 +118,7 @@ class Proxy(http.Controller): else: print "WARNING: Scale already Connected !!!" - @http.route('/hw_proxy/weighting_read_kg', type='json', auth='admin') + @http.route('/hw_proxy/weighting_read_kg', type='json', auth='none') def weighting_read_kg(self): if self.scale == 'open': print "Reading Scale..." @@ -130,7 +130,7 @@ class Proxy(http.Controller): print "WARNING: Reading closed scale !!!" return 0.0 - @http.route('/hw_proxy/weighting_end', type='json', auth='admin') + @http.route('/hw_proxy/weighting_end', type='json', auth='none') def weighting_end(self): if self.scale == 'open': print "Closing Connection to Scale ..." @@ -141,7 +141,7 @@ class Proxy(http.Controller): else: print "WARNING: Scale already Closed !!!" - @http.route('/hw_proxy/payment_request', type='json', auth='admin') + @http.route('/hw_proxy/payment_request', type='json', auth='none') def payment_request(self, price): """ The PoS will activate the method payment @@ -149,55 +149,55 @@ class Proxy(http.Controller): print "payment_request: price:"+str(price) return 'ok' - @http.route('/hw_proxy/payment_status', type='json', auth='admin') + @http.route('/hw_proxy/payment_status', type='json', auth='none') def payment_status(self): print "payment_status" return { 'status':'waiting' } - @http.route('/hw_proxy/payment_cancel', type='json', auth='admin') + @http.route('/hw_proxy/payment_cancel', type='json', auth='none') def payment_cancel(self): print "payment_cancel" - @http.route('/hw_proxy/transaction_start', type='json', auth='admin') + @http.route('/hw_proxy/transaction_start', type='json', auth='none') def transaction_start(self): print 'transaction_start' - @http.route('/hw_proxy/transaction_end', type='json', auth='admin') + @http.route('/hw_proxy/transaction_end', type='json', auth='none') def transaction_end(self): print 'transaction_end' - @http.route('/hw_proxy/cashier_mode_activated', type='json', auth='admin') + @http.route('/hw_proxy/cashier_mode_activated', type='json', auth='none') def cashier_mode_activated(self): print 'cashier_mode_activated' - @http.route('/hw_proxy/cashier_mode_deactivated', type='json', auth='admin') + @http.route('/hw_proxy/cashier_mode_deactivated', type='json', auth='none') def cashier_mode_deactivated(self): print 'cashier_mode_deactivated' - @http.route('/hw_proxy/open_cashbox', type='json', auth='admin') + @http.route('/hw_proxy/open_cashbox', type='json', auth='none') def open_cashbox(self): print 'open_cashbox' - @http.route('/hw_proxy/print_receipt', type='json', auth='admin') + @http.route('/hw_proxy/print_receipt', type='json', auth='none') def print_receipt(self, receipt): print 'print_receipt' + str(receipt) - @http.route('/hw_proxy/is_scanner_connected', type='json', auth='admin') + @http.route('/hw_proxy/is_scanner_connected', type='json', auth='none') def print_receipt(self, receipt): print 'is_scanner_connected?' return False - @http.route('/hw_proxy/scanner', type='json', auth='admin') + @http.route('/hw_proxy/scanner', type='json', auth='none') def print_receipt(self, receipt): print 'scanner' time.sleep(10) return '' - @http.route('/hw_proxy/log', type='json', auth='admin') + @http.route('/hw_proxy/log', type='json', auth='none') def log(self, arguments): _logger.info(' '.join(str(v) for v in arguments)) - @http.route('/hw_proxy/print_pdf_invoice', type='json', auth='admin') + @http.route('/hw_proxy/print_pdf_invoice', type='json', auth='none') def print_pdf_invoice(self, pdfinvoice): print 'print_pdf_invoice' + str(pdfinvoice) diff --git a/addons/hw_scanner/controllers/main.py b/addons/hw_scanner/controllers/main.py index 3e487cab404..664606954c3 100644 --- a/addons/hw_scanner/controllers/main.py +++ b/addons/hw_scanner/controllers/main.py @@ -109,8 +109,8 @@ class Scanner(Thread): if not evdev: return None devices = [ device for device in listdir(self.input_dir)] - keyboards = [ device for device in devices if 'kbd' in device ] - scanners = [ device for device in devices if ('barcode' in device.lower()) or ('scanner' in device.lower()) ] + keyboards = [ device for device in devices if ('kbd' in device) and ('keyboard' not in device.lower())] + scanners = [ device for device in devices if ('barcode' in device.lower()) or ('scanner' in device.lower())] if len(scanners) > 0: self.set_status('connected','Connected to '+scanners[0]) return evdev.InputDevice(join(self.input_dir,scanners[0])) @@ -124,7 +124,7 @@ class Scanner(Thread): self.set_status('error',str(e)) return None - @http.route('/hw_proxy/Vis_scanner_connected', type='json', auth='admin') + @http.route('/hw_proxy/Vis_scanner_connected', type='json', auth='none') def is_scanner_connected(self): return self.get_device() != None @@ -144,6 +144,8 @@ class Scanner(Thread): return '' def get_status(self): + if not s.isAlive(): + s.start() return self.status def run(self): @@ -205,7 +207,7 @@ s = Scanner() hw_proxy.drivers['scanner'] = s class ScannerDriver(hw_proxy.Proxy): - @http.route('/hw_proxy/scanner', type='json', auth='admin') + @http.route('/hw_proxy/scanner', type='json', auth='none') def scanner(self): if not s.isAlive(): s.start() diff --git a/addons/im/im.py b/addons/im/im.py index 20b8a966172..af3e8e5b10b 100644 --- a/addons/im/im.py +++ b/addons/im/im.py @@ -185,8 +185,8 @@ class im_message(osv.osv): def post(self, cr, uid, message, to_session_id, technical=False, uuid=None, context=None): assert_uuid(uuid) my_id = self.pool.get('im.user').get_my_id(cr, uid, uuid) - session = self.pool.get('im.session').browse(cr, uid, to_session_id, context) - to_ids = [x.id for x in session.user_ids if x.id != my_id] + session_user_ids = self.pool.get('im.session').get_session_users(cr, uid, to_session_id, context=context).get("user_ids", []) + to_ids = [user_id for user_id in session_user_ids if user_id != my_id] self.create(cr, openerp.SUPERUSER_ID, {"message": message, 'from_id': my_id, 'to_id': [(6, 0, to_ids)], 'session_id': to_session_id, 'technical': technical}, context=context) notify_channel(cr, "im_channel", {'type': 'message', 'receivers': [my_id] + to_ids}) @@ -202,7 +202,7 @@ class im_session(osv.osv): return res _columns = { - 'user_ids': fields.many2many('im.user'), + 'user_ids': fields.many2many('im.user', 'im_session_im_user_rel', 'im_session_id', 'im_user_id', 'Users'), "name": fields.function(_calc_name, string="Name", type='char'), } @@ -225,6 +225,9 @@ class im_session(osv.osv): }, context=context) return self.read(cr, uid, session_id, context=context) + def get_session_users(self, cr, uid, session_id, context=None): + return self.read(cr, openerp.SUPERUSER_ID, session_id, ['user_ids'], context=context) + def add_to_session(self, cr, uid, session_id, user_id, uuid=None, context=None): my_id = self.pool.get("im.user").get_my_id(cr, uid, uuid, context=context) session = self.read(cr, uid, session_id, context=context) @@ -259,7 +262,7 @@ class im_user(osv.osv): return ['&', ('im_last_status', '=', True), ('im_last_status_update', '>', (current - delta).strftime(DEFAULT_SERVER_DATETIME_FORMAT))] else: return ['|', ('im_last_status', '=', False), ('im_last_status_update', '<=', (current - delta).strftime(DEFAULT_SERVER_DATETIME_FORMAT))] - + # TODO: Remove fields arg in trunk. Also in im.js. def search_users(self, cr, uid, text_search, fields, limit, context=None): my_id = self.get_my_id(cr, uid, None, context) group_employee = self.pool['ir.model.data'].get_object_reference(cr, uid, 'base', 'group_user')[1] @@ -271,7 +274,7 @@ class im_user(osv.osv): if len(found) < limit: found += self.search(cr, uid, [["name", "ilike", text_search], ["id", "<>", my_id], ["uuid", "=", False], ["im_status", "=", False], ["id", "not in", found]], order="name asc", limit=limit-len(found), context=context) - users = self.read(cr, uid, found, fields, context=context) + users = self.read(cr,openerp.SUPERUSER_ID, found, ["name", "user_id", "uuid", "im_status"], context=context) users.sort(key=lambda obj: found.index(obj['id'])) return users @@ -319,6 +322,9 @@ class im_user(osv.osv): continue return res + def get_users(self, cr, uid, ids, context=None): + return self.read(cr,openerp.SUPERUSER_ID, ids, ["name", "im_status", "uuid"], context=context) + _columns = { 'name': fields.function(_get_name, type='char', size=200, string="Name", store=True, readonly=True), 'assigned_name': fields.char(string="Assigned Name", size=200, required=False), @@ -341,3 +347,16 @@ class im_user(osv.osv): ('user_uniq', 'unique (user_id)', 'Only one chat user per OpenERP user.'), ('uuid_uniq', 'unique (uuid)', 'Chat identifier already used.'), ] + +class res_users(osv.osv): + _inherit = "res.users" + + def _get_im_user(self, cr, uid, ids, field_name, arg, context=None): + result = dict.fromkeys(ids, False) + for index, im_user in enumerate(self.pool['im.user'].search_read(cr, uid, domain=[('user_id', 'in', ids)], fields=['name', 'user_id'], context=context)): + result[ids[index]] = im_user.get('user_id') and (im_user['user_id'][0], im_user['name']) or False + return result + + _columns = { + 'im_user_id' : fields.function(_get_im_user, type='many2one', string="IM User", relation="im.user"), + } diff --git a/addons/im/security/im_security.xml b/addons/im/security/im_security.xml index d6b854c8a98..6713b88cda6 100644 --- a/addons/im/security/im_security.xml +++ b/addons/im/security/im_security.xml @@ -2,10 +2,10 @@ - Can only read messages that you sent or messages sent to you + Can only read messages from a session where user is - ["|", ('to_id.user_id', 'in', [user.id]), ('from_id.user_id', '=', user.id)] + [('session_id.user_ids', 'in', user.im_user_id.id)] diff --git a/addons/im/security/ir.model.access.csv b/addons/im/security/ir.model.access.csv index 651aa68c1bb..5137990498a 100644 --- a/addons/im/security/ir.model.access.csv +++ b/addons/im/security/ir.model.access.csv @@ -1,4 +1,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_im_message,im.message,model_im_message,base.group_user,1,0,1,0 +access_im_message,im.message,model_im_message,,1,0,1,0 access_im_user,im.user,model_im_user,,1,1,1,0 access_im_session,im.session,model_im_session,,1,0,0,0 \ No newline at end of file diff --git a/addons/im/static/src/js/im.js b/addons/im/static/src/js/im.js index aa2e7d963b6..95f29bc5c49 100644 --- a/addons/im/static/src/js/im.js +++ b/addons/im/static/src/js/im.js @@ -92,6 +92,7 @@ search_changed: function(e) { var users = new instance.web.Model("im.user"); var self = this; + // TODO: Remove fields arg in trunk. Also in im.js. return this.user_search_dm.add(users.call("search_users", [this.get("current_search"), ["name", "user_id", "uuid", "im_status"], USERS_LIMIT], {context:new instance.web.CompoundContext()})).then(function(users) { var logged_users = _.filter(users, function(u) { return !!u.im_status; }); diff --git a/addons/im/static/src/js/im_common.js b/addons/im/static/src/js/im_common.js index 83f92265ec6..5a88ddfb581 100644 --- a/addons/im/static/src/js/im_common.js +++ b/addons/im/static/src/js/im_common.js @@ -145,7 +145,7 @@ function declare($, _, openerp) { if (_.size(no_cache) === 0) def = $.when(); else - def = im_common.connection.model("im.user").call("read", [_.values(no_cache), []]).then(function(users) { + def = im_common.connection.model("im.user").call("get_users", [_.values(no_cache)]).then(function(users) { self.add_to_user_cache(users); }); return def.then(function() { @@ -230,7 +230,8 @@ function declare($, _, openerp) { return self.chat_with_users(users); }); }, - activate_session: function(session_id, focus) { + activate_session: function(session_id, focus, message) { + var self = this; var conv = _.find(this.conversations, function(conv) {return conv.session_id == session_id;}); var def = $.when(); if (! conv) { @@ -244,6 +245,9 @@ function declare($, _, openerp) { this.calc_positions(); this.trigger("new_conversation", conv); }, this)); + def = def.then(function(){ + return self.load_history(conv, message); + }); } if (focus) { def = def.then(function() { @@ -252,13 +256,32 @@ function declare($, _, openerp) { } return def.then(function() {return conv}); }, - received_messages: function(messages) { + load_history: function(conv, message){ + var self = this; + var domain = [["session_id", "=", conv.session_id]]; + if (!_.isUndefined(message)){ + domain.push(["date", "<", message.date]); + } + return im_common.connection.model("im.message").call("search_read", [domain, [], 0, 10]).then(function(messages){ + messages.reverse(); + var users = _.unique(_.map(messages, function(message){ + return message.from_id[0]; + })); + return self.ensure_users(users).then(function(){ + return self.received_messages(messages, true); + }); + }); + }, + received_messages: function(messages, seen) { var self = this; var defs = []; var received = false; + if (_.isUndefined(seen)){ + seen = false; + } _.each(messages, function(message) { if (! message.technical) { - defs.push(self.activate_session(message.session_id[0]).then(function(conv) { + defs.push(self.activate_session(message.session_id[0], false, message).then(function(conv) { received = self.my_id !== message.from_id[0]; return conv.received_message(message); })); @@ -269,7 +292,7 @@ function declare($, _, openerp) { } }); return $.when.apply($, defs).then(function(){ - if (! self.get("window_focus") && received) { + if (! self.get("window_focus") && received && !seen) { self.set("waiting_messages", self.get("waiting_messages") + messages.length); self.ting.play(); self.create_ting(); @@ -368,7 +391,7 @@ function declare($, _, openerp) { refresh_users: function() { var self = this; var user_ids; - return im_common.connection.model("im.session").call("read", [self.session_id]).then(function(session) { + return im_common.connection.model("im.session").call("get_session_users", [self.session_id]).then(function(session) { user_ids = _.without(session.user_ids, self.c_manager.me.get("id")); return self.c_manager.ensure_users(user_ids); }).then(function(users) { @@ -449,7 +472,7 @@ function declare($, _, openerp) { date = "" + zpad(date.getHours(), 2) + ":" + zpad(date.getMinutes(), 2); var to_show = _.map(items, im_common.escape_keep_url); this.last_bubble = $(openerp.qweb.render("im_common.conversation_bubble", {"items": to_show, "user": user, "time": date})); - $(this.$(".oe_im_chatview_content").children()[0]).append(this.last_bubble); + $(this.$(".oe_im_chatview_conversation")).append(this.last_bubble); this._go_bottom(); }, _go_bottom: function() { diff --git a/addons/im/static/src/xml/im_common.xml b/addons/im/static/src/xml/im_common.xml index 462e242befd..efaaf602577 100644 --- a/addons/im/static/src/xml/im_common.xml +++ b/addons/im/static/src/xml/im_common.xml @@ -11,7 +11,7 @@ All users are offline. They will receive your messages on their next connection.
-
+
@@ -878,6 +862,22 @@