Merge branch11 into patch18

Branch patch11 had somewhere been lost in bzr hell.

Conflicts:
	account/account_move_line.py
	document_webdav/nodes.py
	document_webdav/webdav_server.py
	event_project/__init__.py
	users_ldap/users_ldap.py

bzr revid: p_christ@hol.gr-20101223155444-ym8r0g4208gm88j9
This commit is contained in:
P. Christeas 2010-12-23 17:54:44 +02:00
commit 98ab6360ed
15 changed files with 504 additions and 345 deletions

View File

@ -117,7 +117,7 @@ class res_partner_bank(osv.osv):
iban_country = self.browse(cr, uid, ids)[0].iban[:2] iban_country = self.browse(cr, uid, ids)[0].iban[:2]
if default_iban_check(iban_country): if default_iban_check(iban_country):
iban_example = iban_country in _ref_iban and _ref_iban[iban_country] + ' \nWhere A = Account number, B = National bank code, S = Branch code, C = account No, N = branch No, K = National check digits....' or '' iban_example = iban_country in _ref_iban and _ref_iban[iban_country] + ' \nWhere A = Account number, B = National bank code, S = Branch code, C = account No, N = branch No, K = National check digits....' or ''
return _('The IBAN does not seems to be correct. You should have entered something like this %s'), (iban_example) return _('The IBAN does not seem to be correct. You should have entered something like this %s'), (iban_example)
return _('The IBAN is invalid, It should begin with the country code'), () return _('The IBAN is invalid, It should begin with the country code'), ()
def name_get(self, cr, uid, ids, context=None): def name_get(self, cr, uid, ids, context=None):

View File

@ -165,6 +165,15 @@ class document_file(osv.osv):
return False return False
return True return True
def check(self, cr, uid, ids, mode, context=None, values=None):
"""Check access wrt. res_model, relax the rule of ir.attachment parent
With 'document' installed, everybody will have access to attachments of
any resources they can *read*.
"""
return super(document_file, self).check(cr, uid, ids, mode='read',
context=context, values=values)
def copy(self, cr, uid, id, default=None, context=None): def copy(self, cr, uid, id, default=None, context=None):
if not default: if not default:
default = {} default = {}

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################## ##############################################################################
# #
# OpenERP, Open Source Management Solution # OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
# #
@ -15,22 +15,24 @@
# GNU Affero General Public License for more details. # GNU Affero General Public License for more details.
# #
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
############################################################################## ##############################################################################
from osv import osv, fields from osv import osv, fields
class document_configuration(osv.osv_memory): class document_configuration(osv.osv_memory):
_name='document.configuration' _name='document.configuration'
_description = 'Auto Directory Configuration' _description = 'Auto Directory Configuration'
_inherit = 'res.config' _inherit = 'res.config'
_columns = { _columns = {
'sale_order' : fields.boolean('Sale Order', help="Auto directory configuration for Sale Orders and Quotation with report."), 'sale_order' : fields.boolean('Sale Order', help="Auto directory configuration for Sale Orders and Quotation with report."),
'product' : fields.boolean('Product', help="Auto directory configuration for Products."), 'product' : fields.boolean('Product', help="Auto directory configuration for Products."),
'project': fields.boolean('Project', help="Auto directory configuration for Projects."), 'project': fields.boolean('Project', help="Auto directory configuration for Projects."),
} }
def execute(self, cr, uid, ids, context=None): def execute(self, cr, uid, ids, context=None):
conf_id = ids and ids[0] or False conf_id = ids and ids[0] or False
@ -58,7 +60,7 @@ class document_configuration(osv.osv_memory):
quta_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id quta_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id
else: else:
quta_dir_id = data_pool.create(cr, uid, {'name': 'Sale Quotations'}) quta_dir_id = data_pool.create(cr, uid, {'name': 'Sale Quotations'})
dir_pool.write(cr, uid, [quta_dir_id], { dir_pool.write(cr, uid, [quta_dir_id], {
'type':'ressource', 'type':'ressource',
'ressource_type_id': mid[0], 'ressource_type_id': mid[0],
@ -86,7 +88,7 @@ class document_configuration(osv.osv_memory):
'include_name': 1, 'include_name': 1,
'directory_id': quta_dir_id, 'directory_id': quta_dir_id,
}) })
if conf.product and self.pool.get('product.product'): if conf.product and self.pool.get('product.product'):
# Product # Product
@ -95,12 +97,12 @@ class document_configuration(osv.osv_memory):
product_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id product_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id
else: else:
product_dir_id = data_pool.create(cr, uid, {'name': 'Products'}) product_dir_id = data_pool.create(cr, uid, {'name': 'Products'})
mid = model_pool.search(cr, uid, [('model','=','product.product')]) mid = model_pool.search(cr, uid, [('model','=','product.product')])
dir_pool.write(cr, uid, [product_dir_id], { dir_pool.write(cr, uid, [product_dir_id], {
'type':'ressource', 'type':'ressource',
'ressource_type_id': mid[0], 'ressource_type_id': mid[0],
}) })
if conf.project and self.pool.get('account.analytic.account'): if conf.project and self.pool.get('account.analytic.account'):
# Project # Project
@ -109,12 +111,12 @@ class document_configuration(osv.osv_memory):
project_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id project_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id
else: else:
project_dir_id = data_pool.create(cr, uid, {'name': 'Projects'}) project_dir_id = data_pool.create(cr, uid, {'name': 'Projects'})
mid = model_pool.search(cr, uid, [('model','=','account.analytic.account')]) mid = model_pool.search(cr, uid, [('model','=','account.analytic.account')])
dir_pool.write(cr, uid, [project_dir_id], { dir_pool.write(cr, uid, [project_dir_id], {
'type':'ressource', 'type':'ressource',
'ressource_type_id': mid[0], 'ressource_type_id': mid[0],
'domain': '[]', 'domain': '[]',
'ressource_tree': 1 'ressource_tree': 1
}) })
document_configuration() document_configuration()

