From 9048db178d4165b88cdb599f406dcac73a8c5d39 Mon Sep 17 00:00:00 2001 From: Julien Thewys Date: Thu, 5 Aug 2010 15:46:26 +0200 Subject: [PATCH 001/192] [IMP] Allows longer record rules (text). bzr revid: jth@openerp.com-20100805134626-m0roia22nc6fv7f9 --- bin/addons/base/ir/ir_rule.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/addons/base/ir/ir_rule.py b/bin/addons/base/ir/ir_rule.py index 23981458606..1104d2d3770 100644 --- a/bin/addons/base/ir/ir_rule.py +++ b/bin/addons/base/ir/ir_rule.py @@ -55,8 +55,8 @@ class ir_rule(osv.osv): 'model_id': fields.many2one('ir.model', 'Object',select=1, required=True), 'global': fields.function(_get_value, method=True, string='Global', type='boolean', store=True, help="If no group is specified the rule is global and applied to everyone"), 'groups': fields.many2many('res.groups', 'rule_group_rel', 'rule_group_id', 'group_id', 'Groups'), - 'domain_force': fields.char('Domain', size=250), - 'domain': fields.function(_domain_force_get, method=True, string='Domain', type='char', size=250), + 'domain_force': fields.text('Domain'), + 'domain': fields.function(_domain_force_get, method=True, string='Domain', type='text'), 'perm_read': fields.boolean('Apply For Read'), 'perm_write': fields.boolean('Apply For Write'), 'perm_create': fields.boolean('Apply For Create'), From eeffc06f2afddcd6cdb1f19ca6b31b3acfa3396b Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:37:23 +0300 Subject: [PATCH 002/192] base_calendar, caldav: revise SQL calls. bzr revid: p_christ@hol.gr-20101012103723-alnh7rkf1g8tthlo --- addons/base_calendar/base_calendar.py | 4 ++-- addons/caldav/calendar.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/base_calendar/base_calendar.py b/addons/base_calendar/base_calendar.py index 1ff7777cf36..2a0b0cc60ad 100644 --- a/addons/base_calendar/base_calendar.py +++ b/addons/base_calendar/base_calendar.py @@ -1065,7 +1065,7 @@ class calendar_event(osv.osv): 'rule_type': rrule_type, 'id': id, }) - cr.execute(qry % val) + cr.execute(qry, val) # Hopefully psycopg2 works with dicts. But, FIXME return True def _get_rulestring(self, cr, uid, ids, name, arg, context=None): @@ -1682,7 +1682,7 @@ class calendar_todo(osv.osv): @param args: list of tuples of form [(‘name_of_the_field’, ‘operator’, value), ...]. @param context: A standard dictionary for contextual values """ - + assert name == 'date' return self.write(cr, uid, id, { 'date_start': value }, context=context) diff --git a/addons/caldav/calendar.py b/addons/caldav/calendar.py index 068af56459f..883688c38f6 100644 --- a/addons/caldav/calendar.py +++ b/addons/caldav/calendar.py @@ -63,8 +63,8 @@ def uid2openobjectid(cr, uidval, oomodel, rdate): return (False, None) qry = 'SELECT DISTINCT(id) FROM %s' % model_obj._table if rdate: - qry += " where recurrent_id='%s'" % (rdate) #TOFIX: sql injection - cr.execute(qry) + qry += " WHERE recurrent_id=%s" + cr.execute(qry, (rdate,)) r_id = cr.fetchone() if r_id: return (id, r_id[0]) From 96499ac6ce8497c7ab8720a2b4516e00c5e1d314 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:38:30 +0300 Subject: [PATCH 003/192] doc webdav: fix namespaces, properties bzr revid: p_christ@hol.gr-20101012103830-0mfo4ecuu4lu78ia --- addons/document_webdav/dav_fs.py | 7 ++++++- addons/document_webdav/webdav.py | 16 ++++++++++------ addons/document_webdav/webdav_server.py | 3 ++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/addons/document_webdav/dav_fs.py b/addons/document_webdav/dav_fs.py index f3c8201bafc..9b04d2f3ff0 100644 --- a/addons/document_webdav/dav_fs.py +++ b/addons/document_webdav/dav_fs.py @@ -194,7 +194,12 @@ class openerp_dav_handler(dav_interface): pname -- name of the property """ if self.M_NS.has_key(ns): - return dav_interface.get_prop(self, uri, ns, propname) + try: + # if it's not in the interface class, a "DAV:" property + # may be at the node class. So shouldn't give up early. + return dav_interface.get_prop(self, uri, ns, propname) + except DAV_NotFound: + pass cr, uid, pool, dbname, uri2 = self.get_cr(uri) if not dbname: if cr: cr.close() diff --git a/addons/document_webdav/webdav.py b/addons/document_webdav/webdav.py index 3b6cf582067..6b24198e320 100644 --- a/addons/document_webdav/webdav.py +++ b/addons/document_webdav/webdav.py @@ -48,7 +48,7 @@ def mk_prop_response(self, uri, good_props, bad_props, doc): re=doc.createElement("D:response") # append namespaces to response nsnum=0 - namespaces = self.namespaces + namespaces = self.namespaces[:] if 'DAV:' in namespaces: namespaces.remove('DAV:') for nsname in namespaces: @@ -108,10 +108,14 @@ def mk_prop_response(self, uri, good_props, bad_props, doc): ve=doc.createElement(ns_prefix+v[0]) if need_ns: ve.setAttribute("xmlns:ns"+str(nsnum), v[1]) - if len(v) > 2 and isinstance(v[2], list): - # support nested elements like: - # ( 'elem', 'ns:', [('sub-elem1', 'ns1'), ...] - _prop_elem_child(ve, v[1], v[2], ns_prefix) + if len(v) > 2: + if isinstance(v[2], list): + # support nested elements like: + # ( 'elem', 'ns:', [('sub-elem1', 'ns1'), ...] + _prop_elem_child(ve, v[1], v[2], ns_prefix) + else: + vt =doc.createTextNode(tools.ustr(v[2])) + ve.appendChild(vt) pnode.appendChild(ve) else: ve=doc.createTextNode(tools.ustr(v)) @@ -141,7 +145,7 @@ def mk_prop_response(self, uri, good_props, bad_props, doc): else: ns_prefix="ns"+str(namespaces.index(ns))+":" for p,v in good_props[ns].items(): - if not v: + if v is None: continue _prop_child(gp, ns, p, v) diff --git a/addons/document_webdav/webdav_server.py b/addons/document_webdav/webdav_server.py index 44592243480..c86cde2b096 100644 --- a/addons/document_webdav/webdav_server.py +++ b/addons/document_webdav/webdav_server.py @@ -208,8 +208,9 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler): if self.headers.has_key("Content-Type"): ct=self.headers['Content-Type'] try: - location = dc.put(uri,body,ct) + location = dc.put(uri, body, ct) except DAV_Error, (ec,dd): + self._logger.warning("Cannot PUT to %s: %s", uri, dd, exc_info=True) return self.send_status(ec) headers = {} From ef59ed0203296335f16a1e2cc698f19ce1d021c1 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:38:30 +0300 Subject: [PATCH 004/192] crm_caldav: fix for empty 'date_deadline' bzr revid: p_christ@hol.gr-20101012103830-5439bl3x0gnza141 --- addons/crm_caldav/crm_caldav.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/crm_caldav/crm_caldav.py b/addons/crm_caldav/crm_caldav.py index 22f64f4d63d..1a2deae90be 100644 --- a/addons/crm_caldav/crm_caldav.py +++ b/addons/crm_caldav/crm_caldav.py @@ -77,7 +77,7 @@ class crm_meeting(osv.osv): try: for val in vals: # Compute value of duration - if 'date_deadline' in val and 'duration' not in val: + if val.get('date_deadline', False) and 'duration' not in val: start = datetime.strptime(val['date'], '%Y-%m-%d %H:%M:%S') end = datetime.strptime(val['date_deadline'], '%Y-%m-%d %H:%M:%S') diff = end - start From 2655d0e091357c7f70dde96f216f59444b8297df Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:38:31 +0300 Subject: [PATCH 005/192] Doc Webdav: when scanning children, protect the cursor against exceptions. bzr revid: p_christ@hol.gr-20101012103831-36qutrtz7avzb2ny --- addons/document_webdav/dav_fs.py | 37 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/addons/document_webdav/dav_fs.py b/addons/document_webdav/dav_fs.py index 9b04d2f3ff0..8cd00da2a12 100644 --- a/addons/document_webdav/dav_fs.py +++ b/addons/document_webdav/dav_fs.py @@ -288,26 +288,27 @@ class openerp_dav_handler(dav_interface): result = [] node = self.uri2object(cr, uid, pool, uri2[:]) - if not node: - if cr: cr.close() - raise DAV_NotFound2(uri2) - else: - fp = node.full_path() - if fp and len(fp): - self.parent.log_message('childs: @%s' % fp) - fp = '/'.join(fp) + try: + if not node: + raise DAV_NotFound2(uri2) else: - fp = None - domain = None - if filters: - domain = node.get_domain(cr, filters) - for d in node.children(cr, domain): - self.parent.log_message('child: %s' % d.path) - if fp: - result.append( self.urijoin(dbname,fp,d.path) ) + fp = node.full_path() + if fp and len(fp): + self.parent.log_message('childs: @%s' % fp) + fp = '/'.join(fp) else: - result.append( self.urijoin(dbname,d.path) ) - if cr: cr.close() + fp = None + domain = None + if filters: + domain = node.get_domain(cr, filters) + for d in node.children(cr, domain): + self.parent.log_message('child: %s' % d.path) + if fp: + result.append( self.urijoin(dbname,fp,d.path) ) + else: + result.append( self.urijoin(dbname,d.path) ) + finally: + if cr: cr.close() return result def uri2local(self, uri): From 402d407b2c3f630ca57d31f72b48ed397fec1119 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:38:56 +0300 Subject: [PATCH 006/192] Document: make node classes pluggable (part 1) Refactor and put hooks in document/nodes so that addons can override the classes and enhance the node behaviour, per database. bzr revid: p_christ@hol.gr-20101012103856-j115po98jqff6j02 --- addons/document/document_directory.py | 32 ++++++++++++++++++++ addons/document/nodes.py | 42 +++++++++++++-------------- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/addons/document/document_directory.py b/addons/document/document_directory.py index 3486359f183..0c208ad04b7 100644 --- a/addons/document/document_directory.py +++ b/addons/document/document_directory.py @@ -175,6 +175,38 @@ class document_directory(osv.osv): return nodes.get_node_context(cr, uid, context).get_uri(cr, uri) + def get_node_class(self, cr, uid, ids, dbro=None, context=None): + """Retrieve the class of nodes for this directory + + This function can be overriden by inherited classes ;) + @param dbro The browse object, if caller already has it + """ + if dbro is None: + dbro = self.browse(cr, uid, ids, context=context) + + if dbro.type == 'directory': + return nodes.node_dir + elif dbro.type == 'ressource': + assert not dbro.ressource_parent_type_id, \ + "resource and parent_id at #%d: %r" % (dbro.id, dbro.ressource_parent_type_id) + return nodes.node_res_dir + else: + raise ValueError("dir node for %s type", dbro.type) + + def _prepare_context(self, cr, uid, nctx, context): + """ Fill nctx with properties for this database + @param nctx instance of nodes.node_context, to be filled + @param context ORM context (dict) for us + + Note that this function is called *without* a list of ids, + it should behave the same for the whole database (based on the + ORM instance of document.directory). + + Some databases may override this and attach properties to the + node_context. See WebDAV, CalDAV. + """ + return + def get_dir_permissions(self, cr, uid, ids ): """Check what permission user 'uid' has on directory 'id' """ diff --git a/addons/document/nodes.py b/addons/document/nodes.py index b849396498d..e4be756db3d 100644 --- a/addons/document/nodes.py +++ b/addons/document/nodes.py @@ -64,13 +64,16 @@ class node_context(object): """ This is the root node, representing access to some particular context """ cached_roots = {} + node_file_class = None def __init__(self, cr, uid, context=None): self.dbname = cr.dbname self.uid = uid self.context = context self._dirobj = pooler.get_pool(cr.dbname).get('document.directory') + self.node_file_class = node_file assert self._dirobj + self._dirobj._prepare_context(cr, uid, self, context=context) self.rootdir = False #self._dirobj._get_root_directory(cr,uid,context) def __eq__(self, other): @@ -104,14 +107,10 @@ class node_context(object): """Create (or locate) a node for a directory @param dbro a browse object of document.directory """ - fullpath = self._dirobj.get_full_path(cr, self.uid, dbro.id, self.context) - if dbro.type == 'directory': - return node_dir(fullpath, None ,self, dbro) - elif dbro.type == 'ressource': - assert dbro.ressource_parent_type_id == False - return node_res_dir(fullpath, None, self, dbro) - else: - raise ValueError("dir node for %s type", dbro.type) + + fullpath = dbro.get_full_path(context=self.context) + klass = dbro.get_node_class(dbro, context=self.context) + return klass(fullpath, None ,self, dbro) def get_file_node(self, cr, fbro): """ Create or locate a node for a static file @@ -121,7 +120,7 @@ class node_context(object): if fbro.parent_id: parent = self.get_dir_node(cr, fbro.parent_id) - return node_file(fbro.name, parent, self, fbro) + return self.node_file_class(fbro.name, parent, self, fbro) class node_descriptor(object): @@ -438,22 +437,20 @@ class node_database(node_class): if not domain: domain = [] - where2 = where + domain + [('type', '=', 'directory')] + where2 = where + domain + ['|', ('type', '=', 'directory'), \ + '&', ('type', '=', 'ressource'), ('ressource_parent_type_id','=',False)] ids = dirobj.search(cr, uid, where2, context=ctx) res = [] for dirr in dirobj.browse(cr, uid, ids, context=ctx): - res.append(node_dir(dirr.name, self, self.context,dirr)) - - where2 = where + domain + [('type', '=', 'ressource'), ('ressource_parent_type_id','=',False)] - ids = dirobj.search(cr, uid, where2, context=ctx) - for dirr in dirobj.browse(cr, uid, ids, context=ctx): - res.append(node_res_dir(dirr.name, self, self.context, dirr)) + klass = dirr.get_node_class(dirr, context=ctx) + res.append(klass(dirr.name, self, self.context,dirr)) fil_obj = dirobj.pool.get('ir.attachment') ids = fil_obj.search(cr, uid, where, context=ctx) if ids: for fil in fil_obj.browse(cr, uid, ids, context=ctx): - res.append(node_file(fil.name, self, self.context, fil)) + klass = self.context.node_file_class + res.append(klass(fil.name, self, self.context, fil)) return res def _file_get(self,cr, nodename=False): @@ -910,6 +907,7 @@ class node_res_obj(node_class): where = [('directory_id','=',self.dir_id) ] ids = cntobj.search(cr,uid,where,context=ctx) for content in cntobj.browse(cr, uid, ids, context=ctx): + # TODO: remove relic of GroupDAV if content.extension == '.ics': # FIXME: call the content class! return ('vevent-collection','http://groupdav.org/') return None @@ -944,6 +942,7 @@ class node_res_obj(node_class): res_name = getattr(bo, namefield) if not res_name: continue + # TODO Revise res.append(node_res_obj(res_name, self.dir_id, self, self.context, self.res_model, res_bo = bo)) @@ -956,9 +955,6 @@ class node_res_obj(node_class): # child resources can be controlled by properly set dctx res.append(node_res_dir(dirr.name,self,self.context, dirr, {'active_id': self.res_id})) - - - fil_obj = dirobj.pool.get('ir.attachment') if self.res_find_all: where2 = where @@ -967,7 +963,8 @@ class node_res_obj(node_class): ids = fil_obj.search(cr, uid, where3, context=ctx) if ids: for fil in fil_obj.browse(cr, uid, ids, context=ctx): - res.append(node_file(fil.name, self, self.context, fil)) + klass = self.context.node_file_class + res.append(klass(fil.name, self, self.context, fil)) # Get Child Ressource Directories @@ -1038,7 +1035,8 @@ class node_res_obj(node_class): fil_id = fil_obj.create(cr, uid, val, context=ctx) fil = fil_obj.browse(cr, uid, fil_id, context=ctx) - fnode = node_file(path, self, self.context, fil) + klass = self.context.node_file_class + fnode = klass(path, self, self.context, fil) if data is not None: fnode.set_data(cr, data, fil) return fnode From c52b73b587971da28020c47dcb5395a82e2b20ea Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:38:58 +0300 Subject: [PATCH 007/192] doc webdav: initial override of nodes.py bzr revid: p_christ@hol.gr-20101012103858-hykw2g2ezbwaobmn --- addons/document_webdav/__init__.py | 4 ++ addons/document_webdav/dav_fs.py | 1 - addons/document_webdav/document_webdav.py | 47 +++++++++++++++++++ addons/document_webdav/nodes.py | 57 +++++++++++++++++++++++ 4 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 addons/document_webdav/document_webdav.py create mode 100644 addons/document_webdav/nodes.py diff --git a/addons/document_webdav/__init__.py b/addons/document_webdav/__init__.py index 69312668a74..ac417e18087 100644 --- a/addons/document_webdav/__init__.py +++ b/addons/document_webdav/__init__.py @@ -21,3 +21,7 @@ import webdav import webdav_server + +import document_webdav + +#eof \ No newline at end of file diff --git a/addons/document_webdav/dav_fs.py b/addons/document_webdav/dav_fs.py index 8cd00da2a12..9e04673555a 100644 --- a/addons/document_webdav/dav_fs.py +++ b/addons/document_webdav/dav_fs.py @@ -37,7 +37,6 @@ from DAV.iface import * import urllib from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree -from document.nodes import node_res_dir, node_res_obj from cache import memoize from tools import misc CACHE_SIZE=20000 diff --git a/addons/document_webdav/document_webdav.py b/addons/document_webdav/document_webdav.py new file mode 100644 index 00000000000..2db183e4ec6 --- /dev/null +++ b/addons/document_webdav/document_webdav.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2010 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from osv import osv, fields +import nodes + +class document_davdir(osv.osv): + _inherit = 'document.directory' + + def get_node_class(self, cr, uid, ids, dbro=None, context=None): + # Note: in this function, nodes come from document_webdav/nodes.py ! + if dbro is None: + dbro = self.browse(cr, uid, ids, context=context) + + if dbro.type == 'directory': + return nodes.node_dir + elif dbro.type == 'ressource': + assert not dbro.ressource_parent_type_id, \ + "resource and parent_id at #%d: %r" % (dbro.id, dbro.ressource_parent_type_id) + return nodes.node_res_dir + else: + raise ValueError("dir node for %s type", dbro.type) + + def _prepare_context(self, cr, uid, nctx, context): + nctx.node_file_class = nodes.node_file + return + +document_davdir() +#eof \ No newline at end of file diff --git a/addons/document_webdav/nodes.py b/addons/document_webdav/nodes.py new file mode 100644 index 00000000000..6e9d85a0ae3 --- /dev/null +++ b/addons/document_webdav/nodes.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2010 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + + +from document import nodes + +class node_acl_mixin(object): + def _get_dav_owner(self, cr): + return self.uuser + + def _get_dav_group(self, cr): + return self.ugroup + + def _get_dav_supported_privilege_set(self, cr): + return '' # TODO + + def _get_dav_current_user_privilege_set(self, cr): + return '' # TODO + +class node_dir(node_acl_mixin, nodes.node_dir): + DAV_PROPS = { "DAV:": ('owner', 'group', + 'supported-privilege-set', + 'current-user-privilege-set'), + } + DAV_M_NS = { "DAV:" : '_get_dav',} + http_options = { 'DAV': ['access-control',] } + + +class node_file(node_acl_mixin, nodes.node_file): + DAV_PROPS = { "DAV:": ('owner', 'group', + 'supported-privilege-set', + 'current-user-privilege-set'), + } + DAV_M_NS = { "DAV:" : '_get_dav',} + http_options = { 'DAV': ['access-control', ] } + pass + + +#eof From 7f97ed973f1c3b7b59a291b7c9ae8b816a8d69c6 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:38:59 +0300 Subject: [PATCH 008/192] Caldav: put some logging around skipped data, fix tzinfo var. bzr revid: p_christ@hol.gr-20101012103859-0pae3clyllabjs0y --- addons/caldav/calendar.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/addons/caldav/calendar.py b/addons/caldav/calendar.py index 883688c38f6..b5a62686494 100644 --- a/addons/caldav/calendar.py +++ b/addons/caldav/calendar.py @@ -30,6 +30,7 @@ import pytz import re import tools import time +import logging from caldav_node import res_node_calendar try: @@ -232,6 +233,7 @@ 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 @@ -294,6 +296,7 @@ class CalDAV(object): att_data = [] exdates = [] + _server_tzinfo = pytz.timezone(tools.get_server_timezone()) for cal_data in child.getChildren(): if cal_data.name.lower() == 'organizer': @@ -668,6 +671,15 @@ class Calendar(CalDAV, osv.osv): val = self.parse_ics(cr, uid, child, cal_children=cal_children, context=context) vals.append(val) 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) + elif child.name.upper() in ('PRODID', 'VERSION'): + pass + elif child.name.upper().startswith('X-'): + self._logger.debug("skipping custom node %s", child.name) + else: + self._logger.debug("skipping node %s", child.name) res = [] for obj_name in list(set(objs)): From 58a95955bac32c58e516ce9434ae8f4a541a96ab Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:38:59 +0300 Subject: [PATCH 009/192] Doc Webdav: implement model for DAV properties (at dirs) In normative setups of WebDAV, we need to place some extra properties on selected directories. bzr revid: p_christ@hol.gr-20101012103859-dcwsrzylxwpzbpq1 --- addons/document_webdav/__openerp__.py | 4 +- addons/document_webdav/document_webdav.py | 40 +++++++ addons/document_webdav/nodes.py | 104 ++++++++++++++++++ .../security/ir.model.access.csv | 4 + addons/document_webdav/webdav_view.xml | 98 +++++++++++++++++ 5 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 addons/document_webdav/security/ir.model.access.csv create mode 100644 addons/document_webdav/webdav_view.xml diff --git a/addons/document_webdav/__openerp__.py b/addons/document_webdav/__openerp__.py index 39330f68dea..4daaa8824cb 100644 --- a/addons/document_webdav/__openerp__.py +++ b/addons/document_webdav/__openerp__.py @@ -30,7 +30,7 @@ { "name" : "WebDAV server for Document Management", - "version" : "2.0", + "version" : "2.1", "author" : "OpenERP SA", "category" : "Generic Modules/Others", "website": "http://www.openerp.com", @@ -41,7 +41,7 @@ """, "depends" : ["base", "document"], "init_xml" : [], - "update_xml" : [], + "update_xml" : ['security/ir.model.access.csv', "webdav_view.xml", ], "demo_xml" : [], "active": False, "installable": True diff --git a/addons/document_webdav/document_webdav.py b/addons/document_webdav/document_webdav.py index 2db183e4ec6..fb0b62c3df6 100644 --- a/addons/document_webdav/document_webdav.py +++ b/addons/document_webdav/document_webdav.py @@ -25,6 +25,11 @@ import nodes class document_davdir(osv.osv): _inherit = 'document.directory' + _columns = { + # Placed here just for a reference + 'dav_prop_ids': fields.one2many('document.webdav.dir.property', 'dir_id', 'DAV properties'), + } + def get_node_class(self, cr, uid, ids, dbro=None, context=None): # Note: in this function, nodes come from document_webdav/nodes.py ! if dbro is None: @@ -43,5 +48,40 @@ class document_davdir(osv.osv): nctx.node_file_class = nodes.node_file return + def _locate_child(self, cr, uid, root_id, uri,nparent, ncontext): + """ try to locate the node in uri, + Return a tuple (node_dir, remaining_path) + """ + return (nodes.node_database(context=ncontext), uri) + document_davdir() + +class dav_dir_property(osv.osv): + """ Arbitrary WebDAV properties, attached to document.directories. + + Some DAV properties have to be settable at directories, depending + on the database directory structure. + + Example would be the principal-URL. + + There _can_ be properties without a directory, which means that they + globally apply to all the directories (aka. collections) of the + present database. + """ + _name = 'document.webdav.dir.property' + + _columns = { + 'dir_id': fields.many2one('document.directory', 'Directory', required=False, select=1), + 'namespace': fields.char('Namespace', size=127, required=True), + 'name': fields.char('Name', size=64, required=True), + 'value': fields.text('Value'), + 'do_subst': fields.boolean('Substitute', required=True), + } + + _defaults = { + 'do_subst': False, + } + +dav_dir_property() + #eof \ No newline at end of file diff --git a/addons/document_webdav/nodes.py b/addons/document_webdav/nodes.py index 6e9d85a0ae3..ce847efa48f 100644 --- a/addons/document_webdav/nodes.py +++ b/addons/document_webdav/nodes.py @@ -21,6 +21,8 @@ from document import nodes +from tools.safe_eval import safe_eval as eval +import urllib class node_acl_mixin(object): def _get_dav_owner(self, cr): @@ -35,7 +37,76 @@ class node_acl_mixin(object): def _get_dav_current_user_privilege_set(self, cr): return '' # TODO + def _get_dav_props_hlpr(self, cr, par_class, prop_model, + prop_ref_field, res_id): + """ Helper for dav properties, usable in subclasses + + @param par_class The parent class + @param prop_model The name of the orm model holding the properties + @param prop_ref_field The name of the field at prop_model pointing to us + @param res_id the id of self in the corresponing orm table, that should + match prop_model.prop_ref_field + """ + ret = par_class.get_dav_props(self, cr) + if prop_model: + propobj = self.context._dirobj.pool.get(prop_model) + uid = self.context.uid + ctx = self.context.context.copy() + ctx.update(self.dctx) + sdomain = [(prop_ref_field, '=', False),] + if res_id: + sdomain = ['|', (prop_ref_field, '=', res_id)] + sdomain + prop_ids = propobj.search(cr, uid, sdomain, context=ctx) + if prop_ids: + ret = ret.copy() + for pbro in propobj.browse(cr, uid, prop_ids, context=ctx): + ret[pbro.namespace] = ret.get(pbro.namespace, ()) + \ + (pbro.name,) + # Note that we cannot have properties to conditionally appear + # on the context, yet. + + return ret + + def _get_dav_eprop_hlpr(self, cr, ns, prop, + par_class, prop_model, + prop_ref_field, res_id): + """ Helper for get dav eprop, usable in subclasses + + @param namespace the one to search for + @param name Name to search for + @param par_class The parent class + @param prop_model The name of the orm model holding the properties + @param prop_ref_field The name of the field at prop_model pointing to us + @param res_id the id of self in the corresponing orm table, that should + match prop_model.prop_ref_field + """ + ret = par_class.get_dav_eprop(self, cr, ns, prop) + if ret is not None: + return ret + if prop_model: + propobj = self.context._dirobj.pool.get(prop_model) + uid = self.context.uid + ctx = self.context.context.copy() + ctx.update(self.dctx) + sdomain = [(prop_ref_field, '=', False),('namespace', '=', ns), ('name','=', prop)] + if res_id: + sdomain = ['|', (prop_ref_field, '=', res_id)] + sdomain + prop_ids = propobj.search(cr, uid, sdomain, context=ctx) + if prop_ids: + pbro = propobj.browse(cr, uid, prop_ids[0], context=ctx) + val = pbro.value + if pbro.do_subst: + if val.startswith("('") and val.endswith(")"): + glbls = { 'urlquote': urllib.quote, } + val = eval(val, glbls, ctx) + else: + val = val % ctx + return val + return None + class node_dir(node_acl_mixin, nodes.node_dir): + """ override node_dir and add DAV functionality + """ DAV_PROPS = { "DAV:": ('owner', 'group', 'supported-privilege-set', 'current-user-privilege-set'), @@ -43,6 +114,17 @@ class node_dir(node_acl_mixin, nodes.node_dir): DAV_M_NS = { "DAV:" : '_get_dav',} http_options = { 'DAV': ['access-control',] } + def get_dav_resourcetype(self, cr): + return ('collection', 'DAV:') + + def get_dav_props(self, cr): + return self._get_dav_props_hlpr(cr, nodes.node_dir, + 'document.webdav.dir.property', 'dir_id', self.dir_id) + + def get_dav_eprop(self, cr, ns, prop): + return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_dir, + 'document.webdav.dir.property', 'dir_id', self.dir_id) + class node_file(node_acl_mixin, nodes.node_file): DAV_PROPS = { "DAV:": ('owner', 'group', @@ -53,5 +135,27 @@ class node_file(node_acl_mixin, nodes.node_file): http_options = { 'DAV': ['access-control', ] } pass + def get_dav_resourcetype(self, cr): + return '' + + def get_dav_props(self, cr): + return self._get_dav_props_hlpr(cr, nodes.node_dir, + None, 'file_id', self.file_id) + #'document.webdav.dir.property', 'dir_id', self.dir_id) + + #def get_dav_eprop(self, cr, ns, prop): + +class node_database(nodes.node_database): + def get_dav_resourcetype(self, cr): + return ('collection', 'DAV:') + + def get_dav_props(self, cr): + return self._get_dav_props_hlpr(cr, nodes.node_dir, + 'document.webdav.dir.property', 'dir_id', False) + + def get_dav_eprop(self, cr, ns, prop): + return self._get_dav_eprop_hlpr(cr, nodes.node_dir, ns, prop, + 'document.webdav.dir.property', 'dir_id', False) + #eof diff --git a/addons/document_webdav/security/ir.model.access.csv b/addons/document_webdav/security/ir.model.access.csv new file mode 100644 index 00000000000..a77304eb297 --- /dev/null +++ b/addons/document_webdav/security/ir.model.access.csv @@ -0,0 +1,4 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +"access_webdav_dir_property_all","webdav.dir.property all","model_document_webdav_dir_property",,1,0,0,0 +"access_webdav_dir_property_group_doc_manager","webdav.dir.property document manager","model_document_webdav_dir_property","base.group_system",1,1,1,1 +"access_webdav_dir_property_group_system","webdav.dir.property group system","model_document_webdav_dir_property","base.group_system",1,1,1,1 diff --git a/addons/document_webdav/webdav_view.xml b/addons/document_webdav/webdav_view.xml new file mode 100644 index 00000000000..2ea0bca5279 --- /dev/null +++ b/addons/document_webdav/webdav_view.xml @@ -0,0 +1,98 @@ + + + + + + document.webdav.dir.property.form + document.webdav.dir.property + form + +
+ + + + + + + + + +
+ + + document.webdav.dir.property.tree + document.webdav.dir.property + tree + + + + + + + + + + + Search View: Directory DAV properties + document.webdav.dir.property + search + + + + + + + + + + + + + + + ir.actions.act_window + document.webdav.dir.property + form + tree,form + + + + + + + document.directory.webdav.inherit + document.directory + + form + + + + + + + + + +
+
From b8c7bb81a9d77623d5a2b1bcf61d414b76c65631 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:39:00 +0300 Subject: [PATCH 010/192] Doc webdav: expand tabs at __openerp__.py bzr revid: p_christ@hol.gr-20101012103900-9jt6wn6gmy224yrj --- addons/document_webdav/__openerp__.py | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/addons/document_webdav/__openerp__.py b/addons/document_webdav/__openerp__.py index 4daaa8824cb..3a573dc5a2b 100644 --- a/addons/document_webdav/__openerp__.py +++ b/addons/document_webdav/__openerp__.py @@ -29,20 +29,20 @@ ############################################################################## { - "name" : "WebDAV server for Document Management", - "version" : "2.1", - "author" : "OpenERP SA", - "category" : "Generic Modules/Others", - "website": "http://www.openerp.com", - "description": """ With this module, the WebDAV server for the documents is activated. - You can then use any compatible browser to remotely see the attachments of OpenObject. + "name" : "WebDAV server for Document Management", + "version" : "2.1", + "author" : "OpenERP SA", + "category" : "Generic Modules/Others", + "website": "http://www.openerp.com", + "description": """ With this module, the WebDAV server for the documents is activated. + You can then use any compatible browser to remotely see the attachments of OpenObject. - After installation, the webDAV server can be controlled by a [webdav] section in the server's config. + After installation, the webDAV server can be controlled by a [webdav] section in the server's config. """, - "depends" : ["base", "document"], - "init_xml" : [], - "update_xml" : ['security/ir.model.access.csv', "webdav_view.xml", ], - "demo_xml" : [], - "active": False, - "installable": True + "depends" : ["base", "document"], + "init_xml" : [], + "update_xml" : ['security/ir.model.access.csv', "webdav_view.xml", ], + "demo_xml" : [], + "active": False, + "installable": True } From 333c1c6ca8c9d473ff5de2f8b02f4238202ffcb6 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:39:36 +0300 Subject: [PATCH 011/192] Doc: make sure folders are deleted after YAML tests This will also enable the tests to run multiple times on the same db. bzr revid: p_christ@hol.gr-20101012103936-d0e7o1b5ppu7kri5 --- addons/document/test/document_test2.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/addons/document/test/document_test2.yml b/addons/document/test/document_test2.yml index 34e834a5306..65312eaf10d 100644 --- a/addons/document/test/document_test2.yml +++ b/addons/document/test/document_test2.yml @@ -12,7 +12,7 @@ I create a "Testing" folder where all the test data will go. - !record {model: document.directory, id: dir_tests }: - name: 'Testing' + name: 'Testing (will be deleted!)' parent_id: dir_root - I create an attachment into the root folder (w. empty fields, test that @@ -52,7 +52,7 @@ I search the testing folder for attachments. - !python {model: ir.attachment}: | - ids = self.search(cr, uid, [('parent_id.name','=', 'Testing'), ('name','=','Test renamed 2')]) + ids = self.search(cr, uid, [('parent_id.name','=', 'Testing (will be deleted!)'), ('name','=','Test renamed 2')]) assert ids == [ ref("file_test2") ], ids - I create an attachment to a 3rd resource, eg. a res.country @@ -70,7 +70,14 @@ ids = self.search(cr, uid, [('res_model', '=', 'res.country'), ('res_id', '=', ref("base.za"))]) assert ids == [ ref("attach_3rd")], ids - - I delete the attachment + I delete the attachments - !python {model: ir.attachment}: | + self.unlink(cr, uid, [ref('file_test2')]) self.unlink(cr, uid, [ref('attach_3rd')]) +- + I delete the tests folder +- + !python {model: document.directory}: | + self.unlink(cr, uid, [ref('dir_tests')]) + cr.commit() From 725bbee01d4b1adebec77d3af523eed5d9766d21 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:39:37 +0300 Subject: [PATCH 012/192] Doc ftp: Cleanup after yaml tests bzr revid: p_christ@hol.gr-20101012103937-2jdkd7gkr1u86p48 --- .../document_ftp/test/document_ftp_test2.yml | 26 ++++++++++++++++++- .../document_ftp/test/document_ftp_test3.yml | 10 +++++++ .../document_ftp/test/document_ftp_test4.yml | 8 ++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/addons/document_ftp/test/document_ftp_test2.yml b/addons/document_ftp/test/document_ftp_test2.yml index 32434962ba2..da55c353767 100644 --- a/addons/document_ftp/test/document_ftp_test2.yml +++ b/addons/document_ftp/test/document_ftp_test2.yml @@ -196,10 +196,15 @@ !python {model: ir.attachment}: | from document_ftp import test_easyftp as te ftp = te.get_ftp_folder(cr, uid, self, 'Documents') - ftp.mkd("Test-Folder2") + ftp.mkd("Test-Folder3") # TODO move - I remove the 'Test-Folder3' +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + ftp.rmd("Test-Folder3") - I check that test3.txt is removed. - @@ -228,3 +233,22 @@ - I move the 200 files to 'Test-Folder2' # TODO + +- + I delete the 200 files +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Test-Folder2') + # TODO speed + ftp.delete('test3.txt') + for i in range(0, 200): + ftp.delete('test-name%s.txt' %i) +- + I remove the 'Test-Folder2' +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + ftp.rmd("Test-Folder2") diff --git a/addons/document_ftp/test/document_ftp_test3.yml b/addons/document_ftp/test/document_ftp_test3.yml index dbdab370b97..a3e9bd5cb40 100644 --- a/addons/document_ftp/test/document_ftp_test3.yml +++ b/addons/document_ftp/test/document_ftp_test3.yml @@ -80,3 +80,13 @@ ftp = te.get_ftp_folder(cr, uid, self, 'Documents/������������ ��������/�����') vuvuzela = 'b'+''.join('z' * 200)+'!' ftp.delete(vuvuzela) + +- + I delete the testing folders +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + ftp.rmd('Äïêéìáóôéêüò ÖÜêåëëïò/Üëëïò') + ftp.rmd('Äïêéìáóôéêüò ÖÜêåëëïò') diff --git a/addons/document_ftp/test/document_ftp_test4.yml b/addons/document_ftp/test/document_ftp_test4.yml index eeed4b17dda..7ff473e619b 100644 --- a/addons/document_ftp/test/document_ftp_test4.yml +++ b/addons/document_ftp/test/document_ftp_test4.yml @@ -118,3 +118,11 @@ - | Bonus Piste: I create a 'Partner3' under 'all' +- + I delete the Partners Testing folder +- + !python {model: ir.attachment}: | + from document_ftp import test_easyftp as te + from cStringIO import StringIO + ftp = te.get_ftp_folder(cr, uid, self, 'Documents') + ftp.rmd('Partners Testing') From 034a4166c994839eec1579802b09fcb9c4bdb039 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:39:37 +0300 Subject: [PATCH 013/192] doc ftp: unlink the YAML testing directories, too bzr revid: p_christ@hol.gr-20101012103937-dtgq5tzthu9pdldz --- addons/document_ftp/test/document_ftp_test4.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/addons/document_ftp/test/document_ftp_test4.yml b/addons/document_ftp/test/document_ftp_test4.yml index 7ff473e619b..2bb616dc1bd 100644 --- a/addons/document_ftp/test/document_ftp_test4.yml +++ b/addons/document_ftp/test/document_ftp_test4.yml @@ -121,8 +121,7 @@ - I delete the Partners Testing folder - - !python {model: ir.attachment}: | - from document_ftp import test_easyftp as te - from cStringIO import StringIO - ftp = te.get_ftp_folder(cr, uid, self, 'Documents') - ftp.rmd('Partners Testing') + !python {model: document.directory}: | + self.unlink(cr, uid, [ref('dir_tests2')]) + self.unlink(cr, uid, [ref('dir_respart1')]) + cr.commit() \ No newline at end of file From 4eab25baf3f8f334771196193e1ad5978dc85108 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:39:45 +0300 Subject: [PATCH 014/192] Document: cleanup nodes.py, let node_res_dir be overriden. bzr revid: p_christ@hol.gr-20101012103945-1ziw8331h4wpvp6t --- addons/document/nodes.py | 46 ++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/addons/document/nodes.py b/addons/document/nodes.py index e4be756db3d..19e50d7fe1f 100644 --- a/addons/document/nodes.py +++ b/addons/document/nodes.py @@ -281,6 +281,8 @@ class node_class(object): def get_dav_props(self, cr): """ If this class has special behaviour for GroupDAV etc, export its capabilities """ + # This fn is placed here rather than WebDAV, because we want the + # baseclass methods to apply to all node subclasses return self.DAV_PROPS or {} def match_dav_eprop(self, cr, match, ns, prop): @@ -460,9 +462,6 @@ class node_database(node_class): def _get_ttag(self,cr): return 'db-%s' % cr.dbname - def get_dav_resourcetype(self, cr): - return ('collection', 'DAV:') - def mkdosname(company_name, default='noname'): """ convert a string to a dos-like name""" if not company_name: @@ -502,7 +501,7 @@ class node_dir(node_database): for dfld in dirr.dctx_ids: try: self.dctx['dctx_' + dfld.field] = safe_eval(dfld.expr,dc2) - except Exception,e: + except Exception: print "Cannot eval %s" % dfld.expr print e pass @@ -667,12 +666,14 @@ class node_dir(node_database): return ret class node_res_dir(node_class): - """ A special sibling to node_dir, which does only contain dynamically + """ A folder containing dynamic folders + A special sibling to node_dir, which does only contain dynamically created folders foreach resource in the foreign model. All folders should be of type node_res_obj and merely behave like node_dirs (with limited domain). """ our_type = 'collection' + res_obj_class = None def __init__(self, path, parent, context, dirr, dctx=None ): super(node_res_dir,self).__init__(path, parent, context) self.dir_id = dirr.id @@ -779,17 +780,15 @@ class node_res_dir(node_class): continue # Yes! we can't do better but skip nameless records. - res.append(node_res_obj(name, self.dir_id, self, self.context, self.res_model, bo)) + res.append(self.res_obj_class(name, self.dir_id, self, self.context, self.res_model, bo)) return res def _get_ttag(self,cr): return 'rdir-%d' % self.dir_id - def get_dav_resourcetype(self, cr): - return ('collection', 'DAV:') - class node_res_obj(node_class): - """ A special sibling to node_dir, which does only contain dynamically + """ A dynamically created folder. + A special sibling to node_dir, which does only contain dynamically created folders foreach resource in the foreign model. All folders should be of type node_res_obj and merely behave like node_dirs (with limited domain). @@ -825,7 +824,7 @@ class node_res_obj(node_class): for fld,expr in self.dctx_dict.items(): try: self.dctx[fld] = safe_eval(expr, dc2) - except Exception,e: + except Exception: print "Cannot eval %s for %s" % (expr, fld) print e pass @@ -884,6 +883,7 @@ class node_res_obj(node_class): return res def get_dav_props(self, cr): + # Deprecated! (but document_ics must be cleaned, first) res = {} cntobj = self.context._dirobj.pool.get('document.directory.content') uid = self.context.uid @@ -897,6 +897,7 @@ class node_res_obj(node_class): return res def get_dav_eprop(self, cr, ns, prop): + # Deprecated! if ns != 'http://groupdav.org/' or prop != 'resourcetype': logger.warning("Who asked for %s:%s?" % (ns, prop)) return None @@ -943,17 +944,20 @@ class node_res_obj(node_class): if not res_name: continue # TODO Revise - res.append(node_res_obj(res_name, self.dir_id, self, self.context, self.res_model, res_bo = bo)) + klass = self.__class__ + res.append(klass(res_name, self.dir_id, self, self.context, self.res_model, res_bo = bo)) where2 = where + [('parent_id','=',self.dir_id) ] ids = dirobj.search(cr, uid, where2, context=ctx) for dirr in dirobj.browse(cr, uid, ids, context=ctx): if dirr.type == 'directory': - res.append(node_res_obj(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id)) + klass = self.__class__ + res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id)) elif dirr.type == 'ressource': # child resources can be controlled by properly set dctx - res.append(node_res_dir(dirr.name,self,self.context, dirr, {'active_id': self.res_id})) + klass = dirr.get_node_class(dirr, context=ctx) + res.append(klass(dirr.name,self,self.context, dirr, {'active_id': self.res_id})) fil_obj = dirobj.pool.get('ir.attachment') if self.res_find_all: @@ -976,9 +980,11 @@ class node_res_obj(node_class): dirids = dirids + dirobj.search(cr,uid, where5) for dirr in dirobj.browse(cr, uid, dirids, context=ctx): if dirr.type == 'directory' and not dirr.parent_id: - res.append(node_res_obj(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id)) + klass = self.__class__ + res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id)) if dirr.type == 'ressource': - res.append(node_res_dir(dirr.name, self, self.context, dirr, {'active_id': self.res_id})) + klass = dirr.get_node_class(dirr, context=ctx) + res.append(klass(dirr.name, self, self.context, dirr, {'active_id': self.res_id})) return res def create_child_collection(self, cr, objname): @@ -1044,8 +1050,7 @@ class node_res_obj(node_class): def _get_ttag(self,cr): return 'rodir-%d-%d' % (self.dir_id, self.res_id) - def get_dav_resourcetype(self, cr): - return ('collection', 'DAV:') +node_res_dir.res_obj_class = node_res_obj class node_file(node_class): our_type = 'file' @@ -1181,9 +1186,6 @@ class node_file(node_class): def _get_ttag(self,cr): return 'file-%d' % self.file_id - def get_dav_resourcetype(self, cr): - return '' - def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False): if ndir_node and ndir_node.context != self.context: raise NotImplementedError("Cannot move files between contexts") @@ -1380,3 +1382,5 @@ class nodefd_content(StringIO, node_descriptor): finally: cr.close() StringIO.close(self) + +#eof \ No newline at end of file From 55d06c6ea1cce4881422378727120678525eb96a Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:40:15 +0300 Subject: [PATCH 015/192] Document: cleanup, remove restriction on ressource dirs The same code is also called when parent_type_id exists, so the assertion would fire. bzr revid: p_christ@hol.gr-20101012104015-gnwvni1bx35ntyl5 --- addons/document/document.py | 6 +++++- addons/document/document_directory.py | 10 ++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/addons/document/document.py b/addons/document/document.py index 8cfc01f0988..f8b500761f9 100644 --- a/addons/document/document.py +++ b/addons/document/document.py @@ -28,6 +28,7 @@ import os import tools from tools.translate import _ import nodes +import logging DMS_ROOT_PATH = tools.config.get('document_path', os.path.join(tools.config['root_path'], 'filestore')) @@ -107,7 +108,7 @@ class document_file(osv.osv): 'parent_id': __get_def_directory } _sql_constraints = [ - ('filename_uniq', 'unique (name,parent_id,res_id,res_model)', 'The file name must be unique !') + # filename_uniq is not possible in pure SQL ] def _check_duplication(self, cr, uid, vals, ids=[], op='create'): name = vals.get('name', False) @@ -260,6 +261,9 @@ class document_file(osv.osv): r = stor.prepare_unlink(cr, uid, storage_id, f) if r: unres.append(r) + else: + logging.getLogger('document').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) return res diff --git a/addons/document/document_directory.py b/addons/document/document_directory.py index 0c208ad04b7..95099af81a7 100644 --- a/addons/document/document_directory.py +++ b/addons/document/document_directory.py @@ -187,8 +187,6 @@ class document_directory(osv.osv): if dbro.type == 'directory': return nodes.node_dir elif dbro.type == 'ressource': - assert not dbro.ressource_parent_type_id, \ - "resource and parent_id at #%d: %r" % (dbro.id, dbro.ressource_parent_type_id) return nodes.node_res_dir else: raise ValueError("dir node for %s type", dbro.type) @@ -248,6 +246,7 @@ class document_directory(osv.osv): name=directory.name if not parent_id: parent_id=directory.parent_id and directory.parent_id.id or False + # TODO fix algo if not ressource_parent_type_id: ressource_parent_type_id=directory.ressource_parent_type_id and directory.ressource_parent_type_id.id or False if not ressource_id: @@ -268,8 +267,11 @@ class document_directory(osv.osv): def create(self, cr, uid, vals, context=None): if not self._check_duplication(cr, uid, vals): raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!')) - if vals.get('name',False) and (vals.get('name').find('/')+1 or vals.get('name').find('@')+1 or vals.get('name').find('$')+1 or vals.get('name').find('#')+1) : - raise osv.except_osv(_('ValidateError'), _('Directory name contains special characters!')) + newname = vals.get('name',False) + if newname: + for illeg in ('/', '@', '$', '#'): + if illeg in newname: + raise osv.except_osv(_('ValidateError'), _('Directory name contains special characters!')) return super(document_directory,self).create(cr, uid, vals, context) # TODO def unlink(... From d7f96fd603a50b4ca91a09828c0db2586c1af5d3 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:40:19 +0300 Subject: [PATCH 016/192] Doc webdav: Fix, implement node_res_obj for latest document. bzr revid: p_christ@hol.gr-20101012104019-nhoqnvgpr9ecjjky --- addons/document_webdav/document_webdav.py | 2 -- addons/document_webdav/nodes.py | 41 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/addons/document_webdav/document_webdav.py b/addons/document_webdav/document_webdav.py index fb0b62c3df6..6cc565016ea 100644 --- a/addons/document_webdav/document_webdav.py +++ b/addons/document_webdav/document_webdav.py @@ -38,8 +38,6 @@ class document_davdir(osv.osv): if dbro.type == 'directory': return nodes.node_dir elif dbro.type == 'ressource': - assert not dbro.ressource_parent_type_id, \ - "resource and parent_id at #%d: %r" % (dbro.id, dbro.ressource_parent_type_id) return nodes.node_res_dir else: raise ValueError("dir node for %s type", dbro.type) diff --git a/addons/document_webdav/nodes.py b/addons/document_webdav/nodes.py index ce847efa48f..b9483b7b221 100644 --- a/addons/document_webdav/nodes.py +++ b/addons/document_webdav/nodes.py @@ -157,5 +157,46 @@ class node_database(nodes.node_database): return self._get_dav_eprop_hlpr(cr, nodes.node_dir, ns, prop, 'document.webdav.dir.property', 'dir_id', False) +class node_res_obj(node_acl_mixin, nodes.node_res_obj): + DAV_PROPS = { "DAV:": ('owner', 'group', + 'supported-privilege-set', + 'current-user-privilege-set'), + } + DAV_M_NS = { "DAV:" : '_get_dav',} + http_options = { 'DAV': ['access-control',] } + + def get_dav_resourcetype(self, cr): + return ('collection', 'DAV:') + + def get_dav_props(self, cr): + return self._get_dav_props_hlpr(cr, nodes.node_res_obj, + 'document.webdav.dir.property', 'dir_id', self.dir_id) + + def get_dav_eprop(self, cr, ns, prop): + return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_res_obj, + 'document.webdav.dir.property', 'dir_id', self.dir_id) + + +class node_res_dir(node_acl_mixin, nodes.node_res_dir): + DAV_PROPS = { "DAV:": ('owner', 'group', + 'supported-privilege-set', + 'current-user-privilege-set'), + } + DAV_M_NS = { "DAV:" : '_get_dav',} + http_options = { 'DAV': ['access-control',] } + res_obj_class = node_res_obj + + def get_dav_resourcetype(self, cr): + return ('collection', 'DAV:') + + def get_dav_props(self, cr): + return self._get_dav_props_hlpr(cr, nodes.node_res_dir, + 'document.webdav.dir.property', 'dir_id', self.dir_id) + + def get_dav_eprop(self, cr, ns, prop): + return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_res_dir, + 'document.webdav.dir.property', 'dir_id', self.dir_id) + + #eof From 1fe7eb3e8f1454b5846e4e0f432d513813030300 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:40:19 +0300 Subject: [PATCH 017/192] Doc: allow override of dynamic foleders' class bzr revid: p_christ@hol.gr-20101012104019-ci6z0vb9fce7tfiw --- addons/document/document_directory.py | 7 +++++-- addons/document/nodes.py | 7 ++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/addons/document/document_directory.py b/addons/document/document_directory.py index 95099af81a7..30507f1cd2c 100644 --- a/addons/document/document_directory.py +++ b/addons/document/document_directory.py @@ -175,7 +175,7 @@ class document_directory(osv.osv): return nodes.get_node_context(cr, uid, context).get_uri(cr, uri) - def get_node_class(self, cr, uid, ids, dbro=None, context=None): + def get_node_class(self, cr, uid, ids, dbro=None, dynamic=False, context=None): """Retrieve the class of nodes for this directory This function can be overriden by inherited classes ;) @@ -184,7 +184,10 @@ class document_directory(osv.osv): if dbro is None: dbro = self.browse(cr, uid, ids, context=context) - if dbro.type == 'directory': + if dynamic: + assert dbro.type == 'directory' + return nodes.node_res_obj + elif dbro.type == 'directory': return nodes.node_dir elif dbro.type == 'ressource': return nodes.node_res_dir diff --git a/addons/document/nodes.py b/addons/document/nodes.py index 19e50d7fe1f..46674b61caa 100644 --- a/addons/document/nodes.py +++ b/addons/document/nodes.py @@ -72,6 +72,7 @@ class node_context(object): self.context = context self._dirobj = pooler.get_pool(cr.dbname).get('document.directory') self.node_file_class = node_file + self.extra_ctx = {} # Extra keys for context, that do _not_ trigger inequality assert self._dirobj self._dirobj._prepare_context(cr, uid, self, context=context) self.rootdir = False #self._dirobj._get_root_directory(cr,uid,context) @@ -944,7 +945,7 @@ class node_res_obj(node_class): if not res_name: continue # TODO Revise - klass = self.__class__ + klass = directory.get_node_class(directory, dynamic=True, context=ctx) res.append(klass(res_name, self.dir_id, self, self.context, self.res_model, res_bo = bo)) @@ -952,7 +953,7 @@ class node_res_obj(node_class): ids = dirobj.search(cr, uid, where2, context=ctx) for dirr in dirobj.browse(cr, uid, ids, context=ctx): if dirr.type == 'directory': - klass = self.__class__ + klass = dirr.get_node_class(dirr, dynamic=True, context=ctx) res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id)) elif dirr.type == 'ressource': # child resources can be controlled by properly set dctx @@ -980,7 +981,7 @@ class node_res_obj(node_class): dirids = dirids + dirobj.search(cr,uid, where5) for dirr in dirobj.browse(cr, uid, dirids, context=ctx): if dirr.type == 'directory' and not dirr.parent_id: - klass = self.__class__ + klass = dirr.get_node_class(dirr, dynamic=True, context=ctx) res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id)) if dirr.type == 'ressource': klass = dirr.get_node_class(dirr, context=ctx) From f842ccc1ec3beba28238f5027d2c80d23c98e335 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:40:20 +0300 Subject: [PATCH 018/192] Doc webdav: context for DAV properties, dynamic folders bzr revid: p_christ@hol.gr-20101012104020-izmzgobc2kbxb2cj --- addons/document_webdav/document_webdav.py | 16 +++++++++++++-- addons/document_webdav/nodes.py | 24 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/addons/document_webdav/document_webdav.py b/addons/document_webdav/document_webdav.py index 6cc565016ea..28b70c8d7e4 100644 --- a/addons/document_webdav/document_webdav.py +++ b/addons/document_webdav/document_webdav.py @@ -21,6 +21,7 @@ from osv import osv, fields import nodes +from tools import config class document_davdir(osv.osv): _inherit = 'document.directory' @@ -30,12 +31,15 @@ class document_davdir(osv.osv): 'dav_prop_ids': fields.one2many('document.webdav.dir.property', 'dir_id', 'DAV properties'), } - def get_node_class(self, cr, uid, ids, dbro=None, context=None): + def get_node_class(self, cr, uid, ids, dbro=None, dynamic=False, context=None): # Note: in this function, nodes come from document_webdav/nodes.py ! if dbro is None: dbro = self.browse(cr, uid, ids, context=context) - if dbro.type == 'directory': + if dynamic: + assert dbro.type == 'directory' + return nodes.node_res_obj + elif dbro.type == 'directory': return nodes.node_dir elif dbro.type == 'ressource': return nodes.node_res_dir @@ -44,6 +48,14 @@ class document_davdir(osv.osv): def _prepare_context(self, cr, uid, nctx, context): nctx.node_file_class = nodes.node_file + # We can fill some more fields, but avoid any expensive function + # that might be not worth preparing. + nctx.extra_ctx['webdav_path'] = '/'+config.get_misc('webdav','vdir','webdav') + usr_obj = self.pool.get('res.users') + res = usr_obj.read(cr, uid, uid, ['login']) + if res: + nctx.extra_ctx['username'] = res['login'] + # TODO group return def _locate_child(self, cr, uid, root_id, uri,nparent, ncontext): diff --git a/addons/document_webdav/nodes.py b/addons/document_webdav/nodes.py index b9483b7b221..0a45b227095 100644 --- a/addons/document_webdav/nodes.py +++ b/addons/document_webdav/nodes.py @@ -24,6 +24,19 @@ from document import nodes from tools.safe_eval import safe_eval as eval import urllib +def dict_filter(srcdic, keys, res=None): + ''' Return a copy of srcdic that has only keys set. + If any of keys are missing from srcdic, the result won't have them, + either. + @param res If given, result will be updated there, instead of a new dict. + ''' + if res is None: + res = {} + for k in keys: + if k in srcdic: + res[k] = srcdic[k] + return res + class node_acl_mixin(object): def _get_dav_owner(self, cr): return self.uuser @@ -53,6 +66,9 @@ class node_acl_mixin(object): uid = self.context.uid ctx = self.context.context.copy() ctx.update(self.dctx) + # Not really needed because we don't do eval here: + # ctx.update({'uid': uid, 'dbname': self.context.dbname }) + # dict_filter(self.context.extra_ctx, ['username', 'groupname', 'webdav_path'], ctx) sdomain = [(prop_ref_field, '=', False),] if res_id: sdomain = ['|', (prop_ref_field, '=', res_id)] + sdomain @@ -88,6 +104,9 @@ class node_acl_mixin(object): uid = self.context.uid ctx = self.context.context.copy() ctx.update(self.dctx) + ctx.update({'uid': uid, 'dbname': self.context.dbname }) + ctx['node_classname'] = "%s.%s" % (self.__class__.__module__, self.__class__.__name__) + dict_filter(self.context.extra_ctx, ['username', 'groupname', 'webdav_path'], ctx) sdomain = [(prop_ref_field, '=', False),('namespace', '=', ns), ('name','=', prop)] if res_id: sdomain = ['|', (prop_ref_field, '=', res_id)] + sdomain @@ -197,6 +216,11 @@ class node_res_dir(node_acl_mixin, nodes.node_res_dir): return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_res_dir, 'document.webdav.dir.property', 'dir_id', self.dir_id) +# Some copies, so that this module can replace 'from document import nodes' +get_node_context = nodes.get_node_context +node_context = nodes.node_context +node_class = nodes.node_class +node_descriptor = nodes.node_descriptor #eof From f1ccc3a4e203fc255cf1450684b9f601a2ac7027 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:40:26 +0300 Subject: [PATCH 019/192] CalDAV: port to improve WebDAV API, support dynamic collections. bzr revid: p_christ@hol.gr-20101012104026-qju5goq0a42m01dq --- addons/caldav/caldav_node.py | 208 +++++++++++++++------------ addons/caldav/calendar.py | 5 +- addons/caldav/calendar_collection.py | 30 ++-- addons/caldav/doc/Path discovery.rst | 43 ++++++ 4 files changed, 184 insertions(+), 102 deletions(-) create mode 100644 addons/caldav/doc/Path discovery.rst diff --git a/addons/caldav/caldav_node.py b/addons/caldav/caldav_node.py index 11f720bdcab..27f92943d07 100644 --- a/addons/caldav/caldav_node.py +++ b/addons/caldav/caldav_node.py @@ -20,41 +20,41 @@ ############################################################################## import time -from document import nodes +from document_webdav import nodes import StringIO -class node_database(nodes.node_database): - def _child_get(self, cr, name=False, parent_id=False, domain=None): - dirobj = self.context._dirobj - uid = self.context.uid - ctx = self.context.context.copy() - ctx.update(self.dctx) - if not domain: - domain = [] - domain2 = domain + [('calendar_collection','=', False)] - res = super(node_database, self)._child_get(cr, name=name, parent_id=parent_id, domain=domain2) - where = [('parent_id','=',parent_id)] - domain2 = domain + [('calendar_collection','=', True)] - if name: - where.append(('name','=',name)) - if domain2: - where += domain2 +def dict_merge(*dicts): + """ Return a dict with all values of dicts + """ + res = {} + for d in dicts: + res.update(d) + return res - where2 = where + [('type', '=', 'directory')] - ids = dirobj.search(cr, uid, where2, context=ctx) - for dirr in dirobj.browse(cr,uid,ids,context=ctx): - res.append(node_calendar_collection(dirr.name,self,self.context,dirr)) - return res +def dict_merge2(*dicts): + """ Return a dict with all values of dicts. + If some key appears twice and contains iterable objects, the values + are merged (instead of overwritten). + """ + res = {} + for d in dicts: + for k in d.keys(): + if k in res and isinstance(res[k], (list, tuple)): + res[k] = res[k] + d[k] + else: + res[k] = d[k] + return res + +# Assuming that we have set global properties right, we mark *all* +# directories as having calendar-access. +nodes.node_dir.http_options = dict_merge2(nodes.node_dir.http_options, + { 'DAV': ['calendar-access',] }) class node_calendar_collection(nodes.node_dir): - DAV_PROPS = { - "http://calendarserver.org/ns/" : ('getctag',), - } - DAV_M_NS = { - "http://calendarserver.org/ns/" : '_get_dav', - } - - http_options = { 'DAV': ['calendar-access'] } + DAV_PROPS = dict_merge2(nodes.node_dir.DAV_PROPS, + { "http://calendarserver.org/ns/" : ('getctag',), } ) + DAV_M_NS = dict_merge2(nodes.node_dir.DAV_M_NS, + { "http://calendarserver.org/ns/" : '_get_dav', } ) def _file_get(self,cr, nodename=False): return [] @@ -96,26 +96,77 @@ class node_calendar_collection(nodes.node_dir): result = self.get_etag(cr) return str(result) +class node_calendar_res_col(nodes.node_res_obj): + """ Calendar collection, as a dynamically created node + + This class shall be used instead of node_calendar_collection, when the + node is under dynamic ones. + """ + DAV_PROPS = dict_merge2(nodes.node_res_obj.DAV_PROPS, + { "http://calendarserver.org/ns/" : ('getctag',), } ) + DAV_M_NS = dict_merge2(nodes.node_res_obj.DAV_M_NS, + { "http://calendarserver.org/ns/" : '_get_dav', } ) + + def _file_get(self,cr, nodename=False): + return [] + + def _child_get(self, cr, name=False, parent_id=False, domain=None): + dirobj = self.context._dirobj + uid = self.context.uid + ctx = self.context.context.copy() + ctx.update(self.dctx) + where = [('collection_id','=',self.dir_id)] + ext = False + if name and name.endswith('.ics'): + name = name[:-4] + ext = True + if name: + where.append(('name','=',name)) + if not domain: + domain = [] + where = where + domain + fil_obj = dirobj.pool.get('basic.calendar') + ids = fil_obj.search(cr,uid,where,context=ctx) + res = [] + # TODO: shall we use any of our dynamic information?? + for cal in fil_obj.browse(cr, uid, ids, context=ctx): + if (not name) or not ext: + res.append(node_calendar(cal.name, self, self.context, cal)) + if (not name) or ext: + res.append(res_node_calendar(cal.name+'.ics', self, self.context, cal)) + # May be both of them! + return res + + def _get_ttag(self, cr): + return 'calen-dir-%d' % self.dir_id + + def _get_dav_getctag(self, cr): + result = self.get_etag(cr) + return str(result) + class node_calendar(nodes.node_class): our_type = 'collection' DAV_PROPS = { - "DAV:": ('principal-collection-set'), - "http://cal.me.com/_namespace/" : ('user-state'), + "olDAV:": ('principal-collection-set', + 'principal-URL', + 'supported-report-set'), + # "http://cal.me.com/_namespace/" : ('user-state',), "http://calendarserver.org/ns/" : ( - 'dropbox-home-URL', - 'notification-URL', + # 'dropbox-home-URL', + # 'notification-URL', 'getctag',), 'http://groupdav.org/': ('resourcetype',), "urn:ietf:params:xml:ns:caldav" : ( 'calendar-description', 'calendar-data', - 'calendar-home-set', - 'calendar-user-address-set', - 'schedule-inbox-URL', - 'schedule-outbox-URL',)} + # 'calendar-home-set', + # 'calendar-user-address-set', + # 'schedule-inbox-URL', + #'schedule-outbox-URL', + )} DAV_M_NS = { "DAV:" : '_get_dav', - "http://cal.me.com/_namespace/": '_get_dav', + # "http://cal.me.com/_namespace/": '_get_dav', 'http://groupdav.org/': '_get_gdav', "http://calendarserver.org/ns/" : '_get_dav', "urn:ietf:params:xml:ns:caldav" : '_get_caldav'} @@ -136,7 +187,7 @@ class node_calendar(nodes.node_class): result = self._get_ttag(cr) + ':' + str(time.time()) return str(result) - def _get_dav_dropbox_home_URL(self, cr): + def _get_dav_dropbox_home_URL(self, cr): #Depr import urllib uid = self.context.uid ctx = self.context.context.copy() @@ -147,7 +198,7 @@ class node_calendar(nodes.node_class): url = urllib.quote('/%s/%s' % (cr.dbname, res)) return url - def _get_dav_notification_URL(self, cr): + def _get_dav_notification_URL(self, cr): # Depr import urllib uid = self.context.uid ctx = self.context.context.copy() @@ -160,8 +211,7 @@ class node_calendar(nodes.node_class): def _get_dav_user_state(self, cr): #TODO - return True - + return 'online' def get_dav_resourcetype(self, cr): res = [ ('collection', 'DAV:'), @@ -269,6 +319,8 @@ class node_calendar(nodes.node_class): fnodes = fil_obj.get_calendar_objects(cr, uid, [self.calendar_id], self, domain=[('id','=',res[0])], context=ctx) return fnodes[0] + # If we reach this line, it means that we couldn't import any useful + # (and matching type vs. our node kind) data from the iCal content. return None @@ -287,7 +339,6 @@ class node_calendar(nodes.node_class): def rmcol(self, cr): return False - def _get_caldav_calendar_data(self, cr): res = [] for child in self.children(cr): @@ -299,11 +350,13 @@ class node_calendar(nodes.node_class): calendar_obj = self.context._dirobj.pool.get('basic.calendar') ctx = self.context.context.copy() ctx.update(self.dctx) - calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx) - return calendar.description + try: + calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx) + return calendar.description or calendar.name + except Exception, e: + return None def _get_dav_principal_collection_set(self, cr): - import xml import urllib uid = self.context.uid ctx = self.context.context.copy() @@ -311,74 +364,45 @@ class node_calendar(nodes.node_class): calendar_obj = self.context._dirobj.pool.get('basic.calendar') calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx) res = '%s/%s' %(calendar.collection_id.name, calendar.name) - doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None) - href = doc.documentElement - href.tagName = 'D:href' - huri = doc.createTextNode(urllib.quote('/%s/%s' % (cr.dbname, res))) - href.appendChild(huri) - return href - - def _get_caldav_calendar_home_set(self, cr): - import xml.dom.minidom - import urllib - uid = self.context.uid - ctx = self.context.context.copy() - ctx.update(self.dctx) - doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None) - - calendar_obj = self.context._dirobj.pool.get('basic.calendar') - calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx) - huri = doc.createTextNode(urllib.quote('/%s/%s' % (cr.dbname, calendar.collection_id.name))) - href = doc.documentElement - href.tagName = 'D:href' - href.appendChild(huri) - return href + return ('href', 'DAV:', urllib.quote('/%s/%s' % (cr.dbname, res))) def _get_caldav_calendar_user_address_set(self, cr): - import xml.dom.minidom uid = self.context.uid ctx = self.context.context.copy() ctx.update(self.dctx) user_obj = self.context._dirobj.pool.get('res.users') user = user_obj.browse(cr, uid, uid, context=ctx) - doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None) - href = doc.documentElement - href.tagName = 'D:href' - huri = doc.createTextNode('MAILTO:' + str(user.email) or str(user.name)) - href.appendChild(huri) - return href + return ('href', 'DAV:', 'MAILTO:' + str(user.email) or str(user.name)) def _get_caldav_schedule_outbox_URL(self, cr): return self._get_caldav_schedule_inbox_URL(cr) def _get_caldav_schedule_inbox_URL(self, cr): - import xml.dom.minidom import urllib uid = self.context.uid ctx = self.context.context.copy() ctx.update(self.dctx) calendar_obj = self.context._dirobj.pool.get('basic.calendar') calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx) - res = '%s/%s' %(calendar.collection_id.name, calendar.name) - doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None) - href = doc.documentElement - href.tagName = 'D:href' - huri = doc.createTextNode(urllib.quote('/%s/%s' % (cr.dbname, res))) - href.appendChild(huri) - return href + res = '/webdav/%s/%s/%s' %(cr.dbname, calendar.collection_id.name, calendar.name) + + return ('href', 'DAV:', res) + def _get_dav_supported_report_set(self, cr): + return '' # TODO class res_node_calendar(nodes.node_class): our_type = 'file' DAV_PROPS = { - "http://calendarserver.org/ns/" : ('getctag'), + "http://calendarserver.org/ns/" : ('getctag',), "urn:ietf:params:xml:ns:caldav" : ( 'calendar-description', 'calendar-data', - 'calendar-home-set', - 'calendar-user-address-set', - 'schedule-inbox-URL', - 'schedule-outbox-URL',)} + # 'calendar-home-set', + # 'calendar-user-address-set', + #'schedule-inbox-URL', + # 'schedule-outbox-URL', + )} DAV_M_NS = { "http://calendarserver.org/ns/" : '_get_dav', "urn:ietf:params:xml:ns:caldav" : '_get_caldav'} @@ -414,6 +438,7 @@ class res_node_calendar(nodes.node_class): uid = self.context.uid calendar_obj = self.context._dirobj.pool.get('basic.calendar') context = self.context.context.copy() + context.update(self.dctx) context.update({'model': self.model, 'res_id':self.res_id}) res = calendar_obj.export_cal(cr, uid, [self.calendar_id], context=context) return res @@ -426,8 +451,11 @@ class res_node_calendar(nodes.node_class): def set_data(self, cr, data, fil_obj = None): uid = self.context.uid + context = self.context.context.copy() + context.update(self.dctx) + context.update({'model': self.model, 'res_id':self.res_id}) calendar_obj = self.context._dirobj.pool.get('basic.calendar') - res = calendar_obj.import_cal(cr, uid, data, self.calendar_id) + res = calendar_obj.import_cal(cr, uid, data, self.calendar_id, context=context) return res def _get_ttag(self,cr): diff --git a/addons/caldav/calendar.py b/addons/caldav/calendar.py index b5a62686494..2f7d978b5a6 100644 --- a/addons/caldav/calendar.py +++ b/addons/caldav/calendar.py @@ -32,6 +32,7 @@ import tools import time import logging from caldav_node import res_node_calendar +from tools.safe_eval import safe_eval as eval try: import vobject @@ -595,7 +596,7 @@ class Calendar(CalDAV, osv.osv): continue if line.name in ('valarm', 'attendee'): continue - line_domain = eval(line.domain or '[]') + line_domain = eval(line.domain or '[]', context) line_domain += domain if ctx_res_id: line_domain += [('id','=',ctx_res_id)] @@ -627,7 +628,7 @@ class Calendar(CalDAV, osv.osv): continue if line.name in ('valarm', 'attendee'): continue - domain = eval(line.domain or '[]') + domain = eval(line.domain or '[]', context) if ctx_res_id: domain += [('id','=',ctx_res_id)] mod_obj = self.pool.get(line.object_id.model) diff --git a/addons/caldav/calendar_collection.py b/addons/caldav/calendar_collection.py index 8dd4ff053c9..52fe698647c 100644 --- a/addons/caldav/calendar_collection.py +++ b/addons/caldav/calendar_collection.py @@ -18,9 +18,11 @@ # along with this program. If not, see . # ############################################################################## + from osv import osv, fields from tools.translate import _ import caldav_node +import logging class calendar_collection(osv.osv): _inherit = 'document.directory' @@ -30,7 +32,8 @@ class calendar_collection(osv.osv): } _default = { 'calendar_collection' : False, - } + } + def _get_root_calendar_directory(self, cr, uid, context=None): objid = self.pool.get('ir.model.data') try: @@ -40,18 +43,25 @@ class calendar_collection(osv.osv): root_id = objid.read(cr, uid, mid, ['res_id'])['res_id'] root_cal_dir = self.browse(cr,uid, root_id, context=context) return root_cal_dir.name - except Exception, e: - import netsvc - logger = netsvc.Logger() - logger.notifyChannel("document", netsvc.LOG_WARNING, 'Cannot set root directory for Calendars:'+ str(e)) + except Exception: + logger = logging.getLogger('document') + logger.warning('Cannot set root directory for Calendars:', exc_info=True) return False return False - def _locate_child(self, cr, uid, root_id, uri,nparent, ncontext): - """ try to locate the node in uri, - Return a tuple (node_dir, remaining_path) - """ - return (caldav_node.node_database(context=ncontext), uri) + def get_node_class(self, cr, uid, ids, dbro=None, dynamic=False, context=None): + if dbro is None: + dbro = self.browse(cr, uid, ids, context=context) + + if dbro.calendar_collection: + if dynamic: + return caldav_node.node_calendar_res_col + else: + return caldav_node.node_calendar_collection + else: + return super(calendar_collection, self).\ + get_node_class(cr, uid, ids, dbro=dbro,dynamic=dynamic, + context=context) def get_description(self, cr, uid, ids, context=None): #TODO : return description of all calendars diff --git a/addons/caldav/doc/Path discovery.rst b/addons/caldav/doc/Path discovery.rst new file mode 100644 index 00000000000..d2d434b5fb6 --- /dev/null +++ b/addons/caldav/doc/Path discovery.rst @@ -0,0 +1,43 @@ +=============================== +Discovery of calendar resources +=============================== + +1. Srv record +-------------- +Calendar server and port should be advertised by a DNS _srv record. +Although this is beyond the capabilities of the OpenERP server, an +example setup is listed below: + -- TODO -- + +DNS -> http://our-host-ip:port/ + +2. Well-known uris +------------------- +The OpenERP server may have the 'well-known URIs' servlet activated, +which means that it will advertise its main database and the correct +location of the main CalDAV resource. +http://our-host-ip:port/.well-known/caldav -> http://our-host-ip:port/webdav/dbname/calendars/ + + +3. Caldav collection +--------------------- +The CalDAV "collection" is not necessarily a calendar or a folder just +containing calendars under it. It is a DAV resource (aka folder) which +has special DAV properties, so that clients are redirected to the right +urls (like per-user calendars etc.). + +http://our-host-ip:port/webdav/dbname/calendars/ -> http://our-host-ip:port/webdav/dbname/calendars/users/user-login/c/ + +4. Calendar home for user +-------------------------- +There can be one dynamic folder per user, which will in turn contain the calendars + +http://our-host-ip:port/webdav/dbname/calendars/users/user-login/c/ -> +http://our-host-ip:port/webdav/dbname/calendars/users/user-login/c/[Meetings, Tasks] + +5. Calendars +-------------- +Each calendar will contain the resource nodes: + .../c/Meetings/ -> .../c/Meetings/123.ics + +Principal url From f4bc8371430aa5f0fec2a1d52fda558e6ba0e3d0 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:40:29 +0300 Subject: [PATCH 020/192] Doc webdav: enhance the property notation structure When DAV-aware nodes want to return properties, they might need to have xml attributes or single instances of sub-elements. Example: return ('aprop', 'D:', ('foo', 'D:', None, { 'name': 'bar'})) will result in xml: bzr revid: p_christ@hol.gr-20101012104029-gq6hhjki4i52n8qh --- addons/document_webdav/webdav.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/addons/document_webdav/webdav.py b/addons/document_webdav/webdav.py index 6b24198e320..86fd8cd9e71 100644 --- a/addons/document_webdav/webdav.py +++ b/addons/document_webdav/webdav.py @@ -67,6 +67,8 @@ def mk_prop_response(self, uri, good_props, bad_props, doc): string: text node tuple ('elem', 'ns') for empty sub-node tuple ('elem', 'ns', sub-elems) for sub-node with elements + tuple ('elem', 'ns', sub-elems, {attrs}) for sub-node with + optional elements and attributes list, of above tuples """ if ns == 'DAV:': @@ -108,14 +110,18 @@ def mk_prop_response(self, uri, good_props, bad_props, doc): ve=doc.createElement(ns_prefix+v[0]) if need_ns: ve.setAttribute("xmlns:ns"+str(nsnum), v[1]) - if len(v) > 2: - if isinstance(v[2], list): + if len(v) > 2 and v[2] is not None: + if isinstance(v[2], (list, tuple)): # support nested elements like: # ( 'elem', 'ns:', [('sub-elem1', 'ns1'), ...] _prop_elem_child(ve, v[1], v[2], ns_prefix) else: vt =doc.createTextNode(tools.ustr(v[2])) ve.appendChild(vt) + if len(v) > 3 and v[3]: + assert isinstance(v[3], dict) + for ak, av in v[3].items(): + ve.setAttribute(ak, av) pnode.appendChild(ve) else: ve=doc.createTextNode(tools.ustr(v)) From 7882ef46faea31510516a133b5e1dacc39938d18 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:40:30 +0300 Subject: [PATCH 021/192] Caldav node: implement more properties, cleanup old ones. bzr revid: p_christ@hol.gr-20101012104030-o7l7zscrnx4t0fc7 --- addons/caldav/caldav_node.py | 114 +++++++++++++---------------------- 1 file changed, 42 insertions(+), 72 deletions(-) diff --git a/addons/caldav/caldav_node.py b/addons/caldav/caldav_node.py index 27f92943d07..f95af09714c 100644 --- a/addons/caldav/caldav_node.py +++ b/addons/caldav/caldav_node.py @@ -23,6 +23,8 @@ import time from document_webdav import nodes import StringIO +# TODO: implement DAV-aware errors, inherit from IOError + def dict_merge(*dicts): """ Return a dict with all values of dicts """ @@ -147,23 +149,24 @@ class node_calendar_res_col(nodes.node_res_obj): class node_calendar(nodes.node_class): our_type = 'collection' DAV_PROPS = { - "olDAV:": ('principal-collection-set', - 'principal-URL', - 'supported-report-set'), + "DAV:": ('supported-report-set',), # "http://cal.me.com/_namespace/" : ('user-state',), - "http://calendarserver.org/ns/" : ( - # 'dropbox-home-URL', - # 'notification-URL', - 'getctag',), + "http://calendarserver.org/ns/" : ( 'getctag',), 'http://groupdav.org/': ('resourcetype',), "urn:ietf:params:xml:ns:caldav" : ( - 'calendar-description', - 'calendar-data', - # 'calendar-home-set', - # 'calendar-user-address-set', - # 'schedule-inbox-URL', - #'schedule-outbox-URL', + 'calendar-description', + 'supported-calendar-component-set', )} + DAV_PROPS_HIDDEN = { + "urn:ietf:params:xml:ns:caldav" : ( + 'calendar-data', + 'calendar-timezone', + 'supported-calendar-data', + 'max-resource-size', + 'min-date-time', + 'max-date-time', + )} + DAV_M_NS = { "DAV:" : '_get_dav', # "http://cal.me.com/_namespace/": '_get_dav', @@ -187,28 +190,6 @@ class node_calendar(nodes.node_class): result = self._get_ttag(cr) + ':' + str(time.time()) return str(result) - def _get_dav_dropbox_home_URL(self, cr): #Depr - import urllib - uid = self.context.uid - ctx = self.context.context.copy() - ctx.update(self.dctx) - calendar_obj = self.context._dirobj.pool.get('basic.calendar') - calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx) - res = '%s/%s' %(calendar.collection_id.name, calendar.name) - url = urllib.quote('/%s/%s' % (cr.dbname, res)) - return url - - def _get_dav_notification_URL(self, cr): # Depr - import urllib - uid = self.context.uid - ctx = self.context.context.copy() - ctx.update(self.dctx) - calendar_obj = self.context._dirobj.pool.get('basic.calendar') - calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx) - res = '%s/%s' %(calendar.collection_id.name, calendar.name) - url = urllib.quote('/%s/%s' % (cr.dbname, res)) - return url - def _get_dav_user_state(self, cr): #TODO return 'online' @@ -356,40 +337,33 @@ class node_calendar(nodes.node_class): except Exception, e: return None - def _get_dav_principal_collection_set(self, cr): - import urllib - uid = self.context.uid - ctx = self.context.context.copy() - ctx.update(self.dctx) - calendar_obj = self.context._dirobj.pool.get('basic.calendar') - calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx) - res = '%s/%s' %(calendar.collection_id.name, calendar.name) - return ('href', 'DAV:', urllib.quote('/%s/%s' % (cr.dbname, res))) - - def _get_caldav_calendar_user_address_set(self, cr): - uid = self.context.uid - ctx = self.context.context.copy() - ctx.update(self.dctx) - user_obj = self.context._dirobj.pool.get('res.users') - user = user_obj.browse(cr, uid, uid, context=ctx) - return ('href', 'DAV:', 'MAILTO:' + str(user.email) or str(user.name)) - - def _get_caldav_schedule_outbox_URL(self, cr): - return self._get_caldav_schedule_inbox_URL(cr) - - def _get_caldav_schedule_inbox_URL(self, cr): - import urllib - uid = self.context.uid - ctx = self.context.context.copy() - ctx.update(self.dctx) - calendar_obj = self.context._dirobj.pool.get('basic.calendar') - calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx) - res = '/webdav/%s/%s/%s' %(cr.dbname, calendar.collection_id.name, calendar.name) - - return ('href', 'DAV:', res) - def _get_dav_supported_report_set(self, cr): - return '' # TODO + + return ('supported-report', 'DAV:', + ('report','DAV:', + ('principal-match','DAV:') + ) + ) + + def _get_caldav_supported_calendar_component_set(self, cr): + return ('comp', 'urn:ietf:params:xml:ns:caldav', None, + {'name': self.cal_type.upper()} ) + + def _get_caldav_calendar_timezone(self, cr): + return None #TODO + + def _get_caldav_supported_calendar_data(self, cr): + return ('calendar-data', 'urn:ietf:params:xml:ns:caldav', None, + {'content-type': "text/calendar", 'version': "2.0" } ) + + def _get_caldav_max_resource_size(self, cr): + return 65535 + + def _get_caldav_min_date_time(self, cr): + return "19700101T000000Z" + + def _get_caldav_max_date_time(self, cr): + return "21001231T235959Z" # I will be dead by then class res_node_calendar(nodes.node_class): our_type = 'file' @@ -398,10 +372,6 @@ class res_node_calendar(nodes.node_class): "urn:ietf:params:xml:ns:caldav" : ( 'calendar-description', 'calendar-data', - # 'calendar-home-set', - # 'calendar-user-address-set', - #'schedule-inbox-URL', - # 'schedule-outbox-URL', )} DAV_M_NS = { "http://calendarserver.org/ns/" : '_get_dav', From f5117544fa0a0c18d63bbe8acba8882d13c39bb7 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:40:30 +0300 Subject: [PATCH 022/192] caldav: instrument the get_domain() filter function .. although, at the end of the day, this API cannot work since it only operates on one level of depth. bzr revid: p_christ@hol.gr-20101012104030-xwjdda74uhdl37y1 --- addons/caldav/caldav_node.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/addons/caldav/caldav_node.py b/addons/caldav/caldav_node.py index f95af09714c..510e43eabd0 100644 --- a/addons/caldav/caldav_node.py +++ b/addons/caldav/caldav_node.py @@ -21,6 +21,7 @@ import time from document_webdav import nodes +import logging import StringIO # TODO: implement DAV-aware errors, inherit from IOError @@ -205,6 +206,7 @@ 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: @@ -220,10 +222,26 @@ class node_calendar(nodes.node_class): if vevent_filter.nodeType == vevent_filter.TEXT_NODE: continue if vevent_filter.localName == 'comp-filter': - if vevent_filter.getAttribute('name') == 'VEVENT': - res = [('type','=','vevent')] - if vevent_filter.getAttribute('name') == 'VTODO': - res = [('type','=','vtodo')] + if vevent_filter.getAttribute('name'): + res = [('type','=',vevent_filter.getAttribute('name').lower() )] + + for cfe in vevent_filter.childNodes: + if cfe.localName == 'time-range': + if cfe.getAttribute('start'): + _log.warning("Ignore start.. ") + # No, it won't work in this API + #val = cfe.getAttribute('start') + #res += [('dtstart','=', cfe)] + elif cfe.getAttribute('end'): + _log.warning("Ignore end.. ") + else: + _log.debug("Unknown comp-filter: %s", cfe.localName) + else: + _log.debug("Unknown comp-filter: %s", vevent_filter.localName) + else: + _log.debug("Unknown filter element: %s", vcalendar_filter.localName) + else: + _log.debug("Unknown calendar-query element: %s", filter_child.localName) return res elif filters.localName == 'calendar-multiget': names = [] @@ -238,8 +256,12 @@ class node_calendar(nodes.node_class): if len(caluri): caluri = caluri[-2] if caluri not in names : names.append(caluri) + else: + _log.debug("Unknonwn multiget element: %s", filter_child.localName) res = [('name','in',names)] return res + else: + _log.debug("Unknown element in REPORT: %s", filters.localName) return res def children(self, cr, domain=None): From 1b301705258289e13bdcc64e063420792839fe96 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:40:35 +0300 Subject: [PATCH 023/192] doc webdav: switch on the DAV v2 features The underlying python-webdav supports some emulation for lock/unlock, so try to use that. bzr revid: p_christ@hol.gr-20101012104035-jffog0w1dco2lo9i --- addons/document_webdav/dav_fs.py | 17 ++++++++--------- addons/document_webdav/nodes.py | 13 +------------ addons/document_webdav/webdav_server.py | 5 ++--- 3 files changed, 11 insertions(+), 24 deletions(-) diff --git a/addons/document_webdav/dav_fs.py b/addons/document_webdav/dav_fs.py index 9e04673555a..07ffeda4da1 100644 --- a/addons/document_webdav/dav_fs.py +++ b/addons/document_webdav/dav_fs.py @@ -26,8 +26,6 @@ import os import time from string import joinfields, split, lower -from service import security - import netsvc import urlparse @@ -39,6 +37,8 @@ import urllib from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree from cache import memoize from tools import misc +from tools.dict_tools import dict_merge2 + CACHE_SIZE=20000 #hack for urlparse: add webdav in the net protocols @@ -95,8 +95,7 @@ class openerp_dav_handler(dav_interface): return props node = self.uri2object(cr, uid, pool, uri2) if node: - props = props.copy() - props.update(node.get_dav_props(cr)) + props = dict_merge2(props, node.get_dav_props(cr)) cr.close() return props @@ -133,11 +132,11 @@ class openerp_dav_handler(dav_interface): self.parent.log_message("Exc: %s",traceback.format_exc()) raise default_exc("Operation failed") - def _get_dav_lockdiscovery(self, uri): - raise DAV_NotFound + #def _get_dav_lockdiscovery(self, uri): + # raise DAV_NotFound - def _get_dav_supportedlock(self, uri): - raise DAV_NotFound + #def A_get_dav_supportedlock(self, uri): + # raise DAV_NotFound def match_prop(self, uri, match, ns, propname): if self.M_NS.has_key(ns): @@ -293,8 +292,8 @@ class openerp_dav_handler(dav_interface): else: fp = node.full_path() if fp and len(fp): - self.parent.log_message('childs: @%s' % fp) fp = '/'.join(fp) + self.parent.log_message('childs for: %s' % fp) else: fp = None domain = None diff --git a/addons/document_webdav/nodes.py b/addons/document_webdav/nodes.py index 0a45b227095..67dc9fd2a4f 100644 --- a/addons/document_webdav/nodes.py +++ b/addons/document_webdav/nodes.py @@ -22,20 +22,9 @@ from document import nodes from tools.safe_eval import safe_eval as eval +from tools.dict_tools import dict_filter import urllib -def dict_filter(srcdic, keys, res=None): - ''' Return a copy of srcdic that has only keys set. - If any of keys are missing from srcdic, the result won't have them, - either. - @param res If given, result will be updated there, instead of a new dict. - ''' - if res is None: - res = {} - for k in keys: - if k in srcdic: - res[k] = srcdic[k] - return res class node_acl_mixin(object): def _get_dav_owner(self, cr): diff --git a/addons/document_webdav/webdav_server.py b/addons/document_webdav/webdav_server.py index c86cde2b096..7e4bb69fa0a 100644 --- a/addons/document_webdav/webdav_server.py +++ b/addons/document_webdav/webdav_server.py @@ -57,7 +57,7 @@ def OpenDAVConfig(**kw): class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler): verbose = False protocol_version = 'HTTP/1.1' - _HTTP_OPTIONS= { 'DAV' : ['1',], + _HTTP_OPTIONS= { 'DAV' : ['1', '2'], 'Allow' : [ 'GET', 'HEAD', 'COPY', 'MOVE', 'POST', 'PUT', 'PROPFIND', 'PROPPATCH', 'OPTIONS', 'MKCOL', 'DELETE', 'TRACE', 'REPORT', ] @@ -265,8 +265,7 @@ try: handler.debug = config.get_misc('webdav','debug',True) _dc = { 'verbose' : verbose, 'directory' : directory, - 'lockemulation' : False, - + 'lockemulation' : True, } conf = OpenDAVConfig(**_dc) From 369cae941e34f0be498e1786814381a129f52751 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:40:37 +0300 Subject: [PATCH 024/192] CalDAV: properly implement the CTag feature Instead of using the ETag (which looks at the modification time of the base.calendar object), aggregate over the contained calendar entries to find their maximum modification. bzr revid: p_christ@hol.gr-20101012104037-847evso9mryqom5f --- addons/caldav/caldav_node.py | 45 +++++++++++------------------- addons/caldav/calendar.py | 53 ++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 29 deletions(-) diff --git a/addons/caldav/caldav_node.py b/addons/caldav/caldav_node.py index 510e43eabd0..52da451b567 100644 --- a/addons/caldav/caldav_node.py +++ b/addons/caldav/caldav_node.py @@ -21,33 +21,14 @@ import time from document_webdav import nodes +from document.nodes import _str2time import logging import StringIO +from tools.dict_tools import dict_merge, dict_merge2 + # TODO: implement DAV-aware errors, inherit from IOError -def dict_merge(*dicts): - """ Return a dict with all values of dicts - """ - res = {} - for d in dicts: - res.update(d) - return res - -def dict_merge2(*dicts): - """ Return a dict with all values of dicts. - If some key appears twice and contains iterable objects, the values - are merged (instead of overwritten). - """ - res = {} - for d in dicts: - for k in d.keys(): - if k in res and isinstance(res[k], (list, tuple)): - res[k] = res[k] + d[k] - else: - res[k] = d[k] - return res - # Assuming that we have set global properties right, we mark *all* # directories as having calendar-access. nodes.node_dir.http_options = dict_merge2(nodes.node_dir.http_options, @@ -186,10 +167,18 @@ class node_calendar(nodes.node_class): self.content_length = 0 self.displayname = calendar.name self.cal_type = calendar.type + # TODO owner + def _get_dav_getctag(self, cr): - result = self._get_ttag(cr) + ':' + str(time.time()) - return str(result) + dirobj = self.context._dirobj + uid = self.context.uid + ctx = self.context.context.copy() + ctx.update(self.dctx) + + bc_obj = dirobj.pool.get('basic.calendar') + res = bc_obj.get_cal_max_modified(cr, uid, [self.calendar_id], self, domain=[], context=ctx) + return _str2time(res) def _get_dav_user_state(self, cr): #TODO @@ -295,11 +284,9 @@ class node_calendar(nodes.node_class): if not domain: domain = [] - fil_obj = dirobj.pool.get('basic.calendar') - ids = fil_obj.search(cr, uid, domain) - res = [] - if self.calendar_id in ids: - res = fil_obj.get_calendar_objects(cr, uid, [self.calendar_id], self, domain=where, context=ctx) + bc_obj = dirobj.pool.get('basic.calendar') + # we /could/ be supplying an invalid calendar id to bc_obj, it has to check + res = bc_obj.get_calendar_objects(cr, uid, [self.calendar_id], self, domain=where, context=ctx) return res def create_child(self, cr, path, data): diff --git a/addons/caldav/calendar.py b/addons/caldav/calendar.py index 2f7d978b5a6..2133e3be293 100644 --- a/addons/caldav/calendar.py +++ b/addons/caldav/calendar.py @@ -552,6 +552,33 @@ class CalDAV(object): self.ical_reset('value') return res +if True: # we need this indentation level ;) + + def get_last_modified(self, cr, user, args, context=None, access_rights_uid=None): + """Return the last modification date of objects in 'domain' + This function has similar semantics to orm.search(), apart from the + limit, offset and order arguments, which make no sense here. + It is useful when we want to find if the table (aka set of records) + has any modifications we should update at the client. + """ + if context is None: + context = {} + self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read', context=context) + + query = self._where_calc(cr, user, args, context=context) + self._apply_ir_rules(cr, user, query, 'read', context=context) + from_clause, where_clause, where_clause_params = query.get_sql() + + where_str = where_clause and (" WHERE %s" % where_clause) or '' + + cr.execute('SELECT MAX(COALESCE("%s".write_date, "%s".create_date)) FROM ' % (self._table, self._table) + + from_clause + where_str , + where_clause_params, + debug=self._debug) + res = cr.fetchall() + return res[0][0] + + class Calendar(CalDAV, osv.osv): _name = 'basic.calendar' _calname = 'calendar' @@ -610,6 +637,32 @@ class Calendar(CalDAV, osv.osv): node = res_node_calendar('%s.ics' %data.id, parent, ctx, data, line.object_id.model, data.id) res.append(node) return res + + + def get_cal_max_modified(self, cr, uid, ids, parent=None, domain=None, context=None): + if not context: + context = {} + if not domain: + domain = [] + res = None + ctx_res_id = context.get('res_id', None) + ctx_model = context.get('model', None) + for cal in self.browse(cr, uid, ids): + for line in cal.line_ids: + if ctx_model and ctx_model != line.object_id.model: + continue + if line.name in ('valarm', 'attendee'): + continue + line_domain = eval(line.domain or '[]', context) + line_domain += domain + if ctx_res_id: + line_domain += [('id','=',ctx_res_id)] + mod_obj = self.pool.get(line.object_id.model) + max_data = get_last_modified(mod_obj, cr, uid, line_domain, context=context) + if res and res > max_data: + continue + res = max_data + return res def export_cal(self, cr, uid, ids, vobj='vevent', context=None): """ Export Calendar From 371ceab425cdd48e39267adbb108729b5900cab8 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:40:42 +0300 Subject: [PATCH 025/192] Document: conformant ETag, avoid special chars. bzr revid: p_christ@hol.gr-20101012104042-75xq12yk2wrj66ol --- addons/document/nodes.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/document/nodes.py b/addons/document/nodes.py index 46674b61caa..e1a1e8bde49 100644 --- a/addons/document/nodes.py +++ b/addons/document/nodes.py @@ -266,18 +266,18 @@ class node_class(object): """ Get a tag, unique per object + modification. see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """ - return self._get_ttag(cr) + ':' + self._get_wtag(cr) + return '"%s-%s"' % (self._get_ttag(cr), self._get_wtag(cr)) def _get_wtag(self, cr): """ Return the modification time as a unique, compact string """ - return str(_str2time(self.write_date)) + return str(_str2time(self.write_date)).replace('.','') def _get_ttag(self,cr): """ Get a unique tag for this type/id of object. Must be overriden, so that each node is uniquely identified. """ print "node_class.get_ttag()",self - raise NotImplementedError("get_etag stub()") + raise NotImplementedError("get_ttag stub()") def get_dav_props(self, cr): """ If this class has special behaviour for GroupDAV etc, export @@ -883,7 +883,7 @@ class node_res_obj(node_class): return res - def get_dav_props(self, cr): + def get_dav_props_DEPR(self, cr): # Deprecated! (but document_ics must be cleaned, first) res = {} cntobj = self.context._dirobj.pool.get('document.directory.content') @@ -897,7 +897,7 @@ class node_res_obj(node_class): res['http://groupdav.org/'] = ('resourcetype',) return res - def get_dav_eprop(self, cr, ns, prop): + 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)) From f7ce71c475604afca3ca1a13f648fbb944487393 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:40:44 +0300 Subject: [PATCH 026/192] Doc webdav: hack minidom to put proper quotes at ETag. Status first. bzr revid: p_christ@hol.gr-20101012104044-1uq9x7k93aku7ls1 --- addons/document_webdav/webdav.py | 37 +++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/addons/document_webdav/webdav.py b/addons/document_webdav/webdav.py index 86fd8cd9e71..0a9f4cd1111 100644 --- a/addons/document_webdav/webdav.py +++ b/addons/document_webdav/webdav.py @@ -22,6 +22,8 @@ import xml.dom.minidom domimpl = xml.dom.minidom.getDOMImplementation() +from xml.dom.minicompat import StringTypes + import urlparse import urllib from osv import osv @@ -35,6 +37,21 @@ from http://code.google.com/p/pywebdav/downloads/detail?name=PyWebDAV-0.9.4.tar. import tools +class Text2(xml.dom.minidom.Text): + def writexml(self, writer, indent="", addindent="", newl=""): + data = "%s%s%s" % (indent, self.data, newl) + data = data.replace("&", "&").replace("<", "<") + data = data.replace(">", ">") + writer.write(data) + +def createText2Node(doc, data): + if not isinstance(data, StringTypes): + raise TypeError, "node contents must be a string" + t = Text2() + t.data = data + t.ownerDocument = doc + return t + super_mk_prop_response = PROPFIND.mk_prop_response def mk_prop_response(self, uri, good_props, bad_props, doc): @@ -116,7 +133,7 @@ def mk_prop_response(self, uri, good_props, bad_props, doc): # ( 'elem', 'ns:', [('sub-elem1', 'ns1'), ...] _prop_elem_child(ve, v[1], v[2], ns_prefix) else: - vt =doc.createTextNode(tools.ustr(v[2])) + vt=createText2Node(doc,tools.ustr(v[2])) ve.appendChild(vt) if len(v) > 3 and v[3]: assert isinstance(v[3], dict) @@ -124,7 +141,7 @@ def mk_prop_response(self, uri, good_props, bad_props, doc): ve.setAttribute(ak, av) pnode.appendChild(ve) else: - ve=doc.createTextNode(tools.ustr(v)) + ve=createText2Node(doc, tools.ustr(v)) pnode.appendChild(ve) # write href information @@ -143,6 +160,10 @@ def mk_prop_response(self, uri, good_props, bad_props, doc): ps=doc.createElement("D:propstat") if good_props: re.appendChild(ps) + s=doc.createElement("D:status") + t=doc.createTextNode("HTTP/1.1 200 OK") + s.appendChild(t) + ps.appendChild(s) gp=doc.createElement("D:prop") for ns in good_props.keys(): @@ -156,10 +177,6 @@ def mk_prop_response(self, uri, good_props, bad_props, doc): _prop_child(gp, ns, p, v) ps.appendChild(gp) - s=doc.createElement("D:status") - t=doc.createTextNode("HTTP/1.1 200 OK") - s.appendChild(t) - ps.appendChild(s) re.appendChild(ps) # now write the errors! @@ -169,6 +186,10 @@ def mk_prop_response(self, uri, good_props, bad_props, doc): for ecode in bad_props.keys(): ps=doc.createElement("D:propstat") re.appendChild(ps) + s=doc.createElement("D:status") + t=doc.createTextNode(utils.gen_estring(ecode)) + s.appendChild(t) + ps.appendChild(s) bp=doc.createElement("D:prop") ps.appendChild(bp) @@ -182,10 +203,6 @@ def mk_prop_response(self, uri, good_props, bad_props, doc): pe=doc.createElement(ns_prefix+str(p)) bp.appendChild(pe) - s=doc.createElement("D:status") - t=doc.createTextNode(utils.gen_estring(ecode)) - s.appendChild(t) - ps.appendChild(s) re.appendChild(ps) # return the new response element From 95de19744044f9b0e31677322f4726738e74bb97 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:40:44 +0300 Subject: [PATCH 027/192] CalDAV: more properties, hide WebCal support WebCal entries, although permitted by the protocol, confuse some badly implemented CalDAV clients, so hide them with an option at the calendar definition. bzr revid: p_christ@hol.gr-20101012104044-rjx63ucwjk0ys98r --- addons/caldav/caldav_node.py | 57 +++++++++++++++++++++++++---------- addons/caldav/caldav_view.xml | 3 ++ addons/caldav/calendar.py | 35 +++++---------------- addons/caldav/orm_utils.py | 49 ++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 43 deletions(-) create mode 100644 addons/caldav/orm_utils.py diff --git a/addons/caldav/caldav_node.py b/addons/caldav/caldav_node.py index 52da451b567..f104b27ca15 100644 --- a/addons/caldav/caldav_node.py +++ b/addons/caldav/caldav_node.py @@ -24,6 +24,7 @@ from document_webdav import nodes from document.nodes import _str2time import logging import StringIO +from orm_utils import get_last_modified from tools.dict_tools import dict_merge, dict_merge2 @@ -64,21 +65,24 @@ class node_calendar_collection(nodes.node_dir): for cal in fil_obj.browse(cr, uid, ids, context=ctx): if (not name) or not ext: res.append(node_calendar(cal.name, self, self.context, cal)) - if (not name) or ext: + if cal.has_webcal and (not name) or ext: res.append(res_node_calendar(cal.name+'.ics', self, self.context, cal)) # May be both of them! return res - def _get_dav_owner(self, cr): - # Todo? - return False - def _get_ttag(self, cr): return 'calen-dir-%d' % self.dir_id def _get_dav_getctag(self, cr): - result = self.get_etag(cr) - return str(result) + dirobj = self.context._dirobj + uid = self.context.uid + ctx = self.context.context.copy() + ctx.update(self.dctx) + where = [('collection_id','=',self.dir_id)] + bc_obj = dirobj.pool.get('basic.calendar') + + res = get_last_modified(bc_obj, cr, uid, where, context=ctx) + return _str2time(res) class node_calendar_res_col(nodes.node_res_obj): """ Calendar collection, as a dynamically created node @@ -116,7 +120,7 @@ class node_calendar_res_col(nodes.node_res_obj): for cal in fil_obj.browse(cr, uid, ids, context=ctx): if (not name) or not ext: res.append(node_calendar(cal.name, self, self.context, cal)) - if (not name) or ext: + if cal.has_webcal and (not name) or ext: res.append(res_node_calendar(cal.name+'.ics', self, self.context, cal)) # May be both of them! return res @@ -125,8 +129,15 @@ class node_calendar_res_col(nodes.node_res_obj): return 'calen-dir-%d' % self.dir_id def _get_dav_getctag(self, cr): - result = self.get_etag(cr) - return str(result) + dirobj = self.context._dirobj + uid = self.context.uid + ctx = self.context.context.copy() + ctx.update(self.dctx) + where = [('collection_id','=',self.dir_id)] + bc_obj = dirobj.pool.get('basic.calendar') + + res = get_last_modified(bc_obj, cr, uid, where, context=ctx) + return _str2time(res) class node_calendar(nodes.node_class): our_type = 'collection' @@ -138,7 +149,9 @@ class node_calendar(nodes.node_class): "urn:ietf:params:xml:ns:caldav" : ( 'calendar-description', 'supported-calendar-component-set', - )} + ), + "http://apple.com/ns/ical/": ("calendar-color", "calendar-order"), + } DAV_PROPS_HIDDEN = { "urn:ietf:params:xml:ns:caldav" : ( 'calendar-data', @@ -154,7 +167,9 @@ class node_calendar(nodes.node_class): # "http://cal.me.com/_namespace/": '_get_dav', 'http://groupdav.org/': '_get_gdav', "http://calendarserver.org/ns/" : '_get_dav', - "urn:ietf:params:xml:ns:caldav" : '_get_caldav'} + "urn:ietf:params:xml:ns:caldav" : '_get_caldav', + "http://apple.com/ns/ical/": '_get_apple_cal', + } http_options = { 'DAV': ['calendar-access'] } @@ -167,8 +182,12 @@ class node_calendar(nodes.node_class): self.content_length = 0 self.displayname = calendar.name self.cal_type = calendar.type - # TODO owner - + self.cal_color = calendar.calendar_color or None + self.cal_order = calendar.calendar_order or None + try: + self.uuser = (calendar.user_id and calendar.user_id.login) or 'nobody' + except Exception: + self.uuser = 'nobody' def _get_dav_getctag(self, cr): dirobj = self.context._dirobj @@ -186,8 +205,9 @@ class node_calendar(nodes.node_class): def get_dav_resourcetype(self, cr): res = [ ('collection', 'DAV:'), + ('calendar', 'urn:ietf:params:xml:ns:caldav'), (str(self.cal_type + '-collection'), 'http://groupdav.org/'), - ('calendar', 'urn:ietf:params:xml:ns:caldav') ] + ] return res def get_domain(self, cr, filters): @@ -373,6 +393,12 @@ class node_calendar(nodes.node_class): def _get_caldav_max_date_time(self, cr): return "21001231T235959Z" # I will be dead by then + + def _get_apple_cal_calendar_color(self, cr): + return self.cal_color + + def _get_apple_cal_calendar_order(self, cr): + return self.cal_order class res_node_calendar(nodes.node_class): our_type = 'file' @@ -445,7 +471,6 @@ class res_node_calendar(nodes.node_class): res = '%d' % (self.calendar_id) return res - def rm(self, cr): uid = self.context.uid res = False diff --git a/addons/caldav/caldav_view.xml b/addons/caldav/caldav_view.xml index 2e9719efead..97656a954ad 100644 --- a/addons/caldav/caldav_view.xml +++ b/addons/caldav/caldav_view.xml @@ -74,6 +74,9 @@ + + + diff --git a/addons/caldav/calendar.py b/addons/caldav/calendar.py index 2133e3be293..276f08c5f51 100644 --- a/addons/caldav/calendar.py +++ b/addons/caldav/calendar.py @@ -32,6 +32,7 @@ import tools import time 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 try: @@ -552,33 +553,6 @@ class CalDAV(object): self.ical_reset('value') return res -if True: # we need this indentation level ;) - - def get_last_modified(self, cr, user, args, context=None, access_rights_uid=None): - """Return the last modification date of objects in 'domain' - This function has similar semantics to orm.search(), apart from the - limit, offset and order arguments, which make no sense here. - It is useful when we want to find if the table (aka set of records) - has any modifications we should update at the client. - """ - if context is None: - context = {} - self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read', context=context) - - query = self._where_calc(cr, user, args, context=context) - self._apply_ir_rules(cr, user, query, 'read', context=context) - from_clause, where_clause, where_clause_params = query.get_sql() - - where_str = where_clause and (" WHERE %s" % where_clause) or '' - - cr.execute('SELECT MAX(COALESCE("%s".write_date, "%s".create_date)) FROM ' % (self._table, self._table) + - from_clause + where_str , - where_clause_params, - debug=self._debug) - res = cr.fetchall() - return res[0][0] - - class Calendar(CalDAV, osv.osv): _name = 'basic.calendar' _calname = 'calendar' @@ -607,6 +581,13 @@ class Calendar(CalDAV, osv.osv): 'create_date': fields.datetime('Created Date', readonly=True), 'write_date': fields.datetime('Modifided Date', readonly=True), 'description': fields.text("description"), + 'calendar_color': fields.char('Color', size=20, help="For supporting clients, the color of the calendar entries"), + 'calendar_order': fields.integer('Order', help="For supporting clients, the order of this folder among the calendars"), + 'has_webcal': fields.boolean('WebCal', required=True, help="Also export a .ics entry next to the calendar folder, with WebCal content."), + } + + _defaults = { + 'has_webcal': False, } def get_calendar_objects(self, cr, uid, ids, parent=None, domain=None, context=None): diff --git a/addons/caldav/orm_utils.py b/addons/caldav/orm_utils.py new file mode 100644 index 00000000000..badacc855c9 --- /dev/null +++ b/addons/caldav/orm_utils.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2010 OpenERP SA (www.openerp.com) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + + +if True: # we need this indentation level ;) + + def get_last_modified(self, cr, user, args, context=None, access_rights_uid=None): + """Return the last modification date of objects in 'domain' + This function has similar semantics to orm.search(), apart from the + limit, offset and order arguments, which make no sense here. + It is useful when we want to find if the table (aka set of records) + has any modifications we should update at the client. + """ + if context is None: + context = {} + self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read', context=context) + + query = self._where_calc(cr, user, args, context=context) + self._apply_ir_rules(cr, user, query, 'read', context=context) + from_clause, where_clause, where_clause_params = query.get_sql() + + where_str = where_clause and (" WHERE %s" % where_clause) or '' + + cr.execute('SELECT MAX(COALESCE("%s".write_date, "%s".create_date)) FROM ' % (self._table, self._table) + + from_clause + where_str , + where_clause_params, + debug=self._debug) + res = cr.fetchall() + return res[0][0] + +#eof From 04aab94958c387cc9e0e092fc83973d9050a15b6 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:41:00 +0300 Subject: [PATCH 028/192] Doc nodes: Docstring and a node_context.get() convenience fn. bzr revid: p_christ@hol.gr-20101012104100-2y269ne3v7s03xnh --- addons/document/nodes.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/addons/document/nodes.py b/addons/document/nodes.py index e1a1e8bde49..a9b8b7167fc 100644 --- a/addons/document/nodes.py +++ b/addons/document/nodes.py @@ -61,8 +61,11 @@ def get_node_context(cr, uid, context): return node_context(cr, uid, context) class node_context(object): - """ This is the root node, representing access to some particular - context """ + """ This is the root node, representing access to some particular context + + A context is a set of persistent data, which may influence the structure + of the nodes. All other transient information during a data query should + be passed down with function arguments. cached_roots = {} node_file_class = None @@ -92,6 +95,9 @@ class node_context(object): def __ne__(self, other): return not self.__eq__(other) + + def get(self, name, default=None): + return self.context.get(name, default) def get_uri(self, cr, uri): """ Although this fn passes back to doc.dir, it is needed since @@ -272,7 +278,7 @@ class node_class(object): """ Return the modification time as a unique, compact string """ return str(_str2time(self.write_date)).replace('.','') - def _get_ttag(self,cr): + def _get_ttag(self, cr): """ Get a unique tag for this type/id of object. Must be overriden, so that each node is uniquely identified. """ From 51bf88f478faaac7113279279268c3816a35cab1 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:41:02 +0300 Subject: [PATCH 029/192] Doc WebDAV: pass the user-agent as a context to the nodes. It is unavoidable. Some user agents (i-#$^%) have serious trouble with protocol extensions they don't support. So, we have to hide a few of our capabilities with quirk code. bzr revid: p_christ@hol.gr-20101012104102-xakiyykjqsu16q2o --- addons/document_webdav/dav_fs.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/addons/document_webdav/dav_fs.py b/addons/document_webdav/dav_fs.py index 07ffeda4da1..031e8da5a15 100644 --- a/addons/document_webdav/dav_fs.py +++ b/addons/document_webdav/dav_fs.py @@ -184,6 +184,16 @@ class openerp_dav_handler(dav_interface): cr.close() return ret + def reduce_useragent(self): + ua = self.parent.headers.get('User-Agent', False) + ctx = {} + if ua: + if 'iPhone' in ua: + ctx['DAV-client'] = 'iPhone' + elif 'Konqueror' in ua: + ctx['DAV-client'] = 'GroupDAV' + return ctx + def get_prop(self, uri, ns, propname): """ return the value of a given property @@ -342,7 +352,8 @@ class openerp_dav_handler(dav_interface): def uri2object(self, cr, uid, pool, uri): if not uid: return None - return pool.get('document.directory').get_object(cr, uid, uri) + context = self.reduce_useragent() + return pool.get('document.directory').get_object(cr, uid, uri, context=context) def get_data(self,uri, rrange=None): self.parent.log_message('GET: %s' % uri) From 7bd29d41546d0253d136b4a95c075d40947d3b11 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:41:03 +0300 Subject: [PATCH 030/192] CalDAV: quirks for iPhone, based on the node_context bzr revid: p_christ@hol.gr-20101012104103-s7nyv4bsl5r5rdhc --- addons/caldav/caldav_node.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/addons/caldav/caldav_node.py b/addons/caldav/caldav_node.py index f104b27ca15..e974f7c55c0 100644 --- a/addons/caldav/caldav_node.py +++ b/addons/caldav/caldav_node.py @@ -65,6 +65,9 @@ class node_calendar_collection(nodes.node_dir): for cal in fil_obj.browse(cr, uid, ids, context=ctx): if (not name) or not ext: res.append(node_calendar(cal.name, self, self.context, cal)) + if self.context.get('DAV-client', '') in ('iPhone', 'iCalendar'): + # these ones must not see the webcal entry. + continue if cal.has_webcal and (not name) or ext: res.append(res_node_calendar(cal.name+'.ics', self, self.context, cal)) # May be both of them! @@ -120,6 +123,9 @@ class node_calendar_res_col(nodes.node_res_obj): for cal in fil_obj.browse(cr, uid, ids, context=ctx): if (not name) or not ext: res.append(node_calendar(cal.name, self, self.context, cal)) + if self.context.get('DAV-client', '') in ('iPhone', 'iCalendar'): + # these ones must not see the webcal entry. + continue if cal.has_webcal and (not name) or ext: res.append(res_node_calendar(cal.name+'.ics', self, self.context, cal)) # May be both of them! @@ -206,8 +212,9 @@ class node_calendar(nodes.node_class): def get_dav_resourcetype(self, cr): res = [ ('collection', 'DAV:'), ('calendar', 'urn:ietf:params:xml:ns:caldav'), - (str(self.cal_type + '-collection'), 'http://groupdav.org/'), ] + if self.context.get('DAV-client', '') == 'GroupDAV': + res.append((str(self.cal_type + '-collection'), 'http://groupdav.org/')) return res def get_domain(self, cr, filters): @@ -350,6 +357,11 @@ class node_calendar(nodes.node_class): return False def _get_caldav_calendar_data(self, cr): + if self.context.get('DAV-client', '') in ('iPhone', 'iCalendar'): + # Never return collective data to iClients, they get confused + # because they do propfind on the calendar node with Depth=1 + # and only expect the childrens' data + return None res = [] for child in self.children(cr): res.append(child._get_caldav_calendar_data(cr)) From d697bd94e5a860d91ddf3f5ffaa94a4c6f9c5384 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:41:03 +0300 Subject: [PATCH 031/192] Doc webdav, caldav: fix support for the calendar-multiget report This report is a request to fetch multiple calendar entries. The request contains a range of URIs to fetch, which must be processed at the dav_fs.Report level. The report is being called with Depth: 0 , for which the library could not perform an iterator. Hack over it and explictly set Depth:1 in our case. bzr revid: p_christ@hol.gr-20101012104103-eu156146jy4a75af --- addons/caldav/caldav_node.py | 19 +++---------------- addons/document_webdav/dav_fs.py | 24 ++++++++++++++++++++++++ addons/document_webdav/webdav.py | 16 ++++++++++++++++ 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/addons/caldav/caldav_node.py b/addons/caldav/caldav_node.py index e974f7c55c0..2ddd5ffa49e 100644 --- a/addons/caldav/caldav_node.py +++ b/addons/caldav/caldav_node.py @@ -260,22 +260,9 @@ class node_calendar(nodes.node_class): _log.debug("Unknown calendar-query element: %s", filter_child.localName) return res elif filters.localName == 'calendar-multiget': - names = [] - for filter_child in filters.childNodes: - if filter_child.nodeType == filter_child.TEXT_NODE: - continue - if filter_child.localName == 'href': - if not filter_child.firstChild: - continue - uri = filter_child.firstChild.data - caluri = uri.split('/') - if len(caluri): - caluri = caluri[-2] - if caluri not in names : names.append(caluri) - else: - _log.debug("Unknonwn multiget element: %s", filter_child.localName) - res = [('name','in',names)] - return res + # this is not the place to process, as it wouldn't support multi-level + # hrefs. So, the code is moved to document_webdav/dav_fs.py + pass else: _log.debug("Unknown element in REPORT: %s", filters.localName) return res diff --git a/addons/document_webdav/dav_fs.py b/addons/document_webdav/dav_fs.py index 031e8da5a15..7deb18f714c 100644 --- a/addons/document_webdav/dav_fs.py +++ b/addons/document_webdav/dav_fs.py @@ -309,12 +309,36 @@ class openerp_dav_handler(dav_interface): domain = None if filters: domain = node.get_domain(cr, filters) + + if hasattr(filters, 'getElementsByTagNameNS'): + hrefs = filters.getElementsByTagNameNS('DAV:', 'href') + if hrefs: + ul = self.parent.davpath + self.uri2local(uri) + for hr in hrefs: + turi = '' + for tx in hr.childNodes: + if tx.nodeType == hr.TEXT_NODE: + turi += tx.data + + if turi.startswith(ul): + result.append( turi[len(self.parent.davpath):]) + else: + self.parent.log_error("ignore href %s because it is not under request path", turi) + return result + # We don't want to continue with the children found below + # Note the exceptions and that 'finally' will close the + # cursor for d in node.children(cr, domain): self.parent.log_message('child: %s' % d.path) if fp: result.append( self.urijoin(dbname,fp,d.path) ) else: result.append( self.urijoin(dbname,d.path) ) + except DAV_Error: + raise + except Exception: + self.parent.log_error("cannot get_childs: "+ str(e)) + raise finally: if cr: cr.close() return result diff --git a/addons/document_webdav/webdav.py b/addons/document_webdav/webdav.py index 0a9f4cd1111..484a1054475 100644 --- a/addons/document_webdav/webdav.py +++ b/addons/document_webdav/webdav.py @@ -31,6 +31,7 @@ from osv import osv try: from DAV import utils from DAV.propfind import PROPFIND + from DAV.report import REPORT except ImportError: raise osv.except_osv('PyWebDAV Import Error!','Please install PyWebDAV \ from http://code.google.com/p/pywebdav/downloads/detail?name=PyWebDAV-0.9.4.tar.gz&can=2&q=/') @@ -257,3 +258,18 @@ def mk_propname_response(self,uri,propnames,doc): PROPFIND.mk_prop_response = mk_prop_response PROPFIND.mk_propname_response = mk_propname_response +super_create_prop = REPORT.create_prop + +def create_prop(self): + try: + if (self.filter is not None) and self._depth == "0": + hrefs = self.filter.getElementsByTagNameNS('DAV:', 'href') + if hrefs: + self._depth = "1" + except Exception: + pass + return super_create_prop(self) + +REPORT.create_prop = create_prop + +#eof From 39aa2bd391025246e4520e8e7d3b2dc0af23ec3d Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:41:18 +0300 Subject: [PATCH 032/192] base_calendar, caldav: fixes for the queries. bzr revid: p_christ@hol.gr-20101012104118-o02tug6x3t9gewaa --- addons/base_calendar/base_calendar.py | 18 +++++++----------- addons/caldav/calendar.py | 3 ++- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/addons/base_calendar/base_calendar.py b/addons/base_calendar/base_calendar.py index 2a0b0cc60ad..a37630f8d96 100644 --- a/addons/base_calendar/base_calendar.py +++ b/addons/base_calendar/base_calendar.py @@ -1017,7 +1017,8 @@ class calendar_event(osv.osv): elif int(val.get('interval')) > 1: #If interval is other than 1 rule is custom rrule_type = 'custom' - qry = "UPDATE %(table)s set rrule_type=\'%(rule_type)s\' " + qry = "UPDATE \"%s\" set rrule_type=%%s " % self._table + qry_args = [ rrule_type, ] if rrule_type == 'custom': new_val = val.copy() @@ -1055,17 +1056,12 @@ class calendar_event(osv.osv): new_val.pop('bymonth') for k, v in new_val.items(): - temp = ", %s='%s'" % (k, v) - qry += temp + qry += ", %s=%%s" % k + qry_args.append(v) - whr = " where id=%(id)s" - qry = qry + whr - val.update({ - 'table': self._table, - 'rule_type': rrule_type, - 'id': id, - }) - cr.execute(qry, val) # Hopefully psycopg2 works with dicts. But, FIXME + qry = qry + " where id=%s" + qry_args.append(id) + cr.execute(qry, qry_args) return True def _get_rulestring(self, cr, uid, ids, name, arg, context=None): diff --git a/addons/caldav/calendar.py b/addons/caldav/calendar.py index 276f08c5f51..ae9aa909642 100644 --- a/addons/caldav/calendar.py +++ b/addons/caldav/calendar.py @@ -71,7 +71,8 @@ def uid2openobjectid(cr, uidval, oomodel, rdate): r_id = cr.fetchone() if r_id: return (id, r_id[0]) - + else: + return (False, None) cr.execute(qry) ids = map(lambda x: str(x[0]), cr.fetchall()) if id in ids: From 0bdc45e068d584220d2a1b399d5f0389fcd6e092 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:41:20 +0300 Subject: [PATCH 033/192] Document: fix behavior of dir.dctx lines, when dir is removed. bzr revid: p_christ@hol.gr-20101012104120-n53iyh31z3zry6x9 --- addons/document/document_directory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/document/document_directory.py b/addons/document/document_directory.py index 30507f1cd2c..2b3aad859c0 100644 --- a/addons/document/document_directory.py +++ b/addons/document/document_directory.py @@ -293,7 +293,7 @@ class document_directory_dctx(osv.osv): _name = 'document.directory.dctx' _description = 'Directory Dynamic Context' _columns = { - 'dir_id': fields.many2one('document.directory', 'Directory', required=True), + 'dir_id': fields.many2one('document.directory', 'Directory', required=True, ondelete="cascade"), 'field': fields.char('Field', size=20, required=True, select=1, help="The name of the field. Note that the prefix \"dctx_\" will be prepended to what is typed here."), 'expr': fields.char('Expression', size=64, required=True, help="A python expression used to evaluate the field.\n" + \ "You can use 'dir_id' for current dir, 'res_id', 'res_model' as a reference to the current record, in dynamic folders"), From 849548ccf167ead6755fc52d5baf4b43319436df Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 13:41:21 +0300 Subject: [PATCH 034/192] WebDAV, CalDAV: setup data for folders, calendars, properties bzr revid: p_christ@hol.gr-20101012104121-e5k6f4vg8rnktiqh --- addons/caldav/__openerp__.py | 14 +- addons/caldav/caldav_setup.xml | 88 ++++++ addons/crm_caldav/__openerp__.py | 28 +- addons/crm_caldav/crm_caldav_setup.xml | 287 ++++++++++++++++++ addons/document_webdav/__openerp__.py | 7 +- addons/document_webdav/webdav_demo.xml | 22 ++ addons/document_webdav/webdav_setup.xml | 99 ++++++ addons/project_caldav/__openerp__.py | 6 +- addons/project_caldav/project_caldav_data.xml | 8 +- .../project_caldav/project_caldav_setup.xml | 272 +++++++++++++++++ 10 files changed, 803 insertions(+), 28 deletions(-) create mode 100644 addons/caldav/caldav_setup.xml create mode 100644 addons/crm_caldav/crm_caldav_setup.xml create mode 100644 addons/document_webdav/webdav_demo.xml create mode 100644 addons/document_webdav/webdav_setup.xml create mode 100644 addons/project_caldav/project_caldav_setup.xml diff --git a/addons/caldav/__openerp__.py b/addons/caldav/__openerp__.py index cdb06a17a1f..40088cb0bac 100644 --- a/addons/caldav/__openerp__.py +++ b/addons/caldav/__openerp__.py @@ -21,12 +21,12 @@ { - "name" : "Share Calendar using Caldav", - "version" : "1.0", + "name" : "Share Calendar using CalDAV", + "version" : "1.1", "depends" : [ "base", "document_webdav", - ], + ], 'description': """ This module Contains basic functionality for caldav system like: - Webdav server that provides remote access to calendar @@ -34,7 +34,10 @@ - Customize calendar event and todo attribute with any of OpenERP model - Provides iCal Import/Export functionality - To access OpenERP Calendar using caldav to remote site use the URL like: + To access Calendars using CalDAV clients, point them to: + http://HOSTNAME:PORT/webdav/DATABASE_NAME/calendars/users/USERNAME/c + + To access OpenERP Calendar using WebCal to remote site use the URL like: http://HOSTNAME:PORT/webdav/DATABASE_NAME/Calendars/CALENDAR_NAME.ics Where, @@ -53,7 +56,8 @@ 'wizard/calendar_event_export_view.xml', 'wizard/calendar_event_import_view.xml', 'wizard/calendar_event_subscribe_view.xml', - 'caldav_view.xml' + 'caldav_view.xml', + 'caldav_setup.xml' ], "installable" : True, "active" : False, diff --git a/addons/caldav/caldav_setup.xml b/addons/caldav/caldav_setup.xml new file mode 100644 index 00000000000..c33edbc75f7 --- /dev/null +++ b/addons/caldav/caldav_setup.xml @@ -0,0 +1,88 @@ + + + + + + [] + + + + + + directory + + calendars + + + [] + + + + + directory + + resources + + + [] + + + + + + ressource + + + __uids__ + + + [] + + + + + + + ressource + + + users + + + + user_id + res_id + + + [] + + + + + + directory + + c + + + + [] + + + + + + directory + + groups + + + + urn:ietf:params:xml:ns:caldav + + calendar-home-set + ('href','DAV:','/%s/%s/calendars/users/%s/c' % ('webdav',dbname,username) ) + + + + diff --git a/addons/crm_caldav/__openerp__.py b/addons/crm_caldav/__openerp__.py index ce8f72a3e17..d0e04576b3b 100644 --- a/addons/crm_caldav/__openerp__.py +++ b/addons/crm_caldav/__openerp__.py @@ -22,24 +22,24 @@ { 'name': 'Extened Module to Add CalDav future on Meeting', - 'version': '1.0', + 'version': '1.1', 'category': 'Generic Modules/CRM & SRM', 'description': """ - New Futures in Meeting: - * Share meeting with other calendar clients like sunbird -""", - 'author': 'OpenERP SA', - 'website': 'http://www.openerp.com', - 'depends': ['caldav', 'crm' - ], - 'init_xml': [ - 'crm_caldav_data.xml', - ], + New Futures in Meeting: + * Share meeting with other calendar clients like sunbird +""", + 'author': 'OpenERP SA', + 'website': 'http://www.openerp.com', + 'depends': ['caldav', 'crm' ], + 'init_xml': [ + 'crm_caldav_data.xml', + 'crm_caldav_setup.xml', + ], 'update_xml': [], - 'demo_xml': [], - 'installable': True, - 'active': False, + 'demo_xml': [], + 'installable': True, + 'active': False, } # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/crm_caldav/crm_caldav_setup.xml b/addons/crm_caldav/crm_caldav_setup.xml new file mode 100644 index 00000000000..1a103f61811 --- /dev/null +++ b/addons/crm_caldav/crm_caldav_setup.xml @@ -0,0 +1,287 @@ + + + + + + + Meetings per user + #1E25FF + + vevent + Meetings + + + + + [] + attendee + + + + + + [] + valarm + + + + + + [('user_id','=', dctx_user_id)] + vevent + + + + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + {'tentative': 'draft', 'confirmed': 'open', 'cancelled': 'cancel'} + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + diff --git a/addons/document_webdav/__openerp__.py b/addons/document_webdav/__openerp__.py index 3a573dc5a2b..1c2e232e914 100644 --- a/addons/document_webdav/__openerp__.py +++ b/addons/document_webdav/__openerp__.py @@ -30,7 +30,7 @@ { "name" : "WebDAV server for Document Management", - "version" : "2.1", + "version" : "2.2", "author" : "OpenERP SA", "category" : "Generic Modules/Others", "website": "http://www.openerp.com", @@ -41,7 +41,10 @@ """, "depends" : ["base", "document"], "init_xml" : [], - "update_xml" : ['security/ir.model.access.csv', "webdav_view.xml", ], + "update_xml" : ['security/ir.model.access.csv', + 'webdav_view.xml', + 'webdav_setup.xml', + ], "demo_xml" : [], "active": False, "installable": True diff --git a/addons/document_webdav/webdav_demo.xml b/addons/document_webdav/webdav_demo.xml new file mode 100644 index 00000000000..45cd17ed288 --- /dev/null +++ b/addons/document_webdav/webdav_demo.xml @@ -0,0 +1,22 @@ + + + + + debug: + node_class + %(node_classname)s + + + + + + + diff --git a/addons/document_webdav/webdav_setup.xml b/addons/document_webdav/webdav_setup.xml new file mode 100644 index 00000000000..2bd41ddb69c --- /dev/null +++ b/addons/document_webdav/webdav_setup.xml @@ -0,0 +1,99 @@ + + + + + + + + [] + + + + + + directory + + principals + + + + [] + + + + + + + directory + + groups + + + + [] + + + + + + directory + + resources + + + [] + + + + + + + ressource + + + __uids__ + + + [] + + + + + + + + ressource + + + users + + + [] + + + + + + directory + + locations + + + DAV: + current-user-principal + ('href','DAV:','/%s/%s/principals/users/%s' % ('webdav',dbname, username ) ) + + + + DAV: + principal-URL + ('href','DAV:','/%s/%s/principals/users/%s' % ('webdav',dbname, username ) ) + + + + + diff --git a/addons/project_caldav/__openerp__.py b/addons/project_caldav/__openerp__.py index 1e78db79bf0..f298fc9e1d5 100644 --- a/addons/project_caldav/__openerp__.py +++ b/addons/project_caldav/__openerp__.py @@ -20,13 +20,13 @@ ############################################################################## { - "name": "Caldav task management", - "version": "1.0", + "name": "CalDAV for task management", + "version": "1.1", "author": "OpenERP SA", "category": "Generic Modules/Others", "description": """ Synchronize between Project task and Caldav Vtodo.""", "depends": ["project", "caldav", "base_calendar"], - "init_xml": ["project_caldav_data.xml"], + "init_xml": ["project_caldav_data.xml", 'project_caldav_setup.xml', ], "demo_xml": [], "update_xml": ["project_caldav_view.xml"], "active": False, diff --git a/addons/project_caldav/project_caldav_data.xml b/addons/project_caldav/project_caldav_data.xml index 0a4d36580fc..3030b28dba9 100644 --- a/addons/project_caldav/project_caldav_data.xml +++ b/addons/project_caldav/project_caldav_data.xml @@ -3,10 +3,10 @@ - Tasks - - vtodo - + Tasks + + vtodo + vtodo diff --git a/addons/project_caldav/project_caldav_setup.xml b/addons/project_caldav/project_caldav_setup.xml new file mode 100644 index 00000000000..96d9c0ad3b6 --- /dev/null +++ b/addons/project_caldav/project_caldav_setup.xml @@ -0,0 +1,272 @@ + + + + + + Tasks per user + #FFEF74 + + vtodo + Tasks + + + + + [] + attendee + + + + + [] + valarm + + + + + [('user_id','=', dctx_user_id)] + vtodo + + + + + + + + {'needs-action': 'draft', 'completed': 'done', 'in-process': 'open', 'cancelled': 'cancelled'} + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + {'1': '0', '2': '1', '3': '1','4': '1', '5': '2', '6': '3', '7': '3', '8': '3', '9': '4'} + field + + + + + + field + + + + + + field + + + + + + hours + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + + + field + + + + \ No newline at end of file From 305b48abe3195f3a7d93852637c79228885d01a2 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 14:20:30 +0300 Subject: [PATCH 035/192] Document, calendar: Patch code from trunk-xrg branch several fixes that hadn't been backported or applied correctly. bzr revid: p_christ@hol.gr-20101012112030-snnb4fjkya4ndqkm --- .../security/ir.model.access.csv | 2 +- addons/caldav/caldav_node.py | 2 +- addons/caldav/calendar.py | 4 +- addons/document/__openerp__.py | 2 +- addons/document/content_index.py | 2 +- addons/document/nodes.py | 23 ++++++--- .../document_ftp/ftpserver/abstracted_fs.py | 1 - .../document_ftp/test/document_ftp_test3.yml | 48 +++++++++---------- addons/document_webdav/webdav_server.py | 9 +++- 9 files changed, 55 insertions(+), 38 deletions(-) diff --git a/addons/base_calendar/security/ir.model.access.csv b/addons/base_calendar/security/ir.model.access.csv index 0ae628fb720..aa269e9ff18 100644 --- a/addons/base_calendar/security/ir.model.access.csv +++ b/addons/base_calendar/security/ir.model.access.csv @@ -1,5 +1,5 @@ "id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" -"access_calendar_attendee","calendar.attendee","model_calendar_attendee","base.group_user",1,1,1,0 +"access_calendar_attendee","calendar.attendee","model_calendar_attendee","base.group_user",1,1,1,1 "access_calendar_alarm","calendar.alarm","model_calendar_alarm","base.group_user",1,1,1,1 "access_res_alarm","res.alarm","model_res_alarm","base.group_user",1,1,1,1 "access_calendar_todo","calendar.todo","model_calendar_todo","base.group_user",1,1,1,1 diff --git a/addons/caldav/caldav_node.py b/addons/caldav/caldav_node.py index 2ddd5ffa49e..e1181726bbf 100644 --- a/addons/caldav/caldav_node.py +++ b/addons/caldav/caldav_node.py @@ -421,7 +421,7 @@ class res_node_calendar(nodes.node_class): self.calendar_id = hasattr(parent, 'calendar_id') and parent.calendar_id or False if res_obj: if not self.calendar_id: self.calendar_id = res_obj.id - pr = res_obj.perm_read()[0] + pr = res_obj.perm_read(context=context, details=False)[0] self.create_date = pr.get('create_date') self.write_date = pr.get('write_date') or pr.get('create_date') self.displayname = res_obj.name diff --git a/addons/caldav/calendar.py b/addons/caldav/calendar.py index ae9aa909642..9ca6b924e4d 100644 --- a/addons/caldav/calendar.py +++ b/addons/caldav/calendar.py @@ -335,8 +335,8 @@ class CalDAV(object): if cal_data.name.lower() in self.__attribute__: if cal_data.params.get('X-VOBJ-ORIGINAL-TZID'): self.ical_set('vtimezone', cal_data.params.get('X-VOBJ-ORIGINAL-TZID'), 'value') - date_utc = cal_data.value.astimezone(pytz.utc) - self.ical_set(cal_data.name.lower(), date_utc, 'value') + date_local = cal_data.value.astimezone(_server_tzinfo) + self.ical_set(cal_data.name.lower(), date_local, 'value') continue self.ical_set(cal_data.name.lower(), cal_data.value, 'value') vals = map_data(cr, uid, self, context=context) diff --git a/addons/document/__openerp__.py b/addons/document/__openerp__.py index 16bbcd41c83..5741a7223e0 100644 --- a/addons/document/__openerp__.py +++ b/addons/document/__openerp__.py @@ -22,7 +22,7 @@ { 'name': 'Integrated Document Management System', - 'version': '1.1', + 'version': '2.0', 'category': 'Generic Modules/Others', 'description': """This is a complete document management system: * User Authentication diff --git a/addons/document/content_index.py b/addons/document/content_index.py index e3b37914f8c..df2b12b5456 100644 --- a/addons/document/content_index.py +++ b/addons/document/content_index.py @@ -21,11 +21,11 @@ import logging import os import tempfile +from subprocess import Popen, PIPE class NhException(Exception): pass -from subprocess import Popen, PIPE class indexer(object): """ An indexer knows how to parse the content of some file. diff --git a/addons/document/nodes.py b/addons/document/nodes.py index a9b8b7167fc..67105e6bd05 100644 --- a/addons/document/nodes.py +++ b/addons/document/nodes.py @@ -66,6 +66,7 @@ class node_context(object): A context is a set of persistent data, which may influence the structure of the nodes. All other transient information during a data query should be passed down with function arguments. + """ cached_roots = {} node_file_class = None @@ -101,7 +102,8 @@ class node_context(object): def get_uri(self, cr, uri): """ Although this fn passes back to doc.dir, it is needed since - it is a potential caching point """ + it is a potential caching point. + """ (ndir, duri) = self._dirobj._locate_child(cr, self.uid, self.rootdir, uri, None, self) while duri: ndir = ndir.child(cr, duri[0]) @@ -495,7 +497,10 @@ class node_dir(node_database): self.write_date = dirr and (dirr.write_date or dirr.create_date) or False self.content_length = 0 self.unixperms = 040750 - self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody' + try: + self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody' + except Exception: + self.uuser = 'nobody' self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup') self.uidperms = dirr.get_dir_permissions() if dctx: @@ -692,7 +697,10 @@ class node_res_dir(node_class): self.write_date = dirr.write_date or dirr.create_date self.content_length = 0 self.unixperms = 040750 - self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody' + try: + self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody' + except Exception: + self.uuser = 'nobody' self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup') self.uidperms = dirr.get_dir_permissions() self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False @@ -1003,9 +1011,9 @@ class node_res_obj(node_class): uid = self.context.uid ctx = self.context.context.copy() ctx.update(self.dctx) - res_obj = dirobj.pool.get(self.context.context['res_model']) + res_obj = dirobj.pool.get(self.res_model) - object2 = res_obj.browse(cr, uid, self.context.context['res_id']) or False + object2 = res_obj.browse(cr, uid, self.res_id) or False obj = dirobj.browse(cr, uid, self.dir_id) if obj and (obj.type == 'ressource') and not object2: @@ -1079,7 +1087,10 @@ class node_file(node_class): elif not parent.check_perms('w'): self.uidperms = 4 - self.uuser = (fil.user_id and fil.user_id.login) or 'nobody' + try: + self.uuser = (fil.user_id and fil.user_id.login) or 'nobody' + except Exception: + self.uuser = 'nobody' self.ugroup = mkdosname(fil.company_id and fil.company_id.name, default='nogroup') # This only propagates the problem to get_data. Better diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index f69a0bbbaad..9c16e7dde55 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -363,7 +363,6 @@ class abstracted_fs(object): """Remove the specified directory.""" cr, node, rem = datacr assert node - cr = self.get_node_cr(node) node.rmcol(cr) cr.commit() diff --git a/addons/document_ftp/test/document_ftp_test3.yml b/addons/document_ftp/test/document_ftp_test3.yml index a3e9bd5cb40..a51f259f44e 100644 --- a/addons/document_ftp/test/document_ftp_test3.yml +++ b/addons/document_ftp/test/document_ftp_test3.yml @@ -5,10 +5,10 @@ from document_ftp import test_easyftp as te ftp = te.get_plain_ftp(timeout=1.0) - - I create a folder called '������������ ��������' in the server. + I create in the server a folder called 'Äïêéìáóôéêüò ÖÜêåëëïò' - !record {model: document.directory, id: dir_itests }: - name: '������������ ��������' + name: 'Äïêéìáóôéêüò ÖÜêåëëïò' parent_id: document.dir_root - And then I create another folder, under it, through FTP @@ -16,68 +16,68 @@ !python {model: ir.attachment}: | cr.commit() from document_ftp import test_easyftp as te - ftp = te.get_ftp_folder(cr, uid, self, 'Documents/������������ ��������') - ftp.mkd("�������� ��� ����") + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò') + ftp.mkd("ÖÜêåëëïò áðü êÜôù") - I check that this folder exists at the server - - !assert {model: document.directory, id: , search: "[('name','=','�������� ��� ����')]" }: + !assert {model: document.directory, id: , search: "[('name','=','ÖÜêåëëïò áðü êÜôù')]" }: - parent_id != False - - I login with FTP and check that '������������ ��������' is there + I login with FTP and check that 'Äïêéìáóôéêüò ÖÜêåëëïò' is there - !python {model: ir.attachment}: | from document_ftp import test_easyftp as te - ftp = te.get_ftp_folder(cr, uid, self, 'Documents/������������ ��������/�������� ��� ����') + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/ÖÜêåëëïò áðü êÜôù') - - I create a file named '������' into that folder + I create a file named 'ÄïêéìÞ' into that folder - !python {model: ir.attachment}: | from document_ftp import test_easyftp as te from cStringIO import StringIO - ftp = te.get_ftp_folder(cr, uid, self, 'Documents/������������ ��������/�������� ��� ����') - fdata = StringIO('������� �� utf-8') - ftp.storbinary('STOR ������.txt', fdata) + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/ÖÜêåëëïò áðü êÜôù') + fdata = StringIO('êåßìåíï ìå utf-8') + ftp.storbinary('STOR ÄïêéìÞ.txt', fdata) - - I remove the '������.txt' file + I remove the 'ÄïêéìÞ.txt' file - !python {model: ir.attachment}: | from document_ftp import test_easyftp as te from cStringIO import StringIO - ftp = te.get_ftp_folder(cr, uid, self, 'Documents/������������ ��������/�������� ��� ����') - ftp.delete('������.txt') + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/ÖÜêåëëïò áðü êÜôù') + ftp.delete('ÄïêéìÞ.txt') - - I rename '�������� ��� ����' into '�����' + I rename 'ÖÜêåëëïò áðü êÜôù' into 'Üëëïò' - !python {model: ir.attachment}: | from document_ftp import test_easyftp as te - ftp = te.get_ftp_folder(cr, uid, self, 'Documents/������������ ��������') - ftp.rename("�������� ��� ����", "�����") + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò') + ftp.rename("ÖÜêåëëïò áðü êÜôù", "Üëëïò") - - I place a file 'file �3' in '�����' + I place a file 'file Ö3' in 'Üëëïò' - !python {model: ir.attachment}: | from document_ftp import test_easyftp as te from cStringIO import StringIO - ftp = te.get_ftp_folder(cr, uid, self, 'Documents/������������ ��������/�����') - fdata = StringIO('�� ���� �������') - ftp.storbinary('STOR file �3.txt', fdata) + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/Üëëïò') + fdata = StringIO('êé Üëëï êåßìåíï') + ftp.storbinary('STOR file Ö3.txt', fdata) - I rename the file into file+range(1..200) (large filename) - !python {model: ir.attachment}: | from document_ftp import test_easyftp as te from cStringIO import StringIO - ftp = te.get_ftp_folder(cr, uid, self, 'Documents/������������ ��������/�����') + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/Üëëïò') vuvuzela = 'b'+''.join('z' * 200)+'!' - ftp.rename("file �3.txt", vuvuzela) + ftp.rename("file Ö3.txt", vuvuzela) - I delete the file with the large name - !python {model: ir.attachment}: | from document_ftp import test_easyftp as te from cStringIO import StringIO - ftp = te.get_ftp_folder(cr, uid, self, 'Documents/������������ ��������/�����') + ftp = te.get_ftp_folder(cr, uid, self, 'Documents/Äïêéìáóôéêüò ÖÜêåëëïò/Üëëïò') vuvuzela = 'b'+''.join('z' * 200)+'!' ftp.delete(vuvuzela) diff --git a/addons/document_webdav/webdav_server.py b/addons/document_webdav/webdav_server.py index 7e4bb69fa0a..548c9539b96 100644 --- a/addons/document_webdav/webdav_server.py +++ b/addons/document_webdav/webdav_server.py @@ -81,7 +81,14 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler): def setup(self): self.davpath = '/'+config.get_misc('webdav','vdir','webdav') - self.baseuri = "http://%s:%d/"% (self.server.server_name, self.server.server_port) + addr, port = self.server.server_name, self.server.server_port + server_proto = getattr(self.server,'proto', 'http').lower() + try: + addr, port = self.request.getsockname() + except Exception, e: + self.log_error("Cannot calculate own address:" , e) + # Too early here to use self.headers + self.baseuri = "%s://%s:%d/"% (server_proto, addr, port) self.IFACE_CLASS = openerp_dav_handler(self, self.verbose) def copymove(self, CLASS): From 60f2488febb54174ff0b8e2dfd51b5c1963efb41 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 14:27:33 +0300 Subject: [PATCH 036/192] Document, caldav: inhibit features not found on trunk server. bzr revid: p_christ@hol.gr-20101012112733-sqcnvobi5q8ztj3l --- addons/caldav/caldav_node.py | 5 ++- addons/caldav/orm_utils.py | 3 +- addons/document/dict_tools.py | 57 ++++++++++++++++++++++++++++++++ addons/document_webdav/dav_fs.py | 5 ++- addons/document_webdav/nodes.py | 6 +++- 5 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 addons/document/dict_tools.py diff --git a/addons/caldav/caldav_node.py b/addons/caldav/caldav_node.py index e1181726bbf..d91db94fc90 100644 --- a/addons/caldav/caldav_node.py +++ b/addons/caldav/caldav_node.py @@ -26,7 +26,10 @@ import logging import StringIO from orm_utils import get_last_modified -from tools.dict_tools import dict_merge, dict_merge2 +try: + from tools.dict_tools import dict_merge, dict_merge2 +except ImportError: + from document.dict_tools import dict_merge, dict_merge2 # TODO: implement DAV-aware errors, inherit from IOError diff --git a/addons/caldav/orm_utils.py b/addons/caldav/orm_utils.py index badacc855c9..ab0b4bbb8db 100644 --- a/addons/caldav/orm_utils.py +++ b/addons/caldav/orm_utils.py @@ -41,8 +41,7 @@ if True: # we need this indentation level ;) cr.execute('SELECT MAX(COALESCE("%s".write_date, "%s".create_date)) FROM ' % (self._table, self._table) + from_clause + where_str , - where_clause_params, - debug=self._debug) + where_clause_params) res = cr.fetchall() return res[0][0] diff --git a/addons/document/dict_tools.py b/addons/document/dict_tools.py new file mode 100644 index 00000000000..10d2fdc69f3 --- /dev/null +++ b/addons/document/dict_tools.py @@ -0,0 +1,57 @@ +# -*- encoding: utf-8 -*- +# Copyright P. Christeas 2008-2010 +# Copyright 2010 OpenERP SA. http://www.openerp.com +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +############################################################################### + + +def dict_merge(*dicts): + """ Return a dict with all values of dicts + """ + res = {} + for d in dicts: + res.update(d) + return res + +def dict_merge2(*dicts): + """ Return a dict with all values of dicts. + If some key appears twice and contains iterable objects, the values + are merged (instead of overwritten). + """ + res = {} + for d in dicts: + for k in d.keys(): + if k in res and isinstance(res[k], (list, tuple)): + res[k] = res[k] + d[k] + elif k in res and isinstance(res[k], dict): + res[k].update(d[k]) + else: + res[k] = d[k] + return res + +def dict_filter(srcdic, keys, res=None): + ''' Return a copy of srcdic that has only keys set. + If any of keys are missing from srcdic, the result won't have them, + either. + @param res If given, result will be updated there, instead of a new dict. + ''' + if res is None: + res = {} + for k in keys: + if k in srcdic: + res[k] = srcdic[k] + return res + +#eof diff --git a/addons/document_webdav/dav_fs.py b/addons/document_webdav/dav_fs.py index 7deb18f714c..5b5b1341679 100644 --- a/addons/document_webdav/dav_fs.py +++ b/addons/document_webdav/dav_fs.py @@ -37,7 +37,10 @@ import urllib from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree from cache import memoize from tools import misc -from tools.dict_tools import dict_merge2 +try: + from tools.dict_tools import dict_merge2 +except ImportError: + from document.dict_tools import dict_merge2 CACHE_SIZE=20000 diff --git a/addons/document_webdav/nodes.py b/addons/document_webdav/nodes.py index 67dc9fd2a4f..8bea54ab7d3 100644 --- a/addons/document_webdav/nodes.py +++ b/addons/document_webdav/nodes.py @@ -22,7 +22,11 @@ from document import nodes from tools.safe_eval import safe_eval as eval -from tools.dict_tools import dict_filter +try: + from tools.dict_tools import dict_filter +except ImportError: + from document.dict_tools import dict_filter + import urllib From 829a82a598f9cbf872a488484c6277ad86bf2422 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 14:44:36 +0300 Subject: [PATCH 037/192] document: fix for directory.get_full_path() The trunk ORM would call this function with dir_id=[id,] rather than the pg84's dir_id=id argument. bzr revid: p_christ@hol.gr-20101012114436-9xtfjm3oe1oj7d3f --- addons/document/document_directory.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/document/document_directory.py b/addons/document/document_directory.py index 2b3aad859c0..0c49bb53952 100644 --- a/addons/document/document_directory.py +++ b/addons/document/document_directory.py @@ -126,6 +126,10 @@ class document_directory(osv.osv): def get_full_path(self, cr, uid, dir_id, context=None): """ Return the full path to this directory, in a list, root first """ + if isinstance(dir_id, (tuple, list)): + assert len(dir_id) == 1 + dir_id = dir_id[0] + def _parent(dir_id, path): parent=self.browse(cr, uid, dir_id) if parent.parent_id and not parent.ressource_parent_type_id: From 8ce30bcea65ae55572506e2f8b895192fc944305 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 12 Oct 2010 14:55:34 +0300 Subject: [PATCH 038/192] doc webdav: fix against older websrv_lib that has no getsockname() bzr revid: p_christ@hol.gr-20101012115534-0un3ip9jpw8hia4g --- addons/document_webdav/webdav_server.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/document_webdav/webdav_server.py b/addons/document_webdav/webdav_server.py index 548c9539b96..299789bec11 100644 --- a/addons/document_webdav/webdav_server.py +++ b/addons/document_webdav/webdav_server.py @@ -84,9 +84,10 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler): addr, port = self.server.server_name, self.server.server_port server_proto = getattr(self.server,'proto', 'http').lower() try: - addr, port = self.request.getsockname() + if hasattr(self.request, 'getsockname'): + addr, port = self.request.getsockname() except Exception, e: - self.log_error("Cannot calculate own address:" , e) + self.log_error("Cannot calculate own address: %s" , e) # Too early here to use self.headers self.baseuri = "%s://%s:%d/"% (server_proto, addr, port) self.IFACE_CLASS = openerp_dav_handler(self, self.verbose) From 2e6d551372b260b301123ee6a387d07da1037d01 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 12 Oct 2010 17:26:02 +0200 Subject: [PATCH 039/192] [IMP] do not delete res.log items after reading them Just mark them as read bzr revid: xmo@openerp.com-20101012152602-0adn5ed1ztgch2xa --- bin/addons/base/res/res_log.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/bin/addons/base/res/res_log.py b/bin/addons/base/res/res_log.py index 8165d05acac..39a9bac9983 100644 --- a/bin/addons/base/res/res_log.py +++ b/bin/addons/base/res/res_log.py @@ -33,20 +33,26 @@ class res_log(osv.osv_memory): 'res_id': fields.integer('Object ID'), 'secondary': fields.boolean('Secondary Log', help='Do not display this log if it belongs to the same object the user is working on'), 'create_date': fields.datetime('Created Date', readonly=True), + 'read': field.boolean('Read status', help="If this log item has been read, get() should not send it to the client") } _defaults = { 'user_id': lambda self,cr,uid,ctx: uid, 'create_date': time.strftime('%Y-%m-%d %H:%M:%S'), - 'context': lambda *args: "{}" + 'context': lambda *args: "{}", + 'read': False } _order='create_date desc' # TODO: do not return secondary log if same object than in the model (but unlink it) - def get(self, cr, uid, context={}): - ids = self.search(cr, uid, [('user_id','=',uid)], context=context) - result = self.read(cr, uid, ids, ['name','res_model','res_id'], context=context) - self.unlink(cr, uid, ids, context=context) - return result + def get(self, cr, uid, context=None): + unread_log_ids = self.search(cr, uid, [('user_id','=',uid), + ('read', '=', False)], + context=context) + unread_logs = self.read(cr, uid, unread_log_ids, + ['name','res_model','res_id'], + context=context) + self.write(cr, uid, ids, {'read': True}, context=context) + return unread_logs def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): res = [] From d515172dd982402c35b7296d86ca9f4c9e5f31b4 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 12 Oct 2010 17:26:30 +0200 Subject: [PATCH 040/192] [IMP] res.log: no need to use lambda for a constant in trunk bzr revid: xmo@openerp.com-20101012152630-3sf9k6p3yc6e3s78 --- bin/addons/base/res/res_log.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/addons/base/res/res_log.py b/bin/addons/base/res/res_log.py index 39a9bac9983..1c0102c1c79 100644 --- a/bin/addons/base/res/res_log.py +++ b/bin/addons/base/res/res_log.py @@ -38,7 +38,7 @@ class res_log(osv.osv_memory): _defaults = { 'user_id': lambda self,cr,uid,ctx: uid, 'create_date': time.strftime('%Y-%m-%d %H:%M:%S'), - 'context': lambda *args: "{}", + 'context': "{}", 'read': False } _order='create_date desc' @@ -66,4 +66,3 @@ class res_log(osv.osv_memory): return tools.flatten(res) res_log() - From 040f2ce15b8d6c512116ae75cb7d8bad5e499b81 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 12 Oct 2010 18:01:30 +0200 Subject: [PATCH 041/192] [FIX] use correct default callable for a datetime to get the current datetime as of the object's creation Add staticmethods to fields.time, fields.date and fields.datetime so they can generate default values which they can actually consume bzr revid: xmo@openerp.com-20101012160130-jwgl7cok853fiyap --- bin/addons/base/res/res_log.py | 7 +++---- bin/osv/fields.py | 29 +++++++++++++++++++++++++++++ bin/tools/misc.py | 5 ++++- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/bin/addons/base/res/res_log.py b/bin/addons/base/res/res_log.py index 1c0102c1c79..8d5f501866a 100644 --- a/bin/addons/base/res/res_log.py +++ b/bin/addons/base/res/res_log.py @@ -33,11 +33,11 @@ class res_log(osv.osv_memory): 'res_id': fields.integer('Object ID'), 'secondary': fields.boolean('Secondary Log', help='Do not display this log if it belongs to the same object the user is working on'), 'create_date': fields.datetime('Created Date', readonly=True), - 'read': field.boolean('Read status', help="If this log item has been read, get() should not send it to the client") + 'read': fields.boolean('Read status', help="If this log item has been read, get() should not send it to the client") } _defaults = { 'user_id': lambda self,cr,uid,ctx: uid, - 'create_date': time.strftime('%Y-%m-%d %H:%M:%S'), + 'create_date': fields.datetime.now, 'context': "{}", 'read': False } @@ -51,7 +51,7 @@ class res_log(osv.osv_memory): unread_logs = self.read(cr, uid, unread_log_ids, ['name','res_model','res_id'], context=context) - self.write(cr, uid, ids, {'read': True}, context=context) + self.write(cr, uid, unread_log_ids, {'read': True}, context=context) return unread_logs def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): @@ -64,5 +64,4 @@ class res_log(osv.osv_memory): logs.update({log.res_model: res_dict}) res = map(lambda x: x.values(), logs.values()) return tools.flatten(res) - res_log() diff --git a/bin/osv/fields.py b/bin/osv/fields.py index 64432ba15d7..f4442761bb8 100644 --- a/bin/osv/fields.py +++ b/bin/osv/fields.py @@ -32,6 +32,7 @@ # size # from collections import defaultdict +import datetime as DT import string import netsvc import sys @@ -189,14 +190,42 @@ class float(_column): class date(_column): _type = 'date' + @staticmethod + def today(*args): + """ Returns the current date in a format fit for being a + default value to a ``date`` field. + This method should be provided as is to the _defaults dict, it + should not be called. + """ + return DT.date.today().strftime( + tools.DEFAULT_SERVER_DATE_FORMAT) class datetime(_column): _type = 'datetime' + @staticmethod + def now(*args): + """ Returns the current datetime in a format fit for being a + default value to a ``datetime`` field. + This method should be provided as is to the _defaults dict, it + should not be called. + """ + return DT.datetime.now().strftime( + tools.DEFAULT_SERVER_DATETIME_FORMAT) class time(_column): _type = 'time' + @staticmethod + def now( *args): + """ Returns the current time in a format fit for being a + default value to a ``time`` field. + + This method should be proivided as is to the _defaults dict, + it should not be called. + """ + return DT.datetime.now().strftime( + tools.DEFAULT_SERVER_TIME_FORMAT) class binary(_column): _type = 'binary' diff --git a/bin/tools/misc.py b/bin/tools/misc.py index b3b3d7d9baa..81707156244 100644 --- a/bin/tools/misc.py +++ b/bin/tools/misc.py @@ -1323,7 +1323,10 @@ def get_server_timezone(): DEFAULT_SERVER_DATE_FORMAT = "%Y-%m-%d" -DEFAULT_SERVER_DATETIME_FORMAT = DEFAULT_SERVER_DATE_FORMAT + " %H:%M:%S" +DEFAULT_SERVER_TIME_FORMAT = "%H:%M:%S" +DEFAULT_SERVER_DATETIME_FORMAT = "%s %s" % ( + DEFAULT_SERVER_DATE_FORMAT, + DEFAULT_SERVER_TIME_FORMAT) def server_to_local_timestamp(src_tstamp_str, src_format, dst_format, dst_tz_name, tz_offset=True, ignore_unparsable_time=True): From 727c9f171abec26a4824cfa742205cb23943fc88 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 12 Oct 2010 18:23:48 +0200 Subject: [PATCH 042/192] [IMP] labelling of read field on res.log bzr revid: xmo@openerp.com-20101012162348-94jbd5ufwgh1cm5c --- bin/addons/base/res/res_log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/addons/base/res/res_log.py b/bin/addons/base/res/res_log.py index 8d5f501866a..e475f2f6278 100644 --- a/bin/addons/base/res/res_log.py +++ b/bin/addons/base/res/res_log.py @@ -33,7 +33,7 @@ class res_log(osv.osv_memory): 'res_id': fields.integer('Object ID'), 'secondary': fields.boolean('Secondary Log', help='Do not display this log if it belongs to the same object the user is working on'), 'create_date': fields.datetime('Created Date', readonly=True), - 'read': fields.boolean('Read status', help="If this log item has been read, get() should not send it to the client") + 'read': fields.boolean('Read', help="If this log item has been read, get() should not send it to the client") } _defaults = { 'user_id': lambda self,cr,uid,ctx: uid, From 370f2a477d6439bc3ef3717cb3389c2352e12df4 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 12 Oct 2010 18:32:41 +0200 Subject: [PATCH 043/192] [IMP] add read status in the log views (and search) bzr revid: xmo@openerp.com-20101012163241-ly59hdn375nfg0pt --- bin/addons/base/res/res_log_view.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bin/addons/base/res/res_log_view.xml b/bin/addons/base/res/res_log_view.xml index 6dac6c044c6..aa85272cfac 100644 --- a/bin/addons/base/res/res_log_view.xml +++ b/bin/addons/base/res/res_log_view.xml @@ -13,6 +13,7 @@ + @@ -22,9 +23,10 @@ tree - + + @@ -34,8 +36,11 @@ form
+ + + From 9efc48eadfb896a70197f8963caa6034d9b4856d Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 12 Oct 2010 19:09:04 +0200 Subject: [PATCH 044/192] [IMP] store res_log in database instead of memory, add index bzr revid: xmo@openerp.com-20101012170904-o3woe7qpdie2pflv --- bin/addons/base/res/res_log.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/bin/addons/base/res/res_log.py b/bin/addons/base/res/res_log.py index e475f2f6278..39fa8ec8969 100644 --- a/bin/addons/base/res/res_log.py +++ b/bin/addons/base/res/res_log.py @@ -23,7 +23,7 @@ from osv import fields, osv import tools import time -class res_log(osv.osv_memory): +class res_log(osv.osv): _name = 'res.log' _columns = { 'name': fields.char('Message', size=128, help='The logging message.', required=True), @@ -37,12 +37,20 @@ class res_log(osv.osv_memory): } _defaults = { 'user_id': lambda self,cr,uid,ctx: uid, - 'create_date': fields.datetime.now, 'context': "{}", 'read': False } _order='create_date desc' + _index_name = 'res_log_uid_read' + def _auto_init(self, cr, context={}): + super(res_log, self)._auto_init(cr, context) + cr.execute('SELECT 1 FROM pg_indexes WHERE indexname=%s', + (self._index_name,)) + if not cr.fetchone(): + cr.execute('CREATE INDEX %s ON res_log (user_id, read)' % + self._index_name) + # TODO: do not return secondary log if same object than in the model (but unlink it) def get(self, cr, uid, context=None): unread_log_ids = self.search(cr, uid, [('user_id','=',uid), From 3af1a322d4a246a20df58bb9541b1671231e31ab Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 12 Oct 2010 20:20:42 +0200 Subject: [PATCH 045/192] [IMP] tools.translate: log at debug level rather than warning when a translation fails bzr revid: odo@openerp.com-20101012182042-d2wcjr2jru9k7j17 --- bin/tools/translate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/tools/translate.py b/bin/tools/translate.py index 64c30af5600..e2b4d64949e 100644 --- a/bin/tools/translate.py +++ b/bin/tools/translate.py @@ -170,8 +170,8 @@ class GettextAlias(object): cr.execute('SELECT value FROM ir_translation WHERE lang=%s AND type IN (%s, %s) AND src=%s', (lang, 'code','sql_constraint', source)) res_trans = cr.fetchone() res = res_trans and res_trans[0] or source - except: - logger.warn('translation went wrong for string %s', repr(source)) + except Exception: + logger.debug('translation went wrong for string %s', repr(source)) finally: if is_new_cr: cr.close() From 22919bb1de3c44af9a21476d941cb651eee0f2d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BEAU=20-=20http=3A//www=2Eakretion=2Ecom?= <> Date: Wed, 13 Oct 2010 00:52:59 +0530 Subject: [PATCH 046/192] [FIX]Speed improvement : Call of write() should go when there is vals bzr revid: jvo@tinyerp.com-20101012192259-w3jbfm9ocb2zkr5m --- bin/osv/orm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/osv/orm.py b/bin/osv/orm.py index bc2d1b03d86..7e3529f9b6f 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -3497,7 +3497,8 @@ class orm(orm_template): for val in updend: if self._inherit_fields[val][0] == table: v[val] = vals[val] - self.pool.get(table).write(cr, user, nids, v, context) + if v: + self.pool.get(table).write(cr, user, nids, v, context) self._validate(cr, user, ids, context) From 9a1dfb0ce86d62e8ec40909d191fcbd819e6ab7c Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of openerp <> Date: Wed, 13 Oct 2010 04:58:23 +0000 Subject: [PATCH 047/192] Launchpad automatic translations update. bzr revid: launchpad_translations_on_behalf_of_openerp-20101013045823-oz0caykqxg95ofxd --- bin/addons/base/i18n/bg.po | 27 ++++++++++---- bin/addons/base/i18n/ca.po | 27 ++++++++++---- bin/addons/base/i18n/de.po | 35 +++++++++++++----- bin/addons/base/i18n/el.po | 27 ++++++++++---- bin/addons/base/i18n/en_GB.po | 28 ++++++++++---- bin/addons/base/i18n/es.po | 27 ++++++++++---- bin/addons/base/i18n/fa.po | 27 ++++++++++---- bin/addons/base/i18n/fi.po | 43 ++++++++++++++++------ bin/addons/base/i18n/fr.po | 69 +++++++++++++++++++++++------------ bin/addons/base/i18n/it.po | 29 +++++++++++---- bin/addons/base/i18n/lv.po | 44 +++++++++++++++++----- bin/addons/base/i18n/mn.po | 26 +++++++++---- bin/addons/base/i18n/nl.po | 29 +++++++++++---- bin/addons/base/i18n/pl.po | 29 +++++++++++---- bin/addons/base/i18n/pt.po | 26 +++++++++---- bin/addons/base/i18n/pt_BR.po | 27 ++++++++++---- bin/addons/base/i18n/ru.po | 29 +++++++++++---- bin/addons/base/i18n/tr.po | 26 +++++++++---- bin/addons/base/i18n/zh_CN.po | 20 +++++++--- debian/po/fr.po | 16 ++++---- 20 files changed, 443 insertions(+), 168 deletions(-) diff --git a/bin/addons/base/i18n/bg.po b/bin/addons/base/i18n/bg.po index f81214368cc..779d1d960bc 100644 --- a/bin/addons/base/i18n/bg.po +++ b/bin/addons/base/i18n/bg.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-09-09 18:37+0000\n" +"PO-Revision-Date: 2010-10-12 08:03+0000\n" "Last-Translator: Boris \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-29 04:42+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:56+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -1405,8 +1405,14 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "Въведете поле/израз, който връща списък. Например, изберете поръчка за продажба в обект и ще може да имате цикъл върху редовете от поръчка за продажба. Израз = `object.order_line`." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"Въведете поле/израз, който връща списък. Например, изберете поръчка за " +"продажба в обект и ще може да имате цикъл върху редовете от поръчка за " +"продажба. Израз = `object.order_line`." #. module: base #: selection:ir.translation,type:0 @@ -3244,8 +3250,13 @@ msgstr "Групиране по" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" -msgstr "'%s' съдържа твърде много точки. XML-идентификаторите не трябва да съдържат точки! Те се използват за свързване към данни от други модули, например в module.reference_id" +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" +msgstr "" +"'%s' съдържа твърде много точки. XML-идентификаторите не трябва да съдържат " +"точки! Те се използват за свързване към данни от други модули, например в " +"module.reference_id" #. module: base #: selection:ir.ui.menu,icon:0 @@ -8165,7 +8176,9 @@ msgstr "" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" #~ msgid "Others Partners" diff --git a/bin/addons/base/i18n/ca.po b/bin/addons/base/i18n/ca.po index b0963e20e96..33b033fe95f 100644 --- a/bin/addons/base/i18n/ca.po +++ b/bin/addons/base/i18n/ca.po @@ -7,14 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-09-09 18:36+0000\n" +"PO-Revision-Date: 2010-10-12 07:54+0000\n" "Last-Translator: Jordi Esteve (www.zikzakmedia.com) " "\n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-29 04:42+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:56+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -1405,8 +1405,14 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "Introduïu el camp/expressió que tornarà la llista. Per ex. si seleccioneu la comanda de venda en Objecte podrà realitzar un bucle sobre les línies del comanda de venda. Expressió = `object.order_line`." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"Introduïu el camp/expressió que tornarà la llista. Per ex. si seleccioneu " +"la comanda de venda en Objecte podrà realitzar un bucle sobre les línies del " +"comanda de venda. Expressió = `object.order_line`." #. module: base #: selection:ir.translation,type:0 @@ -3243,8 +3249,13 @@ msgstr "Agrupa per" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" -msgstr "'%s' conté massa punts. ¡Els ids l'XML no haurien de contenir punts! Els punts es fan servir per referir-se a dades d'altres mòduls, per exemple mòdul.referència_id" +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" +msgstr "" +"'%s' conté massa punts. ¡Els ids l'XML no haurien de contenir punts! Els " +"punts es fan servir per referir-se a dades d'altres mòduls, per exemple " +"mòdul.referència_id" #. module: base #: selection:ir.ui.menu,icon:0 @@ -8161,7 +8172,9 @@ msgstr "" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" #~ msgid "Attached ID" diff --git a/bin/addons/base/i18n/de.po b/bin/addons/base/i18n/de.po index b6e59ea5a83..497b88f9d30 100644 --- a/bin/addons/base/i18n/de.po +++ b/bin/addons/base/i18n/de.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-10-11 17:20+0000\n" -"Last-Translator: Ferdinand @ ChriCar \n" +"PO-Revision-Date: 2010-10-12 08:02+0000\n" +"Last-Translator: Ferdinand-chricar \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-10-12 04:51+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:56+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -1409,8 +1409,14 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "Eingabe des Feldes/Ausdruckes, das die Liste erzeugt. zB Auswahl von sale order in Objekt und alle Averkaufsauftragszeilen werden ausgewählt.Ausdruck = `object.order_line`." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"Eingabe des Feldes/Ausdruckes, das die Liste erzeugt. zB Auswahl von sale " +"order in Objekt und alle Averkaufsauftragszeilen werden ausgewählt.Ausdruck " +"= `object.order_line`." #. module: base #: selection:ir.translation,type:0 @@ -3243,8 +3249,13 @@ msgstr "Gruppiere nach" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" -msgstr "'%s' beinhaltet zu viele Punkte. XML IDs sollen keine Punkte enthalten. Punkte werdfen verwendet um andere Module zu ferferenzieren wie zB. module.reference_id" +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" +msgstr "" +"'%s' beinhaltet zu viele Punkte. XML IDs sollen keine Punkte enthalten. " +"Punkte werdfen verwendet um andere Module zu ferferenzieren wie zB. " +"module.reference_id" #. module: base #: selection:ir.ui.menu,icon:0 @@ -4406,8 +4417,14 @@ msgstr "Objekt" #. module: base #: help:ir.actions.report.xml,attachment:0 -msgid "This is the filename of the attachment used to store the printing result. Keep empty to not save the printed reports. You can use a python expression with the object and time variables." -msgstr "Das ist der Dateiname des Anhanges unter dem Ausdrucke gespeichert werden. Leer lassen, wenn Ausdrucke nicht gespeichert wreden sollen. Die Verwendung von Python Ausdrücken sowie Objekt und Zeitvariablen ist möglich." +msgid "" +"This is the filename of the attachment used to store the printing result. " +"Keep empty to not save the printed reports. You can use a python expression " +"with the object and time variables." +msgstr "" +"Das ist der Dateiname des Anhanges unter dem Ausdrucke gespeichert werden. " +"Leer lassen, wenn Ausdrucke nicht gespeichert wreden sollen. Die Verwendung " +"von Python Ausdrücken sowie Objekt und Zeitvariablen ist möglich." #. module: base #: model:res.country,name:base.ng diff --git a/bin/addons/base/i18n/el.po b/bin/addons/base/i18n/el.po index cd8089d36d0..e5a7c3b2bc6 100644 --- a/bin/addons/base/i18n/el.po +++ b/bin/addons/base/i18n/el.po @@ -6,13 +6,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.0\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-09-09 18:06+0000\n" +"PO-Revision-Date: 2010-10-12 07:49+0000\n" "Last-Translator: Fabien (Open ERP) \n" "Language-Team: nls@hellug.gr \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-29 04:43+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:57+0000\n" "X-Generator: Launchpad (build Unknown)\n" "X-Poedit-Country: GREECE\n" "X-Poedit-Language: Greek\n" @@ -1405,8 +1405,14 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." #. module: base #: selection:ir.translation,type:0 @@ -3248,8 +3254,13 @@ msgstr "Ομαδοποίηση Ανά" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" -msgstr "'%s' περιέχει πολλές τελείες. Οι XML ταυτότητες (ids) δεν πρέπει να περιέχουν τελείες! Οι τελείες χρησιμοποιούνται σε αναφορές σε άλλες ενότητες όπως στο module.reference_id" +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" +msgstr "" +"'%s' περιέχει πολλές τελείες. Οι XML ταυτότητες (ids) δεν πρέπει να " +"περιέχουν τελείες! Οι τελείες χρησιμοποιούνται σε αναφορές σε άλλες ενότητες " +"όπως στο module.reference_id" #. module: base #: selection:ir.ui.menu,icon:0 @@ -8169,7 +8180,9 @@ msgstr "" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" #, python-format diff --git a/bin/addons/base/i18n/en_GB.po b/bin/addons/base/i18n/en_GB.po index 68d70c6c092..3281e740ae9 100644 --- a/bin/addons/base/i18n/en_GB.po +++ b/bin/addons/base/i18n/en_GB.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: openobject-server\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-09-29 08:03+0000\n" -"Last-Translator: OpenERP Administrators \n" +"PO-Revision-Date: 2010-10-12 07:42+0000\n" +"Last-Translator: Anup (OpenERP) \n" "Language-Team: English (United Kingdom) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-30 04:38+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:58+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -1402,8 +1402,14 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." #. module: base #: selection:ir.translation,type:0 @@ -3237,8 +3243,12 @@ msgstr "Group By" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" -msgstr "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" +msgstr "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" #. module: base #: selection:ir.ui.menu,icon:0 @@ -8146,5 +8156,7 @@ msgstr "" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" diff --git a/bin/addons/base/i18n/es.po b/bin/addons/base/i18n/es.po index 6e5c5d3ca68..3f33a004833 100644 --- a/bin/addons/base/i18n/es.po +++ b/bin/addons/base/i18n/es.po @@ -7,14 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-09-09 18:09+0000\n" +"PO-Revision-Date: 2010-10-12 07:44+0000\n" "Last-Translator: Jordi Esteve (www.zikzakmedia.com) " "\n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-29 04:46+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:57+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -1408,8 +1408,14 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "Introduzca el campo/expresión que devolverá la lista. Por ej. si seleccione el pedido de venta en Objeto podrá realizar un bucle sobre las líneas del pedido de venta. Expresión = `object.order_line`." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"Introduzca el campo/expresión que devolverá la lista. Por ej. si seleccione " +"el pedido de venta en Objeto podrá realizar un bucle sobre las líneas del " +"pedido de venta. Expresión = `object.order_line`." #. module: base #: selection:ir.translation,type:0 @@ -3247,8 +3253,13 @@ msgstr "Agrupar por" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" -msgstr "'%s' contiene demasiados puntos. ¡Los ids del XML no deberían contener puntos! Los puntos se usan para referirse a datos de otros módulos, por ejemplo módulo.referencia_id" +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" +msgstr "" +"'%s' contiene demasiados puntos. ¡Los ids del XML no deberían contener " +"puntos! Los puntos se usan para referirse a datos de otros módulos, por " +"ejemplo módulo.referencia_id" #. module: base #: selection:ir.ui.menu,icon:0 @@ -8168,7 +8179,9 @@ msgstr "" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" #~ msgid "Attached ID" diff --git a/bin/addons/base/i18n/fa.po b/bin/addons/base/i18n/fa.po index cc76d6182e3..e0e1eed02c6 100644 --- a/bin/addons/base/i18n/fa.po +++ b/bin/addons/base/i18n/fa.po @@ -3,13 +3,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.0\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-09-09 18:36+0000\n" +"PO-Revision-Date: 2010-10-12 07:54+0000\n" "Last-Translator: Sadegh Ismael Nattaj \n" "Language-Team: OpenERP Iran \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-29 04:44+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:57+0000\n" "X-Generator: Launchpad (build Unknown)\n" "X-Poedit-Country: IRAN, ISLAMIC REPUBLIC OF\n" "X-Poedit-Language: Persian\n" @@ -1402,8 +1402,14 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "فیلد/عبارتی که فهرست را باز خواهد گرداند وارد کنید. برای نمونه برگزیدن سفارش فروش در شی، بنابراین شما می‌توانید در ردیف‌های سفارشات فروش حلقه داشته باشید. عبارت = 'object.order_line'." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"فیلد/عبارتی که فهرست را باز خواهد گرداند وارد کنید. برای نمونه برگزیدن سفارش " +"فروش در شی، بنابراین شما می‌توانید در ردیف‌های سفارشات فروش حلقه داشته " +"باشید. عبارت = 'object.order_line'." #. module: base #: selection:ir.translation,type:0 @@ -3238,8 +3244,13 @@ msgstr "گروه‌بندی برمبنای" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" -msgstr "'%s' دارای تیک نقطه بیش از حد است. شناسه‌های XML نباید دارای نقطه باشنتد!آنها برای ارجاع به داده‌های پیمانه‌های دیگر آنگونه که در module.reference_id آمده، هستند." +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" +msgstr "" +"'%s' دارای تیک نقطه بیش از حد است. شناسه‌های XML نباید دارای نقطه " +"باشنتد!آنها برای ارجاع به داده‌های پیمانه‌های دیگر آنگونه که در " +"module.reference_id آمده، هستند." #. module: base #: selection:ir.ui.menu,icon:0 @@ -8146,7 +8157,9 @@ msgstr "" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" #~ msgid "Attached ID" diff --git a/bin/addons/base/i18n/fi.po b/bin/addons/base/i18n/fi.po index 165b6a929a6..e459976e809 100644 --- a/bin/addons/base/i18n/fi.po +++ b/bin/addons/base/i18n/fi.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-03-24 05:45+0000\n" -"Last-Translator: OpenERP Administrators \n" +"PO-Revision-Date: 2010-10-12 07:53+0000\n" +"Last-Translator: Anup (OpenERP) \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-29 04:43+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:56+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -1395,8 +1395,14 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "Syötä kenttä tai lauseke joka palauttaa listan. Esim. valitse myyntitilaus objektista ja voit luoda silmukan myyntitilauksen rivistä. Lauseke = `object.order_line`." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"Syötä kenttä tai lauseke joka palauttaa listan. Esim. valitse myyntitilaus " +"objektista ja voit luoda silmukan myyntitilauksen rivistä. Lauseke = " +"`object.order_line`." #. module: base #: selection:ir.translation,type:0 @@ -3232,8 +3238,13 @@ msgstr "Ryhmittele" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" -msgstr "'%s' sisältää liikaa pisteitä. XML ids eivät saa sisältää pisteitä ! Näitä käytetään viittaamaan toisten moodulien tietoihin kuten module.reference_id kerrotaan" +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" +msgstr "" +"'%s' sisältää liikaa pisteitä. XML ids eivät saa sisältää pisteitä ! Näitä " +"käytetään viittaamaan toisten moodulien tietoihin kuten module.reference_id " +"kerrotaan" #. module: base #: selection:ir.ui.menu,icon:0 @@ -7739,8 +7750,16 @@ msgstr "Logo - Käytä kokoa noin 450x150 pikseliä." #. module: base #: help:res.lang,grouping:0 -msgid "The Separator Format should be like [,n] where 0 < n :starting from Unit digit.-1 will end the separation. e.g. [3,2,-1] will represent 106500 to be 1,06,500;[1,2,-1] will represent it to be 106,50,0;[3] will represent it as 106,500. Provided ',' as the thousand separator in each case." -msgstr "Erottimen formaatin tulisi olla muotoa [,n] jossa n > 0 ja -1 lopettaa erottelun. Esimerkiksi luku 106500 (tuhaterottimena on käytetty pilkkua):[3,2,-1] esittää sen muodossa 1,06,500;[1,2,-1] esittää sen muodossa 106,50,0;[3] esittää sen muodossa 106,500." +msgid "" +"The Separator Format should be like [,n] where 0 < n :starting from Unit " +"digit.-1 will end the separation. e.g. [3,2,-1] will represent 106500 to be " +"1,06,500;[1,2,-1] will represent it to be 106,50,0;[3] will represent it as " +"106,500. Provided ',' as the thousand separator in each case." +msgstr "" +"Erottimen formaatin tulisi olla muotoa [,n] jossa n > 0 ja -1 lopettaa " +"erottelun. Esimerkiksi luku 106500 (tuhaterottimena on käytetty " +"pilkkua):[3,2,-1] esittää sen muodossa 1,06,500;[1,2,-1] esittää sen " +"muodossa 106,50,0;[3] esittää sen muodossa 106,500." #. module: base #: model:ir.actions.act_window,name:base.action_partner_customer_form_new @@ -8123,7 +8142,7 @@ msgstr "" #: code:addons/base/res/res_lang.py:0 #, python-format msgid "User Error" -msgstr "" +msgstr "Käyttäjä virhe" #. module: base #: code:addons/base/res/res_lang.py:0 @@ -8140,7 +8159,9 @@ msgstr "" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" #~ msgid "Attached Model" diff --git a/bin/addons/base/i18n/fr.po b/bin/addons/base/i18n/fr.po index cbc62e30b72..69beee59704 100644 --- a/bin/addons/base/i18n/fr.po +++ b/bin/addons/base/i18n/fr.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-09-29 08:01+0000\n" -"Last-Translator: OpenERP Administrators \n" +"PO-Revision-Date: 2010-10-12 07:54+0000\n" +"Last-Translator: Anup (OpenERP) \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-30 04:37+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:56+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -1408,8 +1408,14 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "Entrez le champ/expression qui retournera la liste. Exemple: sélectionnez la commande client dans l'objet, et vous pourrez boucler sur les lignes de commandes client. Expression = `object.order_line`." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"Entrez le champ/expression qui retournera la liste. Exemple: sélectionnez la " +"commande client dans l'objet, et vous pourrez boucler sur les lignes de " +"commandes client. Expression = `object.order_line`." #. module: base #: selection:ir.translation,type:0 @@ -3249,7 +3255,9 @@ msgstr "Grouper par" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" msgstr "" "\"%s\" contient trop de points. Les ids XML ne devraient pas contenir de " "points! Les points sont utilisés pour faire référence à des données d'autres " @@ -8065,75 +8073,77 @@ msgstr "Vous ne pouvez pas avoir deux utilsiateurs avec le même login !" #. module: base #: sql_constraint:ir.model.fields:0 msgid "Size of the field can never be less than 1 !" -msgstr "" +msgstr "La taille du champ ne doit jamais être inférieure à 1 !" #. module: base #: sql_constraint:ir.ui.view_sc:0 msgid "Shortcut for this menu already exists!" -msgstr "" +msgstr "Un raccourci pour ce menu existe déjà !" #. module: base #: sql_constraint:ir.model.data:0 msgid "" "You cannot have multiple records with the same id for the same module !" msgstr "" +"Vous ne pouvez pas avoir plusieurs enregistrements avec le même identifiant " +"pour le même module !" #. module: base #: sql_constraint:maintenance.contract:0 msgid "Your maintenance contract is already subscribed in the system !" -msgstr "" +msgstr "Votre contrat de maintenance est déjà enregistré dans le système !" #. module: base #: sql_constraint:ir.module.module:0 #: sql_constraint:ir.module.web:0 msgid "The name of the module must be unique !" -msgstr "" +msgstr "Le nom d'un module doit être unique !" #. module: base #: sql_constraint:ir.module.module:0 msgid "The certificate ID of the module must be unique !" -msgstr "" +msgstr "L'ID du certificat pour un module doit être unique !" #. module: base #: sql_constraint:res.partner.function:0 msgid "The Code of the Partner Function must be unique !" -msgstr "" +msgstr "Le Code de la Fonction du Partenaire doit être unique !" #. module: base #: sql_constraint:res.partner:0 msgid "The name of the Partner must be unique !" -msgstr "" +msgstr "Le nom du Partenaire doit être unique !" #. module: base #: sql_constraint:res.country:0 msgid "The name of the country must be unique !" -msgstr "" +msgstr "Le nom du pays doit être unique !" #. module: base #: sql_constraint:res.country:0 msgid "The code of the country must be unique !" -msgstr "" +msgstr "Le code du pays doit être unique !" #. module: base #: sql_constraint:res.lang:0 msgid "The name of the language must be unique !" -msgstr "" +msgstr "Le nom de la langue doit être unique !" #. module: base #: sql_constraint:res.lang:0 msgid "The code of the language must be unique !" -msgstr "" +msgstr "Le code de la langue doit être unique" #. module: base #: sql_constraint:res.groups:0 msgid "The name of the group must be unique !" -msgstr "" +msgstr "Le nom du groupe doit être unique !" #. module: base #: code:addons/osv/osv.py:0 #, python-format msgid "Constraint Error" -msgstr "" +msgstr "Erreur de contrainte" #. module: base #: code:addons/osv/osv.py:0 @@ -8144,6 +8154,10 @@ msgid "" "reference it\n" "- creation/update: a mandatory field is not correctly set" msgstr "" +"L'opération n'a pas pu être terminée, probablement à la suite d'une :\n" +"- suppression : vous avez essayer de supprimer un enregistrement auquel " +"d'autres enregistrements font référence\n" +"- création/modification : un champ requis n'a pas été correctement rempli" #. module: base #: code:addons/osv/osv.py:0 @@ -8153,36 +8167,45 @@ msgid "" "\n" "[object with reference: %s - %s]" msgstr "" +"\n" +"\n" +"[objet ayant pour référence : %s - %s]" #. module: base #: code:addons/osv/osv.py:0 #, python-format msgid "Integrity Error" -msgstr "" +msgstr "Erreur d'intégrité" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format msgid "User Error" -msgstr "" +msgstr "Erreur Utilisateur" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format msgid "Base Language 'en_US' can not be deleted !" -msgstr "" +msgstr "Langue de base 'en_US' ne peut pas être supprimée !" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format msgid "You cannot delete the language which is User's Preferred Language !" msgstr "" +"Vous ne pouvez pas supprimer une langue qui est utilisée par un des " +"utilisateurs !" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" +"Vous ne pouvez pas supprimer une langue qui est Active !\n" +"Veuillez désactiver cette langue." #~ msgid "Others Partners" #~ msgstr "Autres partenaires" diff --git a/bin/addons/base/i18n/it.po b/bin/addons/base/i18n/it.po index 473adcfb865..cf7e0535c12 100644 --- a/bin/addons/base/i18n/it.po +++ b/bin/addons/base/i18n/it.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-09-29 08:28+0000\n" -"Last-Translator: OpenERP Administrators \n" +"PO-Revision-Date: 2010-10-12 08:02+0000\n" +"Last-Translator: Anup (OpenERP) \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-30 04:37+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:57+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -1408,8 +1408,14 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "Inserire il campo/espressione che ritornerà la lista. Per esempio, selezionare l'ordine di vendita fra gli oggetti, è possibile evere un loop nelle righe dellìordine di vendita. Expression = `object.order_line`." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"Inserire il campo/espressione che ritornerà la lista. Per esempio, " +"selezionare l'ordine di vendita fra gli oggetti, è possibile evere un loop " +"nelle righe dellìordine di vendita. Expression = `object.order_line`." #. module: base #: selection:ir.translation,type:0 @@ -3246,8 +3252,13 @@ msgstr "Raggruppa per" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" -msgstr "'%s' contiene troppi punti. Gli IDS XML non devono contenere punti! Questi sono usati per riferisti ad altri modelli di dato, come in: module.reference_id" +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" +msgstr "" +"'%s' contiene troppi punti. Gli IDS XML non devono contenere punti! Questi " +"sono usati per riferisti ad altri modelli di dato, come in: " +"module.reference_id" #. module: base #: selection:ir.ui.menu,icon:0 @@ -8176,7 +8187,9 @@ msgstr "" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" #~ msgid "Attached ID" diff --git a/bin/addons/base/i18n/lv.po b/bin/addons/base/i18n/lv.po index c5aca228c86..02a43f834c3 100644 --- a/bin/addons/base/i18n/lv.po +++ b/bin/addons/base/i18n/lv.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: openobject-addons\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-05-19 12:29+0000\n" -"Last-Translator: Olivier Dony (OpenERP) \n" +"PO-Revision-Date: 2010-10-12 07:46+0000\n" +"Last-Translator: Anup (OpenERP) \n" "Language-Team: Latvian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-29 04:44+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:57+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -785,8 +785,15 @@ msgstr ">=" #. module: base #: view:res.lang:0 -msgid "%%W - Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0." -msgstr "%%W - gada nedēļas numurs kā decimāls skaitlis (pirmdiena kā pirmā nedēļas diena).Visas jauna gada dienas, kas iekrīt pirms pirmās pirmdienas tiek ieskaitītas 0 nedēļā." +msgid "" +"%W - Week number of the year (Monday as the first day of the week) as a " +"decimal number [00,53]. All days in a new year preceding the first Monday " +"are considered to be in week 0." +msgstr "" +"%W - gada nedēļas numurs kā decimāls skaitlis (pirmdiena kā pirmā nedēļas " +"diena).\r\n" +"Visas jauna gada dienas, kas iekrīt pirms pirmās pirmdienas tiek ieskaitītas " +"0 nedēļā." #. module: base #: field:res.partner.event,planned_cost:0 @@ -1389,8 +1396,14 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "Ievadiet lauku/izteiksmi, kas atspoguļos sarakstu. Piem., izvēloties tirdzn. pasūtījumu noteiktā objektā, var iegūt \"cilpu\" tirdzniecības saraksta rindai. Izteiksme = `object.order_line`." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"Ievadiet lauku/izteiksmi, kas atspoguļos sarakstu. Piem., izvēloties tirdzn. " +"pasūtījumu noteiktā objektā, var iegūt \"cilpu\" tirdzniecības saraksta " +"rindai. Izteiksme = `object.order_line`." #. module: base #: selection:ir.translation,type:0 @@ -3231,7 +3244,9 @@ msgstr "Grupēt pēc" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" msgstr "" #. module: base @@ -8134,7 +8149,9 @@ msgstr "" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" #, python-format @@ -8337,3 +8354,12 @@ msgstr "" #~ msgstr "" #~ "Padarīt noteikumus globālus, pretējā gadījumā tie jāpieliek lietotājam vai " #~ "grupai." + +#~ msgid "" +#~ "%%W - Week number of the year (Monday as the first day of the week) as a " +#~ "decimal number [00,53]. All days in a new year preceding the first Monday " +#~ "are considered to be in week 0." +#~ msgstr "" +#~ "%%W - gada nedēļas numurs kā decimāls skaitlis (pirmdiena kā pirmā nedēļas " +#~ "diena).Visas jauna gada dienas, kas iekrīt pirms pirmās pirmdienas tiek " +#~ "ieskaitītas 0 nedēļā." diff --git a/bin/addons/base/i18n/mn.po b/bin/addons/base/i18n/mn.po index 93b90ef3e82..8c30a28818f 100644 --- a/bin/addons/base/i18n/mn.po +++ b/bin/addons/base/i18n/mn.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: openobject-addons\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-09-29 08:13+0000\n" +"PO-Revision-Date: 2010-10-12 08:01+0000\n" "Last-Translator: ub121 \n" "Language-Team: Mongolian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-30 04:37+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:57+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -1399,8 +1399,14 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "Жагсаалт гаргах талбар эсвэл илэрхийлэл бичнэ. Жнь. Object дээр борлуулалтын захиалгыг сонговол борлуулалтын захиалгын мөрүүдээр гүйх боломжтой болно. Expression = `object.order_line`." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"Жагсаалт гаргах талбар эсвэл илэрхийлэл бичнэ. Жнь. Object дээр борлуулалтын " +"захиалгыг сонговол борлуулалтын захиалгын мөрүүдээр гүйх боломжтой болно. " +"Expression = `object.order_line`." #. module: base #: selection:ir.translation,type:0 @@ -3226,8 +3232,12 @@ msgstr "Бүлэглэх" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" -msgstr "'%s' нь хэт олон цэг агуулж байна. XML id цэг агуулах ёсгүй ! Бусад модульд байгаа өгөгдлийг module.reference_id байдлаар заахад цэгийг хэрэглэнэ" +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" +msgstr "" +"'%s' нь хэт олон цэг агуулж байна. XML id цэг агуулах ёсгүй ! Бусад модульд " +"байгаа өгөгдлийг module.reference_id байдлаар заахад цэгийг хэрэглэнэ" #. module: base #: selection:ir.ui.menu,icon:0 @@ -8103,7 +8113,9 @@ msgstr "" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" #, python-format diff --git a/bin/addons/base/i18n/nl.po b/bin/addons/base/i18n/nl.po index d364e1b388d..22856b19ad6 100644 --- a/bin/addons/base/i18n/nl.po +++ b/bin/addons/base/i18n/nl.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.0\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-10-08 06:06+0000\n" -"Last-Translator: OpenERP Administrators \n" +"PO-Revision-Date: 2010-10-12 08:02+0000\n" +"Last-Translator: Anup (OpenERP) \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-10-09 12:12+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:56+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -1408,8 +1408,14 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "Voer het veld/expressie in die de lijst teruggeeft. Bijv. kies verkooporder in object, en u kunt een lus hebben op verkoopopdrachtregel. Expressie = `object.order_line`." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"Voer het veld/expressie in die de lijst teruggeeft. Bijv. kies verkooporder " +"in object, en u kunt een lus hebben op verkoopopdrachtregel. Expressie = " +"`object.order_line`." #. module: base #: selection:ir.translation,type:0 @@ -3246,8 +3252,13 @@ msgstr "Groeperen op" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" -msgstr "'%s' bevat teveel punten. XML-ids behoren geen punten te bevatten ! Punten worden gebruikt om te verwijzen naar gegevens in andere modules, zoals in module.reference_id" +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" +msgstr "" +"'%s' bevat teveel punten. XML-ids behoren geen punten te bevatten ! Punten " +"worden gebruikt om te verwijzen naar gegevens in andere modules, zoals in " +"module.reference_id" #. module: base #: selection:ir.ui.menu,icon:0 @@ -8175,7 +8186,9 @@ msgstr "" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" #~ msgid "Main Company" diff --git a/bin/addons/base/i18n/pl.po b/bin/addons/base/i18n/pl.po index 2f772243af9..3f980010537 100644 --- a/bin/addons/base/i18n/pl.po +++ b/bin/addons/base/i18n/pl.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-09-29 08:34+0000\n" -"Last-Translator: OpenERP Administrators \n" +"PO-Revision-Date: 2010-10-12 07:53+0000\n" +"Last-Translator: Anup (OpenERP) \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-30 04:37+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:57+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -1404,8 +1404,14 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "Wprowadź pole/wyrażenie, które zwróci listę. Tzn. wybierz zamówienie sprzedaży w obiekcie, a będziesz mógł uzyskać pętlę na pozycjach zamówienia sprzedaży. Wyrażenie = `object.order_line`." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"Wprowadź pole/wyrażenie, które zwróci listę. Tzn. wybierz zamówienie " +"sprzedaży w obiekcie, a będziesz mógł uzyskać pętlę na pozycjach zamówienia " +"sprzedaży. Wyrażenie = `object.order_line`." #. module: base #: selection:ir.translation,type:0 @@ -3239,8 +3245,13 @@ msgstr "Pogrupuj wg" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" -msgstr "'%s' zawiera zbyt dużo kropek. XML ids nie powinno zawierać kropek ! Kropki są stosowane di budowania odnośników do innych modułów jak module.reference_id" +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" +msgstr "" +"'%s' zawiera zbyt dużo kropek. XML ids nie powinno zawierać kropek ! Kropki " +"są stosowane di budowania odnośników do innych modułów jak " +"module.reference_id" #. module: base #: selection:ir.ui.menu,icon:0 @@ -8157,7 +8168,9 @@ msgstr "" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" #, python-format diff --git a/bin/addons/base/i18n/pt.po b/bin/addons/base/i18n/pt.po index 0886f8cacdc..9dda7254c18 100644 --- a/bin/addons/base/i18n/pt.po +++ b/bin/addons/base/i18n/pt.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.0\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-09-09 18:37+0000\n" +"PO-Revision-Date: 2010-10-12 08:03+0000\n" "Last-Translator: Fabien (Open ERP) \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-29 04:45+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:57+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -1404,8 +1404,14 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "Preencha o campo/expressão que irá devolver a lista. Ex: Seleccione a ordem da venda no Objecto e terá acesso às linhas da ordem de venda. Expressão = `object.order_line`." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"Preencha o campo/expressão que irá devolver a lista. Ex: Seleccione a ordem " +"da venda no Objecto e terá acesso às linhas da ordem de venda. Expressão = " +"`object.order_line`." #. module: base #: selection:ir.translation,type:0 @@ -3238,8 +3244,12 @@ msgstr "Agrupar por" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" -msgstr "'%s' contem demasiados pontos. Ids XML não devem conter pontos! Pontos são usados para referir dados de outros módulos, como em module.reference_id" +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" +msgstr "" +"'%s' contem demasiados pontos. Ids XML não devem conter pontos! Pontos são " +"usados para referir dados de outros módulos, como em module.reference_id" #. module: base #: selection:ir.ui.menu,icon:0 @@ -8153,7 +8163,9 @@ msgstr "" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" #~ msgid "Attached ID" diff --git a/bin/addons/base/i18n/pt_BR.po b/bin/addons/base/i18n/pt_BR.po index ffd25625bfb..90c7aebf8cc 100644 --- a/bin/addons/base/i18n/pt_BR.po +++ b/bin/addons/base/i18n/pt_BR.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: pt_BR\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-09-29 08:12+0000\n" +"PO-Revision-Date: 2010-10-12 07:48+0000\n" "Last-Translator: Anup (OpenERP) \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-30 04:37+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:58+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -1404,8 +1404,14 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "Entre com campo / expressão que devolverá a lista. Por exemplo. Selecione o pedido de vendas no objeto, e voce terá as linhas dos itens. Expressão = `object.order_line`." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"Entre com campo / expressão que devolverá a lista. Por exemplo. Selecione o " +"pedido de vendas no objeto, e voce terá as linhas dos itens. Expressão = " +"`object.order_line`." #. module: base #: selection:ir.translation,type:0 @@ -3242,8 +3248,13 @@ msgstr "Agrupar Por" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" -msgstr "'%s' contem muitos pontos. Identificações XML não devem conter ponto final! Estes devem ser utilizados para referência a dados de outros módulos como em: module.reference_id" +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" +msgstr "" +"'%s' contem muitos pontos. Identificações XML não devem conter ponto final! " +"Estes devem ser utilizados para referência a dados de outros módulos como " +"em: module.reference_id" #. module: base #: selection:ir.ui.menu,icon:0 @@ -8154,7 +8165,9 @@ msgstr "" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" #~ msgid "Attached Model" diff --git a/bin/addons/base/i18n/ru.po b/bin/addons/base/i18n/ru.po index 3a10ca26aaa..b7a7d63c77d 100644 --- a/bin/addons/base/i18n/ru.po +++ b/bin/addons/base/i18n/ru.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-09-02 06:48+0000\n" -"Last-Translator: OpenERP Administrators \n" +"PO-Revision-Date: 2010-10-12 07:58+0000\n" +"Last-Translator: Anup (OpenERP) \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-29 04:45+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:57+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -1404,8 +1404,14 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "Введите поле/выражение, которое возвращает список. Например, выберите счёт в Object, и Вы сможете получить все позиции в счёте на оплату. Выражение = `object.order_line`." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"Введите поле/выражение, которое возвращает список. Например, выберите счёт в " +"Object, и Вы сможете получить все позиции в счёте на оплату. Выражение = " +"`object.order_line`." #. module: base #: selection:ir.translation,type:0 @@ -3243,7 +3249,9 @@ msgstr "Группировать по" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" msgstr "" "\"%s\" содержит слишком много точек. XML идентификаторы не должны содержать " "точки! Они используются для ссылки на данные в других модулях, например, " @@ -3789,6 +3797,11 @@ msgstr "" msgid "Inherited View" msgstr "Унаследованный вид" +#. module: base +#: model:ir.model,name:base.model_ir_translation +msgid "ir.translation" +msgstr "ir.translation" + #. module: base #: model:ir.actions.act_window,name:base.open_module_tree_install #: model:ir.ui.menu,name:base.menu_module_tree_install @@ -8131,7 +8144,9 @@ msgstr "" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" #~ msgid "Others Partners" diff --git a/bin/addons/base/i18n/tr.po b/bin/addons/base/i18n/tr.po index 3bd4bcc0d76..07a33dbf62c 100644 --- a/bin/addons/base/i18n/tr.po +++ b/bin/addons/base/i18n/tr.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-09-09 18:06+0000\n" +"PO-Revision-Date: 2010-10-12 07:49+0000\n" "Last-Translator: Fabien (Open ERP) \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-29 04:46+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:58+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -1402,8 +1402,13 @@ msgstr "" #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "Bu listeyi gönderecek alan/ifade. Örneğin, Object'de satış emrini seçin, ve satış emri satırında döngü yapabilirsiniz. Expression = `object.order_line`." +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"Bu listeyi gönderecek alan/ifade. Örneğin, Object'de satış emrini seçin, ve " +"satış emri satırında döngü yapabilirsiniz. Expression = `object.order_line`." #. module: base #: selection:ir.translation,type:0 @@ -3234,8 +3239,13 @@ msgstr "Gruplama Anahtarı" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" -msgstr "'%s' çok fazla nokta içermektedir. XML belirteçlerinde nokta bulunmaz ! Nokta diğer modüllerin verilerine atıfta bulunmak için kullanılır. Örneğin, module.reference_id ." +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" +msgstr "" +"'%s' çok fazla nokta içermektedir. XML belirteçlerinde nokta bulunmaz ! " +"Nokta diğer modüllerin verilerine atıfta bulunmak için kullanılır. Örneğin, " +"module.reference_id ." #. module: base #: selection:ir.ui.menu,icon:0 @@ -8141,7 +8151,9 @@ msgstr "" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" #, python-format diff --git a/bin/addons/base/i18n/zh_CN.po b/bin/addons/base/i18n/zh_CN.po index 038d1fb7c8a..da9fef6cc4e 100644 --- a/bin/addons/base/i18n/zh_CN.po +++ b/bin/addons/base/i18n/zh_CN.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2009-12-18 08:39+0000\n" -"PO-Revision-Date: 2010-09-09 18:09+0000\n" +"PO-Revision-Date: 2010-10-12 07:39+0000\n" "Last-Translator: Anup (OpenERP) \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-29 04:47+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:58+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. module: base @@ -1372,8 +1372,12 @@ msgstr "本向导将自动检测应用程序中的新术语以便您手工更新 #. module: base #: help:ir.actions.server,expression:0 -msgid "Enter the field/expression that will return the list. E.g. select the sale order in Object, and you can have loop on the sales order line. Expression = `object.order_line`." -msgstr "输入返回列表的字段/表达式。比如当选择的对象是销售订单,那么您可以在销售订单明细中使用循环,表达式:`object.order_line`。" +msgid "" +"Enter the field/expression that will return the list. E.g. select the sale " +"order in Object, and you can have loop on the sales order line. Expression = " +"`object.order_line`." +msgstr "" +"输入返回列表的字段/表达式。比如当选择的对象是销售订单,那么您可以在销售订单明细中使用循环,表达式:`object.order_line`。" #. module: base #: selection:ir.translation,type:0 @@ -3177,7 +3181,9 @@ msgstr "分组方式" #. module: base #: code:addons/base/ir/ir_model.py:0 #, python-format -msgid "'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id" +msgid "" +"'%s' contains too many dots. XML ids should not contain dots ! These are " +"used to refer to other modules data, as in module.reference_id" msgstr "'%s'包含太多点。XML 标识符不应该包括点!其作用是引用到其他模块数据,如module.reference_id" #. module: base @@ -8035,7 +8041,9 @@ msgstr "" #. module: base #: code:addons/base/res/res_lang.py:0 #, python-format -msgid "You cannot delete the language which is Active !\nPlease de-activate the language first." +msgid "" +"You cannot delete the language which is Active !\n" +"Please de-activate the language first." msgstr "" #~ msgid "Main Company" diff --git a/debian/po/fr.po b/debian/po/fr.po index 414a4999f6b..52dabb310be 100644 --- a/debian/po/fr.po +++ b/debian/po/fr.po @@ -7,20 +7,20 @@ msgstr "" "Project-Id-Version: 5.0.1-0-2\n" "Report-Msgid-Bugs-To: openerp-server@packages.debian.org\n" "POT-Creation-Date: 2009-08-24 22:41+0300\n" -"PO-Revision-Date: 2009-11-26 07:45+0000\n" -"Last-Translator: Steve Petruzzello \n" +"PO-Revision-Date: 2010-10-12 07:15+0000\n" +"Last-Translator: Quentin THEURET \n" "Language-Team: French \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2010-09-29 04:47+0000\n" +"X-Launchpad-Export-Date: 2010-10-13 04:58+0000\n" "X-Generator: Launchpad (build Unknown)\n" #. Type: string #. Description #: ../openerp-server.templates:1001 msgid "Dedicated system account for the Open ERP server:" -msgstr "Identifiant dédié pour le serveur Open ERP:" +msgstr "Compte dédié au serveur Open ERP :" #. Type: string #. Description @@ -30,12 +30,12 @@ msgid "" "the system's security is not compromised by running it with superuser " "privileges." msgstr "" -"Le serveur Open ERP doit être exécuté avec un identifiant spécifique, " -"différent du superutilisateur, afin de ne pas compromettre la sécurité du " -"système." +"Afin de ne pas compromettre la sécurité du système, les opérations du " +"serveur Open ERP doivent être exécutée avec un compte dédié et différent du " +"superutilisateur." #. Type: string #. Description #: ../openerp-server.templates:1001 msgid "Please choose that account's username." -msgstr "Veuillez choisir cet identifiant." +msgstr "Veuillez choisir l'identifiant de ce compte." From dcc2dff5213763b21a898d9f792f951c0be1ae66 Mon Sep 17 00:00:00 2001 From: "DBR (OpenERP)" Date: Wed, 13 Oct 2010 11:42:42 +0530 Subject: [PATCH 048/192] [MOD/IMP]: Usability Improvement, Change Icon of Project Filter/Button bzr revid: dbr@tinyerp.com-20101013061242-ojqo1ff1db368sab --- addons/project/project_view.xml | 2 +- addons/project/report/project_report_view.xml | 4 ++-- addons/project_issue/project_issue_view.xml | 2 +- addons/project_issue/report/project_issue_report_view.xml | 2 +- addons/project_long_term/project_long_term_view.xml | 6 +++--- addons/project_messages/project_messages_view.xml | 2 +- addons/project_scrum/project_scrum_view.xml | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index 6fd11bd245f..fe9aa5c4e43 100644 --- a/addons/project/project_view.xml +++ b/addons/project/project_view.xml @@ -403,7 +403,7 @@ - + diff --git a/addons/project/report/project_report_view.xml b/addons/project/report/project_report_view.xml index cb30a4b861c..b7c9cb20dba 100644 --- a/addons/project/report/project_report_view.xml +++ b/addons/project/report/project_report_view.xml @@ -89,7 +89,7 @@ domain="[('state','=','done')]"/> - @@ -119,7 +119,7 @@ - + diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml index 4623f968c05..3592afee3a8 100644 --- a/addons/project_issue/project_issue_view.xml +++ b/addons/project_issue/project_issue_view.xml @@ -236,7 +236,7 @@ - diff --git a/addons/project_issue/report/project_issue_report_view.xml b/addons/project_issue/report/project_issue_report_view.xml index 365f217dee4..bb9e665290e 100644 --- a/addons/project_issue/report/project_issue_report_view.xml +++ b/addons/project_issue/report/project_issue_report_view.xml @@ -121,7 +121,7 @@ domain="[]" context="{'group_by':'section_id'}" /> - + - + @@ -268,7 +268,7 @@ - + @@ -277,7 +277,7 @@ - + diff --git a/addons/project_messages/project_messages_view.xml b/addons/project_messages/project_messages_view.xml index e8a013cf2ae..ef9c72f7ce6 100644 --- a/addons/project_messages/project_messages_view.xml +++ b/addons/project_messages/project_messages_view.xml @@ -82,7 +82,7 @@ - + diff --git a/addons/project_scrum/project_scrum_view.xml b/addons/project_scrum/project_scrum_view.xml index 7ae07d9eea8..e022434bf58 100644 --- a/addons/project_scrum/project_scrum_view.xml +++ b/addons/project_scrum/project_scrum_view.xml @@ -167,7 +167,7 @@ - + @@ -331,7 +331,7 @@ - + From 0e53783ff20fc451d02a8c19958cbcce75ba2d74 Mon Sep 17 00:00:00 2001 From: "DBR (OpenERP)" Date: Wed, 13 Oct 2010 11:59:06 +0530 Subject: [PATCH 049/192] [MOD/IMP]: Usability Improvement, Change Icon of Analytic account Filter/Button bzr revid: dbr@tinyerp.com-20101013062906-lted0vxn51u52o8d --- addons/hr_expense/report/hr_expense_report_view.xml | 2 +- .../report/hr_timesheet_invoice_report_view.xml | 2 +- addons/hr_timesheet_sheet/report/hr_timesheet_report_view.xml | 2 +- addons/sale/report/sale_report_view.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/hr_expense/report/hr_expense_report_view.xml b/addons/hr_expense/report/hr_expense_report_view.xml index 3de4776b29f..ecb2ae37d9a 100644 --- a/addons/hr_expense/report/hr_expense_report_view.xml +++ b/addons/hr_expense/report/hr_expense_report_view.xml @@ -94,7 +94,7 @@ - + diff --git a/addons/hr_timesheet_invoice/report/hr_timesheet_invoice_report_view.xml b/addons/hr_timesheet_invoice/report/hr_timesheet_invoice_report_view.xml index 33b92d09241..2e83f863992 100644 --- a/addons/hr_timesheet_invoice/report/hr_timesheet_invoice_report_view.xml +++ b/addons/hr_timesheet_invoice/report/hr_timesheet_invoice_report_view.xml @@ -468,7 +468,7 @@ - + diff --git a/addons/hr_timesheet_sheet/report/hr_timesheet_report_view.xml b/addons/hr_timesheet_sheet/report/hr_timesheet_report_view.xml index 05cba7f1e49..82ee593910f 100644 --- a/addons/hr_timesheet_sheet/report/hr_timesheet_report_view.xml +++ b/addons/hr_timesheet_sheet/report/hr_timesheet_report_view.xml @@ -72,7 +72,7 @@ - + diff --git a/addons/sale/report/sale_report_view.xml b/addons/sale/report/sale_report_view.xml index 3d93d45cfc5..0835c849ebd 100644 --- a/addons/sale/report/sale_report_view.xml +++ b/addons/sale/report/sale_report_view.xml @@ -102,7 +102,7 @@ - + From fc7ac059851a109dbaede976bef1dfb72c2b55b6 Mon Sep 17 00:00:00 2001 From: "Vir (Open ERP)" Date: Wed, 13 Oct 2010 13:04:25 +0530 Subject: [PATCH 050/192] [REM] Removed not used security files bzr revid: vir@tinyerp.com-20101013073425-03z20ylsxwq1or3y --- addons/hr_attendance/__openerp__.py | 1 - addons/hr_attendance/security/hr_security.xml | 6 ------ addons/hr_contract/__openerp__.py | 1 - addons/hr_contract/security/hr_contract_security.xml | 6 ------ 4 files changed, 14 deletions(-) delete mode 100644 addons/hr_attendance/security/hr_security.xml delete mode 100644 addons/hr_contract/security/hr_contract_security.xml diff --git a/addons/hr_attendance/__openerp__.py b/addons/hr_attendance/__openerp__.py index 3714050cbdb..8a52d0e2577 100644 --- a/addons/hr_attendance/__openerp__.py +++ b/addons/hr_attendance/__openerp__.py @@ -32,7 +32,6 @@ 'author': 'OpenERP SA', 'depends': ['hr'], 'update_xml': [ - 'security/hr_security.xml', 'security/ir.model.access.csv', 'hr_attendance_view.xml', 'hr_attendance_report.xml', diff --git a/addons/hr_attendance/security/hr_security.xml b/addons/hr_attendance/security/hr_security.xml deleted file mode 100644 index 39f923a895b..00000000000 --- a/addons/hr_attendance/security/hr_security.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/addons/hr_contract/__openerp__.py b/addons/hr_contract/__openerp__.py index 2851a9aa86b..aea686e4279 100755 --- a/addons/hr_contract/__openerp__.py +++ b/addons/hr_contract/__openerp__.py @@ -36,7 +36,6 @@ 'depends': ['hr'], 'init_xml': ['hr_contract_data.xml'], 'update_xml': [ - 'security/hr_contract_security.xml', 'security/ir.model.access.csv', 'hr_contract_view.xml' ], diff --git a/addons/hr_contract/security/hr_contract_security.xml b/addons/hr_contract/security/hr_contract_security.xml deleted file mode 100644 index 39f923a895b..00000000000 --- a/addons/hr_contract/security/hr_contract_security.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - From cd5659e4a6053bfa39accdebacbbd3cb5cf7e436 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 13 Oct 2010 12:57:13 +0200 Subject: [PATCH 051/192] [IMP] netsvc: remove plain password from logs, safer for forwarding for diagnostics/debugging bzr revid: odo@openerp.com-20101013105713-y1m8baczvdjbab1c --- bin/netsvc.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bin/netsvc.py b/bin/netsvc.py index 54a8ac68b14..7cb7e73c9df 100644 --- a/bin/netsvc.py +++ b/bin/netsvc.py @@ -464,6 +464,14 @@ class OpenERPDispatcherException(Exception): self.exception = exception self.traceback = traceback +def replace_request_password(args): + # password is always 3rd argument in a request, we replace it in RPC logs + # so it's easier to forward logs for diagnostics/debugging purposes... + args = list(args) + if len(args) > 2: + args[2] = '*' + return args + class OpenERPDispatcher: def log(self, title, msg, channel=logging.DEBUG_RPC, depth=2): logger = logging.getLogger(title) @@ -476,7 +484,7 @@ class OpenERPDispatcher: logger = logging.getLogger('result') self.log('service', service_name) self.log('method', method) - self.log('params', params, depth=(logger.isEnabledFor(logging.DEBUG_RPC_ANSWER) and 3 or 1)) + self.log('params', replace_request_password(params), depth=(logger.isEnabledFor(logging.DEBUG_RPC_ANSWER) and 3 or 1)) auth = getattr(self, 'auth_provider', None) result = ExportService.getService(service_name).dispatch(method, auth, params) self.log('result', result, channel=logging.DEBUG_RPC_ANSWER, depth=(logger.isEnabledFor(logging.DEBUG_SQL) and 5 or 3)) From ce485ebb66b1683e5d491a599339a4dd183ed9c4 Mon Sep 17 00:00:00 2001 From: "ron@tinyerp.com" <> Date: Wed, 13 Oct 2010 16:59:57 +0530 Subject: [PATCH 052/192] [FIX] project_issue_sheet,ir.model.access.csv:Problem to install Module bzr revid: ron@tinyerp.com-20101013112957-pitx78yvk1zpfeu5 --- addons/hr_timesheet_sheet/security/ir.model.access.csv | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/hr_timesheet_sheet/security/ir.model.access.csv b/addons/hr_timesheet_sheet/security/ir.model.access.csv index c11a3ce7099..9d0159bbc1a 100644 --- a/addons/hr_timesheet_sheet/security/ir.model.access.csv +++ b/addons/hr_timesheet_sheet/security/ir.model.access.csv @@ -4,6 +4,5 @@ "access_hr_timesheet_sheet_sheet_day","hr_timesheet_sheet.sheet.day","model_hr_timesheet_sheet_sheet_day","base.group_hr_user",1,1,1,1 "access_hr_timesheet_sheet_sheet_account","hr_timesheet_sheet.sheet.account","model_hr_timesheet_sheet_sheet_account","base.group_hr_user",1,1,1,1 "access_hr_timesheet_report","hr.timesheet.report","model_hr_timesheet_report","base.group_hr_manager",1,1,1,1 -"access_timesheet_report_employee","timesheet.report.employee","model_timesheet_report","base.group_hr_user",1,1,1,1 "access_hr_analytic_timesheet_system_user","hr.analytic.timesheet.system.user","model_hr_analytic_timesheet","base.group_user",1,0,0,0 "access_hr_timesheet_sheet_sheet_day","hr.timesheet.sheet.sheet.day.user","model_hr_timesheet_sheet_sheet_day","base.group_user",1,0,0,0 From 2d426ad6cef57dcf62cccf333d6ff8e1f66f1f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BEAU=20-=20http=3A//www=2Eakretion=2Ecom?= <> Date: Wed, 13 Oct 2010 17:47:05 +0530 Subject: [PATCH 053/192] [FIX] Fields : correction on get() of O2M lp bug: https://launchpad.net/bugs/603708 fixed bzr revid: jvo@tinyerp.com-20101013121705-2dqetjqp9viavk0e --- bin/osv/fields.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/osv/fields.py b/bin/osv/fields.py index f4442761bb8..fddb9742fe1 100644 --- a/bin/osv/fields.py +++ b/bin/osv/fields.py @@ -457,7 +457,8 @@ class one2many(_column): ids2 = obj.pool.get(self._obj).search(cr, user, self._domain + [(self._fields_id, 'in', ids)], limit=self._limit, context=context) for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_write'): - res[r[self._fields_id]].append(r['id']) + if r[self._fields_id] in res: + res[r[self._fields_id]].append(r['id']) return res def set(self, cr, obj, id, field, values, user=None, context=None): From 92e84f4bf22b5a4039654199b300ee987ca97bf3 Mon Sep 17 00:00:00 2001 From: "olt@tinyerp.com" <> Date: Wed, 13 Oct 2010 15:21:49 +0200 Subject: [PATCH 054/192] [FIX] base: added access right for everybody for res.log object bzr revid: olt@tinyerp.com-20101013132149-je1q80yp5mpk7ld7 --- bin/addons/base/security/ir.model.access.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/addons/base/security/ir.model.access.csv b/bin/addons/base/security/ir.model.access.csv index a8144a44834..96576dd945d 100644 --- a/bin/addons/base/security/ir.model.access.csv +++ b/bin/addons/base/security/ir.model.access.csv @@ -122,4 +122,4 @@ "access_res_partner_address","res.partner.address","model_res_partner_address","group_system",1,1,1,1 "access_res_widget","res.widget","model_res_widget","group_erp_manager",1,1,1,1 "access_res_widget_user","res.widget.user","model_res_widget",,1,0,0,0 - +"access_res_log_all","res.log","model_res_log",,1,1,1,1 From b77cad685a012325d466338bb31a7ff089340dfa Mon Sep 17 00:00:00 2001 From: "AMP (OpenERP)" Date: Wed, 13 Oct 2010 18:53:17 +0530 Subject: [PATCH 055/192] [MOD/IMP] hr_holidays : Remove Leave Analysis report and changes in view file bzr revid: amp@tinyerp.com-20101013132317-do4ylbfso07qdc66 --- addons/hr_holidays/hr_holidays_view.xml | 2 +- .../report/available_holidays_view.xml | 4 +- .../hr_holidays/report/hr_holidays_report.py | 61 ----------- .../report/hr_holidays_report_view.xml | 102 ------------------ .../hr_holidays/security/ir.model.access.csv | 1 - 5 files changed, 3 insertions(+), 167 deletions(-) diff --git a/addons/hr_holidays/hr_holidays_view.xml b/addons/hr_holidays/hr_holidays_view.xml index 1f57b25d402..b334d340b98 100644 --- a/addons/hr_holidays/hr_holidays_view.xml +++ b/addons/hr_holidays/hr_holidays_view.xml @@ -137,7 +137,7 @@ - +