diff --git a/addons/account/account.py b/addons/account/account.py index 6e96ce73962..caf1d68c45d 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -24,11 +24,12 @@ from datetime import datetime from dateutil.relativedelta import relativedelta from operator import itemgetter -import netsvc +import logging import pooler from osv import fields, osv import decimal_precision as dp from tools.translate import _ +_logger = logging.getLogger(__name__) def check_cycle(self, cr, uid, ids, context=None): """ climbs the ``self._table.parent_id`` chains for 100 levels or @@ -212,7 +213,6 @@ class account_account(osv.osv): _name = "account.account" _description = "Account" _parent_store = True - logger = netsvc.Logger() def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): @@ -295,8 +295,7 @@ class account_account(osv.osv): if aml_query.strip(): wheres.append(aml_query.strip()) filters = " AND ".join(wheres) - self.logger.notifyChannel('addons.'+self._name, netsvc.LOG_DEBUG, - 'Filters: %s'%filters) + _logger.debug('Filters: %s',(filters)) # IN might not work ideally in case there are too many # children_and_consolidated, in that case join on a # values() e.g.: @@ -312,8 +311,7 @@ class account_account(osv.osv): " GROUP BY l.account_id") params = (tuple(children_and_consolidated),) + query_params cr.execute(request, params) - self.logger.notifyChannel('addons.'+self._name, netsvc.LOG_DEBUG, - 'Status: %s'%cr.statusmessage) + _logger.debug('Status: %s',(cr.statusmessage)) for res in cr.dictfetchall(): accounts[res['id']] = res @@ -2095,9 +2093,7 @@ class account_tax(osv.osv): } def compute(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None): - logger = netsvc.Logger() - logger.notifyChannel("warning", netsvc.LOG_WARNING, - "Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included") + _logger.warning("Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included") return self._compute(cr, uid, taxes, price_unit, quantity, product, partner) def _compute(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None): diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 68a723aa4a0..a10cba705e4 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -39,7 +39,7 @@ - + @@ -61,8 +61,8 @@ on_change="uos_id_change(product_id, uos_id, quantity, name, parent.type, parent.partner_id, parent.fiscal_position, price_unit, parent.currency_id, context, parent.company_id)"/> - - + + @@ -98,7 +98,7 @@ - + @@ -148,7 +148,8 @@
- - + account.invoice.customer.pay + account.invoice + form + + + + + +
@@ -56,7 +56,7 @@
diff --git a/addons/base_vat/base_vat.py b/addons/base_vat/base_vat.py index 7b8363ffd6b..e76442b62fe 100644 --- a/addons/base_vat/base_vat.py +++ b/addons/base_vat/base_vat.py @@ -23,11 +23,12 @@ import logging import string import datetime import re +_logger = logging.getLogger(__name__) try: import vatnumber except ImportError: - logging.getLogger('base_vat').warning("VAT validation partially unavailable because the `vatnumber` Python library cannot be found. " + _logger.warning("VAT validation partially unavailable because the `vatnumber` Python library cannot be found. " "Install it to support more countries, for example with `easy_install vatnumber`.") vatnumber = None diff --git a/addons/caldav/caldav_node.py b/addons/caldav/caldav_node.py index 71e9bdc2468..da5c5f017a6 100644 --- a/addons/caldav/caldav_node.py +++ b/addons/caldav/caldav_node.py @@ -23,6 +23,7 @@ from document_webdav import nodes from document.nodes import _str2time, nodefd_static import logging from orm_utils import get_last_modified +_logger = logging.getLogger(__name__) try: from tools.dict_tools import dict_merge2 @@ -223,7 +224,6 @@ class node_calendar(nodes.node_class): res = [] if not filters: return res - _log = logging.getLogger('caldav.query') if filters.localName == 'calendar-query': res = [] for filter_child in filters.childNodes: diff --git a/addons/caldav/calendar.py b/addons/caldav/calendar.py index 94d28e4ad37..fb98e00a86f 100644 --- a/addons/caldav/calendar.py +++ b/addons/caldav/calendar.py @@ -34,6 +34,7 @@ import logging from caldav_node import res_node_calendar from orm_utils import get_last_modified from tools.safe_eval import safe_eval as eval +_logger = logging.getLogger(__name__) try: import vobject @@ -240,7 +241,6 @@ def map_data(cr, uid, obj, context=None): class CalDAV(object): __attribute__ = {} - _logger = logging.getLogger('document.caldav') def ical_set(self, name, value, type): """ set calendar Attribute @@ -725,13 +725,13 @@ class Calendar(CalDAV, osv.osv): objs.append(cal_children[child.name.lower()]) elif child.name.upper() == 'CALSCALE': if child.value.upper() != 'GREGORIAN': - self._logger.warning('How do I handle %s calendars?',child.value) + _logger.warning('How do I handle %s calendars?',child.value) elif child.name.upper() in ('PRODID', 'VERSION'): pass elif child.name.upper().startswith('X-'): - self._logger.debug("skipping custom node %s", child.name) + _logger.debug("skipping custom node %s", child.name) else: - self._logger.debug("skipping node %s", child.name) + _logger.debug("skipping node %s", child.name) res = [] for obj_name in list(set(objs)): diff --git a/addons/caldav/calendar_collection.py b/addons/caldav/calendar_collection.py index 1abbf573ef0..3a5604b2f67 100644 --- a/addons/caldav/calendar_collection.py +++ b/addons/caldav/calendar_collection.py @@ -23,6 +23,7 @@ from osv import osv, fields from tools.translate import _ import caldav_node import logging +_logger = logging.getLogger(__name__) class calendar_collection(osv.osv): _inherit = 'document.directory' @@ -44,8 +45,7 @@ class calendar_collection(osv.osv): root_cal_dir = self.browse(cr,uid, root_id, context=context) return root_cal_dir.name except Exception: - logger = logging.getLogger('document') - logger.warning('Cannot set root directory for Calendars:', exc_info=True) + _logger.warning('Cannot set root directory for Calendars:', exc_info=True) return False return False diff --git a/addons/crm/crm_meeting.py b/addons/crm/crm_meeting.py index cadd813a959..1e21d535c55 100644 --- a/addons/crm/crm_meeting.py +++ b/addons/crm/crm_meeting.py @@ -26,6 +26,7 @@ import logging from osv import fields, osv import tools from tools.translate import _ +_logger = logging.getLogger(__name__) class crm_lead(base_stage, osv.osv): """ CRM Leads """ @@ -180,7 +181,7 @@ class res_users(osv.osv): 'user_id': user_id}, context=context) except: # Tolerate a missing shortcut. See product/product.py for similar code. - logging.getLogger('orm').debug('Skipped meetings shortcut for user "%s"', data.get('name',' - + diff --git a/addons/crm_claim/crm_claim_menu.xml b/addons/crm_claim/crm_claim_menu.xml index 75bfd9ce211..d4bfd8f21bb 100644 --- a/addons/crm_claim/crm_claim_menu.xml +++ b/addons/crm_claim/crm_claim_menu.xml @@ -51,9 +51,13 @@ parent="base.menu_aftersale" action="crm_case_categ_claim0" sequence="1"/> - - - + + + + + diff --git a/addons/document/content_index.py b/addons/document/content_index.py index 64b480ee736..f24c033a7ec 100644 --- a/addons/document/content_index.py +++ b/addons/document/content_index.py @@ -22,7 +22,7 @@ import logging import os import tempfile from subprocess import Popen, PIPE - +_logger = logging.getLogger(__name__) class NhException(Exception): pass @@ -116,7 +116,7 @@ def mime_match(mime, mdict): return (None, None) class contentIndex(object): - __logger = logging.getLogger('addons.document.content_index') + def __init__(self): self.mimes = {} self.exts = {} @@ -132,7 +132,7 @@ class contentIndex(object): f = True if f: - self.__logger.debug('Register content indexer: %r', obj) + _logger.debug('Register content indexer: %r', obj) if not f: raise Exception("Your indexer should at least suport a mimetype or extension") @@ -169,22 +169,22 @@ class contentIndex(object): (result, _) = pop.communicate() mime2 = result.split(';')[0] - self.__logger.debug('File gave us: %s', mime2) + _logger.debug('File gave us: %s', mime2) # Note that the temporary file still exists now. mime,fobj = mime_match(mime2, self.mimes) if not mime: mime = mime2 except Exception: - self.__logger.exception('Cannot determine mime type') + _logger.exception('Cannot determine mime type') try: if fobj: res = (mime, fobj.indexContent(content,filename,fname or realfname) ) else: - self.__logger.debug("Have no object, return (%s, None)", mime) + _logger.debug("Have no object, return (%s, None)", mime) res = (mime, None ) except Exception: - self.__logger.exception("Could not index file %s (%s)", + _logger.exception("Could not index file %s (%s)", filename, fname or realfname) res = None @@ -193,8 +193,7 @@ class contentIndex(object): try: os.unlink(fname) except Exception: - self.__logger.exception("Could not unlink %s", fname) - + _logger.exception("Could not unlink %s", fname) return res cntIndex = contentIndex() diff --git a/addons/document/document.py b/addons/document/document.py index 92ece063a47..fa65f8d9966 100644 --- a/addons/document/document.py +++ b/addons/document/document.py @@ -30,12 +30,15 @@ from tools.translate import _ import nodes import logging +_logger = logging.getLogger(__name__) + DMS_ROOT_PATH = tools.config.get('document_path', os.path.join(tools.config['root_path'], 'filestore')) class document_file(osv.osv): _inherit = 'ir.attachment' _rec_name = 'datas_fname' - + + def _attach_parent_id(self, cr, uid, ids=None, context=None): """Migrate ir.attachments to the document module. @@ -54,7 +57,7 @@ class document_file(osv.osv): parent_id = self.pool.get('document.directory')._get_root_directory(cr,uid) if not parent_id: - logging.getLogger('document').warning("at _attach_parent_id(), still not able to set the parent!") + _logger.warning("at _attach_parent_id(), still not able to set the parent!") return False if ids is not None: @@ -140,8 +143,8 @@ class document_file(osv.osv): _defaults = { 'user_id': lambda self, cr, uid, ctx:uid, + 'parent_id': __get_def_directory, 'file_size': lambda self, cr, uid, ctx:0, - 'parent_id': __get_def_directory } _sql_constraints = [ # filename_uniq is not possible in pure SQL @@ -336,7 +339,7 @@ class document_file(osv.osv): if r: unres.append(r) else: - logging.getLogger('document').warning("Unlinking attachment #%s %s that has no storage", + self.loggerdoc.warning("Unlinking attachment #%s %s that has no storage", f.id, f.name) res = super(document_file, self).unlink(cr, uid, ids, context) stor.do_unlink(cr, uid, unres) diff --git a/addons/document/document_data.xml b/addons/document/document_data.xml index ac7a174b05f..a7bc98de6f3 100644 --- a/addons/document/document_data.xml +++ b/addons/document/document_data.xml @@ -104,6 +104,5 @@ - diff --git a/addons/document/document_directory.py b/addons/document/document_directory.py index d09d2cc86d5..03b2dcc47bf 100644 --- a/addons/document/document_directory.py +++ b/addons/document/document_directory.py @@ -22,10 +22,10 @@ from osv import osv, fields from osv.orm import except_orm - +import logging import nodes from tools.translate import _ - +_logger = logging.getLogger(__name__) class document_directory(osv.osv): _name = 'document.directory' _description = 'Directory' @@ -78,9 +78,8 @@ class document_directory(osv.osv): root_id = objid.read(cr, uid, mid, ['res_id'])['res_id'] return root_id except Exception, e: - import netsvc - logger = netsvc.Logger() - logger.notifyChannel("document", netsvc.LOG_WARNING, 'Cannot set directory root:'+ str(e)) + + _logger.warning('Cannot set directory root:'+ str(e)) return False return objid.browse(cr, uid, mid, context=context).res_id diff --git a/addons/document/document_storage.py b/addons/document/document_storage.py index fe757e7876d..339b320e9a7 100644 --- a/addons/document/document_storage.py +++ b/addons/document/document_storage.py @@ -29,18 +29,15 @@ import logging import shutil from StringIO import StringIO import psycopg2 - from tools.misc import ustr from tools.translate import _ - from osv.orm import except_orm - import random import string import pooler import nodes from content_index import cntIndex - +_logger = logging.getLogger(__name__) DMS_ROOT_PATH = tools.config.get('document_path', os.path.join(tools.config.get('root_path'), 'filestore')) @@ -130,7 +127,7 @@ class nodefd_file(nodes.node_descriptor): mime, icont = cntIndex.doIndex(None, filename=filename, content_type=None, realfname=fname) except Exception: - logging.getLogger('document.storage').debug('Cannot index file:', exc_info=True) + _logger.debug('Cannot index file:', exc_info=True) pass try: @@ -150,7 +147,7 @@ class nodefd_file(nodes.node_descriptor): cr.commit() cr.close() except Exception: - logging.getLogger('document.storage').warning('Cannot save file indexed content:', exc_info=True) + _logger.warning('Cannot save file indexed content:', exc_info=True) elif self.mode in ('a', 'a+' ): try: @@ -164,7 +161,7 @@ class nodefd_file(nodes.node_descriptor): cr.commit() cr.close() except Exception: - logging.getLogger('document.storage').warning('Cannot save file appended content:', exc_info=True) + _logger.warning('Cannot save file appended content:', exc_info=True) @@ -191,7 +188,7 @@ class nodefd_db(StringIO, nodes.node_descriptor): elif mode == 'a': StringIO.__init__(self, None) else: - logging.getLogger('document.storage').error("Incorrect mode %s specified", mode) + _logger.error("Incorrect mode %s specified", mode) raise IOError(errno.EINVAL, "Invalid file mode") self.mode = mode @@ -217,7 +214,7 @@ class nodefd_db(StringIO, nodes.node_descriptor): mime, icont = cntIndex.doIndex(data, filename=filename, content_type=None, realfname=None) except Exception: - logging.getLogger('document.storage').debug('Cannot index file:', exc_info=True) + _logger.debug('Cannot index file:', exc_info=True) pass try: @@ -241,7 +238,7 @@ class nodefd_db(StringIO, nodes.node_descriptor): (out, len(data), par.file_id)) cr.commit() except Exception: - logging.getLogger('document.storage').exception('Cannot update db file #%d for close:', par.file_id) + _logger.exception('Cannot update db file #%d for close:', par.file_id) raise finally: cr.close() @@ -271,7 +268,7 @@ class nodefd_db64(StringIO, nodes.node_descriptor): elif mode == 'a': StringIO.__init__(self, None) else: - logging.getLogger('document.storage').error("Incorrect mode %s specified", mode) + _logger.error("Incorrect mode %s specified", mode) raise IOError(errno.EINVAL, "Invalid file mode") self.mode = mode @@ -297,7 +294,7 @@ class nodefd_db64(StringIO, nodes.node_descriptor): mime, icont = cntIndex.doIndex(data, filename=filename, content_type=None, realfname=None) except Exception: - logging.getLogger('document.storage').debug('Cannot index file:', exc_info=True) + self.logger.debug('Cannot index file:', exc_info=True) pass try: @@ -320,7 +317,7 @@ class nodefd_db64(StringIO, nodes.node_descriptor): (base64.encodestring(data), len(data), par.file_id)) cr.commit() except Exception: - logging.getLogger('document.storage').exception('Cannot update db file #%d for close:', par.file_id) + _logger.exception('Cannot update db file #%d for close:', par.file_id) raise finally: cr.close() @@ -339,7 +336,6 @@ class document_storage(osv.osv): """ _name = 'document.storage' _description = 'Storage Media' - _doclog = logging.getLogger('document') _columns = { 'name': fields.char('Name', size=64, required=True, select=1), @@ -402,7 +398,7 @@ class document_storage(osv.osv): npath = filter(lambda x: x is not None, npath) # if self._debug: - # self._doclog.debug('Npath: %s', npath) + # self._logger.debug('Npath: %s', npath) for n in npath: if n == '..': raise ValueError("Invalid '..' element in path") @@ -413,7 +409,7 @@ class document_storage(osv.osv): dpath += npath[:-1] path = os.path.join(*dpath) if not os.path.isdir(path): - self._doclog.debug("Create dirs: %s", path) + _logger.debug("Create dirs: %s", path) os.makedirs(path) return path, npath @@ -451,7 +447,7 @@ class document_storage(osv.osv): # try to fix their directory. if mode in ('r','r+'): if ira.file_size: - self._doclog.warning( "ir.attachment #%d does not have a filename, but is at filestore, fix it!" % ira.id) + _logger.warning( "ir.attachment #%d does not have a filename, but is at filestore, fix it!" % ira.id) raise IOError(errno.ENOENT, 'No file can be located') else: store_fname = self.__get_random_fname(boo.path) @@ -493,7 +489,7 @@ class document_storage(osv.osv): # On a migrated db, some files may have the wrong storage type # try to fix their directory. if ira.file_size: - self._doclog.warning( "ir.attachment #%d does not have a filename, but is at filestore, fix it!" % ira.id) + _logger.warning( "ir.attachment #%d does not have a filename, but is at filestore, fix it!" % ira.id) return None fpath = os.path.join(boo.path, ira.store_fname) return file(fpath, 'rb').read() @@ -517,7 +513,7 @@ class document_storage(osv.osv): # On a migrated db, some files may have the wrong storage type # try to fix their directory. if ira.file_size: - self._doclog.warning("ir.attachment #%d does not have a filename, trying the name." %ira.id) + _logger.warning("ir.attachment #%d does not have a filename, trying the name." %ira.id) # sfname = ira.name fpath = os.path.join(boo.path,ira.store_fname or ira.name) if os.path.exists(fpath): @@ -550,7 +546,7 @@ class document_storage(osv.osv): if boo.readonly: raise IOError(errno.EPERM, "Readonly medium") - self._doclog.debug( "Store data for ir.attachment #%d" % ira.id) + _logger.debug( "Store data for ir.attachment #%d" % ira.id) store_fname = None fname = None if boo.type == 'filestore': @@ -563,13 +559,13 @@ class document_storage(osv.osv): fp.write(data) finally: fp.close() - self._doclog.debug( "Saved data to %s" % fname) + _logger.debug( "Saved data to %s" % fname) filesize = len(data) # os.stat(fname).st_size # TODO Here, an old file would be left hanging. except Exception, e: - self._doclog.warning( "Couldn't save data to %s", path, exc_info=True) + _logger.warning( "Couldn't save data to %s", path, exc_info=True) raise except_orm(_('Error!'), str(e)) elif boo.type == 'db': filesize = len(data) @@ -592,12 +588,12 @@ class document_storage(osv.osv): fp.write(data) finally: fp.close() - self._doclog.debug("Saved data to %s", fname) + _logger.debug("Saved data to %s", fname) filesize = len(data) # os.stat(fname).st_size store_fname = os.path.join(*npath) # TODO Here, an old file would be left hanging. except Exception,e : - self._doclog.warning("Couldn't save data:", exc_info=True) + _logger.warning("Couldn't save data:", exc_info=True) raise except_orm(_('Error!'), str(e)) elif boo.type == 'virtual': @@ -616,7 +612,7 @@ class document_storage(osv.osv): mime, icont = cntIndex.doIndex(data, ira.datas_fname, ira.file_type or None, fname) except Exception: - self._doclog.debug('Cannot index file:', exc_info=True) + _logger.debug('Cannot index file:', exc_info=True) pass try: @@ -633,7 +629,7 @@ class document_storage(osv.osv): file_node.content_type = mime return True except Exception, e : - self._doclog.warning("Couldn't save data:", exc_info=True) + self._logger.warning("Couldn't save data:", exc_info=True) # should we really rollback once we have written the actual data? # at the db case (only), that rollback would be safe raise except_orm(_('Error at doc write!'), str(e)) @@ -671,9 +667,9 @@ class document_storage(osv.osv): try: os.unlink(fname) except Exception: - self._doclog.warning("Could not remove file %s, please remove manually.", fname, exc_info=True) + _logger.warning("Could not remove file %s, please remove manually.", fname, exc_info=True) else: - self._doclog.warning("Unknown unlink key %s" % ktype) + _logger.warning("Unknown unlink key %s" % ktype) return True @@ -703,9 +699,9 @@ class document_storage(osv.osv): fname = ira.store_fname if not fname: - self._doclog.warning("Trying to rename a non-stored file") + _logger.warning("Trying to rename a non-stored file") if fname != os.path.join(*npath): - self._doclog.warning("inconsistency in realstore: %s != %s" , fname, repr(npath)) + _logger.warning("inconsistency in realstore: %s != %s" , fname, repr(npath)) oldpath = os.path.join(path, npath[-1]) newpath = os.path.join(path, new_name) @@ -743,7 +739,7 @@ class document_storage(osv.osv): break par = par.parent_id if file_node.storage_id != psto: - self._doclog.debug('Cannot move file %r from %r to %r', file_node, file_node.parent, ndir_bro.name) + _logger.debug('Cannot move file %r from %r to %r', file_node, file_node.parent, ndir_bro.name) raise NotImplementedError('Cannot move files between storage media') if sbro.type in ('filestore', 'db', 'db64'): @@ -756,9 +752,9 @@ class document_storage(osv.osv): fname = ira.store_fname if not fname: - self._doclog.warning("Trying to rename a non-stored file") + _logger.warning("Trying to rename a non-stored file") if fname != os.path.join(*opath): - self._doclog.warning("inconsistency in realstore: %s != %s" , fname, repr(opath)) + _logger.warning("inconsistency in realstore: %s != %s" , fname, repr(opath)) oldpath = os.path.join(path, opath[-1]) @@ -766,12 +762,12 @@ class document_storage(osv.osv): npath = filter(lambda x: x is not None, npath) newdir = os.path.join(*npath) if not os.path.isdir(newdir): - self._doclog.debug("Must create dir %s", newdir) + _logger.debug("Must create dir %s", newdir) os.makedirs(newdir) npath.append(opath[-1]) newpath = os.path.join(*npath) - self._doclog.debug("Going to move %s from %s to %s", opath[-1], oldpath, newpath) + _logger.debug("Going to move %s from %s to %s", opath[-1], oldpath, newpath) shutil.move(oldpath, newpath) store_path = npath[1:] + [opath[-1],] diff --git a/addons/document/nodes.py b/addons/document/nodes.py index ee89f3d4c6c..273be7effa4 100644 --- a/addons/document/nodes.py +++ b/addons/document/nodes.py @@ -41,8 +41,7 @@ from StringIO import StringIO # file: objct = ir.attachement # root: if we are at the first directory of a ressource # - -logger = logging.getLogger('doc2.nodes') +_logger = logging.getLogger(__name__) def _str2time(cre): """ Convert a string with time representation (from db) into time (float) @@ -328,7 +327,7 @@ class node_class(object): if self.DAV_M_NS.has_key(ns): prefix = self.DAV_M_NS[ns] else: - logger.debug('No namespace: %s ("%s")',ns, prop) + _logger.debug('No namespace: %s ("%s")',ns, prop) return None mname = prefix + "_" + prop.replace('-','_') @@ -341,7 +340,7 @@ class node_class(object): r = m(cr) return r except AttributeError: - logger.debug('Property %s not supported' % prop, exc_info=True) + _logger.debug('Property %s not supported' % prop, exc_info=True) return None def get_dav_resourcetype(self, cr): @@ -384,13 +383,13 @@ class node_class(object): def create_child(self, cr, path, data=None): """ Create a regular file under this node """ - logger.warning("Attempted to create a file under %r, not possible.", self) + _logger.warning("Attempted to create a file under %r, not possible.", self) raise IOError(errno.EPERM, "Not allowed to create files here") def create_child_collection(self, cr, objname): """ Create a child collection (directory) under self """ - logger.warning("Attempted to create a collection under %r, not possible.", self) + _logger.warning("Attempted to create a collection under %r, not possible.", self) raise IOError(errno.EPERM, "Not allowed to create folders here") def rm(self, cr): @@ -725,7 +724,7 @@ class node_dir(node_database): assert self.parent if self.parent != ndir_node: - logger.debug('Cannot move dir %r from %r to %r', self, self.parent, ndir_node) + _logger.debug('Cannot move dir %r from %r to %r', self, self.parent, ndir_node) raise NotImplementedError('Cannot move dir to another dir') ret = {} @@ -998,7 +997,7 @@ class node_res_obj(node_class): def get_dav_eprop_DEPR(self, cr, ns, prop): # Deprecated! if ns != 'http://groupdav.org/' or prop != 'resourcetype': - logger.warning("Who asked for %s:%s?" % (ns, prop)) + _logger.warning("Who asked for %s:%s?" % (ns, prop)) return None cntobj = self.context._dirobj.pool.get('document.directory.content') uid = self.context.uid @@ -1328,7 +1327,7 @@ class node_file(node_class): ret = {} if ndir_node and self.parent != ndir_node: if not (isinstance(self.parent, node_dir) and isinstance(ndir_node, node_dir)): - logger.debug('Cannot move file %r from %r to %r', self, self.parent, ndir_node) + _logger.debug('Cannot move file %r from %r to %r', self, self.parent, ndir_node) raise NotImplementedError('Cannot move files between dynamic folders') if not ndir_obj: @@ -1452,6 +1451,7 @@ class node_content(node_class): return '' class nodefd_content(StringIO, node_descriptor): + """ A descriptor to content nodes """ def __init__(self, parent, cr, mode, ctx): @@ -1473,7 +1473,7 @@ class nodefd_content(StringIO, node_descriptor): elif mode == 'a': StringIO.__init__(self, None) else: - logging.getLogger('document.content').error("Incorrect mode %s specified", mode) + _logger.error("Incorrect mode %s specified", mode) raise IOError(errno.EINVAL, "Invalid file mode") self.mode = mode @@ -1499,13 +1499,14 @@ class nodefd_content(StringIO, node_descriptor): raise NotImplementedError cr.commit() except Exception: - logging.getLogger('document.content').exception('Cannot update db content #%d for close:', par.cnt_id) + _logger.exception('Cannot update db content #%d for close:', par.cnt_id) raise finally: cr.close() StringIO.close(self) class nodefd_static(StringIO, node_descriptor): + """ A descriptor to nodes with static data. """ def __init__(self, parent, cr, mode, ctx=None): @@ -1526,7 +1527,7 @@ class nodefd_static(StringIO, node_descriptor): elif mode == 'a': StringIO.__init__(self, None) else: - logging.getLogger('document.nodes').error("Incorrect mode %s specified", mode) + _logger.error("Incorrect mode %s specified", mode) raise IOError(errno.EINVAL, "Invalid file mode") self.mode = mode @@ -1551,7 +1552,7 @@ class nodefd_static(StringIO, node_descriptor): raise NotImplementedError cr.commit() except Exception: - logging.getLogger('document.nodes').exception('Cannot update db content #%d for close:', par.cnt_id) + _logger.exception('Cannot update db content #%d for close:', par.cnt_id) raise finally: cr.close() diff --git a/addons/document/std_index.py b/addons/document/std_index.py index e153d018bf0..49ac9a5c906 100644 --- a/addons/document/std_index.py +++ b/addons/document/std_index.py @@ -25,6 +25,7 @@ import StringIO import odt2txt import sys, zipfile, xml.dom.minidom import logging +_logger = logging.getLogger(__name__) def _to_unicode(s): try: @@ -101,9 +102,9 @@ class DocIndex(indexer): (data, _) = pop.communicate() return _to_unicode(data) except OSError: - logger = logging.getLogger('document.DocIndex') - logger.warn("Failed attempt to execute antiword (MS Word reader). Antiword is necessary to index the file %s of MIME type %s. Detailed error available at DEBUG level.", fname, self._getMimeTypes()[0]) - logger.debug("Trace of the failed file indexing attempt: ", exc_info=True) + + _logger.warn("Failed attempt to execute antiword (MS Word reader). Antiword is necessary to index the file %s of MIME type %s. Detailed error available at DEBUG level.", fname, self._getMimeTypes()[0]) + _logger.debug("Trace of the failed file indexing attempt: ", exc_info=True) return False cntIndex.register(DocIndex()) diff --git a/addons/document_ftp/ftpserver/__init__.py b/addons/document_ftp/ftpserver/__init__.py index 2e09a938a05..59aff85c633 100644 --- a/addons/document_ftp/ftpserver/__init__.py +++ b/addons/document_ftp/ftpserver/__init__.py @@ -23,9 +23,9 @@ import threading import ftpserver import authorizer import abstracted_fs -import netsvc +import logging from tools import config - +_logger = logging.getLogger(__name__) def start_server(): HOST = config.get('ftp_server_host', '127.0.0.1') PORT = int(config.get('ftp_server_port', '8021')) @@ -35,10 +35,7 @@ def start_server(): PASSIVE_PORTS = int(pps[0]), int(pps[1]) class ftp_server(threading.Thread): - def log(self, level, message): - logger = netsvc.Logger() - logger.notifyChannel('FTP', level, message) - + def run(self): autho = authorizer.authorizer() ftpserver.FTPHandler.authorizer = autho @@ -48,17 +45,17 @@ def start_server(): if PASSIVE_PORTS: ftpserver.FTPHandler.passive_ports = PASSIVE_PORTS - ftpserver.log = lambda msg: self.log(netsvc.LOG_INFO, msg) + ftpserver.log = lambda msg: _logger.info(msg) ftpserver.logline = lambda msg: None - ftpserver.logerror = lambda msg: self.log(netsvc.LOG_ERROR, msg) + ftpserver.logerror = lambda msg: _logger.error(msg) ftpd = ftpserver.FTPServer((HOST, PORT), ftpserver.FTPHandler) ftpd.serve_forever() if HOST.lower() == 'none': - netsvc.Logger().notifyChannel("FTP", netsvc.LOG_INFO, "\n Server FTP Not Started\n") + _logger.info("\n Server FTP Not Started\n") else: - netsvc.Logger().notifyChannel("FTP", netsvc.LOG_INFO, "\n Serving FTP on %s:%s\n" % (HOST, PORT)) + _logger.info("\n Serving FTP on %s:%s\n" % (HOST, PORT)) ds = ftp_server() ds.daemon = True ds.start() diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 83cd1cde725..429016a2376 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -56,7 +56,7 @@ class abstracted_fs(object): self.cwd = '/' self.cwd_node = None self.rnfr = None - self._log = logging.getLogger('FTP.fs') + self._log = logging.getLogger(__name__) # Ok def db_list(self): diff --git a/addons/document_webdav/redirect.py b/addons/document_webdav/redirect.py index 7bc0883f9e8..31f462bcbbe 100644 --- a/addons/document_webdav/redirect.py +++ b/addons/document_webdav/redirect.py @@ -24,9 +24,9 @@ import logging import urlparse from service.websrv_lib import FixSendError, HTTPHandler, HttpOptions from service.http_server import HttpLogHandler - +_logger = logging.getLogger(__name__) class RedirectHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler): - _logger = logging.getLogger('httpd.well-known') + _HTTP_OPTIONS = { 'Allow': ['OPTIONS', 'GET', 'HEAD', 'PROPFIND'] } redirect_paths = {} @@ -80,7 +80,7 @@ class RedirectHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler self.send_header("Content-Length", 0) self.end_headers() # Do we need a Cache-content: header here? - self._logger.debug("redirecting %s to %s", self.path, redir_path) + _logger.debug("redirecting %s to %s", self.path, redir_path) return None def do_PROPFIND(self): diff --git a/addons/document_webdav/test_davclient.py b/addons/document_webdav/test_davclient.py index 3c20cfed5b8..a4dda231bd8 100755 --- a/addons/document_webdav/test_davclient.py +++ b/addons/document_webdav/test_davclient.py @@ -43,7 +43,7 @@ from xmlrpclib import Transport, ProtocolError import StringIO import base64 -log = logging.getLogger('http-client') +_logger = logging.getLogger(__name__) class HTTP11(httplib.HTTP): _http_vsn = 11 @@ -62,7 +62,7 @@ class PersistentTransport(Transport): if not self._http.has_key(host): host, extra_headers, x509 = self.get_host_info(host) self._http[host] = HTTP11(host) - log.debug("New connection to %s", host) + _logger.debug("New connection to %s", host) return self._http[host] def get_host_info(self, host): @@ -170,7 +170,7 @@ class SafePersistentTransport(PersistentTransport): if not self._http.has_key(host): host, extra_headers, x509 = self.get_host_info(host) self._http[host] = httplib.HTTPS(host, None, **(x509 or {})) - log.debug("New connection to %s", host) + _logger.debug("New connection to %s", host) return self._http[host] class AuthClient(object): @@ -191,8 +191,8 @@ class BasicAuthClient(AuthClient): return super(BasicAuthClient,self).getAuth(atype, realm) if not self._realm_dict.has_key(realm): - log.debug("realm dict: %r", self._realm_dict) - log.debug("missing key: \"%s\"" % realm) + _logger.debug("realm dict: %r", self._realm_dict) + _logger.debug("missing key: \"%s\"" % realm) self.resolveFailedRealm(realm) return 'Basic '+ self._realm_dict[realm] @@ -239,7 +239,7 @@ class addAuthTransport: # This line will bork if self.setAuthClient has not # been issued. That is a programming error, fix your code! auths = self._auth_client.getAuth(atype, realm) - log.debug("sending authorization: %s", auths) + _logger.debug("sending authorization: %s", auths) h.putheader('Authorization', auths) self.send_content(h, request_body) @@ -255,8 +255,8 @@ class addAuthTransport: log.warning("Why have data on a 401 auth. message?") if realm.startswith('realm="') and realm.endswith('"'): realm = realm[7:-1] - log.debug("Resp: %r %r", resp.version,resp.isclosed(), resp.will_close) - log.debug("Want to do auth %s for realm %s", atype, realm) + _logger.debug("Resp: %r %r", resp.version,resp.isclosed(), resp.will_close) + _logger.debug("Want to do auth %s for realm %s", atype, realm) if atype != 'Basic': raise ProtocolError(host+handler, 403, "Unknown authentication method: %s" % atype, resp.msg) @@ -315,7 +315,7 @@ class HTTPSConnection(httplib.HTTPSConnection): lf = (len(ssl.PEM_FOOTER)+1) if cert[0-lf] != '\n': cert = cert[:0-lf]+'\n'+cert[0-lf:] - log.debug("len-footer: %s cert: %r", lf, cert[0-lf]) + _logger.debug("len-footer: %s cert: %r", lf, cert[0-lf]) return cert @@ -390,7 +390,7 @@ class DAVClient(object): import base64 dbg = self.dbg hdrs.update(self.hdrs) - log.debug("Getting %s http://%s:%d/%s", method, self.host, self.port, path) + _logger.debug("Getting %s http://%s:%d/%s", method, self.host, self.port, path) conn = httplib.HTTPConnection(self.host, port=self.port, timeout=self.timeout) conn.set_debuglevel(dbg) if not path: @@ -409,8 +409,8 @@ class DAVClient(object): data1 = r1.read() if not self.user: raise Exception('Must auth, have no user/pass!') - log.debug("Ver: %s, closed: %s, will close: %s", r1.version,r1.isclosed(), r1.will_close) - log.debug("Want to do auth %s for realm %s", atype, realm) + _logger.debug("Ver: %s, closed: %s, will close: %s", r1.version,r1.isclosed(), r1.will_close) + _logger.debug("Want to do auth %s for realm %s", atype, realm) if atype == 'Basic' : auths = base64.encodestring(self.user + ':' + self.passwd) if auths[-1] == "\n": @@ -422,22 +422,22 @@ class DAVClient(object): else: raise Exception("Unknown auth type %s" %atype) else: - log.warning("Got 401, cannot auth") + _logger.warning("Got 401, cannot auth") raise Exception('No auth') - log.debug("Reponse: %s %s",r1.status, r1.reason) + _logger.debug("Reponse: %s %s",r1.status, r1.reason) data1 = r1.read() if method != 'GET': - log.debug("Body:\n%s\nEnd of body", data1) + _logger.debug("Body:\n%s\nEnd of body", data1) try: ctype = r1.msg.getheader('content-type') if ctype and ';' in ctype: ctype, encoding = ctype.split(';',1) if ctype == 'text/xml': doc = xml.dom.minidom.parseString(data1) - log.debug("XML Body:\n %s", doc.toprettyxml(indent="\t")) + _logger.debug("XML Body:\n %s", doc.toprettyxml(indent="\t")) except Exception: - log.warning("could not print xml", exc_info=True) + _logger.warning("could not print xml", exc_info=True) pass conn.close() return r1.status, r1.msg, data1 @@ -474,7 +474,7 @@ class DAVClient(object): s, m, d = self._http_request(path, method='OPTIONS', hdrs=hdrs) assert s == 200, "Status: %r" % s assert 'OPTIONS' in m.getheader('Allow') - log.debug('Options: %r', m.getheader('Allow')) + _logger.debug('Options: %r', m.getheader('Allow')) if expect: self._assert_headers(expect, m) @@ -493,10 +493,10 @@ class DAVClient(object): for cnod in node.childNodes: if cnod.nodeType != node.ELEMENT_NODE: if strict: - log.debug("Found %r inside <%s>", cnod, node.tagName) + _logger.debug("Found %r inside <%s>", cnod, node.tagName) continue if namespaces and (cnod.namespaceURI not in namespaces): - log.debug("Ignoring <%s> in <%s>", cnod.tagName, node.localName) + _logger.debug("Ignoring <%s> in <%s>", cnod.tagName, node.localName) continue yield cnod @@ -533,10 +533,10 @@ class DAVClient(object): assert htver == 'HTTP/1.1' rstatus = int(sta) else: - log.debug("What is <%s> inside a ?", pno.tagName) + _logger.debug("What is <%s> inside a ?", pno.tagName) else: - log.debug("Unknown node: %s", cno.tagName) + _logger.debug("Unknown node: %s", cno.tagName) res.setdefault(href,[]).append((status, res_nss)) @@ -637,7 +637,7 @@ class DAVClient(object): if lsp[1] in davprops: lsline[lsp[0]] = lsp[2] else: - log.debug("Strange status: %s", st) + _logger.debug("Strange status: %s", st) res.append(lsline) diff --git a/addons/document_webdav/webdav_server.py b/addons/document_webdav/webdav_server.py index ae75d0b80fc..65ebb49ff5f 100644 --- a/addons/document_webdav/webdav_server.py +++ b/addons/document_webdav/webdav_server.py @@ -54,7 +54,7 @@ from DAV.propfind import PROPFIND # from DAV.constants import DAV_VERSION_1, DAV_VERSION_2 from xml.dom import minidom from redirect import RedirectHTTPHandler - +_logger = logging.getLogger(__name__) khtml_re = re.compile(r' KHTML/([0-9\.]+) ') def OpenDAVConfig(**kw): @@ -73,7 +73,7 @@ def OpenDAVConfig(**kw): class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler): verbose = False - _logger = logging.getLogger('webdav') + protocol_version = 'HTTP/1.1' _HTTP_OPTIONS= { 'DAV' : ['1', '2'], 'Allow' : [ 'GET', 'HEAD', 'COPY', 'MOVE', 'POST', 'PUT', @@ -127,10 +127,10 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler): return self.davpath def log_message(self, format, *args): - self._logger.debug(format % args) + _logger.debug(format % args) def log_error(self, format, *args): - self._logger.warning(format % args) + _logger.warning(format % args) def _prep_OPTIONS(self, opts): ret = opts @@ -477,7 +477,7 @@ class dummy_dav_interface(object): uri2 = uri.split('/') if len(uri2) < 3: return True - logging.getLogger('webdav').debug("Requested uri: %s", uri) + _logger.debug("Requested uri: %s", uri) return None # no def is_collection(self, uri): @@ -487,6 +487,7 @@ class dummy_dav_interface(object): class DAVStaticHandler(http_server.StaticHTTPHandler): """ A variant of the Static handler, which will serve dummy DAV requests """ + verbose = False protocol_version = 'HTTP/1.1' _HTTP_OPTIONS= { 'DAV' : ['1', '2'], @@ -573,7 +574,7 @@ try: conf = OpenDAVConfig(**_dc) handler._config = conf reg_http_service(directory, DAVHandler, DAVAuthProvider) - logging.getLogger('webdav').info("WebDAV service registered at path: %s/ "% directory) + _logger.info("WebDAV service registered at path: %s/ "% directory) if not (config.get_misc('webdav', 'no_root_hack', False)): # Now, replace the static http handler with the dav-enabled one. @@ -595,7 +596,7 @@ try: reg_http_service('/', DAVStaticHandler) except Exception, e: - logging.getLogger('webdav').error('Cannot launch webdav: %s' % e) + _logger.error('Cannot launch webdav: %s' % e) def init_well_known(): @@ -616,6 +617,8 @@ def init_well_known(): init_well_known() class PrincipalsRedirect(RedirectHTTPHandler): + + redirect_paths = {} def _find_redirect(self): @@ -639,7 +642,7 @@ def init_principals_redirect(): if dbname: PrincipalsRedirect.redirect_paths[''] = '/webdav/%s/principals' % dbname reg_http_service('/principals', PrincipalsRedirect) - logging.getLogger("web-services").info( + _logger.info( "Registered HTTP redirect handler for /principals to the %s db.", dbname) diff --git a/addons/document_webdav/webdav_view.xml b/addons/document_webdav/webdav_view.xml index 404e60588e3..44af9f2af3a 100644 --- a/addons/document_webdav/webdav_view.xml +++ b/addons/document_webdav/webdav_view.xml @@ -78,7 +78,7 @@ form - + - + diff --git a/addons/edi/__init__.py b/addons/edi/__init__.py index b4a80c0f1dd..46fabbc2fd0 100644 --- a/addons/edi/__init__.py +++ b/addons/edi/__init__.py @@ -23,12 +23,13 @@ import logging import models import edi_service from models.edi import EDIMixin, edi_document +_logger = logging.getLogger(__name__) # web try: import controllers except ImportError: - logging.getLogger('init.load').warn( + _logger.warn( """Could not load openerp-web section of EDI, EDI will not behave correctly To fix, launch openerp-web in embedded mode""") diff --git a/addons/edi/edi_service.py b/addons/edi/edi_service.py index 074720a03e9..f48cc60950a 100644 --- a/addons/edi/edi_service.py +++ b/addons/edi/edi_service.py @@ -23,7 +23,7 @@ import logging import netsvc import openerp -_logger = logging.getLogger('edi.service') +_logger = logging.getLogger(__name__) class edi(netsvc.ExportService): diff --git a/addons/edi/models/edi.py b/addons/edi/models/edi.py index 589922ff178..383d8c7aaba 100644 --- a/addons/edi/models/edi.py +++ b/addons/edi/models/edi.py @@ -35,6 +35,7 @@ import pooler from osv import osv,fields,orm from tools.translate import _ from tools.safe_eval import safe_eval as eval +_logger = logging.getLogger(__name__) EXTERNAL_ID_PATTERN = re.compile(r'^([^.:]+)(?::([^.]+))?\.(\S+)$') EDI_VIEW_WEB_URL = '%s/edi/view?db=%s&token=%s' @@ -72,7 +73,6 @@ def last_update_for(record): return record_log.get('write_date') or record_log.get('create_date') or False return False -_logger = logging.getLogger('edi') class edi_document(osv.osv): _name = 'edi.document' diff --git a/addons/edi/models/res_partner.py b/addons/edi/models/res_partner.py index 357fdec061b..86d8c81b790 100644 --- a/addons/edi/models/res_partner.py +++ b/addons/edi/models/res_partner.py @@ -24,6 +24,7 @@ from osv import fields,osv from edi import EDIMixin from openerp import SUPERUSER_ID from tools.translate import _ +_logger = logging.getLogger(__name__) RES_PARTNER_EDI_STRUCT = { 'name': True, @@ -63,7 +64,7 @@ class res_partner(osv.osv, EDIMixin): code, label = 'edi_generic', 'Generic Bank Type (auto-created for EDI)' bank_code_ids = res_partner_bank_type.search(cr, uid, [('code','=',code)], context=context) if not bank_code_ids: - logging.getLogger('edi.res_partner').info('Normal bank account type is missing, creating ' + _logger.info('Normal bank account type is missing, creating ' 'a generic bank account type for EDI.') self.res_partner_bank_type.create(cr, SUPERUSER_ID, {'name': label, 'code': label}) @@ -84,7 +85,7 @@ class res_partner(osv.osv, EDIMixin): bank_name, ext_bank_id, context=import_ctx) except osv.except_osv: # failed to import it, try again with unrestricted default type - logging.getLogger('edi.res_partner').warning('Failed to import bank account using' + _logger.warning('Failed to import bank account using' 'bank type: %s, ignoring', import_ctx['default_state'], exc_info=True) return contact_id diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index fff9bdc0f1a..c4ed2eedb85 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -29,11 +29,12 @@ from osv import fields import tools from tools.translate import _ from urllib import quote as quote +_logger = logging.getLogger(__name__) try: from mako.template import Template as MakoTemplate except ImportError: - logging.getLogger('init').warning("email_template: mako templates not available, templating features will not work!") + _logger.warning("email_template: mako templates not available, templating features will not work!") class email_template(osv.osv): "Templates for sending email" @@ -75,7 +76,7 @@ class email_template(osv.osv): result = u'' return result except Exception: - logging.exception("failed to render mako template value %r", template) + _logger.exception("failed to render mako template value %r", template) return u"" def get_email_template(self, cr, uid, template_id=False, record_id=None, context=None): diff --git a/addons/event_sale/event_sale.py b/addons/event_sale/event_sale.py index 0d6497c45f2..9e65a034544 100644 --- a/addons/event_sale/event_sale.py +++ b/addons/event_sale/event_sale.py @@ -78,7 +78,6 @@ class sale_order_line(osv.osv): dic = { 'name': order_line.order_id.partner_invoice_id.name, 'partner_id': order_line.order_id.partner_id.id, - 'contact_id': order_line.order_id.partner_invoice_id.id, 'nb_register': int(order_line.product_uom_qty), 'email': order_line.order_id.partner_id.email, 'phone': order_line.order_id.partner_id.phone, @@ -89,5 +88,5 @@ class sale_order_line(osv.osv): } registration_id = registration_obj.create(cr, uid, dic, context=context) message = _("The registration %s has been created from the Sale Order %s.") % (registration_id, order_line.order_id.name) - registration_obj.log(cr, uid, registration_id, message) + registration_obj.message_append_note(cr, uid, [registration_id], body=message, context=context) return super(sale_order_line, self).button_confirm(cr, uid, ids, context=context) diff --git a/addons/fetchmail/fetchmail.py b/addons/fetchmail/fetchmail.py index 93ebd063051..cddcf642dc4 100644 --- a/addons/fetchmail/fetchmail.py +++ b/addons/fetchmail/fetchmail.py @@ -39,7 +39,7 @@ from osv import osv, fields import tools from tools.translate import _ -logger = logging.getLogger('fetchmail') +_logger = logging.getLogger(__name__) class fetchmail_server(osv.osv): """Incoming POP/IMAP mail server account""" diff --git a/addons/google_docs/__openerp__.py b/addons/google_docs/__openerp__.py index 1df94f34221..8cc5671e08a 100644 --- a/addons/google_docs/__openerp__.py +++ b/addons/google_docs/__openerp__.py @@ -30,6 +30,7 @@ 'web': True, 'js': ['static/src/js/gdocs.js'], 'update_xml': [ + 'security/ir.model.access.csv', 'res_config_user_view.xml' ], 'depends': ['google_base_account'], diff --git a/addons/google_docs/security/ir.model.access.csv b/addons/google_docs/security/ir.model.access.csv new file mode 100644 index 00000000000..9393d586fb8 --- /dev/null +++ b/addons/google_docs/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_google_docs,google.docs.config,model_google_docs_config,,1,0,0,0 +access_google_docs,google.docs.config,model_google_docs_config,base.group_system,1,1,1,1 \ No newline at end of file diff --git a/addons/hr/hr.py b/addons/hr/hr.py index e2bca73a72e..e4c08f60559 100644 --- a/addons/hr/hr.py +++ b/addons/hr/hr.py @@ -25,6 +25,7 @@ import logging from osv import fields, osv from PIL import Image import StringIO +_logger = logging.getLogger(__name__) class hr_employee_category(osv.osv): @@ -304,7 +305,7 @@ class res_users(osv.osv): 'user_id': user_id}, context=context) except: # Tolerate a missing shortcut. See product/product.py for similar code. - logging.getLogger('orm').debug('Skipped meetings shortcut for user "%s"', data.get('name','Francois fpi fpi - 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 Julien jth jth - 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 diff --git a/addons/hr/res_config_view.xml b/addons/hr/res_config_view.xml index a1bf809a5c9..f96f4a80ab2 100644 --- a/addons/hr/res_config_view.xml +++ b/addons/hr/res_config_view.xml @@ -17,7 +17,7 @@
diff --git a/addons/hr_expense/hr_expense_view.xml b/addons/hr_expense/hr_expense_view.xml index 6df675552b0..703060e792e 100644 --- a/addons/hr_expense/hr_expense_view.xml +++ b/addons/hr_expense/hr_expense_view.xml @@ -14,7 +14,7 @@ - + @@ -97,7 +97,7 @@ - + diff --git a/addons/hr_timesheet/hr_timesheet.py b/addons/hr_timesheet/hr_timesheet.py index 12fbbcdedef..66911f25ff5 100644 --- a/addons/hr_timesheet/hr_timesheet.py +++ b/addons/hr_timesheet/hr_timesheet.py @@ -190,4 +190,15 @@ class hr_analytic_timesheet(osv.osv): hr_analytic_timesheet() +class account_analytic_account(osv.osv): + + _inherit = 'account.analytic.account' + _description = 'Analytic Account' + + _columns = { + 'use_timesheets': fields.boolean('Timesheets', help="Check this field if this project manages timesheets"), + } + +account_analytic_account() + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/hr_timesheet/hr_timesheet_demo.xml b/addons/hr_timesheet/hr_timesheet_demo.xml index 3ec7664bcb7..077aaf6179c 100644 --- a/addons/hr_timesheet/hr_timesheet_demo.xml +++ b/addons/hr_timesheet/hr_timesheet_demo.xml @@ -12,6 +12,18 @@ + + True + + + True + + + True + + + True + Requirements analysis and specification @@ -20,7 +32,7 @@ - + @@ -33,7 +45,7 @@ - + @@ -46,7 +58,7 @@ - + @@ -59,7 +71,7 @@ - + @@ -72,7 +84,7 @@ - + diff --git a/addons/hr_timesheet/hr_timesheet_view.xml b/addons/hr_timesheet/hr_timesheet_view.xml index 53def628bfe..a8a361ed633 100644 --- a/addons/hr_timesheet/hr_timesheet_view.xml +++ b/addons/hr_timesheet/hr_timesheet_view.xml @@ -12,7 +12,7 @@ - + @@ -78,6 +78,21 @@ + + + account.analytic.account.invoice.form + account.analytic.account + form + + + + + + + + + + Timesheet Lines diff --git a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py index 0a58efb782b..f1138d7b246 100644 --- a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py +++ b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py @@ -63,39 +63,58 @@ class account_analytic_account(osv.osv): _inherit = "account.analytic.account" _columns = { - 'pricelist_id': fields.many2one('product.pricelist', 'Customer Pricelist', + 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', help="The product to invoice is defined on the employee form, the price will be deduced by this pricelist on the product."), 'amount_max': fields.float('Max. Invoice Price', help="Keep empty if this contract is not limited to a total fixed price."), 'amount_invoiced': fields.function(_invoiced_calc, string='Invoiced Amount', help="Total invoiced"), - 'to_invoice': fields.many2one('hr_timesheet_invoice.factor', 'Invoice on Timesheet & Costs', - help="Fill this field if you plan to automatically generate invoices based " \ - "on the costs in this analytic account: timesheets, expenses, ..." \ - "You can configure an automatic invoice rate on analytic accounts."), + 'to_invoice': fields.many2one('hr_timesheet_invoice.factor', 'Timesheet Invoicing Ratio', + help="This field allows you to define the rate in case you plan to reinvoice " \ + "the costs in this analytic account: timesheets, expenses, ..."), } _defaults = { 'pricelist_id': lambda self, cr, uid, ctx: ctx.get('pricelist_id', False), } - def on_change_partner_id(self, cr, uid, id, partner_id, context={}): - res={} - part = self.pool.get('res.partner').browse(cr, uid, partner_id) + + def on_change_use_timesheets(self, cr, uid, ids, use_timesheets, context=None): + res = {'value': {}} + if use_timesheets: + ir_model_obj = self.pool.get('ir.model.data') + res['value']['to_invoice'] = ir_model_obj.get_object_reference(cr, uid, 'hr_timesheet_invoice', 'timesheet_invoice_factor1')[1] + return res + + def on_change_partner_id(self, cr, uid, ids,partner_id, name, context=None): + res = super(account_analytic_account,self).on_change_partner_id(cr, uid, ids,partner_id, name, context=context) + part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context) pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False if pricelist: - res['pricelist_id'] = pricelist - return {'value': res} + res['value']['pricelist_id'] = pricelist + return res def set_close(self, cr, uid, ids, context=None): - return self.write(cr, uid, ids, {'state':'close'}, context=context) + self.write(cr, uid, ids, {'state':'close'}, context=context) + message = _("Contract has been closed.") + self.message_append_note(cr, uid, ids, body=message, context=context) + return True def set_cancel(self, cr, uid, ids, context=None): - return self.write(cr, uid, ids, {'state':'cancelled'}, context=context) + self.write(cr, uid, ids, {'state':'cancelled'}, context=context) + message = _("Contract has been cancelled.") + self.message_append_note(cr, uid, ids, body=message, context=context) + return True def set_open(self, cr, uid, ids, context=None): - return self.write(cr, uid, ids, {'state':'open'}, context=context) + self.write(cr, uid, ids, {'state':'open'}, context=context) + message = _("Contract has been opened.") + self.message_append_note(cr, uid, ids, body=message, context=context) + return True def set_pending(self, cr, uid, ids, context=None): - return self.write(cr, uid, ids, {'state':'pending'}, context=context) + self.write(cr, uid, ids, {'state':'pending'}, context=context) + message = _("Contract has been set as pending.") + self.message_append_note(cr, uid, ids, body=message, context=context) + return True account_analytic_account() @@ -107,10 +126,6 @@ class account_analytic_line(osv.osv): 'to_invoice': fields.many2one('hr_timesheet_invoice.factor', 'Type of Invoicing', help="It allows to set the discount while making invoice"), } - def unlink(self, cursor, user, ids, context=None): - return super(account_analytic_line,self).unlink(cursor, user, ids, - context=context) - def write(self, cr, uid, ids, vals, context=None): self._check_inv(cr, uid, ids, vals) return super(account_analytic_line,self).write(cr, uid, ids, vals, diff --git a/addons/hr_timesheet_invoice/hr_timesheet_invoice_view.xml b/addons/hr_timesheet_invoice/hr_timesheet_invoice_view.xml index 51743733cd5..64e0ef592b7 100644 --- a/addons/hr_timesheet_invoice/hr_timesheet_invoice_view.xml +++ b/addons/hr_timesheet_invoice/hr_timesheet_invoice_view.xml @@ -5,28 +5,23 @@ account.analytic.account.invoice.form account.analytic.account form - + 30 + - - - - - - + + + - - - - - - - + + + +
+ @@ -23,6 +24,11 @@ + +