View File

@ -45,6 +45,8 @@
<record model="ir.actions.todo" id="config_auto_directory"> <record model="ir.actions.todo" id="config_auto_directory">
<field name="action_id" ref="action_config_auto_directory"/> <field name="action_id" ref="action_config_auto_directory"/>
<field name="groups_id" eval="[(6,0,[ref('base.group_extended')])]"/> <field name="groups_id" eval="[(6,0,[ref('base.group_extended')])]"/>
<field name="state" eval="'skip'" />
<field name="restart" eval="'onskip'" />
</record> </record>
</data> </data>
</openerp> </openerp>

View File

@ -43,9 +43,11 @@ class document_ftp_browse(osv.osv_memory):
url = ftp_url.url and ftp_url.url.split('ftp://') or [] url = ftp_url.url and ftp_url.url.split('ftp://') or []
if url: if url:
url = url[1] url = url[1]
if url[-1] == '/':
url = url[:-1]
else: else:
url = '%s:%s' %(ftpserver.HOST, ftpserver.PORT) url = '%s:%s' %(ftpserver.HOST, ftpserver.PORT)
res['url'] = 'ftp://%s@%s'%(current_user.login, url) res['url'] = 'ftp://%s@%s/%s'%(current_user.login, url, cr.dbname)
return res return res
def browse_ftp(self, cr, uid, ids, context=None): def browse_ftp(self, cr, uid, ids, context=None):

View File

@ -30,7 +30,7 @@
{ {
"name" : "WebDAV server for Document Management", "name" : "WebDAV server for Document Management",
"version" : "2.2", "version" : "2.3",
"author" : "OpenERP SA", "author" : "OpenERP SA",
"category" : "Generic Modules/Others", "category" : "Generic Modules/Others",
"website": "http://www.openerp.com", "website": "http://www.openerp.com",
@ -49,6 +49,9 @@
; since the messages are routed to the python logging, with ; since the messages are routed to the python logging, with
; levels "debug" and "debug_rpc" respectively, you can leave ; levels "debug" and "debug_rpc" respectively, you can leave
; these options on ; these options on
Also implements IETF RFC 5785 for services discovery on a http server,
which needs explicit configuration in openerp-server.conf, too.
""", """,
"depends" : ["base", "document"], "depends" : ["base", "document"],
"init_xml" : [], "init_xml" : [],

View File

@ -686,7 +686,7 @@ class openerp_dav_handler(dav_interface):
except Exception: except Exception:
node = False node = False
objname = uri2[-1] objname = misc.ustr(uri2[-1])
ret = None ret = None
if not node: if not node:
@ -719,7 +719,7 @@ class openerp_dav_handler(dav_interface):
etag = str(newchild.get_etag(cr)) etag = str(newchild.get_etag(cr))
except Exception, e: except Exception, e:
self.parent.log_error("Cannot get etag for node: %s" % e) self.parent.log_error("Cannot get etag for node: %s" % e)
ret = (hurl, etag) ret = (str(hurl), etag)
else: else:
self._try_function(node.set_data, (cr, data), "save %s" % objname, cr=cr) self._try_function(node.set_data, (cr, data), "save %s" % objname, cr=cr)

View File

@ -0,0 +1,29 @@
=================
Well-known URIs
=================
In accordance to IETF RFC 5785 [1], we shall publish a few locations
on the root of our http server, so that clients can discover our
services (CalDAV, eg.).
This module merely installs a special http request handler, that will
redirect the URIs from "http://our-server:port/.well-known/xxx' to
the correct path for each xxx service.
Note that well-known URIs cannot have a database-specific behaviour,
they are server-wide. So, we have to explicitly chose one of our databases
to serve at them. By default, the database of the configuration file
is chosen
Example config:
[http-well-known]
num_services = 2
db_name = openerp-main ; must define that for path_1 below
service_1 = caldav
path_1 = /webdav/%(db_name)s/Calendars/
service_2 = foo
path_2 = /other_db/static/Foo.html
[1] http://www.rfc-editor.org/rfc/rfc5785.txt

View File

@ -299,7 +299,7 @@ class node_file(node_acl_mixin, nodes.node_file):
return '' return ''
def get_dav_props(self, cr): def get_dav_props(self, cr):
return self._get_dav_props_hlpr(cr, nodes.node_dir, return self._get_dav_props_hlpr(cr, nodes.node_file,
'document.webdav.file.property', 'file_id', self.file_id) 'document.webdav.file.property', 'file_id', self.file_id)
def dav_lock(self, cr, lock_data): def dav_lock(self, cr, lock_data):
@ -345,11 +345,11 @@ class node_database(nodes.node_database):
return ('collection', 'DAV:') return ('collection', 'DAV:')
def get_dav_props(self, cr): def get_dav_props(self, cr):
return self._get_dav_props_hlpr(cr, nodes.node_dir, return self._get_dav_props_hlpr(cr, nodes.node_database,
'document.webdav.dir.property', 'dir_id', False) 'document.webdav.dir.property', 'dir_id', False)
def get_dav_eprop(self, cr, ns, prop): def get_dav_eprop(self, cr, ns, prop):
return self._get_dav_eprop_hlpr(cr, nodes.node_dir, ns, prop, return self._get_dav_eprop_hlpr(cr, nodes.node_database, ns, prop,
'document.webdav.dir.property', 'dir_id', False) 'document.webdav.dir.property', 'dir_id', False)
class node_res_obj(node_acl_mixin, nodes.node_res_obj): class node_res_obj(node_acl_mixin, nodes.node_res_obj):

View File

@ -0,0 +1,9 @@
<html>
<head>
<title>OpenERP server</title>
</head>
<body>
This is an OpenERP server. Nothing to GET here.
</body>
</html>

View File

@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2010 OpenERP s.a. (<http://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 <http://www.gnu.org/licenses/>.
#
##############################################################################
import logging
import urlparse
from service.websrv_lib import FixSendError, HTTPHandler, HttpOptions
from service.http_server import HttpLogHandler
class RedirectHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler):
_logger = logging.getLogger('httpd.well-known')
_HTTP_OPTIONS = { 'Allow': ['OPTIONS', 'GET', 'HEAD', 'PROPFIND'] }
redirect_paths = {}
def __init__(self,request, client_address, server):
HTTPHandler.__init__(self,request,client_address,server)
def send_head(self):
"""Common code for GET and HEAD commands.
It will either send the correct redirect (Location) response
or a 404.
"""
if self.path.endswith('/'):
self.path = self.path[:-1]
if not self.path:
# Return an empty page
self.send_response(200)
self.send_header("Content-Length", 0)
self.end_headers()
return None
redir_path = self._find_redirect()
if redir_path is False:
self.send_error(404, "File not found")
return None
elif redir_path is None:
return None
server_proto = getattr(self.server, 'proto', 'http').lower()
addr, port = self.server.server_name, self.server.server_port
try:
addr, port = self.request.getsockname()
except Exception, e:
self.log_error("Cannot calculate own address:" , e)
if self.headers.has_key('Host'):
uparts = list(urlparse.urlparse("%s://%s:%d"% (server_proto, addr,port)))
uparts[1] = self.headers['Host']
baseuri = urlparse.urlunparse(uparts)
else:
baseuri = "%s://%s:%d"% (server_proto, addr, port )
location = baseuri + redir_path
# relative uri: location = self.redirect_paths[self.path]
self.send_response(301)
self.send_header("Location", location)
self.send_header("Content-Length", 0)
self.end_headers()
# Do we need a Cache-content: header here?
self._logger.debug("redirecting %s to %s", self.path, redir_path)
return None
def do_PROPFIND(self):
self._get_ignore_body()
return self.do_HEAD()
def _find_redirect(self):
""" Locate self.path among the redirects we can handle
@return The new path, False for 404 or None for already sent error
"""
return self.redirect_paths.get(self.path, False)
def _get_ignore_body(self):
if not self.headers.has_key("content-length"):
return
max_chunk_size = 10*1024*1024
size_remaining = int(self.headers["content-length"])
got = ''
while size_remaining:
chunk_size = min(size_remaining, max_chunk_size)
got = self.rfile.read(chunk_size)
size_remaining -= len(got)
#eof

View File

@ -45,13 +45,103 @@ class Text2(xml.dom.minidom.Text):
data = data.replace(">", "&gt;") data = data.replace(">", "&gt;")
writer.write(data) writer.write(data)
def createText2Node(doc, data): class Prop2xml(object):
if not isinstance(data, StringTypes): """ A helper class to convert property structs to DAV:XML
raise TypeError, "node contents must be a string"
t = Text2() Written to generalize the use of _prop_child(), a class is
t.data = data needed to hold some persistent data accross the recursions
t.ownerDocument = doc of _prop_elem_child().
return t """
def __init__(self, doc, namespaces, nsnum):
""" Init the structure
@param doc the xml doc element
@param namespaces a dict of namespaces
@param nsnum the next namespace number to define
"""
self.doc = doc
self.namespaces = namespaces
self.nsnum = nsnum
def createText2Node(self, data):
if not isinstance(data, StringTypes):
raise TypeError, "node contents must be a string"
t = Text2()
t.data = data
t.ownerDocument = self.doc
return t
def _prop_child(self, xnode, ns, prop, value):
"""Append a property xml node to xnode, with <prop>value</prop>
And a little smarter than that, it will consider namespace and
also allow nested properties etc.
:param ns the namespace of the <prop/> node
:param prop the name of the property
:param value the value. Can be:
string: text node
tuple ('elem', 'ns') for empty sub-node <ns:elem />
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:':
ns_prefix = 'D:'
else:
ns_prefix="ns"+str(self.namespaces.index(ns))+":"
pe = self.doc.createElement(ns_prefix+str(prop))
if hasattr(value, '__class__') and value.__class__.__name__ == 'Element':
pe.appendChild(value)
else:
if ns == 'DAV:' and prop=="resourcetype" and isinstance(value, int):
# hack, to go..
if value == 1:
ve = self.doc.createElement("D:collection")
pe.appendChild(ve)
else:
self._prop_elem_child(pe, ns, value, ns_prefix)
xnode.appendChild(pe)
def _prop_elem_child(self, pnode, pns, v, pns_prefix):
if isinstance(v, list):
for vit in v:
self._prop_elem_child(pnode, pns, vit, pns_prefix)
elif isinstance(v,tuple):
need_ns = False
if v[1] == pns:
ns_prefix = pns_prefix
elif v[1] == 'DAV:':
ns_prefix = 'D:'
elif v[1] in self.namespaces:
ns_prefix="ns"+str(self.namespaces.index(v[1]))+":"
else:
ns_prefix="ns"+str(self.nsnum)+":"
need_ns = True
ve = self.doc.createElement(ns_prefix+v[0])
if need_ns:
ve.setAttribute("xmlns:ns"+str(self.nsnum), v[1])
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'), ...]
self._prop_elem_child(ve, v[1], v[2], ns_prefix)
else:
vt = self.createText2Node(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 = self.createText2Node(tools.ustr(v))
pnode.appendChild(ve)
super_mk_prop_response = PROPFIND.mk_prop_response super_mk_prop_response = PROPFIND.mk_prop_response
@ -73,78 +163,7 @@ def mk_prop_response(self, uri, good_props, bad_props, doc):
re.setAttribute("xmlns:ns"+str(nsnum),nsname) re.setAttribute("xmlns:ns"+str(nsnum),nsname)
nsnum=nsnum+1 nsnum=nsnum+1
def _prop_child(xnode, ns, prop, value): propgen = Prop2xml(doc, namespaces, nsnum)
"""Append a property xml node to xnode, with <prop>value</prop>
And a little smarter than that, it will consider namespace and
also allow nested properties etc.
:param ns the namespace of the <prop/> node
:param prop the name of the property
:param value the value. Can be:
string: text node
tuple ('elem', 'ns') for empty sub-node <ns:elem />
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:':
ns_prefix = 'D:'
else:
ns_prefix="ns"+str(namespaces.index(ns))+":"
pe=doc.createElement(ns_prefix+str(prop))
if hasattr(value, '__class__') and value.__class__.__name__ == 'Element':
pe.appendChild(value)
else:
if ns == 'DAV:' and prop=="resourcetype" and isinstance(value, int):
# hack, to go..
if value == 1:
ve=doc.createElement("D:collection")
pe.appendChild(ve)
else:
_prop_elem_child(pe, ns, value, ns_prefix)
xnode.appendChild(pe)
def _prop_elem_child(pnode, pns, v, pns_prefix):
if isinstance(v, list):
for vit in v:
_prop_elem_child(pnode, pns, vit, pns_prefix)
elif isinstance(v,tuple):
need_ns = False
if v[1] == pns:
ns_prefix = pns_prefix
elif v[1] == 'DAV:':
ns_prefix = 'D:'
elif v[1] in namespaces:
ns_prefix="ns"+str(namespaces.index(v[1]))+":"
else:
ns_prefix="ns"+str(nsnum)+":"
need_ns = True
ve=doc.createElement(ns_prefix+v[0])
if need_ns:
ve.setAttribute("xmlns:ns"+str(nsnum), v[1])
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=createText2Node(doc,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=createText2Node(doc, tools.ustr(v))
pnode.appendChild(ve)
# write href information # write href information
uparts=urlparse.urlparse(uri) uparts=urlparse.urlparse(uri)
fileloc=uparts[2] fileloc=uparts[2]
@ -180,7 +199,7 @@ def mk_prop_response(self, uri, good_props, bad_props, doc):
for p,v in good_props[ns].items(): for p,v in good_props[ns].items():
if v is None: if v is None:
continue continue
_prop_child(gp, ns, p, v) propgen._prop_child(gp, ns, p, v)
ps.appendChild(gp) ps.appendChild(gp)
re.appendChild(ps) re.appendChild(ps)

View File

@ -1,7 +1,8 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
############################################################################9
# #
# Copyright P. Christeas <p_christ@hol.gr> 2008-2010 # Copyright P. Christeas <p_christ@hol.gr> 2008-2010
# Copyright OpenERP SA, 2010 (http://www.openerp.com )
# #
# Disclaimer: Many of the functions below borrow code from the # Disclaimer: Many of the functions below borrow code from the
# python-webdav library (http://code.google.com/p/pywebdav/ ), # python-webdav library (http://code.google.com/p/pywebdav/ ),
@ -19,7 +20,7 @@
# #
# This program is Free Software; you can redistribute it and/or # This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
@ -33,20 +34,26 @@
############################################################################### ###############################################################################
import logging
import netsvc import netsvc
from dav_fs import openerp_dav_handler from dav_fs import openerp_dav_handler
from tools.config import config from tools.config import config
from DAV.WebDAVServer import DAVRequestHandler from DAV.WebDAVServer import DAVRequestHandler
from service import http_server
from service.websrv_lib import HTTPDir, FixSendError, HttpOptions from service.websrv_lib import HTTPDir, FixSendError, HttpOptions
from BaseHTTPServer import BaseHTTPRequestHandler from BaseHTTPServer import BaseHTTPRequestHandler
import urlparse import urlparse
import urllib import urllib
import re import re
import time
from string import atoi from string import atoi
from DAV.errors import * import addons
from DAV.utils import IfParser, TagList from DAV.utils import IfParser, TagList
from DAV.errors import DAV_Error, DAV_Forbidden, DAV_NotFound
from DAV.propfind import PROPFIND
# from DAV.constants import DAV_VERSION_1, DAV_VERSION_2 # from DAV.constants import DAV_VERSION_1, DAV_VERSION_2
from xml.dom import minidom from xml.dom import minidom
from redirect import RedirectHTTPHandler
khtml_re = re.compile(r' KHTML/([0-9\.]+) ') khtml_re = re.compile(r' KHTML/([0-9\.]+) ')
@ -66,6 +73,7 @@ def OpenDAVConfig(**kw):
class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler): class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
verbose = False verbose = False
_logger = logging.getLogger('webdav')
protocol_version = 'HTTP/1.1' protocol_version = 'HTTP/1.1'
_HTTP_OPTIONS= { 'DAV' : ['1', '2'], _HTTP_OPTIONS= { 'DAV' : ['1', '2'],
'Allow' : [ 'GET', 'HEAD', 'COPY', 'MOVE', 'POST', 'PUT', 'Allow' : [ 'GET', 'HEAD', 'COPY', 'MOVE', 'POST', 'PUT',
@ -75,8 +83,9 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
def get_userinfo(self,user,pw): def get_userinfo(self,user,pw):
return False return False
def _log(self, message): def _log(self, message):
netsvc.Logger().notifyChannel("webdav",netsvc.LOG_DEBUG,message) self._logger.debug(message)
def handle(self): def handle(self):
self._init_buffer() self._init_buffer()
@ -118,10 +127,10 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
return self.davpath return self.davpath
def log_message(self, format, *args): def log_message(self, format, *args):
netsvc.Logger().notifyChannel('webdav', netsvc.LOG_DEBUG_RPC, format % args) self._logger.log(netsvc.logging.DEBUG_RPC,format % args)
def log_error(self, format, *args): def log_error(self, format, *args):
netsvc.Logger().notifyChannel('xmlrpc', netsvc.LOG_WARNING, format % args) self._logger.warning(format % args)
def _prep_OPTIONS(self, opts): def _prep_OPTIONS(self, opts):
ret = opts ret = opts
@ -415,6 +424,140 @@ class DAVAuthProvider(OpenERPAuthProvider):
return True return True
return OpenERPAuthProvider.authenticate(self, db, user, passwd, client_address) return OpenERPAuthProvider.authenticate(self, db, user, passwd, client_address)
class dummy_dav_interface(object):
""" Dummy dav interface """
verbose = True
PROPS={"DAV:" : ('creationdate',
'displayname',
'getlastmodified',
'resourcetype',
),
}
M_NS={"DAV:" : "_get_dav", }
def __init__(self, parent):
self.parent = parent
def get_propnames(self,uri):
return self.PROPS
def get_prop(self,uri,ns,propname):
if self.M_NS.has_key(ns):
prefix=self.M_NS[ns]
else:
raise DAV_NotFound
mname=prefix+"_"+propname.replace('-', '_')
try:
m=getattr(self,mname)
r=m(uri)
return r
except AttributeError:
raise DAV_NotFound
def get_data(self, uri, range=None):
raise DAV_NotFound
def _get_dav_creationdate(self,uri):
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
def _get_dav_getlastmodified(self,uri):
return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
def _get_dav_displayname(self, uri):
return uri
def _get_dav_resourcetype(self, uri):
return ('collection', 'DAV:')
def exists(self, uri):
""" return 1 or None depending on if a resource exists """
uri2 = uri.split('/')
if len(uri2) < 3:
return True
logging.getLogger('webdav').debug("Requested uri: %s", uri)
return None # no
def is_collection(self, uri):
""" return 1 or None depending on if a resource is a collection """
return None # no
class DAVStaticHandler(http_server.StaticHTTPHandler):
""" A variant of the Static handler, which will serve dummy DAV requests
"""
verbose = False
protocol_version = 'HTTP/1.1'
_HTTP_OPTIONS= { 'DAV' : ['1', '2'],
'Allow' : [ 'GET', 'HEAD',
'PROPFIND', 'OPTIONS', 'REPORT', ]
}
def send_body(self, content, code, message='OK', content_type='text/xml'):
self.send_response(int(code), message)
self.send_header("Content-Type", content_type)
# self.send_header('Connection', 'close')
self.send_header('Content-Length', len(content) or 0)
self.end_headers()
if hasattr(self, '_flush'):
self._flush()
if self.command != 'HEAD':
self.wfile.write(content)
def do_PROPFIND(self):
"""Answer to PROPFIND with generic data.
A rough copy of python-webdav's do_PROPFIND, but hacked to work
statically.
"""
dc = dummy_dav_interface(self)
# read the body containing the xml request
# iff there is no body then this is an ALLPROP request
body = None
if self.headers.has_key('Content-Length'):
l = self.headers['Content-Length']
body = self.rfile.read(atoi(l))
path = self.path.rstrip('/')
uri = urllib.unquote(path)
pf = PROPFIND(uri, dc, self.headers.get('Depth', 'infinity'), body)
try:
DATA = '%s\n' % pf.createResponse()
except DAV_Error, (ec,dd):
return self.send_error(ec,dd)
except Exception:
self.log_exception("Cannot PROPFIND")
raise
# work around MSIE DAV bug for creation and modified date
# taken from Resource.py @ Zope webdav
if (self.headers.get('User-Agent') ==
'Microsoft Data Access Internet Publishing Provider DAV 1.1'):
DATA = DATA.replace('<ns0:getlastmodified xmlns:ns0="DAV:">',
'<ns0:getlastmodified xmlns:n="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.rfc1123">')
DATA = DATA.replace('<ns0:creationdate xmlns:ns0="DAV:">',
'<ns0:creationdate xmlns:n="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.tz">')
self.send_body(DATA, '207','Multi-Status','Multiple responses')
def not_get_baseuri(self):
baseuri = '/'
if self.headers.has_key('Host'):
uparts = list(urlparse.urlparse('/'))
uparts[1] = self.headers['Host']
baseuri = urlparse.urlunparse(uparts)
return baseuri
def get_davpath(self):
return ''
try: try:
if (config.get_misc('webdav','enable',True)): if (config.get_misc('webdav','enable',True)):
@ -430,10 +573,80 @@ try:
conf = OpenDAVConfig(**_dc) conf = OpenDAVConfig(**_dc)
handler._config = conf handler._config = conf
reg_http_service(HTTPDir(directory,DAVHandler,DAVAuthProvider())) reg_http_service(HTTPDir(directory,DAVHandler,DAVAuthProvider()))
netsvc.Logger().notifyChannel('webdav', netsvc.LOG_INFO, "WebDAV service registered at path: %s/ "% directory) logging.getLogger('webdav').info("WebDAV service registered at path: %s/ "% directory)
if not (config.get_misc('webdav', 'no_root_hack', False)):
# Now, replace the static http handler with the dav-enabled one.
# If a static-http service has been specified for our server, then
# read its configuration and use that dir_path.
# NOTE: this will _break_ any other service that would be registered
# at the root path in future.
base_path = False
if config.get_misc('static-http','enable', False):
base_path = config.get_misc('static-http', 'base_path', '/')
if base_path and base_path == '/':
dir_path = config.get_misc('static-http', 'dir_path', False)
else:
dir_path = addons.get_module_resource('document_webdav','public_html')
# an _ugly_ hack: we put that dir back in tools.config.misc, so that
# the StaticHttpHandler can find its dir_path.
config.misc.setdefault('static-http',{})['dir_path'] = dir_path
if reg_http_service(HTTPDir('/', DAVStaticHandler)):
logging.getLogger("web-services").info("WebDAV registered HTTP dir %s for /" % \
(dir_path))
except Exception, e: except Exception, e:
logger = netsvc.Logger() logging.getLogger('webdav').error('Cannot launch webdav: %s' % e)
logger.notifyChannel('webdav', netsvc.LOG_ERROR, 'Cannot launch webdav: %s' % e)
def init_well_known():
reps = RedirectHTTPHandler.redirect_paths
num_svcs = config.get_misc('http-well-known', 'num_services', '0')
for nsv in range(1, int(num_svcs)+1):
uri = config.get_misc('http-well-known', 'service_%d' % nsv, False)
path = config.get_misc('http-well-known', 'path_%d' % nsv, False)
if not (uri and path):
continue
reps['/'+uri] = path
if int(num_svcs):
if http_server.reg_http_service(HTTPDir('/.well-known', RedirectHTTPHandler)):
logging.getLogger("web-services").info("Registered HTTP redirect handler at /.well-known" )
init_well_known()
class PrincipalsRedirect(RedirectHTTPHandler):
redirect_paths = {}
def _find_redirect(self):
for b, r in self.redirect_paths.items():
if self.path.startswith(b):
return r + self.path[len(b):]
return False
def init_principals_redirect():
""" Some devices like the iPhone will look under /principals/users/xxx for
the user's properties. In OpenERP we _cannot_ have a stray /principals/...
working path, since we have a database path and the /webdav/ component. So,
the best solution is to redirect the url with 301. Luckily, it does work in
the device. The trick is that we need to hard-code the database to use, either
the one centrally defined in the config, or a "forced" one in the webdav
section.
"""
dbname = config.get_misc('webdav', 'principal_dbname', False)
if (not dbname) and not config.get_misc('webdav', 'no_principals_redirect', False):
dbname = config.get('db_name', False)
if dbname:
PrincipalsRedirect.redirect_paths[''] = '/webdav/%s/principals' % dbname
reg_http_service(HTTPDir('/principals', PrincipalsRedirect))
logging.getLogger("web-services").info(
"Registered HTTP redirect handler for /principals to the %s db.",
dbname)
init_principals_redirect()
#eof #eof

View File

@ -20,5 +20,6 @@
############################################################################## ##############################################################################
import event_project import event_project
import wizard import wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,238 +0,0 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="workflow" id="wkf_pos">
<field name="name">Pos workflow</field>
<field name="osv">pos.order</field>
<field name="on_create">True</field>
</record>
<!-- Roles definition -->
<record model="res.roles" id="role_pos">
<field name="name">POS - Confirmation</field>
</record>
<!--Activities-->
<record model="workflow.activity" id="act_draft">
<field name="wkf_id" ref="wkf_pos"/>
<field name="flow_start">True</field>
<field name="name">draft</field>
</record>
<record model="workflow.activity" id="act_payment">
<field name="wkf_id" ref="wkf_pos" />
<field name="name">payment</field>
<field name="kind">function</field>
<field name="action">write({'state': 'payment'})</field>
</record>
<record model="workflow.activity" id="act_rebate">
<field name="wkf_id" ref="wkf_pos" />
<field name="name">rebate</field>
<field name="kind">function</field>
<field name="action">write({'state': 'rebate'})</field>
</record>
<record model="workflow.activity" id="act_unbalanced">
<field name="wkf_id" ref="wkf_pos" />
<field name="name">unbalanced</field>
<field name="kind">function</field>
<field name="action">write({'state': 'unbalanced'})</field>
</record>
<record model="workflow.activity" id="act_cofinoga">
<field name="wkf_id" ref="wkf_pos" />
<field name="name">cofinoga</field>
<field name="kind">function</field>
<field name="action">write({'state': 'cofinoga'})</field>
</record>
<record model="workflow.activity" id="act_collectivites">
<field name="wkf_id" ref="wkf_pos" />
<field name="name">collectivites</field>
<field name="kind">function</field>
<field name="action">write({'state': 'collectivites'})</field>
</record>
<record model="workflow.activity" id="act_cadeaux">
<field name="wkf_id" ref="wkf_pos" />
<field name="name">cadeaux</field>
<field name="kind">function</field>
<field name="action">write({'state': 'cadeaux'})</field>
</record>
<record model="workflow.activity" id="act_collectivites">
<field name="wkf_id" ref="wkf_pos" />
<field name="name">collectivites</field>
<field name="kind">function</field>
<field name="action">write({'state': 'collectivites', 'invoice_wanted': True})</field>
</record>
<record model="workflow.activity" id="act_cadeaux">
<field name="wkf_id" ref="wkf_pos" />
<field name="name">cadeaux</field>
<field name="kind">function</field>
<field name="action">write({'state': 'cadeaux'})</field>
</record>
<record model="workflow.activity" id="act_paid">
<field name="wkf_id" ref="wkf_pos"/>
<field name="name">paid</field>
<field name="action">action_paid()</field>
<field name="kind">function</field>
</record>
<record model="workflow.activity" id="act_done">
<field name="wkf_id" ref="wkf_pos"/>
<field name="name">done</field>
<field name="flow_stop">True</field>
<field name="action">action_done()</field>
<field name="kind">function</field>
</record>
<record model="workflow.activity" id="act_invoiced">
<field name="wkf_id" ref="wkf_pos"/>
<field name="name">invoiced</field>
<field name="flow_stop">True</field>
<field name="action">action_invoice()</field>
<field name="kind">function</field>
</record>
<record model="workflow.activity" id="act_cancel">
<field name="wkf_id" ref="wkf_pos"/>
<field name="name">cancel</field>
<field name="flow_stop">True</field>
<field name="action">action_cancel()</field>
<field name="kind">function</field>
</record>
<!--Transitions-->
<record model="workflow.transition" id="trans_draft_payment">
<field name="act_from" ref="act_draft" />
<field name="act_to" ref="act_payment" />
<field name="signal">start_payment</field>
</record>
<record model="workflow.transition" id="trans_payment_paid">
<field name="act_from" ref="act_payment"/>
<field name="act_to" ref="act_paid"/>
<field name="condition">test_paid() and not(test_rebate() or test_cofinoga() or test_cadeaux() or test_collectivites())</field>
<field name="signal">payment</field>
</record>
<record model="workflow.transition" id="trans_payment_rebate">
<field name="act_from" ref="act_payment" />
<field name="act_to" ref="act_rebate" />
<field name="condition">test_rebate()</field>
<field name="signal">payment</field>
</record>
<record model="workflow.transition" id="trans_rebate_paid">
<field name="act_from" ref="act_rebate" />
<field name="act_to" ref="act_paid" />
<field name="signal">ok_rebate</field>
</record>
<record model="workflow.transition" id="trans_payment_unbalanced">
<field name="act_from" ref="act_payment" />
<field name="act_to" ref="act_unbalanced" />
<field name="condition">not test_paid()</field>
<field name="signal">payment</field>
</record>
<record model="workflow.transition" id="trans_unbalanced">
<field name="act_from" ref="act_unbalanced" />
<field name="act_to" ref="act_paid" />
<field name="condition">test_paid()</field>
</record>
<record model="workflow.transition" id="trans_payment_cofinoga">
<field name="act_from" ref="act_payment" />
<field name="act_to" ref="act_cofinoga" />
<field name="condition">test_cofinoga()</field>
<field name="signal">payment</field>
</record>
<record model="workflow.transition" id="trans_cofinoga_paid">
<field name="act_from" ref="act_cofinoga" />
<field name="act_to" ref="act_paid" />
<field name="signal">ok_cofinoga</field>
</record>
<record model="workflow.transition" id="trans_payment_collectivites">
<field name="act_from" ref="act_payment" />
<field name="act_to" ref="act_collectivites" />
<field name="condition">test_collectivites()</field>
<field name="signal">payment</field>
</record>
<record model="workflow.transition" id="trans_collectivites_paid">
<field name="act_from" ref="act_collectivites" />
<field name="act_to" ref="act_paid" />
<field name="signal">ok_collectivites</field>
</record>
<record model="workflow.transition" id="trans_payment_cadeaux">
<field name="act_from" ref="act_payment" />
<field name="act_to" ref="act_cadeaux" />
<field name="condition">test_cadeaux()</field>
<field name="signal">payment</field>
</record>
<record model="workflow.transition" id="trans_cadeaux_paid">
<field name="act_from" ref="act_cadeaux" />
<field name="act_to" ref="act_paid" />
<field name="signal">ok_cadeaux</field>
</record>
<record model="workflow.transition" id="trans_payment_collectivites">
<field name="act_from" ref="act_payment" />
<field name="act_to" ref="act_collectivites" />
<field name="condition">test_collectivites()</field>
<field name="signal">payment</field>
</record>
<record model="workflow.transition" id="trans_collectivites_paid">
<field name="act_from" ref="act_collectivites" />
<field name="act_to" ref="act_invoiced" />
<field name="signal">ok_collectivites</field>
</record>
<record model="workflow.transition" id="trans_payment_cadeaux">
<field name="act_from" ref="act_payment" />
<field name="act_to" ref="act_cadeaux" />
<field name="condition">test_cadeaux()</field>
<field name="signal">payment</field>
</record>
<record model="workflow.transition" id="trans_cadeaux_paid">
<field name="act_from" ref="act_cadeaux" />
<field name="act_to" ref="act_paid" />
<field name="signal">ok_cadeaux</field>
</record>
<record model="workflow.transition" id="trans_paid_done">
<field name="act_from" ref="act_paid"/>
<field name="act_to" ref="act_done"/>
<field name="signal">done</field>
</record>
<record model="workflow.transition" id="trans_paid_invoice">
<field name="act_from" ref="act_paid"/>
<field name="act_to" ref="act_invoiced"/>
<field name="signal">invoice</field>
</record>
<record model="workflow.transition" id="trans_paid_cancel">
<field name="act_from" ref="act_paid"/>
<field name="act_to" ref="act_cancel"/>
<field name="signal">cancel</field>
</record>
</data>
</openerp>